forked from google/pigweed
-
Notifications
You must be signed in to change notification settings - Fork 1
/
thread.cc
204 lines (174 loc) · 7.72 KB
/
thread.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_thread/thread.h"
#include "pw_assert/check.h"
#include "pw_preprocessor/compiler.h"
#include "pw_thread/id.h"
#include "pw_thread_threadx/config.h"
#include "pw_thread_threadx/context.h"
#include "pw_thread_threadx/options.h"
#include "tx_event_flags.h"
using pw::thread::threadx::Context;
namespace pw::thread {
namespace {
#if PW_THREAD_JOINING_ENABLED
constexpr ULONG kThreadDoneBit = 1;
#endif // PW_THREAD_JOINING_ENABLED
} // namespace
void Context::ThreadEntryPoint(ULONG void_context_ptr) {
Context& context = *reinterpret_cast<Context*>(void_context_ptr);
// Invoke the user's thread function. This may never return.
context.user_thread_entry_function_(context.user_thread_entry_arg_);
// Raise our preemption threshold as a thread only critical section to guard
// against join() and detach().
UINT original_preemption_threshold = TX_MAX_PRIORITIES; // Invalid.
UINT preemption_success = tx_thread_preemption_change(
&context.tcb(), 0, &original_preemption_threshold);
PW_DCHECK_UINT_EQ(TX_SUCCESS,
preemption_success,
"Failed to enter thread critical section");
if (context.detached()) {
// There is no threadsafe way to re-use detached threads, as there's no way
// to invoke tx_thread_delete() from the running thread! Joining MUST be
// used for this. However to enable unit test coverage we go ahead and clear
// this.
context.set_in_use(false);
#if PW_THREAD_JOINING_ENABLED
// If the thread handle was detached before the thread finished execution,
// i.e. got here, then we are responsible for cleaning up the join event
// group.
const UINT event_group_result =
tx_event_flags_delete(&context.join_event_group());
PW_DCHECK_UINT_EQ(TX_SUCCESS,
event_group_result,
"Failed to delete the join event group");
#endif // PW_THREAD_JOINING_ENABLED
// Note that we do not have to restore our preemption threshold as this
// thread is completing execution.
// WARNING: The thread at this point continues to be registered with the
// kernel in TX_COMPLETED state, as tx_thread_delete cannot be invoked!
return;
}
// Otherwise the task finished before the thread was detached or joined, defer
// cleanup to Thread's join() or detach().
context.set_thread_done();
UINT unused = 0;
preemption_success = tx_thread_preemption_change(
&context.tcb(), original_preemption_threshold, &unused);
PW_DCHECK_UINT_EQ(TX_SUCCESS,
preemption_success,
"Failed to leave thread critical section");
#if PW_THREAD_JOINING_ENABLED
const UINT result =
tx_event_flags_set(&context.join_event_group(), kThreadDoneBit, TX_OR);
PW_DCHECK_UINT_EQ(TX_SUCCESS, result, "Failed to set the join event");
#endif // PW_THREAD_JOINING_ENABLED
return;
}
void Context::DeleteThread(Context& context) {
// Stop the other task first.
UINT thread_result = tx_thread_terminate(&context.tcb());
PW_CHECK_UINT_EQ(TX_SUCCESS, thread_result, "Failed to terminate the thread");
// Delete the thread, removing it out of the kernel.
thread_result = tx_thread_delete(&context.tcb());
PW_CHECK_UINT_EQ(TX_SUCCESS, thread_result, "Failed to delete the thread");
// Mark the context as unused for potential later re-use.
context.set_in_use(false);
#if PW_THREAD_JOINING_ENABLED
// Just in case someone abused our API, ensure their use of the event group is
// properly handled by the kernel regardless.
const UINT event_group_result =
tx_event_flags_delete(&context.join_event_group());
PW_DCHECK_UINT_EQ(
TX_SUCCESS, event_group_result, "Failed to delete the join event group");
#endif // PW_THREAD_JOINING_ENABLED
}
Thread::Thread(const thread::Options& facade_options,
ThreadRoutine entry,
void* arg)
: native_type_(nullptr) {
// Cast the generic facade options to the backend specific option of which
// only one type can exist at compile time.
auto options = static_cast<const threadx::Options&>(facade_options);
PW_DCHECK_NOTNULL(options.context(), "The Context is not optional");
native_type_ = options.context();
// Can't use a context more than once.
PW_DCHECK(!native_type_->in_use());
// Reset the state of the static context in case it was re-used.
native_type_->set_in_use(true);
native_type_->set_detached(false);
native_type_->set_thread_done(false);
#if PW_THREAD_JOINING_ENABLED
static const char* join_event_group_name = "pw::Thread";
const UINT event_group_result =
tx_event_flags_create(&options.context()->join_event_group(),
const_cast<char*>(join_event_group_name));
PW_DCHECK_UINT_EQ(
TX_SUCCESS, event_group_result, "Failed to create the join event group");
#endif // PW_THREAD_JOINING_ENABLED
// Copy over the thread name.
native_type_->set_name(options.name());
// In order to support functions which return and joining, a delegate is
// deep copied into the context with a small wrapping function to actually
// invoke the task with its arg.
native_type_->set_thread_routine(entry, arg);
const UINT thread_result =
tx_thread_create(&options.context()->tcb(),
const_cast<char*>(native_type_->name()),
Context::ThreadEntryPoint,
reinterpret_cast<ULONG>(native_type_),
options.context()->stack().data(),
options.context()->stack().size_bytes(),
options.priority(),
options.preemption_threshold(),
options.time_slice_interval(),
TX_AUTO_START);
PW_CHECK_UINT_EQ(TX_SUCCESS, thread_result, "Failed to create the thread");
}
void Thread::detach() {
PW_CHECK(joinable());
tx_thread_suspend(&native_type_->tcb());
native_type_->set_detached();
const bool thread_done = native_type_->thread_done();
tx_thread_resume(&native_type_->tcb());
if (thread_done) {
// The task finished (hit end of Context::ThreadEntryPoint) before we
// invoked detach, clean up the thread.
Context::DeleteThread(*native_type_);
} else {
// We're detaching before the task finished, defer cleanup to the task at
// the end of Context::ThreadEntryPoint.
}
// Update to no longer represent a thread of execution.
native_type_ = nullptr;
}
#if PW_THREAD_JOINING_ENABLED
void Thread::join() {
PW_CHECK(joinable());
PW_CHECK(this_thread::get_id() != get_id());
ULONG actual_flags = 0;
const UINT result = tx_event_flags_get(&native_type_->join_event_group(),
kThreadDoneBit,
TX_OR_CLEAR,
&actual_flags,
TX_WAIT_FOREVER);
PW_DCHECK_UINT_EQ(TX_SUCCESS, result, "Failed to get the join event");
// No need for a critical section here as the thread at this point is
// waiting to be deleted.
Context::DeleteThread(*native_type_);
// Update to no longer represent a thread of execution.
native_type_ = nullptr;
}
#endif // PW_THREAD_JOINING_ENABLED
} // namespace pw::thread