1 | /* |
2 | * Copyright 2014-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 { |
139 | return threads_.size(); |
140 | } |
141 | |
142 | private: |
143 | // Returns true if this is the first stop(). |
144 | bool stopImpl(); |
145 | |
146 | // Sleep for a duration, or until stop() is called. |
147 | bool waitFor(std::chrono::milliseconds duration) noexcept; |
148 | |
149 | // Noexcept allows us to get a good backtrace on crashes -- otherwise, |
150 | // std::terminate would get called **outside** of the thread function. |
151 | void executeInLoop( |
152 | RepeatingFn, |
153 | std::chrono::milliseconds initialSleep) noexcept; |
154 | |
155 | std::mutex stopMutex_; |
156 | bool stopping_{false}; // protected by stopMutex_ |
157 | std::condition_variable stopCv_; |
158 | |
159 | std::vector<std::thread> threads_; |
160 | }; |
161 | |
162 | } // namespace folly |
163 | |