/* * This file is open source software, licensed to you under the terms * of the Apache License, Version 2.0 (the "License"). See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. You may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * Copyright (C) 2014 Cloudius Systems, Ltd. */ /** @file */ #ifndef CORE_FUTURE_UTIL_HH_ #define CORE_FUTURE_UTIL_HH_ #include "task.hh" #include "future.hh" #include "shared_ptr.hh" #include #include #include /// \cond internal extern __thread size_t task_quota; /// \endcond /// \addtogroup future-util /// @{ /// Run tasks in parallel (iterator version). /// /// Given a range [\c begin, \c end) of objects, run \c func on each \c *i in /// the range, and return a future<> that resolves when all the functions /// complete. \c func should return a future<> that indicates when it is /// complete. All invocations are performed in parallel. /// /// \param begin an \c InputIterator designating the beginning of the range /// \param end an \c InputIterator designating the end of the range /// \param func Function to apply to each element in the range (returning /// a \c future<>) /// \return a \c future<> that resolves when all the function invocations /// complete. If one or more return an exception, the return value /// contains one of the exceptions. template inline future<> parallel_for_each(Iterator begin, Iterator end, Func&& func) { auto ret = make_ready_future<>(); while (begin != end) { auto f = func(*begin++).then([ret = std::move(ret)] () mutable { return std::move(ret); }); ret = std::move(f); } return ret; } /// Run tasks in parallel (range version). /// /// Given a \c range of objects, apply \c func to each object /// in the range, and return a future<> that resolves when all /// the functions complete. \c func should return a future<> that indicates /// when it is complete. All invocations are performed in parallel. /// /// \param range A range of objects to iterate run \c func on /// \param func A callable, accepting reference to the range's /// \c value_type, and returning a \c future<>. /// \return a \c future<> that becomes ready when the entire range /// was processed. If one or more of the invocations of /// \c func returned an exceptional future, then the return /// value will contain one of those exceptions. template inline future<> parallel_for_each(Range&& range, Func&& func) { return parallel_for_each(std::begin(range), std::end(range), std::forward(func)); } // The AsyncAction concept represents an action which can complete later than // the actual function invocation. It is represented by a function which // returns a future which resolves when the action is done. /// \cond internal template static inline void do_until_continued(StopCondition&& stop_cond, AsyncAction&& action, promise<> p) { while (!stop_cond()) { try { auto&& f = action(); if (!f.available()) { f.then_wrapped([action = std::forward(action), stop_cond = std::forward(stop_cond), p = std::move(p)](std::result_of_t fut) mutable { try { fut.get(); do_until_continued(stop_cond, std::forward(action), std::move(p)); } catch(...) { p.set_exception(std::current_exception()); } }); return; } if (f.failed()) { f.forward_to(std::move(p)); return; } } catch (...) { p.set_exception(std::current_exception()); return; } } p.set_value(); } /// \endcond enum class stop_iteration { no, yes }; /// Invokes given action until it fails or the function requests iteration to stop by returning /// \c stop_iteration::yes. /// /// \param action a callable taking no arguments, returning a future. Will /// be called again as soon as the future resolves, unless the /// future fails, action throws, or it resolves with \c stop_iteration::yes. /// If \c action is an r-value it can be moved in the middle of iteration. /// \return a ready future if we stopped successfully, or a failed future if /// a call to to \c action failed. template static inline future<> repeat(AsyncAction&& action) { using futurator = futurize>; static_assert(std::is_same, typename futurator::type>::value, "bad AsyncAction signature"); while (task_quota) { try { auto f = futurator::apply(action); if (!f.available()) { return f.then([action = std::forward(action)] (stop_iteration stop) mutable { if (stop == stop_iteration::yes) { return make_ready_future<>(); } else { return repeat(std::forward(action)); } }); } if (f.get0() == stop_iteration::yes) { return make_ready_future<>(); } } catch (...) { return make_exception_future<>(std::current_exception()); } --task_quota; } promise<> p; auto f = p.get_future(); schedule(make_task([action = std::forward(action), p = std::move(p)] () mutable { repeat(std::forward(action)).forward_to(std::move(p)); })); return f; } /// Invokes given action until it fails or given condition evaluates to true. /// /// \param stop_cond a callable taking no arguments, returning a boolean that /// evalutes to true when you don't want to call \c action /// any longer /// \param action a callable taking no arguments, returning a future<>. Will /// be called again as soon as the future resolves, unless the /// future fails, or \c stop_cond returns \c true. /// \return a ready future if we stopped successfully, or a failed future if /// a call to to \c action failed. template static inline future<> do_until(StopCondition&& stop_cond, AsyncAction&& action) { promise<> p; auto f = p.get_future(); do_until_continued(std::forward(stop_cond), std::forward(action), std::move(p)); return f; } /// Invoke given action until it fails. /// /// Calls \c action repeatedly until it returns a failed future. /// /// \param action a callable taking no arguments, returning a \c future<> /// that becomes ready when you wish it to be called again. /// \return a future<> that will resolve to the first failure of \c action template static inline future<> keep_doing(AsyncAction&& action) { return repeat([action = std::forward(action)] () mutable { return action().then([] { return stop_iteration::no; }); }); } /// Call a function for each item in a range, sequentially (iterator version). /// /// For each item in a range, call a function, waiting for the previous /// invocation to complete before calling the next one. /// /// \param begin an \c InputIterator designating the beginning of the range /// \param end an \c InputIterator designating the endof the range /// \param action a callable, taking a reference to objects from the range /// as a parameter, and returning a \c future<> that resolves /// when it is acceptable to process the next item. /// \return a ready future on success, or the first failed future if /// \c action failed. template static inline future<> do_for_each(Iterator begin, Iterator end, AsyncAction&& action) { if (begin == end) { return make_ready_future<>(); } while (true) { auto f = action(*begin++); if (begin == end) { return f; } if (!f.available()) { return std::move(f).then([action = std::forward(action), begin = std::move(begin), end = std::move(end)] () mutable { return do_for_each(std::move(begin), std::move(end), std::forward(action)); }); } if (f.failed()) { return std::move(f); } } } /// Call a function for each item in a range, sequentially (range version). /// /// For each item in a range, call a function, waiting for the previous /// invocation to complete before calling the next one. /// /// \param range an \c Range object designating input values /// \param action a callable, taking a reference to objects from the range /// as a parameter, and returning a \c future<> that resolves /// when it is acceptable to process the next item. /// \return a ready future on success, or the first failed future if /// \c action failed. template static inline future<> do_for_each(Container& c, AsyncAction&& action) { return do_for_each(std::begin(c), std::end(c), std::forward(action)); } /// \cond internal inline future> when_all() { return make_ready_future>(); } // gcc can't capture a parameter pack, so we need to capture // a tuple and use apply. But apply cannot accept an overloaded // function pointer as its first parameter, so provide this instead. struct do_when_all { template future> operator()(Future&&... fut) const { return when_all(std::move(fut)...); } }; /// \endcond /// Wait for many futures to complete, capturing possible errors (variadic version). /// /// Given a variable number of futures as input, wait for all of them /// to resolve (either successfully or with an exception), and return /// them as a tuple so individual values or exceptions can be examined. /// /// \param fut the first future to wait for /// \param rest more futures to wait for /// \return an \c std::tuple<> of all the futures in the input; when /// ready, all contained futures will be ready as well. template inline future, Rest...>> when_all(future&& fut, Rest&&... rest) { using Future = future; return fut.then_wrapped( [rest = std::make_tuple(std::move(rest)...)] (Future&& fut) mutable { return apply(do_when_all(), std::move(rest)).then_wrapped( [fut = std::move(fut)] (future>&& rest) mutable { return make_ready_future>( std::tuple_cat(std::make_tuple(std::move(fut)), std::get<0>(rest.get()))); }); }); } /// \cond internal template inline size_t when_all_estimate_vector_capacity(Iterator begin, Iterator end, IteratorCategory category) { // For InputIterators we can't estimate needed capacity return 0; } template inline size_t when_all_estimate_vector_capacity(Iterator begin, Iterator end, std::forward_iterator_tag category) { // May be linear time below random_access_iterator_tag, but still better than reallocation return std::distance(begin, end); } // Internal function for when_all(). template inline future> complete_when_all(std::vector&& futures, typename std::vector::iterator pos) { // If any futures are already ready, skip them. while (pos != futures.end() && pos->available()) { ++pos; } // Done? if (pos == futures.end()) { return make_ready_future>(std::move(futures)); } // Wait for unready future, store, and continue. return pos->then_wrapped([futures = std::move(futures), pos] (auto fut) mutable { *pos++ = std::move(fut); return complete_when_all(std::move(futures), pos); }); } /// \endcond /// Wait for many futures to complete, capturing possible errors (iterator version). /// /// Given a range of futures as input, wait for all of them /// to resolve (either successfully or with an exception), and return /// them as a \c std::vector so individual values or exceptions can be examined. /// /// \param begin an \c InputIterator designating the beginning of the range of futures /// \param end an \c InputIterator designating the end of the range of futures /// \return an \c std::vector<> of all the futures in the input; when /// ready, all contained futures will be ready as well. template inline future::value_type>> when_all(FutureIterator begin, FutureIterator end) { using itraits = std::iterator_traits; std::vector ret; ret.reserve(when_all_estimate_vector_capacity(begin, end, typename itraits::iterator_category())); // Important to invoke the *begin here, in case it's a function iterator, // so we launch all computation in parallel. std::move(begin, end, std::back_inserter(ret)); return complete_when_all(std::move(ret), ret.begin()); } template struct reducer_with_get_traits { using result_type = decltype(std::declval().get()); using future_type = future; static future_type maybe_call_get(future<> f, lw_shared_ptr r) { return f.then([r = std::move(r)] () mutable { return make_ready_future(std::move(*r).get()); }); } }; template struct reducer_traits { using future_type = future<>; static future_type maybe_call_get(future<> f, lw_shared_ptr r) { return f.then([r = std::move(r)] {}); } }; template struct reducer_traits().get(), void())> : public reducer_with_get_traits {}; // @Mapper is a callable which transforms values from the iterator range // into a future. @Reducer is an object which can be called with T as // parameter and yields a future<>. It may have a get() method which returns // a value of type U which holds the result of reduction. This value is wrapped // in a future and returned by this function. If the reducer has no get() method // then this function returns future<>. // // TODO: specialize for non-deferring reducer template inline auto map_reduce(Iterator begin, Iterator end, Mapper&& mapper, Reducer&& r) -> typename reducer_traits::future_type { auto r_ptr = make_lw_shared(std::forward(r)); future<> ret = make_ready_future<>(); while (begin != end) { ret = mapper(*begin++).then([ret = std::move(ret), r_ptr] (auto value) mutable { return ret.then([value = std::move(value), r_ptr] () mutable { return (*r_ptr)(std::move(value)); }); }); } return reducer_traits::maybe_call_get(std::move(ret), r_ptr); } /// Asynchronous map/reduce transformation. /// /// Given a range of objects, an asynchronous unary function /// operating on these objects, an initial value, and a /// binary function for reducing, map_reduce() will /// transform each object in the range, then apply /// the the reducing function to the result. /// /// Example: /// /// Calculate the total size of several files: /// /// \code /// map_reduce(files.begin(), files.end(), /// std::mem_fn(file::size), /// size_t(0), /// std::plus()) /// \endcode /// /// Requirements: /// - Iterator: an InputIterator. /// - Mapper: unary function taking Iterator::value_type and producing a future<...>. /// - Initial: any value type /// - Reduce: a binary function taking two Initial values and returning an Initial /// /// Return type: /// - future /// /// \param begin beginning of object range to operate on /// \param end end of object range to operate on /// \param mapper map function to call on each object, returning a future /// \param initial initial input value to reduce function /// \param reduce binary function for merging two result values from \c mapper /// /// \return equivalent to \c reduce(reduce(initial, mapper(obj0)), mapper(obj1)) ... template inline future map_reduce(Iterator begin, Iterator end, Mapper&& mapper, Initial initial, Reduce reduce) { struct state { Initial result; Reduce reduce; }; auto s = make_lw_shared(state{std::move(initial), std::move(reduce)}); future<> ret = make_ready_future<>(); while (begin != end) { ret = mapper(*begin++).then([s = s.get(), ret = std::move(ret)] (auto&& value) mutable { s->result = s->reduce(std::move(s->result), std::move(value)); return std::move(ret); }); } return ret.then([s] { return make_ready_future(std::move(s->result)); }); } /// Asynchronous map/reduce transformation (range version). /// /// Given a range of objects, an asynchronous unary function /// operating on these objects, an initial value, and a /// binary function for reducing, map_reduce() will /// transform each object in the range, then apply /// the the reducing function to the result. /// /// Example: /// /// Calculate the total size of several files: /// /// \code /// std::vector files = ...; /// map_reduce(files, /// std::mem_fn(file::size), /// size_t(0), /// std::plus()) /// \endcode /// /// Requirements: /// - Iterator: an InputIterator. /// - Mapper: unary function taking Iterator::value_type and producing a future<...>. /// - Initial: any value type /// - Reduce: a binary function taking two Initial values and returning an Initial /// /// Return type: /// - future /// /// \param range object range to operate on /// \param mapper map function to call on each object, returning a future /// \param initial initial input value to reduce function /// \param reduce binary function for merging two result values from \c mapper /// /// \return equivalent to \c reduce(reduce(initial, mapper(obj0)), mapper(obj1)) ... template inline future map_reduce(Range&& range, Mapper&& mapper, Initial initial, Reduce reduce) { return map_reduce(std::begin(range), std::end(range), std::forward(mapper), std::move(initial), std::move(reduce)); } // Implements @Reducer concept. Calculates the result by // adding elements to the accumulator. template class adder { private: Result _result; public: future<> operator()(const Addend& value) { _result += value; return make_ready_future<>(); } Result get() && { return std::move(_result); } }; static inline future<> now() { return make_ready_future<>(); } /// @} #endif /* CORE_FUTURE_UTIL_HH_ */