878da72881cd3d9223d07d1462f2929ad9c513ac
[folly.git] / folly / experimental / ThreadedRepeatingFunctionRunner.h
1 /*
2  * Copyright 2015-present Facebook, Inc.
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 <folly/Function.h>
20 #include <condition_variable>
21 #include <thread>
22 #include <vector>
23
24 namespace folly {
25
26 /**
27  * For each function `fn` you add to this object, `fn` will be run in a loop
28  * in its own thread, with the thread sleeping between invocations of `fn`
29  * for the duration returned by `fn`'s previous run.
30  *
31  * To clean up these threads, invoke `stop()`, which will interrupt sleeping
32  * threads.  `stop()` will wait for already-running functions to return.
33  *
34  * == Alternatives ==
35  *
36  * If you want to multiplex multiple functions on the same thread, you can
37  * either use EventBase with AsyncTimeout objects, or FunctionScheduler for
38  * a slightly simpler API.
39  *
40  * == Thread-safety ==
41  *
42  * This type follows the common rule that:
43  *  (1) const member functions are safe to call concurrently with const
44  *      member functions, but
45  *  (2) non-const member functions are not safe to call concurrently with
46  *      any member functions.
47  *
48  * == Pitfalls ==
49  *
50  * Threads and classes don't mix well in C++, so you have to be very careful
51  * if you want to have ThreadedRepeatingFunctionRunner as a member of your
52  * class.  A reasonable pattern looks like this:
53  *
54  * struct MyClass {
55  *   // Note that threads are NOT added in the constructor, for two reasons:
56  *   //
57  *   //   (1) If you added some, and had any subsequent initialization (e.g.
58  *   //       derived class constructors), 'this' would not be fully
59  *   //       constructed when the worker threads came up, causing
60  *   //       heisenbugs.
61  *   //
62  *   //   (2) Also, if your constructor threw after thread creation, the
63  *   //       class destructor would not be invoked, potentially leaving the
64  *   //       threads running too long.
65  *   //
66  *   // It's better to have explicit two-step initialization, or to lazily
67  *   // add threads the first time they are needed.
68  *   MyClass() : count_(0) {}
69  *
70  *   // You must stop the threads as early as possible in the destruction
71  *   // process (or even before).  In the case of a class hierarchy, the
72  *   // final class MUST always call stop() as the first thing in its
73  *   // destructor -- otherwise, the worker threads may access already-
74  *   // destroyed state.
75  *   ~MyClass() {
76  *     // if MyClass is abstract:
77  *     threads_.stopAndWarn("MyClass");
78  *     // Otherwise:
79  *     threads_.stop();
80  *   }
81  *
82  *   // See the constructor for why two-stage initialization is preferred.
83  *   void init() {
84  *     threads_.add(bind(&MyClass::incrementCount, this));
85  *   }
86  *
87  *   std::chrono::milliseconds incrementCount() {
88  *     ++count_;
89  *     return 10;
90  *   }
91  *
92  * private:
93  *   std::atomic<int> count_;
94  *   // Declared last because the threads' functions access other members.
95  *   ThreadedRepeatingFunctionRunner threads_;
96  * };
97  */
98 class ThreadedRepeatingFunctionRunner final {
99  public:
100   // Returns how long to wait before the next repetition. Must not throw.
101   using RepeatingFn = folly::Function<std::chrono::milliseconds() noexcept>;
102
103   ThreadedRepeatingFunctionRunner();
104   ~ThreadedRepeatingFunctionRunner();
105
106   /**
107    * Ideally, you will call this before initiating the destruction of the
108    * host object.  Otherwise, this should be the first thing in the
109    * destruction sequence.  If it comes any later, worker threads may access
110    * class state that had already been destroyed.
111    */
112   void stop();
113
114   /**
115    * Must be called at the TOP of the destructor of any abstract class that
116    * contains ThreadedRepeatingFunctionRunner (directly or through a
117    * parent).  Any non-abstract class destructor must instead stop() at the
118    * top.
119    */
120   void stopAndWarn(const std::string& class_of_destructor);
121
122   /**
123    * Run your noexcept function `f` in a background loop, sleeping between
124    * calls for a duration returned by `f`.  Optionally waits for
125    * `initialSleep` before calling `f` for the first time.
126    *
127    * DANGER: If a non-final class has a ThreadedRepeatingFunctionRunner
128    * member (which, by the way, must be declared last in the class), then
129    * you must not call add() in your constructor.  Otherwise, your thread
130    * risks accessing uninitialized data belonging to a child class.  To
131    * avoid this design bug, prefer to use two-stage initialization to start
132    * your threads.
133    */
134   void add(
135       RepeatingFn f,
136       std::chrono::milliseconds initialSleep = std::chrono::milliseconds(0));
137
138   size_t size() const { return threads_.size(); }
139
140  private:
141   // Returns true if this is the first stop().
142   bool stopImpl();
143
144   // Sleep for a duration, or until stop() is called.
145   bool waitFor(std::chrono::milliseconds duration) noexcept;
146
147   // Noexcept allows us to get a good backtrace on crashes -- otherwise,
148   // std::terminate would get called **outside** of the thread function.
149   void executeInLoop(
150       RepeatingFn,
151       std::chrono::milliseconds initialSleep) noexcept;
152
153   std::mutex stopMutex_;
154   bool stopping_{false}; // protected by stopMutex_
155   std::condition_variable stopCv_;
156
157   std::vector<std::thread> threads_;
158 };
159
160 } // namespace folly