| // Copyright 2016 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef SRC_MODULAR_LIB_ASYNC_CPP_OPERATION_H_ | |
| #define SRC_MODULAR_LIB_ASYNC_CPP_OPERATION_H_ | |
| #include <lib/async/cpp/executor.h> | |
| #include <lib/fit/promise.h> | |
| #include <functional> | |
| #include <memory> | |
| #include <queue> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| #include "src/lib/fxl/macros.h" | |
| #include "src/lib/fxl/memory/weak_ptr.h" | |
| #include "src/modular/lib/async/cpp/future.h" | |
| namespace modular { | |
| class OperationBase; | |
| // An abstract base class which provides methods to hold on to Operation | |
| // instances until they declare themselves to be Done(). Ownership of | |
| // the operations must be managed by implementations. | |
| class OperationContainer { | |
| public: | |
| OperationContainer(); | |
| virtual ~OperationContainer(); | |
| // Adds |o| to this container and takes ownership. | |
| void Add(std::unique_ptr<OperationBase> o); | |
| // Adds |task| to be scheduled on |this|. This mirrors | |
| // the interface in fit::executor, and is here only during | |
| // a transition from Operations to fit::promises. | |
| virtual void ScheduleTask(fit::pending_task task) = 0; | |
| protected: | |
| void Schedule(OperationBase* o); | |
| void InvalidateWeakPtrs(OperationBase* o); | |
| private: | |
| // OperationBase calls the methods below. | |
| friend class OperationBase; | |
| virtual fxl::WeakPtr<OperationContainer> GetWeakPtr() = 0; | |
| // Must take ownership of |o|. | |
| virtual void Hold(std::unique_ptr<OperationBase> o) = 0; | |
| // Must clean up memory for |o|. | |
| virtual void Drop(OperationBase* o) = 0; | |
| virtual void Cont() = 0; | |
| }; | |
| // An implementation of |OperationContainer| which runs every instance of | |
| // Operation as soon as it arrives. | |
| class OperationCollection : public OperationContainer { | |
| public: | |
| OperationCollection(); | |
| ~OperationCollection() override; | |
| // move-only | |
| OperationCollection(const OperationCollection&) = delete; | |
| OperationCollection& operator=(const OperationCollection&) = delete; | |
| void ScheduleTask(fit::pending_task task) override; | |
| private: | |
| fxl::WeakPtr<OperationContainer> GetWeakPtr() override; | |
| void Hold(std::unique_ptr<OperationBase> o) override; | |
| void Drop(OperationBase* o) override; | |
| void Cont() override; | |
| async::Executor executor_; | |
| std::vector<std::unique_ptr<OperationBase>> operations_; | |
| // It is essential that the weak_ptr_factory is defined after the operations_ | |
| // container, so that it gets destroyed before operations_, and hence before | |
| // the Operation instances in it, so that the Done() call on their | |
| // OperationBase sees the container weak pointer to be null. That's also why | |
| // we need the virtual GetWeakPtr() in the base class, rather than to put the | |
| // weak ptr factory in the base class. | |
| fxl::WeakPtrFactory<OperationContainer> weak_ptr_factory_; | |
| }; | |
| // An implementation of |OperationContainer| which runs incoming Operations | |
| // sequentially. This ensures that there are no partially complete operations | |
| // when a new operation starts. | |
| class OperationQueue : public OperationContainer { | |
| public: | |
| OperationQueue(); | |
| ~OperationQueue() override; | |
| // move-only | |
| OperationQueue(const OperationQueue&) = delete; | |
| OperationQueue& operator=(const OperationQueue&) = delete; | |
| void ScheduleTask(fit::pending_task task) override; | |
| private: | |
| fxl::WeakPtr<OperationContainer> GetWeakPtr() override; | |
| void Hold(std::unique_ptr<OperationBase> o) override; | |
| void Drop(OperationBase* o) override; | |
| void Cont() override; | |
| // Are there any operations running? An operation is considered running once | |
| // its |Run()| has been invoked, up until result callback has finished. Note | |
| // that an operation could be running while it is not present in | |
| // |operations_|: its result callback could be executing. | |
| bool idle_ = true; | |
| async::Executor executor_; | |
| std::queue<std::unique_ptr<OperationBase>> operations_; | |
| // It is essential that the weak_ptr_factory is defined after the operations_ | |
| // container, so that it gets destroyed before operations_, and hence before | |
| // the Operation instances in it, so that the Done() call on their | |
| // OperationBase sees the container weak pointer to be null. That's also why | |
| // we need the virtual GetWeakPtr() in the base class, rather than to put the | |
| // weak ptr factory in the base class. | |
| fxl::WeakPtrFactory<OperationContainer> weak_ptr_factory_; | |
| }; | |
| // Something that can be put in an OperationContainer until it calls Done() on | |
| // itself. Used to implement asynchronous operations that need to hold on to | |
| // handles until the operation asynchronously completes and returns a value. | |
| // | |
| // Held by a unique_ptr<> in the OperationContainer. Instances of derived | |
| // classes need to be created with |std::make_unique|. | |
| // | |
| // Advantages of using an Operation instance to implement asynchronous fidl | |
| // method invocations: | |
| // | |
| // 1. To receive the return callback, the interface pointer on which the method | |
| // is invoked needs to be kept around. It's tricky to place a move-only | |
| // interface pointer on the capture list of a lambda passed as argument to | |
| // its own method. An instance is much simpler. | |
| // | |
| // 2. The capture list of the callbacks only holds this, everything else that | |
| // needs to be passed on is in the instance. | |
| // | |
| // 3. Conversion of Handle to Ptr can be done by Bind() because the Ptr is | |
| // already there. | |
| // | |
| // Use of Operation instances must adhere to invariants: | |
| // | |
| // * Deleting the operation container deletes all Operation instances in it, and | |
| // it must be guaranteed that no callbacks of ongoing method invocations are | |
| // invoked after the Operation instance they access is deleted. In order to | |
| // accomplish this, an Operation instance must only invoke methods on fidl | |
| // pointers that are either owned by the Operation instance, or that are owned | |
| // by the immediate owner of the operation container. This way, when the | |
| // operation container is deleted, the destructors of all involved fidl | |
| // pointers are called, close their channels, and cancel all pending method | |
| // callbacks. If a method is invoked on a fidl pointer that lives on beyond | |
| // the lifetime of the operation container, this is not guaranteed. | |
| // | |
| class OperationBase { | |
| public: | |
| virtual ~OperationBase(); | |
| // move-only | |
| OperationBase(const OperationBase&) = delete; | |
| OperationBase& operator=(const OperationBase&) = delete; | |
| // Derived classes implement this method. It is called by the container of | |
| // this Operation when it is ready. At some point after Run() is invoked, | |
| // |Done()| must be called to signal completion of this operation. This | |
| // usually happens implicitly from the destructor of a FlowToken that is | |
| // passed along the asynchronous flow of control of the Operation. | |
| virtual void Run() = 0; | |
| // Needed to guard callbacks to methods on FIDL pointers that are not owned by | |
| // this Operation instance. | |
| // | |
| // Callbacks on methods on FIDL pointers that are owned by Operation instance | |
| // are never invoked after the Operation instance is deleted, because they are | |
| // cancelled by the destructor of the FIDL pointer. However, if the FIDL | |
| // pointer is owned outside of the Operation instance, such a callback may be | |
| // invoked after the Operation instance was destroyed, if the FIDL pointer | |
| // lives longer. | |
| fxl::WeakPtr<OperationBase> GetWeakPtr(); | |
| protected: | |
| // |trace_name| and |trace_info| are used to annotate performance traces. | |
| // |trace_name| must outlive *this. | |
| OperationBase(const char* trace_name, std::string trace_info); | |
| // Useful in log messages. | |
| const char* trace_name() const { return trace_name_; } | |
| class FlowTokenBase; | |
| private: | |
| // OperationContainer calls SetOwner(), InvalidateWeakPtrs() and Schedule(). | |
| friend class OperationContainer; | |
| // Called only by OperationContainer::Add(). | |
| void SetOwner(OperationContainer* c); | |
| // Called only by OperationContainer. | |
| void Schedule(); | |
| // Called only by OperationContainer. | |
| void InvalidateWeakPtrs(); | |
| // Operation<..> class DispatchCallback() and accesses trace info fields | |
| // below. | |
| template <typename... Args> | |
| friend class Operation; | |
| // Called only by Operation<...>. | |
| template <typename... Args, typename ResultCall = fit::function<void(Args...)>> | |
| void DispatchCallback(ResultCall result_call, Args... result_args) { | |
| // Move |container| pointer out of this, because |this| gets deleted before | |
| // we stop using |container|. | |
| auto container = std::move(container_); | |
| container_.reset(); // just make sure | |
| if (container) { | |
| // Deletes |this|. | |
| container->Drop(this); | |
| // Can no longer refer to |this|. | |
| result_call(std::move(result_args)...); | |
| // The result callback may cause the container to be deleted, so we must | |
| // check it still exists before telling it to continue. | |
| if (container) { | |
| container->Cont(); | |
| } | |
| } | |
| } | |
| // Traces the duration of the Operation excution. Begin() is called from | |
| // Schedule(), End() is called from Done(). | |
| void TraceAsyncBegin(); | |
| void TraceAsyncEnd(); | |
| fxl::WeakPtr<OperationContainer> container_; | |
| // Used by FlowTokenBase to suppress Done() calls after the Operation instance | |
| // is deleted. The OperationContainer will invalidate all weak pointers to | |
| // this instance before the destructor is invoked, so it's not necessary that | |
| // this field is last. | |
| fxl::WeakPtrFactory<OperationBase> weak_ptr_factory_; | |
| // Name used to label traces for this operation. | |
| const char* const trace_name_; | |
| // Unique identifier used to correlate trace events for this operation. | |
| const uint64_t trace_id_; | |
| // Additional information added to trace events for this operation. | |
| const std::string trace_info_; | |
| }; | |
| template <typename... Args> | |
| class Operation : public OperationBase { | |
| public: | |
| ~Operation() override = default; | |
| // move-only | |
| Operation(const Operation&) = delete; | |
| Operation& operator=(const Operation&) = delete; | |
| using ResultCall = fit::function<void(Args...)>; | |
| protected: | |
| Operation(const char* const trace_name, ResultCall result_call, | |
| const std::string& trace_info = "") | |
| : OperationBase(trace_name, trace_info), result_call_(std::move(result_call)) {} | |
| // Derived classes call this when they are prepared to be removed from the | |
| // container. Must be the last thing this instance does, as it results in | |
| // destructor invocation. | |
| void Done(Args... result_args) { | |
| TraceAsyncEnd(); | |
| DispatchCallback(std::move(result_call_), std::move(result_args)...); | |
| } | |
| class FlowToken; | |
| class FlowTokenHolder; | |
| private: | |
| ResultCall result_call_; | |
| }; | |
| // The instance of FlowToken at which the refcount reaches zero in the | |
| // destructor calls Done() on the Operation it holds a reference of. | |
| // | |
| // It is an inner class of Operation so that it has access to Done(), which is | |
| // protected. | |
| // | |
| // FlowToken is ref counted so it can be copied to multiple scopes as required, | |
| // to indicate that, as long as a copy of the FlowToken is alive, it's | |
| // Operation is not yet done. But when the last FlowToken goes out of scope, | |
| // the FlowToken destructor automatically invokes Done(). | |
| // | |
| // NOTE: You cannot mix flow tokens and explicit Done() calls. Once an Operation | |
| // uses a flow token, this is how Done() is called, and calling Done() | |
| // explicitly would call it twice. | |
| // | |
| // The parts that are not dependent on the template parameter are factored off | |
| // into a base class as usual. | |
| class OperationBase::FlowTokenBase { | |
| public: | |
| explicit FlowTokenBase(OperationBase* op); | |
| FlowTokenBase(const FlowTokenBase& other); | |
| ~FlowTokenBase(); | |
| protected: | |
| int refcount() const { return *refcount_; } | |
| bool weak_op() const { return weak_op_.get() != nullptr; } | |
| private: | |
| int* const refcount_; // shared between copies of FlowToken. | |
| fxl::WeakPtr<OperationBase> weak_op_; | |
| }; | |
| template <typename... Args> | |
| class Operation<Args...>::FlowToken : OperationBase::FlowTokenBase { | |
| public: | |
| explicit FlowToken(Operation<Args...>* const op, Args* const... result) | |
| : FlowTokenBase(op), op_(op), result_(result...) {} | |
| FlowToken(const FlowToken& other) | |
| : FlowTokenBase(other), op_(other.op_), result_(other.result_) {} | |
| ~FlowToken() { | |
| // If refcount is 1 here, it will become 0 in ~FlowTokenBase. | |
| if (refcount() == 1 && weak_op()) { | |
| apply(std::make_index_sequence<std::tuple_size<decltype(result_)>::value>{}); | |
| } | |
| } | |
| private: | |
| // This usage is based on the implementation of std::apply(), which is only | |
| // available in C++17: http://en.cppreference.com/w/cpp/utility/apply | |
| template <size_t... I> | |
| void apply(std::integer_sequence<size_t, I...> /*unused*/) { | |
| op_->Done(std::move((*std::get<I>(result_)))...); | |
| } | |
| Operation<Args...>* const op_; // not owned | |
| // The pointers that FlowToken() is constructed with are stored in this | |
| // std::tuple. They are then extracted and std::move()'d in the |apply()| | |
| // method. | |
| std::tuple<Args* const...> result_; // the values are not owned | |
| }; | |
| // Sometimes the asynchronous flow of control that is represented by a FlowToken | |
| // branches, but is actually continued on exactly one branch. For example, when | |
| // a method is called on an external fidl service, and the callback from that | |
| // method is also scheduled as a timeout to avoid blocking the operation queue | |
| // in case the external service misbehaves and doesn't respond. | |
| // | |
| // In that situation, it would be wrong to place two copies of the flow token on | |
| // the capture list of both callbacks, because then the flow would only be | |
| // Done() once both callbacks are destroyed. In the case where the second | |
| // callback is a timeout, this might be really late. | |
| // | |
| // Instead, a single copy of the flow token is placed in a shared container, a | |
| // reference to which is placed on the capture list of both callbacks. The first | |
| // callback that is invoked removes the flow token from the shared container and | |
| // propagates it from there. | |
| // | |
| // That way, the flow token in the shared container also acts as call-once flag. | |
| // | |
| // The flow token holder is a simple wrapper of a shared ptr to a unique ptr. We | |
| // define it because such a nested smart pointer is rather unwieldy to write | |
| // every time. | |
| // | |
| // Example use: | |
| // | |
| // FlowTokenHolder branch{flow}; | |
| // | |
| // auto kill_agent = [this, branch] { | |
| // std::unique_ptr<FlowToken> flow = branch.Continue(); | |
| // if (!flow) { | |
| // return; | |
| // } | |
| // | |
| // stopped_ = true; | |
| // }; | |
| // | |
| // StopAgent(kill_agent); | |
| // SetTimeout(kill_agent, 1); | |
| // | |
| /////////// | |
| // TODO(richkadel): There isn't a unit test for the above use case, nor for the | |
| // FlowTokenHolder class (and probably should be, likely in | |
| // operation_unittest.cc. But since this whole approach to modular calls is | |
| // deprecated (see go/fit-promise-guide), I'm just making a note of this; not | |
| // adding the test. | |
| /////////// | |
| // | |
| template <typename... Args> | |
| class Operation<Args...>::FlowTokenHolder { | |
| public: | |
| using FlowToken = Operation<Args...>::FlowToken; | |
| explicit FlowTokenHolder(const FlowToken& flow) | |
| : ptr_(std::make_shared<std::unique_ptr<FlowToken>>(std::make_unique<FlowToken>(flow))) {} | |
| FlowTokenHolder(const FlowTokenHolder& other) : ptr_(other.ptr_) {} | |
| // Calling this method again on any copy of the same FlowTokenHolder yields a | |
| // nullptr. Clients can check for that to enforce call once semantics. | |
| // | |
| // The method is const because it only mutates the pointed to object. It's | |
| // useful to exploit this loophole because this way lambdas that have a | |
| // FlowTokenHolder on their capture list don't need to be mutable. | |
| std::unique_ptr<FlowToken> Continue() const { return std::move(*ptr_); } | |
| private: | |
| std::shared_ptr<std::unique_ptr<FlowToken>> ptr_; | |
| }; | |
| // Following is a list of commonly used operations. | |
| // An operation which wraps a Future<>. See WrapFutureAsOperation() below. | |
| template <typename... Args> | |
| class FutureOperation : public Operation<Args...> { | |
| public: | |
| using ResultCall = fit::function<void(Args...)>; | |
| FutureOperation(const char* trace_name, FuturePtr<> on_run, FuturePtr<Args...> done, | |
| ResultCall result_call) | |
| : Operation<Args...>(trace_name, std::move(result_call)), on_run_(on_run), done_(done) {} | |
| private: | |
| // |OperationBase| | |
| void Run() override { | |
| // FuturePtr is a shared ptr, so the Then() callback is not necessarily | |
| // cancelled by the destructor of this Operation instance. Hence the | |
| // callback must be protected against invocation after delete of this. | |
| done_->WeakThen(this->GetWeakPtr(), [this](Args&&... args) { | |
| this->Done(std::forward<Args>(args)...); | |
| // Can no longer refer to |this|. | |
| }); | |
| on_run_->Complete(); | |
| } | |
| FuturePtr<> on_run_; | |
| FuturePtr<Args...> done_; | |
| }; | |
| // EXPERIMENTAL | |
| // | |
| // This is useful to glue a FIDL call that expects its result as a callback with | |
| // a Future<>, but have the business logic of the Future<> run on an | |
| // OperationContainer. | |
| // | |
| // Usage: | |
| // | |
| // void MyFidlCall(args..., MyFidlCallResult result_call) { | |
| // auto on_run = Future<>::Create(); | |
| // auto done = on_run->Map(...)->Then(...); | |
| // operation_container_.Add(WrapFutureAsOperation( | |
| // "MyFidlCall", on_run, done, result_call)); | |
| // } | |
| // | |
| template <typename... ResultArgs, typename... FutureArgs> | |
| std::unique_ptr<OperationBase> WrapFutureAsOperation( | |
| const char* const trace_name, FuturePtr<> on_run, FuturePtr<FutureArgs...> done, | |
| fit::function<void(ResultArgs...)> result_call) { | |
| return std::make_unique<FutureOperation<ResultArgs...>>(trace_name, std::move(on_run), | |
| std::move(done), std::move(result_call)); | |
| } | |
| template <typename... Args> | |
| class FutureOperation2 : public Operation<Args...> { | |
| public: | |
| using ResultCall = fit::function<void(Args...)>; | |
| using RunOpCall = fit::function<FuturePtr<Args...>(OperationBase*)>; | |
| FutureOperation2(const char* const trace_name, RunOpCall run_op, ResultCall done) | |
| : Operation<Args...>(trace_name, std::move(done)), run_op_(std::move(run_op)) {} | |
| private: | |
| // |OperationBase| | |
| void Run() override { | |
| // FuturePtr is a shared ptr, so the Then() callback is not necessarily | |
| // cancelled by the destructor of this Operation instance. Hence the | |
| // callback must be protected against invocation after delete of this. | |
| run_op_(this)->WeakThen(this->GetWeakPtr(), | |
| [this](Args&&... args) { this->Done(std::forward<Args>(args)...); }); | |
| } | |
| RunOpCall run_op_; | |
| }; | |
| // EXPERIMENTAL | |
| // | |
| // Glue code to define operations where the body of the operation is defined | |
| // inline to where the operation instance is created. It is an alternative to | |
| // WrapFutureAsOperation. | |
| // | |
| // The body of the operation is defined as a callback, and is expected to return | |
| // a Future<> that is completed when the operation is finished. If the Future<> | |
| // is completed with values, those values are used as the result of the | |
| // Operation. | |
| // | |
| // Usage: | |
| // | |
| // void MyFidlCall(args..., MyFidlCallResult result_call) { | |
| // operation_container.Add(NewCallbackOperation( | |
| // "MyFidlService::MyFidlCall", | |
| // [this] (OperationBase* op) { | |
| // // Use |op->GetWeakPtr()| to guard any async calls. | |
| // auto f = Future<>::Create(); | |
| // | |
| // DoSomethingAsync(f->Completer()); | |
| // | |
| // f->WeakMap(op->GetWeakPtr(), [] (int do_something_result) { | |
| // return do_something_result * 2; | |
| // }); | |
| // | |
| // return f; | |
| // }, | |
| // result_call); | |
| // } | |
| template <typename... ResultArgs> | |
| std::unique_ptr<OperationBase> NewCallbackOperation( | |
| const char* const trace_name, typename FutureOperation2<ResultArgs...>::RunOpCall run, | |
| typename FutureOperation2<ResultArgs...>::ResultCall done) { | |
| return std::make_unique<FutureOperation2<ResultArgs...>>(trace_name, std::move(run), | |
| std::move(done)); | |
| } | |
| // An operation which immediately calls its result callback. This is useful for | |
| // making sure that all operations that run before this have completed. | |
| class SyncCall : public Operation<> { | |
| public: | |
| SyncCall(ResultCall result_call) : Operation("SyncCall", std::move(result_call)) {} | |
| void Run() override { Done(); } | |
| }; | |
| } // namespace modular | |
| #endif // SRC_MODULAR_LIB_ASYNC_CPP_OPERATION_H_ |