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
24namespace 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 */
104class 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