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/experimental/fibers/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_);
48 typename std::enable_if<!isFuture<T2>::value, void*>::type>
49 Future<T>::Future(T2&& val) : core_(nullptr) {
51 p.setValue(std::forward<T2>(val));
52 *this = p.getFuture();
57 typename std::enable_if<
58 folly::is_void_or_unit<T2>::value,
60 Future<T>::Future() : core_(nullptr) {
63 *this = p.getFuture();
68 Future<T>::~Future() {
73 void Future<T>::detach() {
75 core_->detachFuture();
81 void Future<T>::throwIfInvalid() const {
88 void Future<T>::setCallback_(F&& func) {
90 core_->setCallback(std::move(func));
97 typename std::enable_if<isFuture<F>::value,
98 Future<typename isFuture<T>::Inner>>::type
100 return then([](Future<typename isFuture<T>::Inner> internal_future) {
101 return internal_future;
107 // Variant: returns a value
108 // e.g. f.then([](Try<T>&& t){ return t.value(); });
110 template <typename F, typename R, bool isTry, typename... Args>
111 typename std::enable_if<!R::ReturnsFuture::value, typename R::Return>::type
112 Future<T>::thenImplementation(F func, detail::argResult<isTry, F, Args...>) {
113 static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument");
114 typedef typename R::ReturnsFuture::Inner B;
118 // wrap these so we can move them into the lambda
119 folly::MoveWrapper<Promise<B>> p;
120 folly::MoveWrapper<F> funcm(std::forward<F>(func));
122 // grab the Future now before we lose our handle on the Promise
123 auto f = p->getFuture();
125 f.setExecutor(getExecutor());
128 /* This is a bit tricky.
130 We can't just close over *this in case this Future gets moved. So we
131 make a new dummy Future. We could figure out something more
132 sophisticated that avoids making a new Future object when it can, as an
133 optimization. But this is correct.
135 core_ can't be moved, it is explicitly disallowed (as is copying). But
136 if there's ever a reason to allow it, this is one place that makes that
137 assumption and would need to be fixed. We use a standard shared pointer
138 for core_ (by copying it in), which means in essence obj holds a shared
139 pointer to itself. But this shouldn't leak because Promise will not
140 outlive the continuation, because Promise will setException() with a
141 broken Promise if it is destructed before completed. We could use a
142 weak pointer but it would have to be converted to a shared pointer when
143 func is executed (because the Future returned by func may possibly
144 persist beyond the callback, if it gets moved), and so it is an
145 optimization to just make it shared from the get-go.
147 We have to move in the Promise and func using the MoveWrapper
148 hack. (func could be copied but it's a big drag on perf).
150 Two subtle but important points about this design. detail::Core has no
151 back pointers to Future or Promise, so if Future or Promise get moved
152 (and they will be moved in performant code) we don't have to do
153 anything fancy. And because we store the continuation in the
154 detail::Core, not in the Future, we can execute the continuation even
155 after the Future has gone out of scope. This is an intentional design
156 decision. It is likely we will want to be able to cancel a continuation
157 in some circumstances, but I think it should be explicit not implicit
158 in the destruction of the Future used to create it.
161 [p, funcm](Try<T>&& t) mutable {
162 if (!isTry && t.hasException()) {
163 p->setException(std::move(t.exception()));
166 return (*funcm)(t.template get<isTry, Args>()...);
174 // Variant: returns a Future
175 // e.g. f.then([](T&& t){ return makeFuture<T>(t); });
177 template <typename F, typename R, bool isTry, typename... Args>
178 typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type
179 Future<T>::thenImplementation(F func, detail::argResult<isTry, F, Args...>) {
180 static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument");
181 typedef typename R::ReturnsFuture::Inner B;
185 // wrap these so we can move them into the lambda
186 folly::MoveWrapper<Promise<B>> p;
187 folly::MoveWrapper<F> funcm(std::forward<F>(func));
189 // grab the Future now before we lose our handle on the Promise
190 auto f = p->getFuture();
192 f.setExecutor(getExecutor());
196 [p, funcm](Try<T>&& t) mutable {
197 if (!isTry && t.hasException()) {
198 p->setException(std::move(t.exception()));
201 auto f2 = (*funcm)(t.template get<isTry, Args>()...);
202 // that didn't throw, now we can steal p
203 f2.setCallback_([p](Try<B>&& b) mutable {
204 p->setTry(std::move(b));
206 } catch (const std::exception& e) {
207 p->setException(exception_wrapper(std::current_exception(), e));
209 p->setException(exception_wrapper(std::current_exception()));
217 template <typename T>
218 template <typename R, typename Caller, typename... Args>
219 Future<typename isFuture<R>::Inner>
220 Future<T>::then(R(Caller::*func)(Args...), Caller *instance) {
221 typedef typename std::remove_cv<
222 typename std::remove_reference<
223 typename detail::ArgType<Args...>::FirstArg>::type>::type FirstArg;
224 return then([instance, func](Try<T>&& t){
225 return (instance->*func)(t.template get<isTry<FirstArg>::value, Args>()...);
232 template <class... Args>
233 auto Future<T>::then(Executor* x, Args&&... args)
234 -> decltype(this->then(std::forward<Args>(args)...))
236 auto oldX = getExecutor();
238 return this->then(std::forward<Args>(args)...).via(oldX);
243 Future<void> Future<T>::then() {
244 return then([] (Try<T>&& t) {});
247 // onError where the callback returns T
250 typename std::enable_if<
251 !detail::callableWith<F, exception_wrapper>::value &&
252 !detail::Extract<F>::ReturnsFuture::value,
254 Future<T>::onError(F&& func) {
255 typedef typename detail::Extract<F>::FirstArg Exn;
257 std::is_same<typename detail::Extract<F>::RawReturn, T>::value,
258 "Return type of onError callback must be T or Future<T>");
261 auto f = p.getFuture();
262 auto pm = folly::makeMoveWrapper(std::move(p));
263 auto funcm = folly::makeMoveWrapper(std::move(func));
264 setCallback_([pm, funcm](Try<T>&& t) mutable {
265 if (!t.template withException<Exn>([&] (Exn& e) {
270 pm->setTry(std::move(t));
277 // onError where the callback returns Future<T>
280 typename std::enable_if<
281 !detail::callableWith<F, exception_wrapper>::value &&
282 detail::Extract<F>::ReturnsFuture::value,
284 Future<T>::onError(F&& func) {
286 std::is_same<typename detail::Extract<F>::Return, Future<T>>::value,
287 "Return type of onError callback must be T or Future<T>");
288 typedef typename detail::Extract<F>::FirstArg Exn;
291 auto f = p.getFuture();
292 auto pm = folly::makeMoveWrapper(std::move(p));
293 auto funcm = folly::makeMoveWrapper(std::move(func));
294 setCallback_([pm, funcm](Try<T>&& t) mutable {
295 if (!t.template withException<Exn>([&] (Exn& e) {
297 auto f2 = (*funcm)(e);
298 f2.setCallback_([pm](Try<T>&& t2) mutable {
299 pm->setTry(std::move(t2));
301 } catch (const std::exception& e2) {
302 pm->setException(exception_wrapper(std::current_exception(), e2));
304 pm->setException(exception_wrapper(std::current_exception()));
307 pm->setTry(std::move(t));
316 Future<T> Future<T>::ensure(F func) {
317 MoveWrapper<F> funcw(std::move(func));
318 return this->then([funcw](Try<T>&& t) {
320 return makeFuture(std::move(t));
326 Future<T> Future<T>::onTimeout(Duration dur, F&& func, Timekeeper* tk) {
327 auto funcw = folly::makeMoveWrapper(std::forward<F>(func));
328 return within(dur, tk)
329 .onError([funcw](TimedOut const&) { return (*funcw)(); });
334 typename std::enable_if<
335 detail::callableWith<F, exception_wrapper>::value &&
336 detail::Extract<F>::ReturnsFuture::value,
338 Future<T>::onError(F&& func) {
340 std::is_same<typename detail::Extract<F>::Return, Future<T>>::value,
341 "Return type of onError callback must be T or Future<T>");
344 auto f = p.getFuture();
345 auto pm = folly::makeMoveWrapper(std::move(p));
346 auto funcm = folly::makeMoveWrapper(std::move(func));
347 setCallback_([pm, funcm](Try<T> t) mutable {
348 if (t.hasException()) {
350 auto f2 = (*funcm)(std::move(t.exception()));
351 f2.setCallback_([pm](Try<T> t2) mutable {
352 pm->setTry(std::move(t2));
354 } catch (const std::exception& e2) {
355 pm->setException(exception_wrapper(std::current_exception(), e2));
357 pm->setException(exception_wrapper(std::current_exception()));
360 pm->setTry(std::move(t));
367 // onError(exception_wrapper) that returns T
370 typename std::enable_if<
371 detail::callableWith<F, exception_wrapper>::value &&
372 !detail::Extract<F>::ReturnsFuture::value,
374 Future<T>::onError(F&& func) {
376 std::is_same<typename detail::Extract<F>::Return, Future<T>>::value,
377 "Return type of onError callback must be T or Future<T>");
380 auto f = p.getFuture();
381 auto pm = folly::makeMoveWrapper(std::move(p));
382 auto funcm = folly::makeMoveWrapper(std::move(func));
383 setCallback_([pm, funcm](Try<T> t) mutable {
384 if (t.hasException()) {
386 return (*funcm)(std::move(t.exception()));
389 pm->setTry(std::move(t));
397 typename std::add_lvalue_reference<T>::type Future<T>::value() {
400 return core_->getTry().value();
404 typename std::add_lvalue_reference<const T>::type Future<T>::value() const {
407 return core_->getTry().value();
411 Try<T>& Future<T>::getTry() {
414 return core_->getTry();
418 Optional<Try<T>> Future<T>::poll() {
420 if (core_->ready()) {
421 o = std::move(core_->getTry());
427 template <typename Executor>
428 inline Future<T> Future<T>::via(Executor* executor) && {
431 setExecutor(executor);
433 return std::move(*this);
437 template <typename Executor>
438 inline Future<T> Future<T>::via(Executor* executor) & {
441 MoveWrapper<Promise<T>> p;
442 auto f = p->getFuture();
443 then([p](Try<T>&& t) mutable { p->setTry(std::move(t)); });
444 return std::move(f).via(executor);
448 bool Future<T>::isReady() const {
450 return core_->ready();
454 void Future<T>::raise(exception_wrapper exception) {
455 core_->raise(std::move(exception));
461 Future<typename std::decay<T>::type> makeFuture(T&& t) {
462 Promise<typename std::decay<T>::type> p;
463 p.setValue(std::forward<T>(t));
464 return p.getFuture();
467 inline // for multiple translation units
468 Future<void> makeFuture() {
471 return p.getFuture();
477 typename std::enable_if<!std::is_reference<F>::value, bool>::type sdf)
478 -> Future<decltype(func())> {
479 Promise<decltype(func())> p;
484 return p.getFuture();
488 auto makeFutureWith(F const& func) -> Future<decltype(func())> {
490 return makeFutureWith(std::move(copy));
494 Future<T> makeFuture(std::exception_ptr const& e) {
497 return p.getFuture();
501 Future<T> makeFuture(exception_wrapper ew) {
503 p.setException(std::move(ew));
504 return p.getFuture();
507 template <class T, class E>
508 typename std::enable_if<std::is_base_of<std::exception, E>::value,
510 makeFuture(E const& e) {
512 p.setException(make_exception_wrapper<E>(e));
513 return p.getFuture();
517 Future<T> makeFuture(Try<T>&& t) {
518 Promise<typename std::decay<T>::type> p;
519 p.setTry(std::move(t));
520 return p.getFuture();
524 inline Future<void> makeFuture(Try<void>&& t) {
525 if (t.hasException()) {
526 return makeFuture<void>(std::move(t.exception()));
533 template <typename Executor>
534 Future<void> via(Executor* executor) {
535 return makeFuture().via(executor);
540 template <typename... Fs>
541 typename detail::VariadicContext<
542 typename std::decay<Fs>::type::value_type...>::type
543 collectAll(Fs&&... fs) {
545 new detail::VariadicContext<typename std::decay<Fs>::type::value_type...>();
546 ctx->total = sizeof...(fs);
547 auto f_saved = ctx->p.getFuture();
548 detail::collectAllVariadicHelper(ctx,
549 std::forward<typename std::decay<Fs>::type>(fs)...);
555 template <class InputIterator>
558 Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
559 collectAll(InputIterator first, InputIterator last) {
561 typename std::iterator_traits<InputIterator>::value_type::value_type T;
564 return makeFuture(std::vector<Try<T>>());
566 size_t n = std::distance(first, last);
568 auto ctx = new detail::WhenAllContext<T>();
570 ctx->results.resize(n);
572 auto f_saved = ctx->p.getFuture();
574 for (size_t i = 0; first != last; ++first, ++i) {
577 f.setCallback_([ctx, i, n](Try<T> t) {
578 ctx->results[i] = std::move(t);
579 if (++ctx->count == n) {
580 ctx->p.setValue(std::move(ctx->results));
591 template <class, class, typename = void> struct CollectContextHelper;
593 template <class T, class VecT>
594 struct CollectContextHelper<T, VecT,
595 typename std::enable_if<std::is_same<T, VecT>::value>::type> {
596 static inline std::vector<T>&& getResults(std::vector<VecT>& results) {
597 return std::move(results);
601 template <class T, class VecT>
602 struct CollectContextHelper<T, VecT,
603 typename std::enable_if<!std::is_same<T, VecT>::value>::type> {
604 static inline std::vector<T> getResults(std::vector<VecT>& results) {
605 std::vector<T> finalResults;
606 finalResults.reserve(results.size());
607 for (auto& opt : results) {
608 finalResults.push_back(std::move(opt.value()));
614 template <typename T>
615 struct CollectContext {
617 typedef typename std::conditional<
618 std::is_default_constructible<T>::value,
623 explicit CollectContext(int n) : count(0), threw(false) {
627 Promise<std::vector<T>> p;
628 std::vector<VecT> results;
629 std::atomic<size_t> count;
630 std::atomic_bool threw;
632 typedef std::vector<T> result_type;
634 static inline Future<std::vector<T>> makeEmptyFuture() {
635 return makeFuture(std::vector<T>());
638 inline void setValue() {
639 p.setValue(CollectContextHelper<T, VecT>::getResults(results));
642 inline void addResult(int i, Try<T>& t) {
643 results[i] = std::move(t.value());
648 struct CollectContext<void> {
650 explicit CollectContext(int n) : count(0), threw(false) {}
653 std::atomic<size_t> count;
654 std::atomic_bool threw;
656 typedef void result_type;
658 static inline Future<void> makeEmptyFuture() {
662 inline void setValue() {
666 inline void addResult(int i, Try<void>& t) {
673 template <class InputIterator>
674 Future<typename detail::CollectContext<
675 typename std::iterator_traits<InputIterator>::value_type::value_type
677 collect(InputIterator first, InputIterator last) {
679 typename std::iterator_traits<InputIterator>::value_type::value_type T;
682 return detail::CollectContext<T>::makeEmptyFuture();
685 size_t n = std::distance(first, last);
686 auto ctx = new detail::CollectContext<T>(n);
687 auto f_saved = ctx->p.getFuture();
689 for (size_t i = 0; first != last; ++first, ++i) {
692 f.setCallback_([ctx, i, n](Try<T> t) {
693 auto c = ++ctx->count;
695 if (t.hasException()) {
696 if (!ctx->threw.exchange(true)) {
697 ctx->p.setException(std::move(t.exception()));
699 } else if (!ctx->threw) {
700 ctx->addResult(i, t);
715 template <class InputIterator>
720 std::iterator_traits<InputIterator>::value_type::value_type> > >
721 collectAny(InputIterator first, InputIterator last) {
723 typename std::iterator_traits<InputIterator>::value_type::value_type T;
725 auto ctx = new detail::WhenAnyContext<T>(std::distance(first, last));
726 auto f_saved = ctx->p.getFuture();
728 for (size_t i = 0; first != last; first++, i++) {
730 f.setCallback_([i, ctx](Try<T>&& t) {
731 if (!ctx->done.exchange(true)) {
732 ctx->p.setValue(std::make_pair(i, std::move(t)));
741 template <class InputIterator>
742 Future<std::vector<std::pair<size_t, Try<typename
743 std::iterator_traits<InputIterator>::value_type::value_type>>>>
744 collectN(InputIterator first, InputIterator last, size_t n) {
746 std::iterator_traits<InputIterator>::value_type::value_type T;
747 typedef std::vector<std::pair<size_t, Try<T>>> V;
754 auto ctx = std::make_shared<ctx_t>();
757 // for each completed Future, increase count and add to vector, until we
758 // have n completed futures at which point we fulfill our Promise with the
763 it->then([ctx, n, i](Try<T>&& t) {
765 auto c = ++ctx->completed;
767 assert(ctx->v.size() < n);
768 v.push_back(std::make_pair(i, std::move(t)));
770 ctx->p.setTry(Try<V>(std::move(v)));
780 ctx->p.setException(std::runtime_error("Not enough futures"));
783 return ctx->p.getFuture();
786 template <class It, class T, class F, class ItT, class Arg>
787 typename std::enable_if<!isFutureResult<F, T, Arg>::value, Future<T>>::type
788 reduce(It first, It last, T initial, F func) {
790 return makeFuture(std::move(initial));
793 typedef isTry<Arg> IsTry;
795 return collectAll(first, last)
796 .then([initial, func](std::vector<Try<ItT>>& vals) mutable {
797 for (auto& val : vals) {
798 initial = func(std::move(initial),
799 // Either return a ItT&& or a Try<ItT>&& depending
800 // on the type of the argument of func.
801 val.template get<IsTry::value, Arg&&>());
807 template <class It, class T, class F, class ItT, class Arg>
808 typename std::enable_if<isFutureResult<F, T, Arg>::value, Future<T>>::type
809 reduce(It first, It last, T initial, F func) {
811 return makeFuture(std::move(initial));
814 typedef isTry<Arg> IsTry;
816 auto f = first->then([initial, func](Try<ItT>& head) mutable {
817 return func(std::move(initial),
818 head.template get<IsTry::value, Arg&&>());
821 for (++first; first != last; ++first) {
822 f = collectAll(f, *first).then([func](std::tuple<Try<T>, Try<ItT>>& t) {
823 return func(std::move(std::get<0>(t).value()),
824 // Either return a ItT&& or a Try<ItT>&& depending
825 // on the type of the argument of func.
826 std::get<1>(t).template get<IsTry::value, Arg&&>());
834 Future<T> Future<T>::within(Duration dur, Timekeeper* tk) {
835 return within(dur, TimedOut(), tk);
840 Future<T> Future<T>::within(Duration dur, E e, Timekeeper* tk) {
843 Context(E ex) : exception(std::move(ex)), promise(), token(false) {}
846 std::atomic<bool> token;
848 auto ctx = std::make_shared<Context>(std::move(e));
851 tk = folly::detail::getTimekeeperSingleton();
855 .then([ctx](Try<void> const& t) {
856 if (ctx->token.exchange(true) == false) {
857 if (t.hasException()) {
858 ctx->promise.setException(std::move(t.exception()));
860 ctx->promise.setException(std::move(ctx->exception));
865 this->then([ctx](Try<T>&& t) {
866 if (ctx->token.exchange(true) == false) {
867 ctx->promise.setTry(std::move(t));
871 return ctx->promise.getFuture();
875 Future<T> Future<T>::delayed(Duration dur, Timekeeper* tk) {
876 return collectAll(*this, futures::sleep(dur, tk))
877 .then([](std::tuple<Try<T>, Try<void>> tup) {
878 Try<T>& t = std::get<0>(tup);
879 return makeFuture<T>(std::move(t));
886 void waitImpl(Future<T>& f) {
887 // short-circuit if there's nothing to do
888 if (f.isReady()) return;
890 folly::fibers::Baton baton;
891 f = f.then([&](Try<T> t) {
893 return makeFuture(std::move(t));
897 // There's a race here between the return here and the actual finishing of
898 // the future. f is completed, but the setup may not have finished on done
899 // after the baton has posted.
900 while (!f.isReady()) {
901 std::this_thread::yield();
906 void waitImpl(Future<T>& f, Duration dur) {
907 // short-circuit if there's nothing to do
908 if (f.isReady()) return;
910 auto baton = std::make_shared<folly::fibers::Baton>();
911 f = f.then([baton](Try<T> t) {
913 return makeFuture(std::move(t));
916 // Let's preserve the invariant that if we did not timeout (timed_wait returns
917 // true), then the returned Future is complete when it is returned to the
918 // caller. We need to wait out the race for that Future to complete.
919 if (baton->timed_wait(dur)) {
920 while (!f.isReady()) {
921 std::this_thread::yield();
927 void waitViaImpl(Future<T>& f, DrivableExecutor* e) {
928 while (!f.isReady()) {
936 Future<T>& Future<T>::wait() & {
937 detail::waitImpl(*this);
942 Future<T>&& Future<T>::wait() && {
943 detail::waitImpl(*this);
944 return std::move(*this);
948 Future<T>& Future<T>::wait(Duration dur) & {
949 detail::waitImpl(*this, dur);
954 Future<T>&& Future<T>::wait(Duration dur) && {
955 detail::waitImpl(*this, dur);
956 return std::move(*this);
960 Future<T>& Future<T>::waitVia(DrivableExecutor* e) & {
961 detail::waitViaImpl(*this, e);
966 Future<T>&& Future<T>::waitVia(DrivableExecutor* e) && {
967 detail::waitViaImpl(*this, e);
968 return std::move(*this);
973 return std::move(wait().value());
977 inline void Future<void>::get() {
982 T Future<T>::get(Duration dur) {
985 return std::move(value());
992 inline void Future<void>::get(Duration dur) {
1002 T Future<T>::getVia(DrivableExecutor* e) {
1003 return std::move(waitVia(e).value());
1007 inline void Future<void>::getVia(DrivableExecutor* e) {
1012 Future<bool> Future<T>::willEqual(Future<T>& f) {
1013 return collectAll(*this, f).then([](const std::tuple<Try<T>, Try<T>>& t) {
1014 if (std::get<0>(t).hasValue() && std::get<1>(t).hasValue()) {
1015 return std::get<0>(t).value() == std::get<1>(t).value();
1024 Future<T> Future<T>::filter(F predicate) {
1025 auto p = folly::makeMoveWrapper(std::move(predicate));
1026 return this->then([p](T val) {
1027 T const& valConstRef = val;
1028 if (!(*p)(valConstRef)) {
1029 throw PredicateDoesNotObtain();
1038 Future<Z> chainHelper(Future<Z> f) {
1042 template <class Z, class F, class Fn, class... Callbacks>
1043 Future<Z> chainHelper(F f, Fn fn, Callbacks... fns) {
1044 return chainHelper<Z>(f.then(fn), fns...);
1048 template <class A, class Z, class... Callbacks>
1049 std::function<Future<Z>(Try<A>)>
1050 chain(Callbacks... fns) {
1051 MoveWrapper<Promise<A>> pw;
1052 MoveWrapper<Future<Z>> fw(chainHelper<Z>(pw->getFuture(), fns...));
1053 return [=](Try<A> t) mutable {
1054 pw->setTry(std::move(t));
1055 return std::move(*fw);
1059 template <class It, class F, class ItT, class Result>
1060 std::vector<Future<Result>> map(It first, It last, F func) {
1061 std::vector<Future<Result>> results;
1062 for (auto it = first; it != last; it++) {
1063 results.push_back(it->then(func));
1069 } // namespace folly
1071 // I haven't included a Future<T&> specialization because I don't forsee us
1072 // using it, however it is not difficult to add when needed. Refer to
1073 // Future<void> for guidance. std::future and boost::future code would also be