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