ThreadedRepeatingFunctionRunner: Name all the threads
[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  * // Your class must be `final` because inheriting from a class with
55  * // threads can cause all sorts of subtle issues:
56  * //  - Your base class might start threads that attempt to access derived
57  * //    class state **before** that state was constructed.
58  * //  - Your base class's destructor will only be able to stop threads
59  * //    **after** the derived class state was destroyed -- and that state
60  * //    might be accessed by the threads.
61  * // In short, any derived class would have to do work to manage the
62  * // threads itself, which makes inheritance a poor means of composition.
63  * struct MyClass final {
64  *   // Note that threads are NOT added in the constructor, for two reasons:
65  *   //
66  *   //   (1) If you first added some threads, and then had additional
67  *   //       initialization (e.g. derived class constructors), `this` might
68  *   //       not be fully constructed by the time the function threads
69  *   //       started running, causing heisenbugs.
70  *   //
71  *   //   (2) If your constructor threw after thread creation, the class
72  *   //       destructor would not be invoked, potentially leaving the
73  *   //       threads running too long.
74  *   //
75  *   // It is much safer to have explicit two-step initialization, or to
76  *   // lazily add threads the first time they are needed.
77  *   MyClass() : count_(0) {}
78  *
79  *   // You must stop the threads as early as possible in the destruction
80  *   // process (or even before).  If MyClass had derived classes, the final
81  *   // derived class MUST always call stop() as the first thing in its
82  *   // destructor -- otherwise, the worker threads might access already-
83  *   // destroyed state.
84  *   ~MyClass() {
85  *     threads_.stop();  // Stop threads BEFORE destroying any state they use.
86  *   }
87  *
88  *   // See the constructor for why two-stage initialization is preferred.
89  *   void init() {
90  *     threads_.add(bind(&MyClass::incrementCount, this));
91  *   }
92  *
93  *   std::chrono::milliseconds incrementCount() {
94  *     ++count_;
95  *     return 10;
96  *   }
97  *
98  * private:
99  *   std::atomic<int> count_;
100  *   // CAUTION: Declare last since the threads access other members of `this`.
101  *   ThreadedRepeatingFunctionRunner threads_;
102  * };
103  */
104 class ThreadedRepeatingFunctionRunner final {
105  public:
106   // Returns how long to wait before the next repetition. Must not throw.
107   using RepeatingFn = folly::Function<std::chrono::milliseconds() noexcept>;
108
109   ThreadedRepeatingFunctionRunner();
110   ~ThreadedRepeatingFunctionRunner();
111
112   /**
113    * Ideally, you will call this before initiating the destruction of the
114    * host object.  Otherwise, this should be the first thing in the
115    * destruction sequence.  If it comes any later, worker threads may access
116    * class state that had already been destroyed.
117    */
118   void stop();
119
120   /**
121    * Run your noexcept function `f` in a background loop, sleeping between
122    * calls for a duration returned by `f`.  Optionally waits for
123    * `initialSleep` before calling `f` for the first time.  Names the thread
124    * using up to the first 15 chars of `name`.
125    *
126    * DANGER: If a non-final class has a ThreadedRepeatingFunctionRunner
127    * member (which, by the way, must be declared last in the class), then
128    * you must not call add() in your constructor.  Otherwise, your thread
129    * risks accessing uninitialized data belonging to a child class.  To
130    * avoid this design bug, prefer to use two-stage initialization to start
131    * your threads.
132    */
133   void add(
134       std::string name,
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