/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * 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. */ #pragma once #include #include #include #include namespace folly { // Structured Async Cleanup // // Structured Async Cleanup - traits // namespace detail { struct cleanup_fn { template < class T, class R = decltype(std::declval().cleanup()), std::enable_if_t>, int> = 0> R operator()(T&& t) const { return ((T &&) t).cleanup(); } }; } // namespace detail template constexpr bool is_cleanup_v = folly::is_invocable_v; template using is_cleanup = std::bool_constant>; // Structured Async Cleanup // // This helps compose a task with async cleanup // The task result is stored until cleanup completes and is then produced // The cleanup task is not allowed to fail. // // This can be used with collectAll to combine multiple async resources // // ensureCleanupAfterTask(collectAll(a.run(), b.run()), collectAll(a.cleanup(), // b.cleanup())).wait(); // template folly::SemiFuture ensureCleanupAfterTask( folly::SemiFuture task, folly::SemiFuture cleanup) { return folly::makeSemiFuture() .deferValue([task_ = std::move(task)](folly::Unit) mutable { return std::move(task_); }) .defer([cleanup_ = std::move(cleanup)](folly::Try taskResult) mutable { return std::move(cleanup_).defer( [taskResult_ = std::move(taskResult)](folly::Try t) { if (t.hasException()) { terminate_with("cleanup must not throw"); } return std::move(taskResult_).value(); }); }); } // Structured Async Cleanup // // This implementation is a base class that collects a set of cleanup tasks // and runs them in reverse order. // // A class derived from Cleanup // - only allows cleanup to be run once // - is required to complete cleanup before running the destructor // - *should not* run cleanup tasks. Running the cleanup task should be // delegated to the owner of the derived class // - *should not* be owned by a shared_ptr. Cleanup is intended to remove // shared ownership. // class Cleanup { public: Cleanup() : safe_to_destruct_(false), cleanup_(folly::makeSemiFuture()) {} ~Cleanup() { if (!safe_to_destruct_) { LOG(FATAL) << "Cleanup must complete before it is destructed."; } } // Returns: a SemiFuture that, just like destructors, sequences the cleanup // tasks added in reverse of the order they were added. // // calls to cleanup() do not mutate state. The returned SemiFuture, once // it has been given an executor, does mutate state and must not overlap with // any calls to addCleanup(). // folly::SemiFuture cleanup() { return folly::makeSemiFuture() .deferValue([this](folly::Unit) { if (!cleanup_.valid()) { LOG(FATAL) << "cleanup already run - cleanup task invalid."; } return std::move(cleanup_); }) .defer([this](folly::Try t) { if (t.hasException()) { LOG(FATAL) << "Cleanup actions must be noexcept."; } this->safe_to_destruct_ = true; }); } protected: // includes the provided SemiFuture under the scope of this. // // when the cleanup() for this started it will get this SemiFuture first. // // order matters, just like destructors, cleanup tasks will be run in reverse // of the order they were added. // // all gets will use the Executor provided to the SemiFuture returned by // cleanup() // // calls to addCleanup() must not overlap with each other and must not overlap // with a running SemiFuture returned from addCleanup(). // void addCleanup(folly::SemiFuture c) { if (!cleanup_.valid()) { LOG(FATAL) << "Cleanup::addCleanup must not be called after Cleanup::cleanup."; } cleanup_ = std::move(c).deferValue( [nested = std::move(cleanup_)](folly::Unit) mutable { return std::move(nested); }); } // includes the provided model of Cleanup under the scope of this // // when the cleanup() for this started it will cleanup this first. // // order matters, just like destructors, cleanup tasks will be run in reverse // of the order they were added. // // all gets will use the Executor provided to the SemiFuture returned by // cleanup() // // calls to addCleanup() must not overlap with each other and must not overlap // with a running SemiFuture returned from addCleanup(). // template < class OtherCleanup, std::enable_if_t, int> = 0> void addCleanup(OtherCleanup&& c) { addCleanup(((OtherCleanup &&) c).cleanup()); } private: bool safe_to_destruct_; folly::SemiFuture cleanup_; }; } // namespace folly