2 * Copyright 2015-present Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <folly/Function.h>
20 #include <condition_variable>
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.
31 * To clean up these threads, invoke `stop()`, which will interrupt sleeping
32 * threads. `stop()` will wait for already-running functions to return.
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.
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.
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:
55 * // Note that threads are NOT added in the constructor, for two reasons:
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
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.
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) {}
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-
76 * // if MyClass is abstract:
77 * threads_.stopAndWarn("MyClass");
82 * // See the constructor for why two-stage initialization is preferred.
84 * threads_.add(bind(&MyClass::incrementCount, this));
87 * std::chrono::milliseconds incrementCount() {
93 * std::atomic<int> count_;
94 * // Declared last because the threads' functions access other members.
95 * ThreadedRepeatingFunctionRunner threads_;
98 class ThreadedRepeatingFunctionRunner final {
100 // Returns how long to wait before the next repetition. Must not throw.
101 using RepeatingFn = folly::Function<std::chrono::milliseconds() noexcept>;
103 ThreadedRepeatingFunctionRunner();
104 ~ThreadedRepeatingFunctionRunner();
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.
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
120 void stopAndWarn(const std::string& class_of_destructor);
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.
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
136 std::chrono::milliseconds initialSleep = std::chrono::milliseconds(0));
138 size_t size() const { return threads_.size(); }
141 // Returns true if this is the first stop().
144 // Sleep for a duration, or until stop() is called.
145 bool waitFor(std::chrono::milliseconds duration) noexcept;
147 // Noexcept allows us to get a good backtrace on crashes -- otherwise,
148 // std::terminate would get called **outside** of the thread function.
151 std::chrono::milliseconds initialSleep) noexcept;
153 std::mutex stopMutex_;
154 bool stopping_{false}; // protected by stopMutex_
155 std::condition_variable stopCv_;
157 std::vector<std::thread> threads_;