• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 //
3 // Copyright 2015 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18 
19 #ifndef GRPC_SRC_CORE_UTIL_THD_H
20 #define GRPC_SRC_CORE_UTIL_THD_H
21 
22 /// Internal thread interface.
23 
24 #include <grpc/support/port_platform.h>
25 #include <grpc/support/thd_id.h>
26 #include <stddef.h>
27 
28 #include <memory>
29 #include <utility>
30 
31 #include "absl/functional/any_invocable.h"
32 #include "absl/log/check.h"
33 
34 namespace grpc_core {
35 namespace internal {
36 
37 /// Base class for platform-specific thread-state
38 class ThreadInternalsInterface {
39  public:
~ThreadInternalsInterface()40   virtual ~ThreadInternalsInterface() {}
41   virtual void Start() = 0;
42   virtual void Join() = 0;
43 };
44 
45 }  // namespace internal
46 
47 class Thread {
48  public:
49   // Send a signal to the thread.
50   // This is not supported on all platforms
51   static void Signal(gpr_thd_id tid, int sig);
52   // Kill the running thread. Likely not a clean operation.
53   // This is not supported on all platforms.
54   static void Kill(gpr_thd_id tid);
55 
56   class Options {
57    public:
Options()58     Options() : joinable_(true), tracked_(true), stack_size_(0) {}
59     /// Set whether the thread is joinable or detached.
set_joinable(bool joinable)60     Options& set_joinable(bool joinable) {
61       joinable_ = joinable;
62       return *this;
63     }
joinable()64     bool joinable() const { return joinable_; }
65 
66     /// Set whether the thread is tracked for fork support.
set_tracked(bool tracked)67     Options& set_tracked(bool tracked) {
68       tracked_ = tracked;
69       return *this;
70     }
tracked()71     bool tracked() const { return tracked_; }
72 
73     /// Sets thread stack size (in bytes). Sets to 0 will use the default stack
74     /// size which is 64KB for Windows threads and 2MB for Posix(x86) threads.
set_stack_size(size_t bytes)75     Options& set_stack_size(size_t bytes) {
76       stack_size_ = bytes;
77       return *this;
78     }
stack_size()79     size_t stack_size() const { return stack_size_; }
80 
81    private:
82     bool joinable_;
83     bool tracked_;
84     size_t stack_size_;
85   };
86   /// Default constructor only to allow use in structs that lack constructors
87   /// Does not produce a validly-constructed thread; must later
88   /// use placement new to construct a real thread. Does not init mu_ and cv_
Thread()89   Thread() : state_(FAKE), impl_(nullptr) {}
90 
91   /// Normal constructor to create a thread with name \a thd_name,
92   /// which will execute a thread based on function \a thd_body
93   /// with argument \a arg once it is started.
94   /// The optional \a success argument indicates whether the thread
95   /// is successfully created.
96   /// The optional \a options can be used to set the thread detachable.
97   Thread(const char* thd_name, void (*thd_body)(void* arg), void* arg,
98          bool* success = nullptr, const Options& options = Options());
99 
100   Thread(const char* thd_name, absl::AnyInvocable<void()> fn,
101          bool* success = nullptr, const Options& options = Options())
102       : Thread(
103             thd_name,
104             [](void* p) {
105               std::unique_ptr<absl::AnyInvocable<void()>> fn_from_p(
106                   static_cast<absl::AnyInvocable<void()>*>(p));
107               (*fn_from_p)();
108             },
109             new absl::AnyInvocable<void()>(std::move(fn)), success, options) {}
110 
111   /// Move constructor for thread. After this is called, the other thread
112   /// no longer represents a living thread object
Thread(Thread && other)113   Thread(Thread&& other) noexcept
114       : state_(other.state_), impl_(other.impl_), options_(other.options_) {
115     other.state_ = MOVED;
116     other.impl_ = nullptr;
117     other.options_ = Options();
118   }
119 
120   /// Move assignment operator for thread. After this is called, the other
121   /// thread no longer represents a living thread object. Not allowed if this
122   /// thread actually exists
123   Thread& operator=(Thread&& other) noexcept {
124     if (this != &other) {
125       // TODO(vjpai): if we can be sure that all Thread's are actually
126       // constructed, then we should assert CHECK(impl_ == nullptr) here.
127       // However, as long as threads come in structures that are
128       // allocated via gpr_malloc, this will not be the case, so we cannot
129       // assert it for the time being.
130       state_ = other.state_;
131       impl_ = other.impl_;
132       options_ = other.options_;
133       other.state_ = MOVED;
134       other.impl_ = nullptr;
135       other.options_ = Options();
136     }
137     return *this;
138   }
139 
140   /// The destructor is strictly optional; either the thread never came to life
141   /// and the constructor itself killed it, or it has already been joined and
142   /// the Join function kills it, or it was detached (non-joinable) and it has
143   /// run to completion and is now killing itself. The destructor shouldn't have
144   /// to do anything.
~Thread()145   ~Thread() { CHECK(!options_.joinable() || impl_ == nullptr); }
146 
Start()147   void Start() {
148     if (impl_ != nullptr) {
149       CHECK(state_ == ALIVE);
150       state_ = STARTED;
151       impl_->Start();
152       // If the Thread is not joinable, then the impl_ will cause the deletion
153       // of this Thread object when the thread function completes. Since no
154       // other operation is allowed to a detached thread after Start, there is
155       // no need to change the value of the impl_ or state_ . The next operation
156       // on this object will be the deletion, which will trigger the destructor.
157     } else {
158       CHECK(state_ == FAILED);
159     }
160   }
161 
162   // It is only legal to call Join if the Thread is created as joinable.
Join()163   void Join() {
164     if (impl_ != nullptr) {
165       impl_->Join();
166       delete impl_;
167       state_ = DONE;
168       impl_ = nullptr;
169     } else {
170       CHECK(state_ == FAILED);
171     }
172   }
173 
174  private:
175   Thread(const Thread&) = delete;
176   Thread& operator=(const Thread&) = delete;
177 
178   /// The thread states are as follows:
179   /// FAKE -- just a phony placeholder Thread created by the default constructor
180   /// ALIVE -- an actual thread of control exists associated with this thread
181   /// STARTED -- the thread of control has been started
182   /// DONE -- the thread of control has completed and been joined
183   /// FAILED -- the thread of control never came alive
184   /// MOVED -- contents were moved out and we're no longer tracking them
185   enum ThreadState { FAKE, ALIVE, STARTED, DONE, FAILED, MOVED };
186   ThreadState state_;
187   internal::ThreadInternalsInterface* impl_;
188   Options options_;
189 };
190 
191 }  // namespace grpc_core
192 
193 #endif  // GRPC_SRC_CORE_UTIL_THD_H
194