2 * Copyright 2015 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
22 #include <folly/Baton.h>
23 #include <folly/Optional.h>
24 #include <folly/futures/detail/Core.h>
25 #include <folly/futures/Timekeeper.h>
32 Timekeeper* getTimekeeperSingleton();
36 Future<T>::Future(Future<T>&& other) noexcept : core_(other.core_) {
37 other.core_ = nullptr;
41 Future<T>& Future<T>::operator=(Future<T>&& other) noexcept {
42 std::swap(core_, other.core_);
49 const typename std::enable_if<!std::is_void<F>::value, F>::type& val)
53 *this = p.getFuture();
59 typename std::enable_if<!std::is_void<F>::value, F>::type&& val)
62 p.setValue(std::forward<F>(val));
63 *this = p.getFuture();
68 typename std::enable_if<std::is_void<F>::value, int>::type>
69 Future<void>::Future() : core_(nullptr) {
72 *this = p.getFuture();
77 Future<T>::~Future() {
82 void Future<T>::detach() {
84 core_->detachFuture();
90 void Future<T>::throwIfInvalid() const {
97 void Future<T>::setCallback_(F&& func) {
99 core_->setCallback(std::move(func));
106 typename std::enable_if<isFuture<F>::value,
107 Future<typename isFuture<T>::Inner>>::type
108 Future<T>::unwrap() {
109 return then([](Future<typename isFuture<T>::Inner> internal_future) {
110 return internal_future;
116 // Variant: returns a value
117 // e.g. f.then([](Try<T>&& t){ return t.value(); });
119 template <typename F, typename R, bool isTry, typename... Args>
120 typename std::enable_if<!R::ReturnsFuture::value, typename R::Return>::type
121 Future<T>::thenImplementation(F func, detail::argResult<isTry, F, Args...>) {
122 static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument");
123 typedef typename R::ReturnsFuture::Inner B;
127 // wrap these so we can move them into the lambda
128 folly::MoveWrapper<Promise<B>> p;
129 folly::MoveWrapper<F> funcm(std::forward<F>(func));
131 // grab the Future now before we lose our handle on the Promise
132 auto f = p->getFuture();
134 f.setExecutor(getExecutor());
137 /* This is a bit tricky.
139 We can't just close over *this in case this Future gets moved. So we
140 make a new dummy Future. We could figure out something more
141 sophisticated that avoids making a new Future object when it can, as an
142 optimization. But this is correct.
144 core_ can't be moved, it is explicitly disallowed (as is copying). But
145 if there's ever a reason to allow it, this is one place that makes that
146 assumption and would need to be fixed. We use a standard shared pointer
147 for core_ (by copying it in), which means in essence obj holds a shared
148 pointer to itself. But this shouldn't leak because Promise will not
149 outlive the continuation, because Promise will setException() with a
150 broken Promise if it is destructed before completed. We could use a
151 weak pointer but it would have to be converted to a shared pointer when
152 func is executed (because the Future returned by func may possibly
153 persist beyond the callback, if it gets moved), and so it is an
154 optimization to just make it shared from the get-go.
156 We have to move in the Promise and func using the MoveWrapper
157 hack. (func could be copied but it's a big drag on perf).
159 Two subtle but important points about this design. detail::Core has no
160 back pointers to Future or Promise, so if Future or Promise get moved
161 (and they will be moved in performant code) we don't have to do
162 anything fancy. And because we store the continuation in the
163 detail::Core, not in the Future, we can execute the continuation even
164 after the Future has gone out of scope. This is an intentional design
165 decision. It is likely we will want to be able to cancel a continuation
166 in some circumstances, but I think it should be explicit not implicit
167 in the destruction of the Future used to create it.
170 [p, funcm](Try<T>&& t) mutable {
171 if (!isTry && t.hasException()) {
172 p->setException(std::move(t.exception()));
175 return (*funcm)(t.template get<isTry, Args>()...);
183 // Variant: returns a Future
184 // e.g. f.then([](T&& t){ return makeFuture<T>(t); });
186 template <typename F, typename R, bool isTry, typename... Args>
187 typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type
188 Future<T>::thenImplementation(F func, detail::argResult<isTry, F, Args...>) {
189 static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument");
190 typedef typename R::ReturnsFuture::Inner B;
194 // wrap these so we can move them into the lambda
195 folly::MoveWrapper<Promise<B>> p;
196 folly::MoveWrapper<F> funcm(std::forward<F>(func));
198 // grab the Future now before we lose our handle on the Promise
199 auto f = p->getFuture();
201 f.setExecutor(getExecutor());
205 [p, funcm](Try<T>&& t) mutable {
206 if (!isTry && t.hasException()) {
207 p->setException(std::move(t.exception()));
210 auto f2 = (*funcm)(t.template get<isTry, Args>()...);
211 // that didn't throw, now we can steal p
212 f2.setCallback_([p](Try<B>&& b) mutable {
213 p->setTry(std::move(b));
215 } catch (const std::exception& e) {
216 p->setException(exception_wrapper(std::current_exception(), e));
218 p->setException(exception_wrapper(std::current_exception()));
226 template <typename T>
227 template <typename R, typename Caller, typename... Args>
228 Future<typename isFuture<R>::Inner>
229 Future<T>::then(R(Caller::*func)(Args...), Caller *instance) {
230 typedef typename std::remove_cv<
231 typename std::remove_reference<
232 typename detail::ArgType<Args...>::FirstArg>::type>::type FirstArg;
233 return then([instance, func](Try<T>&& t){
234 return (instance->*func)(t.template get<isTry<FirstArg>::value, Args>()...);
241 template <class... Args>
242 auto Future<T>::then(Executor* x, Args&&... args)
243 -> decltype(this->then(std::forward<Args>(args)...))
245 auto oldX = getExecutor();
247 return this->then(std::forward<Args>(args)...).via(oldX);
252 Future<void> Future<T>::then() {
253 return then([] (Try<T>&& t) {});
256 // onError where the callback returns T
259 typename std::enable_if<
260 !detail::callableWith<F, exception_wrapper>::value &&
261 !detail::Extract<F>::ReturnsFuture::value,
263 Future<T>::onError(F&& func) {
264 typedef typename detail::Extract<F>::FirstArg Exn;
266 std::is_same<typename detail::Extract<F>::RawReturn, T>::value,
267 "Return type of onError callback must be T or Future<T>");
270 auto f = p.getFuture();
271 auto pm = folly::makeMoveWrapper(std::move(p));
272 auto funcm = folly::makeMoveWrapper(std::move(func));
273 setCallback_([pm, funcm](Try<T>&& t) mutable {
274 if (!t.template withException<Exn>([&] (Exn& e) {
279 pm->setTry(std::move(t));
286 // onError where the callback returns Future<T>
289 typename std::enable_if<
290 !detail::callableWith<F, exception_wrapper>::value &&
291 detail::Extract<F>::ReturnsFuture::value,
293 Future<T>::onError(F&& func) {
295 std::is_same<typename detail::Extract<F>::Return, Future<T>>::value,
296 "Return type of onError callback must be T or Future<T>");
297 typedef typename detail::Extract<F>::FirstArg Exn;
300 auto f = p.getFuture();
301 auto pm = folly::makeMoveWrapper(std::move(p));
302 auto funcm = folly::makeMoveWrapper(std::move(func));
303 setCallback_([pm, funcm](Try<T>&& t) mutable {
304 if (!t.template withException<Exn>([&] (Exn& e) {
306 auto f2 = (*funcm)(e);
307 f2.setCallback_([pm](Try<T>&& t2) mutable {
308 pm->setTry(std::move(t2));
310 } catch (const std::exception& e2) {
311 pm->setException(exception_wrapper(std::current_exception(), e2));
313 pm->setException(exception_wrapper(std::current_exception()));
316 pm->setTry(std::move(t));
325 Future<T> Future<T>::ensure(F func) {
326 MoveWrapper<F> funcw(std::move(func));
327 return this->then([funcw](Try<T>&& t) {
329 return makeFuture(std::move(t));
335 Future<T> Future<T>::onTimeout(Duration dur, F&& func, Timekeeper* tk) {
336 auto funcw = folly::makeMoveWrapper(std::forward<F>(func));
337 return within(dur, tk)
338 .onError([funcw](TimedOut const&) { return (*funcw)(); });
343 typename std::enable_if<
344 detail::callableWith<F, exception_wrapper>::value &&
345 detail::Extract<F>::ReturnsFuture::value,
347 Future<T>::onError(F&& func) {
349 std::is_same<typename detail::Extract<F>::Return, Future<T>>::value,
350 "Return type of onError callback must be T or Future<T>");
353 auto f = p.getFuture();
354 auto pm = folly::makeMoveWrapper(std::move(p));
355 auto funcm = folly::makeMoveWrapper(std::move(func));
356 setCallback_([pm, funcm](Try<T> t) mutable {
357 if (t.hasException()) {
359 auto f2 = (*funcm)(std::move(t.exception()));
360 f2.setCallback_([pm](Try<T> t2) mutable {
361 pm->setTry(std::move(t2));
363 } catch (const std::exception& e2) {
364 pm->setException(exception_wrapper(std::current_exception(), e2));
366 pm->setException(exception_wrapper(std::current_exception()));
369 pm->setTry(std::move(t));
376 // onError(exception_wrapper) that returns T
379 typename std::enable_if<
380 detail::callableWith<F, exception_wrapper>::value &&
381 !detail::Extract<F>::ReturnsFuture::value,
383 Future<T>::onError(F&& func) {
385 std::is_same<typename detail::Extract<F>::Return, Future<T>>::value,
386 "Return type of onError callback must be T or Future<T>");
389 auto f = p.getFuture();
390 auto pm = folly::makeMoveWrapper(std::move(p));
391 auto funcm = folly::makeMoveWrapper(std::move(func));
392 setCallback_([pm, funcm](Try<T> t) mutable {
393 if (t.hasException()) {
395 return (*funcm)(std::move(t.exception()));
398 pm->setTry(std::move(t));
406 typename std::add_lvalue_reference<T>::type Future<T>::value() {
409 return core_->getTry().value();
413 typename std::add_lvalue_reference<const T>::type Future<T>::value() const {
416 return core_->getTry().value();
420 Try<T>& Future<T>::getTry() {
423 return core_->getTry();
427 Optional<Try<T>> Future<T>::poll() {
429 if (core_->ready()) {
430 o = std::move(core_->getTry());
436 template <typename Executor>
437 inline Future<T> Future<T>::via(Executor* executor) && {
440 setExecutor(executor);
442 return std::move(*this);
446 template <typename Executor>
447 inline Future<T> Future<T>::via(Executor* executor) & {
450 MoveWrapper<Promise<T>> p;
451 auto f = p->getFuture();
452 then([p](Try<T>&& t) mutable { p->setTry(std::move(t)); });
453 return std::move(f).via(executor);
457 bool Future<T>::isReady() const {
459 return core_->ready();
463 void Future<T>::raise(exception_wrapper exception) {
464 core_->raise(std::move(exception));
470 Future<typename std::decay<T>::type> makeFuture(T&& t) {
471 Promise<typename std::decay<T>::type> p;
472 p.setValue(std::forward<T>(t));
473 return p.getFuture();
476 inline // for multiple translation units
477 Future<void> makeFuture() {
480 return p.getFuture();
486 typename std::enable_if<!std::is_reference<F>::value, bool>::type sdf)
487 -> Future<decltype(func())> {
488 Promise<decltype(func())> p;
493 return p.getFuture();
497 auto makeFutureTry(F const& func) -> Future<decltype(func())> {
499 return makeFutureTry(std::move(copy));
503 Future<T> makeFuture(std::exception_ptr const& e) {
506 return p.getFuture();
510 Future<T> makeFuture(exception_wrapper ew) {
512 p.setException(std::move(ew));
513 return p.getFuture();
516 template <class T, class E>
517 typename std::enable_if<std::is_base_of<std::exception, E>::value,
519 makeFuture(E const& e) {
521 p.setException(make_exception_wrapper<E>(e));
522 return p.getFuture();
526 Future<T> makeFuture(Try<T>&& t) {
527 Promise<typename std::decay<T>::type> p;
528 p.setTry(std::move(t));
529 return p.getFuture();
533 inline Future<void> makeFuture(Try<void>&& t) {
534 if (t.hasException()) {
535 return makeFuture<void>(std::move(t.exception()));
542 template <typename Executor>
543 Future<void> via(Executor* executor) {
544 return makeFuture().via(executor);
549 template <typename... Fs>
550 typename detail::VariadicContext<
551 typename std::decay<Fs>::type::value_type...>::type
552 whenAll(Fs&&... fs) {
554 new detail::VariadicContext<typename std::decay<Fs>::type::value_type...>();
555 ctx->total = sizeof...(fs);
556 auto f_saved = ctx->p.getFuture();
557 detail::whenAllVariadicHelper(ctx,
558 std::forward<typename std::decay<Fs>::type>(fs)...);
564 template <class InputIterator>
567 Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
568 whenAll(InputIterator first, InputIterator last) {
570 typename std::iterator_traits<InputIterator>::value_type::value_type T;
573 return makeFuture(std::vector<Try<T>>());
575 size_t n = std::distance(first, last);
577 auto ctx = new detail::WhenAllContext<T>();
579 ctx->results.resize(n);
581 auto f_saved = ctx->p.getFuture();
583 for (size_t i = 0; first != last; ++first, ++i) {
586 f.setCallback_([ctx, i, n](Try<T> t) {
587 ctx->results[i] = std::move(t);
588 if (++ctx->count == n) {
589 ctx->p.setValue(std::move(ctx->results));
600 template <class, class, typename = void> struct CollectContextHelper;
602 template <class T, class VecT>
603 struct CollectContextHelper<T, VecT,
604 typename std::enable_if<std::is_same<T, VecT>::value>::type> {
605 static inline std::vector<T>&& getResults(std::vector<VecT>& results) {
606 return std::move(results);
610 template <class T, class VecT>
611 struct CollectContextHelper<T, VecT,
612 typename std::enable_if<!std::is_same<T, VecT>::value>::type> {
613 static inline std::vector<T> getResults(std::vector<VecT>& results) {
614 std::vector<T> finalResults;
615 finalResults.reserve(results.size());
616 for (auto& opt : results) {
617 finalResults.push_back(std::move(opt.value()));
623 template <typename T>
624 struct CollectContext {
626 typedef typename std::conditional<
627 std::is_default_constructible<T>::value,
632 explicit CollectContext(int n) : count(0), threw(false) {
636 Promise<std::vector<T>> p;
637 std::vector<VecT> results;
638 std::atomic<size_t> count;
639 std::atomic_bool threw;
641 typedef std::vector<T> result_type;
643 static inline Future<std::vector<T>> makeEmptyFuture() {
644 return makeFuture(std::vector<T>());
647 inline void setValue() {
648 p.setValue(CollectContextHelper<T, VecT>::getResults(results));
651 inline void addResult(int i, Try<T>& t) {
652 results[i] = std::move(t.value());
657 struct CollectContext<void> {
659 explicit CollectContext(int n) : count(0), threw(false) {}
662 std::atomic<size_t> count;
663 std::atomic_bool threw;
665 typedef void result_type;
667 static inline Future<void> makeEmptyFuture() {
671 inline void setValue() {
675 inline void addResult(int i, Try<void>& t) {
682 template <class InputIterator>
683 Future<typename detail::CollectContext<
684 typename std::iterator_traits<InputIterator>::value_type::value_type
686 collect(InputIterator first, InputIterator last) {
688 typename std::iterator_traits<InputIterator>::value_type::value_type T;
691 return detail::CollectContext<T>::makeEmptyFuture();
694 size_t n = std::distance(first, last);
695 auto ctx = new detail::CollectContext<T>(n);
696 auto f_saved = ctx->p.getFuture();
698 for (size_t i = 0; first != last; ++first, ++i) {
701 f.setCallback_([ctx, i, n](Try<T> t) {
702 auto c = ++ctx->count;
704 if (t.hasException()) {
705 if (!ctx->threw.exchange(true)) {
706 ctx->p.setException(std::move(t.exception()));
708 } else if (!ctx->threw) {
709 ctx->addResult(i, t);
724 template <class InputIterator>
729 std::iterator_traits<InputIterator>::value_type::value_type> > >
730 whenAny(InputIterator first, InputIterator last) {
732 typename std::iterator_traits<InputIterator>::value_type::value_type T;
734 auto ctx = new detail::WhenAnyContext<T>(std::distance(first, last));
735 auto f_saved = ctx->p.getFuture();
737 for (size_t i = 0; first != last; first++, i++) {
739 f.setCallback_([i, ctx](Try<T>&& t) {
740 if (!ctx->done.exchange(true)) {
741 ctx->p.setValue(std::make_pair(i, std::move(t)));
750 template <class InputIterator>
751 Future<std::vector<std::pair<size_t, Try<typename
752 std::iterator_traits<InputIterator>::value_type::value_type>>>>
753 whenN(InputIterator first, InputIterator last, size_t n) {
755 std::iterator_traits<InputIterator>::value_type::value_type T;
756 typedef std::vector<std::pair<size_t, Try<T>>> V;
763 auto ctx = std::make_shared<ctx_t>();
766 // for each completed Future, increase count and add to vector, until we
767 // have n completed futures at which point we fulfill our Promise with the
772 it->then([ctx, n, i](Try<T>&& t) {
774 auto c = ++ctx->completed;
776 assert(ctx->v.size() < n);
777 v.push_back(std::make_pair(i, std::move(t)));
779 ctx->p.setTry(Try<V>(std::move(v)));
789 ctx->p.setException(std::runtime_error("Not enough futures"));
792 return ctx->p.getFuture();
795 template <class It, class T, class F, class ItT, class Arg>
796 typename std::enable_if<!isFutureResult<F, T, Arg>::value, Future<T>>::type
797 reduce(It first, It last, T initial, F func) {
799 return makeFuture(std::move(initial));
802 typedef isTry<Arg> IsTry;
804 return whenAll(first, last)
805 .then([initial, func](std::vector<Try<ItT>>& vals) mutable {
806 for (auto& val : vals) {
807 initial = func(std::move(initial),
808 // Either return a ItT&& or a Try<ItT>&& depending
809 // on the type of the argument of func.
810 val.template get<IsTry::value, Arg&&>());
816 template <class It, class T, class F, class ItT, class Arg>
817 typename std::enable_if<isFutureResult<F, T, Arg>::value, Future<T>>::type
818 reduce(It first, It last, T initial, F func) {
820 return makeFuture(std::move(initial));
823 typedef isTry<Arg> IsTry;
825 auto f = first->then([initial, func](Try<ItT>& head) mutable {
826 return func(std::move(initial),
827 head.template get<IsTry::value, Arg&&>());
830 for (++first; first != last; ++first) {
831 f = whenAll(f, *first).then([func](std::tuple<Try<T>, Try<ItT>>& t) {
832 return func(std::move(std::get<0>(t).value()),
833 // Either return a ItT&& or a Try<ItT>&& depending
834 // on the type of the argument of func.
835 std::get<1>(t).template get<IsTry::value, Arg&&>());
843 Future<T> Future<T>::within(Duration dur, Timekeeper* tk) {
844 return within(dur, TimedOut(), tk);
849 Future<T> Future<T>::within(Duration dur, E e, Timekeeper* tk) {
852 Context(E ex) : exception(std::move(ex)), promise(), token(false) {}
855 std::atomic<bool> token;
857 auto ctx = std::make_shared<Context>(std::move(e));
860 tk = folly::detail::getTimekeeperSingleton();
864 .then([ctx](Try<void> const& t) {
865 if (ctx->token.exchange(true) == false) {
866 if (t.hasException()) {
867 ctx->promise.setException(std::move(t.exception()));
869 ctx->promise.setException(std::move(ctx->exception));
874 this->then([ctx](Try<T>&& t) {
875 if (ctx->token.exchange(true) == false) {
876 ctx->promise.setTry(std::move(t));
880 return ctx->promise.getFuture();
884 Future<T> Future<T>::delayed(Duration dur, Timekeeper* tk) {
885 return whenAll(*this, futures::sleep(dur, tk))
886 .then([](std::tuple<Try<T>, Try<void>> tup) {
887 Try<T>& t = std::get<0>(tup);
888 return makeFuture<T>(std::move(t));
895 void waitImpl(Future<T>& f) {
896 // short-circuit if there's nothing to do
897 if (f.isReady()) return;
900 f = f.then([&](Try<T> t) {
902 return makeFuture(std::move(t));
906 // There's a race here between the return here and the actual finishing of
907 // the future. f is completed, but the setup may not have finished on done
908 // after the baton has posted.
909 while (!f.isReady()) {
910 std::this_thread::yield();
915 void waitImpl(Future<T>& f, Duration dur) {
916 // short-circuit if there's nothing to do
917 if (f.isReady()) return;
919 auto baton = std::make_shared<Baton<>>();
920 f = f.then([baton](Try<T> t) {
922 return makeFuture(std::move(t));
925 // Let's preserve the invariant that if we did not timeout (timed_wait returns
926 // true), then the returned Future is complete when it is returned to the
927 // caller. We need to wait out the race for that Future to complete.
928 if (baton->timed_wait(std::chrono::system_clock::now() + dur)) {
929 while (!f.isReady()) {
930 std::this_thread::yield();
936 void waitViaImpl(Future<T>& f, DrivableExecutor* e) {
937 while (!f.isReady()) {
945 Future<T>& Future<T>::wait() & {
946 detail::waitImpl(*this);
951 Future<T>&& Future<T>::wait() && {
952 detail::waitImpl(*this);
953 return std::move(*this);
957 Future<T>& Future<T>::wait(Duration dur) & {
958 detail::waitImpl(*this, dur);
963 Future<T>&& Future<T>::wait(Duration dur) && {
964 detail::waitImpl(*this, dur);
965 return std::move(*this);
969 Future<T>& Future<T>::waitVia(DrivableExecutor* e) & {
970 detail::waitViaImpl(*this, e);
975 Future<T>&& Future<T>::waitVia(DrivableExecutor* e) && {
976 detail::waitViaImpl(*this, e);
977 return std::move(*this);
982 return std::move(wait().value());
986 inline void Future<void>::get() {
991 T Future<T>::get(Duration dur) {
994 return std::move(value());
1001 inline void Future<void>::get(Duration dur) {
1011 T Future<T>::getVia(DrivableExecutor* e) {
1012 return std::move(waitVia(e).value());
1016 inline void Future<void>::getVia(DrivableExecutor* e) {
1021 Future<bool> Future<T>::willEqual(Future<T>& f) {
1022 return whenAll(*this, f).then([](const std::tuple<Try<T>, Try<T>>& t) {
1023 if (std::get<0>(t).hasValue() && std::get<1>(t).hasValue()) {
1024 return std::get<0>(t).value() == std::get<1>(t).value();
1033 Future<T> Future<T>::filter(F predicate) {
1034 auto p = folly::makeMoveWrapper(std::move(predicate));
1035 return this->then([p](T val) {
1036 T const& valConstRef = val;
1037 if (!(*p)(valConstRef)) {
1038 throw PredicateDoesNotObtain();
1047 Future<Z> chainHelper(Future<Z> f) {
1051 template <class Z, class F, class Fn, class... Callbacks>
1052 Future<Z> chainHelper(F f, Fn fn, Callbacks... fns) {
1053 return chainHelper<Z>(f.then(fn), fns...);
1057 template <class A, class Z, class... Callbacks>
1058 std::function<Future<Z>(Try<A>)>
1059 chain(Callbacks... fns) {
1060 MoveWrapper<Promise<A>> pw;
1061 MoveWrapper<Future<Z>> fw(chainHelper<Z>(pw->getFuture(), fns...));
1062 return [=](Try<A> t) mutable {
1063 pw->setTry(std::move(t));
1064 return std::move(*fw);
1068 template <class It, class F, class ItT, class Result>
1069 std::vector<Future<Result>> map(It first, It last, F func) {
1070 std::vector<Future<Result>> results;
1071 for (auto it = first; it != last; it++) {
1072 results.push_back(it->then(func));
1078 } // namespace folly
1080 // I haven't included a Future<T&> specialization because I don't forsee us
1081 // using it, however it is not difficult to add when needed. Refer to
1082 // Future<void> for guidance. std::future and boost::future code would also be