From c5077ce99b85f6624f7d81305544fbe68e43b340 Mon Sep 17 00:00:00 2001 From: Gleb Natapov Date: Sun, 31 May 2015 15:22:48 +0300 Subject: [PATCH 01/11] tests: add missing header --- tests/thread_context_switch.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/thread_context_switch.cc b/tests/thread_context_switch.cc index c3b19751b8..f462aa22d5 100644 --- a/tests/thread_context_switch.cc +++ b/tests/thread_context_switch.cc @@ -20,6 +20,7 @@ * Copyright (C) 2015 Cloudius Systems, Ltd. */ +#include #include "core/thread.hh" #include "core/semaphore.hh" #include "core/app-template.hh" From 2877364947cae3b7e909438794313f3cc7027520 Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sun, 31 May 2015 15:49:05 +0300 Subject: [PATCH 02/11] future: separate task class into its own header tasks are a lower-level concept than future/promise, so move them to their own header. --- core/future-util.hh | 1 + core/future.hh | 26 ++---------------------- core/reactor.cc | 1 + core/task.hh | 48 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 core/task.hh diff --git a/core/future-util.hh b/core/future-util.hh index a40f43ed67..4291091f58 100644 --- a/core/future-util.hh +++ b/core/future-util.hh @@ -25,6 +25,7 @@ #ifndef CORE_FUTURE_UTIL_HH_ #define CORE_FUTURE_UTIL_HH_ +#include "task.hh" #include "future.hh" #include "shared_ptr.hh" #include diff --git a/core/future.hh b/core/future.hh index 2dde9c8b88..1c8243e2a2 100644 --- a/core/future.hh +++ b/core/future.hh @@ -16,13 +16,14 @@ * under the License. */ /* - * Copyright (C) 2014 Cloudius Systems, Ltd. + * Copyright (C) 2015 Cloudius Systems, Ltd. */ #ifndef FUTURE_HH_ #define FUTURE_HH_ #include "apply.hh" +#include "task.hh" #include #include #include @@ -54,31 +55,8 @@ future make_ready_future(A&&... value); template future make_exception_future(std::exception_ptr value) noexcept; -class task { -public: - virtual ~task() noexcept {} - virtual void run() noexcept = 0; -}; - -void schedule(std::unique_ptr t); void engine_exit(std::exception_ptr eptr = {}); -template -class lambda_task final : public task { - Func _func; -public: - lambda_task(const Func& func) : _func(func) {} - lambda_task(Func&& func) : _func(std::move(func)) {} - virtual void run() noexcept override { _func(); } -}; - -template -inline -std::unique_ptr -make_task(Func&& func) { - return std::make_unique>(std::forward(func)); -} - void report_failed_future(std::exception_ptr ex); // diff --git a/core/reactor.cc b/core/reactor.cc index bfdd629c22..8e5992a2d0 100644 --- a/core/reactor.cc +++ b/core/reactor.cc @@ -20,6 +20,7 @@ */ #include +#include "task.hh" #include "reactor.hh" #include "memory.hh" #include "core/posix.hh" diff --git a/core/task.hh b/core/task.hh new file mode 100644 index 0000000000..80b0db506e --- /dev/null +++ b/core/task.hh @@ -0,0 +1,48 @@ +/* + * 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) 2015 Cloudius Systems, Ltd. + */ + +#pragma once + +#include + +class task { +public: + virtual ~task() noexcept {} + virtual void run() noexcept = 0; +}; + +void schedule(std::unique_ptr t); + +template +class lambda_task final : public task { + Func _func; +public: + lambda_task(const Func& func) : _func(func) {} + lambda_task(Func&& func) : _func(std::move(func)) {} + virtual void run() noexcept override { _func(); } +}; + +template +inline +std::unique_ptr +make_task(Func&& func) { + return std::make_unique>(std::forward(func)); +} From aa519f86a860a95548be7ce97b128228bd74e544 Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sun, 31 May 2015 17:29:31 +0300 Subject: [PATCH 03/11] future: user-level documentation --- core/future.hh | 226 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 3 deletions(-) diff --git a/core/future.hh b/core/future.hh index 1c8243e2a2..153ce5fd89 100644 --- a/core/future.hh +++ b/core/future.hh @@ -29,6 +29,37 @@ #include #include + +/// \defgroup future Futures and Promises +/// +/// \brief +/// Futures and promises are the basic tools for asynchronous +/// programming in seastar. A future represents a result that +/// may not have been computed yet, for example a buffer that +/// is being read from the disk, or the result of a function +/// that is executed on another cpu. A promise object allows +/// the future to be eventually resolved by assigning it a value. +/// +/// \brief +/// Another way to look at futures and promises are as the reader +/// and writer sides, respectively, of a single-item, single use +/// queue. You read from the future, and write to the promise, +/// and the system takes care that it works no matter what the +/// order of operations is. +/// +/// \brief +/// The normal way of working with futures is to chain continuations +/// to them. A continuation is a block of code (usually a lamba) +/// that is called when the future is assigned a value (the future +/// is resolved); the continuation can then access the actual value. +/// + +/// \defgroup future-util Future Utilities +/// +/// \brief +/// These utilities are provided to help perform operations on futures. + + namespace seastar { class thread_context; @@ -43,21 +74,39 @@ void switch_out(thread_context* from); } + +/// \addtogroup future +/// @{ + template class promise; template class future; +/// \brief Creates a \ref future in an available, value state. +/// +/// Creates a \ref future object that is already resolved. This +/// is useful when it is determined that no I/O needs to be performed +/// to perform a computation (for example, because the data is cached +/// in some buffer). template future make_ready_future(A&&... value); +/// \brief Creates a \ref future in an available, failed state. +/// +/// Creates a \ref future object that is already resolved in a failed +/// state. This no I/O needs to be performed to perform a computation +/// (for example, because the connection is closed and we cannot read +/// from it). template future make_exception_future(std::exception_ptr value) noexcept; +/// \cond internal void engine_exit(std::exception_ptr eptr = {}); void report_failed_future(std::exception_ptr ex); +/// \endcond // // A future/promise pair maintain one logical value (a future_state). @@ -77,6 +126,7 @@ void report_failed_future(std::exception_ptr ex); // called) or due to the promise or future being mobved around. // +/// \cond internal template struct future_state { static constexpr bool move_noexcept = std::is_nothrow_move_constructible>::value; @@ -277,7 +327,11 @@ struct continuation final : task { future_state _state; Func _func; }; +/// \endcond +/// \brief promise - allows a future value to be made available at a later time. +/// +/// template class promise { future* _future = nullptr; @@ -287,7 +341,12 @@ class promise { static constexpr bool move_noexcept = future_state::move_noexcept; static constexpr bool copy_noexcept = future_state::copy_noexcept; public: + /// \brief Constructs an empty \c promise. + /// + /// Creates promise with no associated future yet (see get_future()). promise() noexcept : _state(&_local_state) {} + + /// \brief Moves a \c promise object. promise(promise&& x) noexcept(move_noexcept) : _future(x._future), _state(x._state), _task(std::move(x._task)) { if (_state == &x._local_state) { _state = &_local_state; @@ -310,24 +369,56 @@ public: return *this; } void operator=(const promise&) = delete; + + /// \brief Gets the promise's associated future. + /// + /// The future and promise will be remember each other, even if either or + /// both are moved. When \c set_value() or \c set_exception() are called + /// on the promise, the future will be become ready, and if a continuation + /// was attached to the future, it will run. future get_future() noexcept; + + /// \brief Sets the promise's value (as tuple; by copying) + /// + /// Copies the tuple argument and makes it available to the associated + /// future. May be called either before or after \c get_future(). void set_value(const std::tuple& result) noexcept(copy_noexcept) { _state->set(result); make_ready(); } + + /// \brief Sets the promises value (as tuple; by moving) + /// + /// Moves the tuple argument and makes it available to the associated + /// future. May be called either before or after \c get_future(). void set_value(std::tuple&& result) noexcept(move_noexcept) { _state->set(std::move(result)); make_ready(); } + + /// \brief Sets the promises value (variadic) + /// + /// Forwards the arguments and makes them available to the associated + /// future. May be called either before or after \c get_future(). template void set_value(A&&... a) noexcept { _state->set(std::forward(a)...); make_ready(); } + + /// \brief Marks the promise as failed + /// + /// Forwards the exception argument to the future and makes it + /// available. May be called either before or after \c get_future(). void set_exception(std::exception_ptr ex) noexcept { _state->set_exception(std::move(ex)); make_ready(); } + + /// \brief Marks the promise as failed + /// + /// Forwards the exception argument to the future and makes it + /// available. May be called either before or after \c get_future(). template void set_exception(Exception&& e) noexcept { set_exception(make_exception_ptr(std::forward(e))); @@ -348,10 +439,29 @@ private: friend class future; }; +/// \brief Specialization of \c promise +/// +/// This is an alias for \c promise<>, for generic programming purposes. +/// For example, You may have a \c promise where \c T can legally be +/// \c void. template<> class promise : public promise<> {}; +/// @} + +/// \addtogroup future-util +/// @{ + + +/// \brief Check whether a type is a future +/// +/// This is a type trait evaluating to \c true if the given type is a +/// future. +/// template struct is_future : std::false_type {}; + +/// \cond internal +/// \addtogroup future-util template struct is_future> : std::true_type {}; struct ready_future_marker {}; @@ -359,25 +469,34 @@ struct ready_future_from_tuple_marker {}; struct exception_future_marker {}; extern __thread size_t future_avail_count; +/// \endcond -// Converts a type to a future type, if it isn't already. -// -// Result in member type 'type'. + +/// \brief Converts a type to a future type, if it isn't already. +/// +/// \return Result in member type 'type'. template struct futurize; template struct futurize { + /// If \c T is a future, \c T; otherwise \c future using type = future; + /// The promise type associated with \c type. using promise_type = promise; + /// Apply a function to an argument list (expressed as a tuple) + /// and return the result, as a future (if it wasn't already). template static inline type apply(Func&& func, std::tuple&& args); + /// Apply a function to an argument list + /// and return the result, as a future (if it wasn't already). template static inline type apply(Func&& func, FuncArgs&&... args); }; +/// \cond internal template <> struct futurize { using type = future<>; @@ -401,11 +520,30 @@ struct futurize> { template static inline type apply(Func&& func, FuncArgs&&... args); }; +/// \endcond // Converts a type to a future type, if it isn't already. template using futurize_t = typename futurize::type; +/// @} + +/// \addtogroup future +/// @{ + +/// \brief A representation of a possibly not-yet-computed value. +/// +/// A \c future represents a value that has not yet been computed +/// (an asynchronous computation). It can be in one of several +/// states: +/// - unavailable: the computation has not been completed yet +/// - value: the computation has been completed successfully and a +/// value is available. +/// - failed: the computation completed with an exception. +/// +/// methods in \c future allow querying the state and, most importantly, +/// scheduling a \c continuation to be executed when the future becomes +/// available. Only one such continuation may be scheduled. template class future { promise* _promise; @@ -470,8 +608,11 @@ private: } public: + /// \brief The data type carried by the future. using value_type = std::tuple; + /// \brief The data type carried by the future. using promise_type = promise; + /// \brief Moves the future into a new object. future(future&& x) noexcept(move_noexcept) : _promise(x._promise) { if (!_promise) { _local_state = std::move(x._local_state); @@ -499,6 +640,15 @@ public: report_failed_future(state()->get_exception()); } } + /// \brief gets the value returned by the computation + /// + /// Requires that the future be available. If the value + /// was computed successfully, it is returned (as an + /// \c std::tuple). Otherwise, an exception is thrown. + /// + /// If get() is called in a \ref seastar::thread context, + /// then it need not be available; instead, the thread will + /// be paused until the future becomes available. std::tuple get() { if (!state()->available()) { wait(); @@ -506,6 +656,7 @@ public: return state()->get(); } + /// \cond internal void wait() { auto thread = seastar::thread_impl::get(); assert(thread); @@ -515,26 +666,73 @@ public: }); seastar::thread_impl::switch_out(thread); } + /// \endcond + /// \brief Checks whether the future is available. + /// + /// \return \c true if the future has a value, or has failed. bool available() noexcept { return state()->available(); } + /// \brief Checks whether the future has failed. + /// + /// \return \c true if the future is availble and has failed. bool failed() noexcept { return state()->failed(); } + /// \brief Schedule a block of code to run when the future is ready. + /// + /// Schedules a function (often a lambda) to run when the future becomes + /// available. The function is called with the result of this future's + /// computation as parameters. The return value of the function becomes + /// the return value of then(), itself as a future; this allows then() + /// calls to be chained. + /// + /// If the future failed, the function is not called, and the exception + /// is propagated into the return value of then(). + /// + /// \param func - function to be called when the future becomes available, + /// unless it has failed. + /// \return a \c future representing the return value of \c func, applied + /// to the eventual value of this future. template futurize_t> then(Func&& func) noexcept { return then>(std::forward(func), [] (future_state&& state) { return state.get(); }); } + /// \brief Schedule a block of code to run when the future is ready, allowing + /// for exception handling. + /// + /// Schedules a function (often a lambda) to run when the future becomes + /// available. The function is called with the this future as a parameter; + /// it will be in an available state. The return value of the function becomes + /// the return value of then_wrapped(), itself as a future; this allows + /// then_wrapped() calls to be chained. + /// + /// Unlike then(), the function will be called for both value and exceptional + /// futures. + /// + /// \param func - function to be called when the future becomes available, + /// \return a \c future representing the return value of \c func, applied + /// to the eventual value of this future. template futurize_t)>> then_wrapped(Func&& func) noexcept { return then)>>(std::forward(func), [] (future_state&& state) { return future(std::move(state)); }); } + /// \brief Satisfy some \ref promise object with this future as a result. + /// + /// Arranges so that when this future is resolve, it will be used to + /// satisfy an unrelated promise. This is similar to scheduling a + /// continuation that moves the result of this future into the promise + /// (using promise::set_value() or promise::set_exception(), except + /// that it is more efficient. + /// + /// \param pr a promise that will be fulfilled with the results of this + /// future. void forward_to(promise&& pr) noexcept { if (state()->available()) { state()->forward_to(pr); @@ -566,6 +764,10 @@ public: }); } + /// \brief Terminate the program if this future fails. + /// + /// Terminates the entire program is this future resolves + /// to an exception. Use with caution. future<> or_terminate() noexcept { return then_wrapped([] (auto&& f) { try { @@ -576,10 +778,15 @@ public: }); } + /// \brief Discards the value carried by this future. + /// + /// Converts the future into a no-value \c future<>, by + /// ignorting any result. Exceptions are propagated unchanged. future<> discard_result() noexcept { return then([] (T&&...) {}); } + /// \cond internal template friend class promise; template @@ -588,6 +795,7 @@ public: friend future make_exception_future(std::exception_ptr ex) noexcept; template friend future make_exception_future(Exception&& ex) noexcept; + /// \endcond }; inline @@ -648,12 +856,22 @@ future make_exception_future(std::exception_ptr ex) noexcept { return future(exception_future_marker(), std::move(ex)); } +/// \brief Creates a \ref future in an available, failed state. +/// +/// Creates a \ref future object that is already resolved in a failed +/// state. This no I/O needs to be performed to perform a computation +/// (for example, because the connection is closed and we cannot read +/// from it). template inline future make_exception_future(Exception&& ex) noexcept { return make_exception_future(std::make_exception_ptr(std::forward(ex))); } +/// @} + +/// \cond internal + template template typename futurize::type futurize::apply(Func&& func, std::tuple&& args) { @@ -690,4 +908,6 @@ typename futurize>::type futurize>::apply(Func&& return func(std::forward(args)...); } +/// \endcond + #endif /* FUTURE_HH_ */ From 65ff797ec177a8fd20c935e02bdada825578e606 Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sun, 31 May 2015 17:51:53 +0300 Subject: [PATCH 04/11] doc: add main doxygen page --- core/seastar.hh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/seastar.hh b/core/seastar.hh index 92622fd6bb..eae2c77f25 100644 --- a/core/seastar.hh +++ b/core/seastar.hh @@ -22,6 +22,16 @@ #pragma once +/// \mainpage +/// +/// Seastar is a high performance C++ application framework for high +/// concurrency server applications. +/// +/// Please see: +/// - \ref future Documentation on futures and promises, which are +/// the seastar building blocks. + + #include "sstring.hh" #include "future.hh" From abaad55a66e5baaec6d9c20c077633d704e2a44a Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sun, 31 May 2015 18:10:54 +0300 Subject: [PATCH 05/11] future: fix documentation errors Noted by Nadav. --- core/future.hh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/future.hh b/core/future.hh index 153ce5fd89..a097963291 100644 --- a/core/future.hh +++ b/core/future.hh @@ -49,7 +49,7 @@ /// /// \brief /// The normal way of working with futures is to chain continuations -/// to them. A continuation is a block of code (usually a lamba) +/// to them. A continuation is a block of code (usually a lamdba) /// that is called when the future is assigned a value (the future /// is resolved); the continuation can then access the actual value. /// @@ -96,9 +96,9 @@ future make_ready_future(A&&... value); /// \brief Creates a \ref future in an available, failed state. /// /// Creates a \ref future object that is already resolved in a failed -/// state. This no I/O needs to be performed to perform a computation -/// (for example, because the connection is closed and we cannot read -/// from it). +/// state. This is useful when no I/O needs to be performed to perform +/// a computation (for example, because the connection is closed and +/// we cannot read from it). template future make_exception_future(std::exception_ptr value) noexcept; From e83c39212076e25a5a0e802b9950653da4d12e9e Mon Sep 17 00:00:00 2001 From: Amnon Heiman Date: Thu, 28 May 2015 15:16:36 +0300 Subject: [PATCH 06/11] http: Support file transformers This runs the file transformer on a file before returning the result. This is used for templating support. Signed-off-by: Amnon Heiman --- http/file_handler.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/http/file_handler.cc b/http/file_handler.cc index 8c0717b6d7..78aa3fe4dc 100644 --- a/http/file_handler.cc +++ b/http/file_handler.cc @@ -98,10 +98,13 @@ future> file_interaction_handler::read( sstring extension = get_extension(file_name); rep->set_content_type(extension); return engine().open_file_dma(file_name, open_flags::ro).then( - [rep = std::move(rep)](file f) mutable { + [rep = std::move(rep), extension, this, req = std::move(req)](file f) mutable { std::shared_ptr r = std::make_shared(std::move(f), std::move(rep)); - return r->is.consume(*r).then([r]() { + return r->is.consume(*r).then([r, extension, this, req = std::move(req)]() { + if (transformer != nullptr) { + transformer->transform(r->_rep->_content, *req, extension); + } r->_rep->done(); return make_ready_future>(std::move(r->_rep)); }); From 3a63706f976ae0b5315131036ecd4ce8d5d6e3ad Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sun, 31 May 2015 19:00:10 +0300 Subject: [PATCH 07/11] future: document more future-util functions --- core/future-util.hh | 110 +++++++++++++++++++++++++++++++++++++------- core/seastar.hh | 1 + 2 files changed, 94 insertions(+), 17 deletions(-) diff --git a/core/future-util.hh b/core/future-util.hh index 4291091f58..96a5d677d2 100644 --- a/core/future-util.hh +++ b/core/future-util.hh @@ -32,14 +32,28 @@ #include #include +/// \cond internal extern __thread size_t task_quota; +/// \endcond -// parallel_for_each - run tasks in parallel -// -// Given a range [@begin, @end) of objects, run func(*i) for each i in -// the range, and return a future<> that resolves when all the functions -// complete. @func should return a future<> that indicates when it is -// complete. + +/// \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<> @@ -54,12 +68,12 @@ parallel_for_each(Iterator begin, Iterator end, Func&& func) { return ret; } -/// \brief parallel_for_each - run tasks in parallel +/// Run tasks in parallel (range version). /// -/// Given a range [\c begin, \c end) of objects, run \c func(object) for -/// each object in the range, and return a future<> that resolves when all -/// the functions complete. @func should return a future<> that indicates -/// when it is complete. +/// 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 @@ -80,6 +94,7 @@ parallel_for_each(Range&& range, Func&& func) { // 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) { @@ -111,8 +126,18 @@ void do_until_continued(StopCondition&& stop_cond, AsyncAction&& action, promise p.set_value(); } +/// \endcond -// Invokes given action until it fails or given condition evaluates to true. +/// 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 to 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) { @@ -123,7 +148,13 @@ future<> do_until(StopCondition&& stop_cond, AsyncAction&& action) { return f; } -// Invoke given action until it fails. +/// 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) { @@ -151,6 +182,18 @@ future<> keep_doing(AsyncAction&& action) { return f; } +/// 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) { @@ -174,12 +217,24 @@ future<> do_for_each(Iterator begin, Iterator end, AsyncAction&& action) { } } +/// 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() { @@ -195,7 +250,18 @@ struct do_when_all { 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...>> @@ -211,6 +277,7 @@ when_all(future&& fut, Rest&&... rest) { }); } +/// \cond internal template inline size_t @@ -246,11 +313,18 @@ complete_when_all(std::vector&& futures, typename std::vector::i return complete_when_all(std::move(futures), pos); }); } +/// \endcond -// Given a range of futures (denoted by an iterator pair), wait for -// all of them to be available, and return a future containing a -// vector of each input future (in available state, with either a -// value or exception stored). +/// 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>> @@ -365,4 +439,6 @@ future<> now() { return make_ready_future<>(); } +/// @} + #endif /* CORE_FUTURE_UTIL_HH_ */ diff --git a/core/seastar.hh b/core/seastar.hh index eae2c77f25..5cc2bb2e1a 100644 --- a/core/seastar.hh +++ b/core/seastar.hh @@ -30,6 +30,7 @@ /// Please see: /// - \ref future Documentation on futures and promises, which are /// the seastar building blocks. +/// - \ref future-util Utililty functions for working with futures #include "sstring.hh" From 3d4314b7e4579f90e3860497922290a0e718cc87 Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sun, 31 May 2015 19:08:16 +0300 Subject: [PATCH 08/11] doc: disambigute class future from the future documentation module --- core/future.hh | 6 +++--- core/seastar.hh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/future.hh b/core/future.hh index a097963291..625fc35cc2 100644 --- a/core/future.hh +++ b/core/future.hh @@ -30,7 +30,7 @@ #include -/// \defgroup future Futures and Promises +/// \defgroup future-module Futures and Promises /// /// \brief /// Futures and promises are the basic tools for asynchronous @@ -75,7 +75,7 @@ void switch_out(thread_context* from); } -/// \addtogroup future +/// \addtogroup future-module /// @{ template @@ -528,7 +528,7 @@ using futurize_t = typename futurize::type; /// @} -/// \addtogroup future +/// \addtogroup future-module /// @{ /// \brief A representation of a possibly not-yet-computed value. diff --git a/core/seastar.hh b/core/seastar.hh index 5cc2bb2e1a..7fdfa0b15d 100644 --- a/core/seastar.hh +++ b/core/seastar.hh @@ -28,7 +28,7 @@ /// concurrency server applications. /// /// Please see: -/// - \ref future Documentation on futures and promises, which are +/// - \ref future-module Documentation on futures and promises, which are /// the seastar building blocks. /// - \ref future-util Utililty functions for working with futures From 23d874f7864ba4297f17f591fb31215bd661bccc Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Mon, 1 Jun 2015 11:35:37 +0300 Subject: [PATCH 09/11] doc: low-level memory management --- core/memory.cc | 5 +++++ core/memory.hh | 36 +++++++++++++++++++++++++++++++++++- core/seastar.hh | 1 + 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/core/memory.cc b/core/memory.cc index 3d1c916148..d60ad295fd 100644 --- a/core/memory.cc +++ b/core/memory.cc @@ -19,6 +19,9 @@ * Copyright (C) 2014 Cloudius Systems, Ltd. */ + +/// \cond internal + // // Seastar memory allocator // @@ -1198,3 +1201,5 @@ void operator delete[](void* ptr, with_alignment wa) { } #endif + +/// \endcond diff --git a/core/memory.hh b/core/memory.hh index 0c1b8a2e1e..22bc1e6e31 100644 --- a/core/memory.hh +++ b/core/memory.hh @@ -27,8 +27,31 @@ #include #include + +/// \defgroup memory-module Memory management +/// +/// Functions and classes for managing memory. +/// +/// Memory management in seastar consists of the following: +/// +/// - Low-level memory management in the \ref memory namespace. +/// - Various smart pointers: \ref shared_ptr, \ref lw_shared_ptr, +/// and \ref foreign_ptr. +/// - zero-copy support: \ref temporary_buffer and \ref deleter. + +/// Low-level memory management support +/// +/// The \c memory namespace provides functions and classes for interfacing +/// with the seastar memory allocator. +/// +/// The seastar memory allocator splits system memory into a pool per +/// logical core (lcore). Memory allocated one an lcore should be freed +/// on the same lcore; failing to do so carries a severe performance +/// penalty. It is possible to share memory with another core, but this +/// should be limited to avoid cache coherency traffic. namespace memory { +/// \cond internal // TODO: Use getpagesize() in order to learn a size of a system PAGE. static constexpr size_t page_bits = 12; static constexpr size_t page_size = 1 << page_bits; // 4K @@ -53,11 +76,12 @@ public: // Returns @true if any work was actually performed. bool drain_cross_cpu_freelist(); + // We don't want the memory code calling back into the rest of // the system, so allow the rest of the system to tell the memory // code how to initiate reclaim. // -// When memory is low, calling hook(fn) will result in fn being called +// When memory is low, calling \c hook(fn) will result in fn being called // in a safe place wrt. allocations. void set_reclaim_hook( std::function)> hook); @@ -78,9 +102,14 @@ struct translation { // translation is not known. translation translate(const void* addr, size_t size); +/// \endcond + class statistics; + +/// Capture a snapshot of memory allocation statistics for this lcore. statistics stats(); +/// Memory allocation statistics. class statistics { uint64_t _mallocs; uint64_t _frees; @@ -89,9 +118,14 @@ private: statistics(uint64_t mallocs, uint64_t frees, uint64_t cross_cpu_frees) : _mallocs(mallocs), _frees(frees), _cross_cpu_frees(cross_cpu_frees) {} public: + /// Total number of memory allocations calls since the system was started. uint64_t mallocs() const { return _mallocs; } + /// Total number of memory deallocations calls since the system was started. uint64_t frees() const { return _frees; } + /// Total number of memory deallocations that occured on a different lcore + /// than the one on which they were allocated. uint64_t cross_cpu_frees() const { return _cross_cpu_frees; } + /// Total number of objects which were allocated but not freed. size_t live_objects() const { return mallocs() - frees(); } friend statistics stats(); }; diff --git a/core/seastar.hh b/core/seastar.hh index 7fdfa0b15d..3955b4d5bd 100644 --- a/core/seastar.hh +++ b/core/seastar.hh @@ -31,6 +31,7 @@ /// - \ref future-module Documentation on futures and promises, which are /// the seastar building blocks. /// - \ref future-util Utililty functions for working with futures +/// - \ref memory-module Memory management #include "sstring.hh" From b0abe9230b01208284aa231185844cc732dc82da Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Mon, 1 Jun 2015 11:37:42 +0300 Subject: [PATCH 10/11] doc: fix typo in do_until() --- core/future-util.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/future-util.hh b/core/future-util.hh index 96a5d677d2..aff86fb25c 100644 --- a/core/future-util.hh +++ b/core/future-util.hh @@ -133,7 +133,7 @@ void do_until_continued(StopCondition&& stop_cond, AsyncAction&& action, promise /// \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 to arguments, returning a future<>. Will +/// \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 From 606368f6f64570c2d115e7151725256897e4dcea Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Tue, 24 Mar 2015 10:43:48 +0200 Subject: [PATCH 11/11] shared_ptr: add debug option for detecting copies on wrong cpus Track the cpu on which the reference count referred to by shared_ptr was created, and prevent modifying it on the wrong cpu. --- configure.py | 2 +- core/shared_ptr.hh | 13 +++++-- core/shared_ptr_debug_helper.hh | 66 +++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 core/shared_ptr_debug_helper.hh diff --git a/configure.py b/configure.py index afa8fb2ab4..42457c150e 100755 --- a/configure.py +++ b/configure.py @@ -105,7 +105,7 @@ modes = { 'debug': { 'sanitize': '-fsanitize=address -fsanitize=leak -fsanitize=undefined', 'sanitize_libs': '-lubsan -lasan', - 'opt': '-O0 -DDEBUG -DDEFAULT_ALLOCATOR', + 'opt': '-O0 -DDEBUG -DDEBUG_SHARED_PTR -DDEFAULT_ALLOCATOR', 'libs': '', }, 'release': { diff --git a/core/shared_ptr.hh b/core/shared_ptr.hh index 4f5786e849..95715a872a 100644 --- a/core/shared_ptr.hh +++ b/core/shared_ptr.hh @@ -22,6 +22,7 @@ #ifndef SHARED_PTR_HH_ #define SHARED_PTR_HH_ +#include "shared_ptr_debug_helper.hh" #include #include #include @@ -46,6 +47,12 @@ // and lw_enable_shared_from_this<>(). // +#ifndef DEBUG_SHARED_PTR +using shared_ptr_counter_type = long; +#else +using shared_ptr_counter_type = debug_shared_ptr_counter_type; +#endif + template class lw_shared_ptr; @@ -103,7 +110,7 @@ shared_ptr const_pointer_cast(const shared_ptr& p); // CRTP from this to enable shared_from_this: template class enable_lw_shared_from_this { - long _count = 0; + shared_ptr_counter_type _count = 0; using ctor = T; T* to_value() { return static_cast(this); } T* to_internal_object() { return static_cast(this); } @@ -119,7 +126,7 @@ public: template struct shared_ptr_no_esft { - long _count = 0; + shared_ptr_counter_type _count = 0; T _value; using ctor = shared_ptr_no_esft; @@ -298,7 +305,7 @@ std::ostream& operator<<(std::ostream& out, const lw_shared_ptr& p) { struct shared_ptr_count_base { // destructor is responsible for fully-typed deletion virtual ~shared_ptr_count_base() {} - long count = 0; + shared_ptr_counter_type count = 0; }; template diff --git a/core/shared_ptr_debug_helper.hh b/core/shared_ptr_debug_helper.hh new file mode 100644 index 0000000000..869fb96348 --- /dev/null +++ b/core/shared_ptr_debug_helper.hh @@ -0,0 +1,66 @@ +/* + * 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) 2015 Cloudius Systems, Ltd. + */ + +#pragma once + +#ifdef DEBUG_SHARED_PTR + +#include +#include + +// A counter that is only comfortable being incremented on the cpu +// it was created on. Useful for verifying that a shared_ptr +// or lw_shared_ptr isn't misued across cores. +class debug_shared_ptr_counter_type { + long _counter = 0; + std::thread::id _cpu = std::this_thread::get_id(); +public: + debug_shared_ptr_counter_type(long x) : _counter(x) {} + operator long() const { + check(); + return _counter; + } + debug_shared_ptr_counter_type& operator++() { + check(); + ++_counter; + return *this; + } + long operator++(int) { + check(); + return _counter++; + } + debug_shared_ptr_counter_type& operator--() { + check(); + --_counter; + return *this; + } + long operator--(int) { + check(); + return _counter--; + } +private: + void check() const { + assert(_cpu == std::this_thread::get_id()); + } +}; + +#endif +