1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17 #pragma once
18
19 #include <memory>
20 #include <mutex>
21
22 #include <glog/logging.h>
23
24 #include <folly/futures/Future.h>
25
26 namespace folly {
27
28 // Structured Async Cleanup
29 //
30
31 // Structured Async Cleanup - traits
32 //
33
34 namespace detail {
35 struct cleanup_fn {
36 template <
37 class T,
38 class R = decltype(std::declval<T>().cleanup()),
39 std::enable_if_t<std::is_same_v<R, folly::SemiFuture<folly::Unit>>, int> =
40 0>
operatorcleanup_fn41 R operator()(T&& t) const {
42 return ((T &&) t).cleanup();
43 }
44 };
45 } // namespace detail
46
47 template <class T>
48 constexpr bool is_cleanup_v = folly::is_invocable_v<detail::cleanup_fn, T>;
49
50 template <typename T>
51 using is_cleanup = std::bool_constant<is_cleanup_v<T>>;
52
53 // Structured Async Cleanup
54 //
55 // This helps compose a task with async cleanup
56 // The task result is stored until cleanup completes and is then produced
57 // The cleanup task is not allowed to fail.
58 //
59 // This can be used with collectAll to combine multiple async resources
60 //
61 // ensureCleanupAfterTask(collectAll(a.run(), b.run()), collectAll(a.cleanup(),
62 // b.cleanup())).wait();
63 //
64 template <typename T>
ensureCleanupAfterTask(folly::SemiFuture<T> task,folly::SemiFuture<folly::Unit> cleanup)65 folly::SemiFuture<T> ensureCleanupAfterTask(
66 folly::SemiFuture<T> task, folly::SemiFuture<folly::Unit> cleanup) {
67 return folly::makeSemiFuture()
68 .deferValue([task_ = std::move(task)](folly::Unit) mutable {
69 return std::move(task_);
70 })
71 .defer([cleanup_ = std::move(cleanup)](folly::Try<T> taskResult) mutable {
72 return std::move(cleanup_).defer(
73 [taskResult_ = std::move(taskResult)](folly::Try<folly::Unit> t) {
74 if (t.hasException()) {
75 terminate_with<std::logic_error>("cleanup must not throw");
76 }
77 return std::move(taskResult_).value();
78 });
79 });
80 }
81
82 // Structured Async Cleanup
83 //
84 // This implementation is a base class that collects a set of cleanup tasks
85 // and runs them in reverse order.
86 //
87 // A class derived from Cleanup
88 // - only allows cleanup to be run once
89 // - is required to complete cleanup before running the destructor
90 // - *should not* run cleanup tasks. Running the cleanup task should be
91 // delegated to the owner of the derived class
92 // - *should not* be owned by a shared_ptr. Cleanup is intended to remove
93 // shared ownership.
94 //
95 class Cleanup {
96 public:
Cleanup()97 Cleanup() : safe_to_destruct_(false), cleanup_(folly::makeSemiFuture()) {}
~Cleanup()98 ~Cleanup() {
99 if (!safe_to_destruct_) {
100 LOG(FATAL) << "Cleanup must complete before it is destructed.";
101 }
102 }
103
104 // Returns: a SemiFuture that, just like destructors, sequences the cleanup
105 // tasks added in reverse of the order they were added.
106 //
107 // calls to cleanup() do not mutate state. The returned SemiFuture, once
108 // it has been given an executor, does mutate state and must not overlap with
109 // any calls to addCleanup().
110 //
cleanup()111 folly::SemiFuture<folly::Unit> cleanup() {
112 return folly::makeSemiFuture()
113 .deferValue([this](folly::Unit) {
114 if (!cleanup_.valid()) {
115 LOG(FATAL) << "cleanup already run - cleanup task invalid.";
116 }
117 return std::move(cleanup_);
118 })
119 .defer([this](folly::Try<folly::Unit> t) {
120 if (t.hasException()) {
121 LOG(FATAL) << "Cleanup actions must be noexcept.";
122 }
123 this->safe_to_destruct_ = true;
124 });
125 }
126
127 protected:
128 // includes the provided SemiFuture under the scope of this.
129 //
130 // when the cleanup() for this started it will get this SemiFuture first.
131 //
132 // order matters, just like destructors, cleanup tasks will be run in reverse
133 // of the order they were added.
134 //
135 // all gets will use the Executor provided to the SemiFuture returned by
136 // cleanup()
137 //
138 // calls to addCleanup() must not overlap with each other and must not overlap
139 // with a running SemiFuture returned from addCleanup().
140 //
addCleanup(folly::SemiFuture<folly::Unit> c)141 void addCleanup(folly::SemiFuture<folly::Unit> c) {
142 if (!cleanup_.valid()) {
143 LOG(FATAL)
144 << "Cleanup::addCleanup must not be called after Cleanup::cleanup.";
145 }
146 cleanup_ = std::move(c).deferValue(
147 [nested = std::move(cleanup_)](folly::Unit) mutable {
148 return std::move(nested);
149 });
150 }
151
152 // includes the provided model of Cleanup under the scope of this
153 //
154 // when the cleanup() for this started it will cleanup this first.
155 //
156 // order matters, just like destructors, cleanup tasks will be run in reverse
157 // of the order they were added.
158 //
159 // all gets will use the Executor provided to the SemiFuture returned by
160 // cleanup()
161 //
162 // calls to addCleanup() must not overlap with each other and must not overlap
163 // with a running SemiFuture returned from addCleanup().
164 //
165 template <
166 class OtherCleanup,
167 std::enable_if_t<is_cleanup_v<OtherCleanup>, int> = 0>
addCleanup(OtherCleanup && c)168 void addCleanup(OtherCleanup&& c) {
169 addCleanup(((OtherCleanup &&) c).cleanup());
170 }
171
172 private:
173 bool safe_to_destruct_;
174 folly::SemiFuture<folly::Unit> cleanup_;
175 };
176
177 } // namespace folly
178