/src/brpc/src/bthread/task_group.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. 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, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | // bthread - An M:N threading library to make applications more concurrent. |
19 | | |
20 | | // Date: Tue Jul 10 17:40:58 CST 2012 |
21 | | |
22 | | #include <sys/types.h> |
23 | | #include <stddef.h> // size_t |
24 | | #include <gflags/gflags.h> |
25 | | #include "butil/compat.h" // OS_MACOSX |
26 | | #include "butil/macros.h" // ARRAY_SIZE |
27 | | #include "butil/scoped_lock.h" // BAIDU_SCOPED_LOCK |
28 | | #include "butil/fast_rand.h" |
29 | | #include "butil/unique_ptr.h" |
30 | | #include "butil/third_party/murmurhash3/murmurhash3.h" // fmix64 |
31 | | #include "butil/reloadable_flags.h" |
32 | | #include "bthread/errno.h" // ESTOP |
33 | | #include "bthread/butex.h" // butex_* |
34 | | #include "bthread/sys_futex.h" // futex_wake_private |
35 | | #include "bthread/processor.h" // cpu_relax |
36 | | #include "bthread/task_control.h" |
37 | | #include "bthread/task_group.h" |
38 | | #include "bthread/timer_thread.h" |
39 | | #include "bthread/bthread.h" |
40 | | |
41 | | #ifdef __x86_64__ |
42 | | #include <x86intrin.h> |
43 | | #endif // __x86_64__ |
44 | | |
45 | | #ifdef __ARM_NEON |
46 | | #include <arm_neon.h> |
47 | | #endif // __ARM_NEON |
48 | | |
49 | | namespace bthread { |
50 | | |
51 | | // Global span function pointers for bthread lifecycle tracing. |
52 | | // These are set by brpc layer via bthread_set_span_funcs(). |
53 | | void* (*g_create_bthread_span)() = NULL; |
54 | | void (*g_rpcz_parent_span_dtor)(void*) = NULL; |
55 | | void (*g_end_bthread_span)() = NULL; |
56 | | |
57 | | static const bthread_attr_t BTHREAD_ATTR_TASKGROUP = { |
58 | | BTHREAD_STACKTYPE_UNKNOWN, 0, NULL, BTHREAD_TAG_INVALID, {0} }; |
59 | | |
60 | | DEFINE_bool(show_bthread_creation_in_vars, false, "When this flags is on, The time " |
61 | | "from bthread creation to first run will be recorded and shown in /vars"); |
62 | | BUTIL_VALIDATE_GFLAG(show_bthread_creation_in_vars, butil::PassValidate); |
63 | | |
64 | | DEFINE_bool(show_per_worker_usage_in_vars, false, |
65 | | "Show per-worker usage in /vars/bthread_per_worker_usage_<tid>"); |
66 | | BUTIL_VALIDATE_GFLAG(show_per_worker_usage_in_vars, butil::PassValidate); |
67 | | |
68 | | DEFINE_bool(bthread_enable_cpu_clock_stat, false, |
69 | | "Enable CPU clock statistics for bthread"); |
70 | | BUTIL_VALIDATE_GFLAG(bthread_enable_cpu_clock_stat, butil::PassValidate); |
71 | | |
72 | | BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group, NULL); |
73 | | // Sync with TaskMeta::local_storage when a bthread is created or destroyed. |
74 | | // During running, the two fields may be inconsistent, use tls_bls as the |
75 | | // groundtruth. |
76 | | BAIDU_VOLATILE_THREAD_LOCAL(LocalStorage, tls_bls, BTHREAD_LOCAL_STORAGE_INITIALIZER); |
77 | | |
78 | | // defined in bthread/key.cpp |
79 | | extern void return_keytable(bthread_keytable_pool_t*, KeyTable*); |
80 | | |
81 | | // [Hacky] This is a special TLS set by bthread-rpc privately... to save |
82 | | // overhead of creation keytable, may be removed later. |
83 | | BAIDU_VOLATILE_THREAD_LOCAL(void*, tls_unique_user_ptr, NULL); |
84 | | |
85 | | const TaskStatistics EMPTY_STAT = { 0, 0, 0 }; |
86 | | |
87 | 0 | AtomicInteger128::Value AtomicInteger128::load() const { |
88 | 0 | #ifdef __x86_64__ |
89 | 0 | (void)_mutex; |
90 | 0 | (void)_seq; |
91 | 0 | __m128i value = _mm_load_si128(reinterpret_cast<const __m128i*>(&_value)); |
92 | 0 | return {value[0], value[1]}; |
93 | | #elif defined(__ARM_NEON) |
94 | | (void)_mutex; |
95 | | (void)_seq; |
96 | | int64x2_t value = vld1q_s64(reinterpret_cast<const int64_t*>(&_value)); |
97 | | return {value[0], value[1]}; |
98 | | #elif defined(__riscv) && __riscv_xlen == 64 |
99 | | (void)_mutex; |
100 | | // RISC-V: Seqlock-based atomic 128-bit load. |
101 | | int64_t v1, v2; |
102 | | uint64_t seq0, seq1; |
103 | | do { |
104 | | __asm__ volatile( |
105 | | "ld %0, %1\n\t" |
106 | | : "=r"(seq0) |
107 | | : "m"(_seq) |
108 | | : "memory" |
109 | | ); |
110 | | if (seq0 & 1) continue; |
111 | | __asm__ volatile("fence r, rw\n\t" ::: "memory"); |
112 | | __asm__ volatile( |
113 | | "ld %0, %2\n\t" |
114 | | "ld %1, %3\n\t" |
115 | | : "=r"(v1), "=r"(v2) |
116 | | : "m"(_value.v1), "m"(_value.v2) |
117 | | : "memory" |
118 | | ); |
119 | | __asm__ volatile("fence r, rw\n\t" ::: "memory"); |
120 | | __asm__ volatile( |
121 | | "ld %0, %1\n\t" |
122 | | : "=r"(seq1) |
123 | | : "m"(_seq) |
124 | | : "memory" |
125 | | ); |
126 | | } while (seq0 != seq1); |
127 | | return {v1, v2}; |
128 | | #else |
129 | | BAIDU_SCOPED_LOCK(const_cast<FastPthreadMutex&>(_mutex)); |
130 | | return _value; |
131 | | #endif |
132 | 0 | } |
133 | | |
134 | 0 | void AtomicInteger128::store(Value value) { |
135 | 0 | #ifdef __x86_64__ |
136 | 0 | (void)_seq; |
137 | 0 | __m128i v = _mm_load_si128(reinterpret_cast<__m128i*>(&value)); |
138 | 0 | _mm_store_si128(reinterpret_cast<__m128i*>(&_value), v); |
139 | | #elif defined(__ARM_NEON) |
140 | | (void)_seq; |
141 | | int64x2_t v = vld1q_s64(reinterpret_cast<int64_t*>(&value)); |
142 | | vst1q_s64(reinterpret_cast<int64_t*>(&_value), v); |
143 | | #elif defined(__riscv) && __riscv_xlen == 64 |
144 | | (void)_mutex; |
145 | | // RISC-V: Seqlock-based atomic 128-bit store. |
146 | | uint64_t old_seq; |
147 | | __asm__ volatile( |
148 | | "ld %0, %1\n\t" |
149 | | : "=r"(old_seq) |
150 | | : "m"(_seq) |
151 | | : "memory" |
152 | | ); |
153 | | uint64_t new_seq = old_seq + 1; |
154 | | __asm__ volatile( |
155 | | "fence w, w\n\t" |
156 | | "sd %1, %0\n\t" |
157 | | : "=m"(_seq) |
158 | | : "r"(new_seq) |
159 | | : "memory" |
160 | | ); |
161 | | __asm__ volatile("fence w, w\n\t" ::: "memory"); |
162 | | __asm__ volatile( |
163 | | "sd %2, %0\n\t" |
164 | | "sd %3, %1\n\t" |
165 | | : "=m"(_value.v1), "=m"(_value.v2) |
166 | | : "r"(value.v1), "r"(value.v2) |
167 | | : "memory" |
168 | | ); |
169 | | __asm__ volatile("fence w, w\n\t" ::: "memory"); |
170 | | new_seq++; |
171 | | __asm__ volatile( |
172 | | "sd %1, %0\n\t" |
173 | | : "=m"(_seq) |
174 | | : "r"(new_seq) |
175 | | : "memory" |
176 | | ); |
177 | | #else |
178 | | BAIDU_SCOPED_LOCK(const_cast<FastPthreadMutex&>(_mutex)); |
179 | | _value = value; |
180 | | #endif |
181 | 0 | } |
182 | | |
183 | | |
184 | 0 | int TaskGroup::get_attr(bthread_t tid, bthread_attr_t* out) { |
185 | 0 | TaskMeta* const m = address_meta(tid); |
186 | 0 | if (m != NULL) { |
187 | 0 | const uint32_t given_ver = get_version(tid); |
188 | 0 | BAIDU_SCOPED_LOCK(m->version_lock); |
189 | 0 | if (given_ver == *m->version_butex) { |
190 | 0 | *out = m->attr; |
191 | 0 | return 0; |
192 | 0 | } |
193 | 0 | } |
194 | 0 | errno = EINVAL; |
195 | 0 | return -1; |
196 | 0 | } |
197 | | |
198 | 0 | void TaskGroup::set_stopped(bthread_t tid) { |
199 | 0 | TaskMeta* const m = address_meta(tid); |
200 | 0 | if (m != NULL) { |
201 | 0 | const uint32_t given_ver = get_version(tid); |
202 | 0 | BAIDU_SCOPED_LOCK(m->version_lock); |
203 | 0 | if (given_ver == *m->version_butex) { |
204 | 0 | m->stop = true; |
205 | 0 | } |
206 | 0 | } |
207 | 0 | } |
208 | | |
209 | 0 | bool TaskGroup::is_stopped(bthread_t tid) { |
210 | 0 | TaskMeta* const m = address_meta(tid); |
211 | 0 | if (m != NULL) { |
212 | 0 | const uint32_t given_ver = get_version(tid); |
213 | 0 | BAIDU_SCOPED_LOCK(m->version_lock); |
214 | 0 | if (given_ver == *m->version_butex) { |
215 | 0 | return m->stop; |
216 | 0 | } |
217 | 0 | } |
218 | | // If the tid does not exist or version does not match, it's intuitive |
219 | | // to treat the thread as "stopped". |
220 | 0 | return true; |
221 | 0 | } |
222 | | |
223 | 0 | bool TaskGroup::wait_task(bthread_t* tid) { |
224 | 0 | do { |
225 | 0 | #ifndef BTHREAD_DONT_SAVE_PARKING_STATE |
226 | 0 | if (_last_pl_state.stopped()) { |
227 | 0 | return false; |
228 | 0 | } |
229 | 0 | _pl->wait(_last_pl_state); |
230 | 0 | if (steal_task(tid)) { |
231 | 0 | return true; |
232 | 0 | } |
233 | | #else |
234 | | const ParkingLot::State st = _pl->get_state(); |
235 | | if (st.stopped()) { |
236 | | return false; |
237 | | } |
238 | | if (steal_task(tid)) { |
239 | | return true; |
240 | | } |
241 | | _pl->wait(st); |
242 | | #endif |
243 | 0 | } while (true); |
244 | 0 | } |
245 | | |
246 | 0 | static double get_cumulated_cputime_from_this(void* arg) { |
247 | 0 | return static_cast<TaskGroup*>(arg)->cumulated_cputime_ns() / 1000000000.0; |
248 | 0 | } |
249 | | |
250 | 0 | int64_t TaskGroup::cumulated_cputime_ns() const { |
251 | 0 | CPUTimeStat cpu_time_stat = _cpu_time_stat.load(); |
252 | | // Add the elapsed time of running bthread. |
253 | 0 | int64_t cumulated_cputime_ns = cpu_time_stat.cumulated_cputime_ns(); |
254 | 0 | if (!cpu_time_stat.is_main_task()) { |
255 | 0 | cumulated_cputime_ns += butil::cpuwide_time_ns() - cpu_time_stat.last_run_ns(); |
256 | 0 | } |
257 | 0 | return cumulated_cputime_ns; |
258 | 0 | } |
259 | | |
260 | 0 | void TaskGroup::run_main_task() { |
261 | 0 | bvar::PassiveStatus<double> cumulated_cputime( |
262 | 0 | get_cumulated_cputime_from_this, this); |
263 | 0 | std::unique_ptr<bvar::PerSecond<bvar::PassiveStatus<double> > > usage_bvar; |
264 | |
|
265 | 0 | TaskGroup* dummy = this; |
266 | 0 | bthread_t tid; |
267 | 0 | while (wait_task(&tid)) { |
268 | 0 | sched_to(&dummy, tid); |
269 | 0 | DCHECK_EQ(this, dummy); |
270 | 0 | DCHECK_EQ(_cur_meta->stack, _main_stack); |
271 | 0 | if (_cur_meta->tid != _main_tid) { |
272 | 0 | task_runner(1/*skip remained*/); |
273 | 0 | } |
274 | 0 | if (FLAGS_show_per_worker_usage_in_vars && !usage_bvar) { |
275 | 0 | char name[32]; |
276 | | #if defined(OS_MACOSX) |
277 | | snprintf(name, sizeof(name), "bthread_worker_usage_%" PRIu64, |
278 | | pthread_numeric_id()); |
279 | | #else |
280 | 0 | snprintf(name, sizeof(name), "bthread_worker_usage_%ld", |
281 | 0 | (long)syscall(SYS_gettid)); |
282 | 0 | #endif |
283 | 0 | usage_bvar.reset(new bvar::PerSecond<bvar::PassiveStatus<double> > |
284 | 0 | (name, &cumulated_cputime, 1)); |
285 | 0 | } |
286 | 0 | } |
287 | | // Don't forget to add elapse of last wait_task. |
288 | 0 | current_task()->stat.cputime_ns += |
289 | 0 | butil::cpuwide_time_ns() - _cpu_time_stat.load_unsafe().last_run_ns(); |
290 | 0 | } |
291 | | |
292 | | TaskGroup::TaskGroup(TaskControl* c) |
293 | 0 | : _control(c) { |
294 | 0 | CHECK(c); |
295 | 0 | } |
296 | | |
297 | 0 | TaskGroup::~TaskGroup() { |
298 | 0 | if (_main_tid) { |
299 | 0 | TaskMeta* m = address_meta(_main_tid); |
300 | 0 | CHECK(_main_stack == m->stack); |
301 | | #ifdef BUTIL_USE_ASAN |
302 | | _main_stack->storage.bottom = NULL; |
303 | | _main_stack->storage.stacksize = 0; |
304 | | #endif // BUTIL_USE_ASAN |
305 | 0 | return_stack(m->release_stack()); |
306 | 0 | return_resource(get_slot(_main_tid)); |
307 | 0 | _main_tid = 0; |
308 | 0 | } |
309 | 0 | } |
310 | | |
311 | | #ifdef BUTIL_USE_ASAN |
312 | | // Returns the **highest** address of the calling pthread's stack and its |
313 | | // total size, matching brpc's `StackStorage::bottom` convention (see comment |
314 | | // in bthread/stack.h: "Assume stack grows upwards"). Note that on Linux |
315 | | // `pthread_attr_getstack(3)` returns the lowest address of the region, so |
316 | | // we have to translate it; on macOS `pthread_get_stackaddr_np(3)` already |
317 | | // returns the stack base (highest address), so we use it as-is. |
318 | | int PthreadAttrGetStack(void*& stack_addr, size_t& stack_size) { |
319 | | #if defined(OS_MACOSX) |
320 | | stack_addr = pthread_get_stackaddr_np(pthread_self()); |
321 | | stack_size = pthread_get_stacksize_np(pthread_self()); |
322 | | return 0; |
323 | | #else |
324 | | pthread_attr_t attr; |
325 | | int rc = pthread_getattr_np(pthread_self(), &attr); |
326 | | if (0 != rc) { |
327 | | LOG(ERROR) << "Fail to get pthread attributes: " << berror(rc); |
328 | | return rc; |
329 | | } |
330 | | void* stack_lowest = NULL; |
331 | | rc = pthread_attr_getstack(&attr, &stack_lowest, &stack_size); |
332 | | if (0 != rc) { |
333 | | LOG(ERROR) << "Fail to get pthread stack: " << berror(rc); |
334 | | } else { |
335 | | // Translate lowest -> highest to match StackStorage::bottom. |
336 | | stack_addr = (char*)stack_lowest + stack_size; |
337 | | } |
338 | | pthread_attr_destroy(&attr); |
339 | | return rc; |
340 | | #endif // OS_MACOSX |
341 | | } |
342 | | #endif // BUTIL_USE_ASAN |
343 | | |
344 | 0 | int TaskGroup::init(size_t runqueue_capacity) { |
345 | 0 | if (_rq.init(runqueue_capacity) != 0) { |
346 | 0 | LOG(FATAL) << "Fail to init _rq"; |
347 | 0 | return -1; |
348 | 0 | } |
349 | 0 | if (_remote_rq.init(runqueue_capacity / 2) != 0) { |
350 | 0 | LOG(FATAL) << "Fail to init _remote_rq"; |
351 | 0 | return -1; |
352 | 0 | } |
353 | | |
354 | | #ifdef BUTIL_USE_ASAN |
355 | | void* stack_addr = NULL; |
356 | | size_t stack_size = 0; |
357 | | if (0 != PthreadAttrGetStack(stack_addr, stack_size)) { |
358 | | return -1; |
359 | | } |
360 | | #endif // BUTIL_USE_ASAN |
361 | | |
362 | 0 | ContextualStack* stk = get_stack(STACK_TYPE_MAIN, NULL); |
363 | 0 | if (NULL == stk) { |
364 | 0 | LOG(FATAL) << "Fail to get main stack container"; |
365 | 0 | return -1; |
366 | 0 | } |
367 | 0 | butil::ResourceId<TaskMeta> slot; |
368 | 0 | TaskMeta* m = butil::get_resource<TaskMeta>(&slot); |
369 | 0 | if (NULL == m) { |
370 | 0 | LOG(FATAL) << "Fail to get TaskMeta"; |
371 | 0 | return -1; |
372 | 0 | } |
373 | 0 | m->sleep_failed = false; |
374 | 0 | m->stop = false; |
375 | 0 | m->interrupted = false; |
376 | 0 | m->about_to_quit = false; |
377 | 0 | m->fn = NULL; |
378 | 0 | m->arg = NULL; |
379 | 0 | m->local_storage = LOCAL_STORAGE_INIT; |
380 | 0 | m->cpuwide_start_ns = butil::cpuwide_time_ns(); |
381 | 0 | m->stat = EMPTY_STAT; |
382 | 0 | m->attr = BTHREAD_ATTR_TASKGROUP; |
383 | 0 | m->tid = make_tid(*m->version_butex, slot); |
384 | 0 | m->set_stack(stk); |
385 | |
|
386 | | #ifdef BUTIL_USE_ASAN |
387 | | stk->storage.bottom = stack_addr; |
388 | | stk->storage.stacksize = stack_size; |
389 | | // No guard size required for ASan. |
390 | | #endif // BUTIL_USE_ASAN |
391 | |
|
392 | 0 | _cur_meta = m; |
393 | 0 | _main_tid = m->tid; |
394 | 0 | _main_stack = stk; |
395 | |
|
396 | 0 | CPUTimeStat cpu_time_stat; |
397 | 0 | cpu_time_stat.set_last_run_ns(m->cpuwide_start_ns, true); |
398 | 0 | _cpu_time_stat.store(cpu_time_stat); |
399 | 0 | _last_cpu_clock_ns = 0; |
400 | |
|
401 | 0 | return 0; |
402 | 0 | } |
403 | | |
404 | | #ifdef BUTIL_USE_ASAN |
405 | | void TaskGroup::asan_task_runner(intptr_t) { |
406 | | // This is a new thread, and it doesn't have the fake stack yet. ASan will |
407 | | // create it lazily, for now just pass NULL. |
408 | | internal::FinishSwitchFiber(NULL); |
409 | | task_runner(0); |
410 | | } |
411 | | #endif // BUTIL_USE_ASAN |
412 | | |
413 | 0 | void TaskGroup::task_runner(intptr_t skip_remained) { |
414 | | // NOTE: tls_task_group is volatile since tasks are moved around |
415 | | // different groups. |
416 | 0 | TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); |
417 | | #ifdef BRPC_BTHREAD_TRACER |
418 | | TaskTracer::set_running_status(g->tid(), g->_cur_meta); |
419 | | #endif // BRPC_BTHREAD_TRACER |
420 | |
|
421 | 0 | if (!skip_remained) { |
422 | 0 | while (g->_last_context_remained) { |
423 | 0 | RemainedFn fn = g->_last_context_remained; |
424 | 0 | g->_last_context_remained = NULL; |
425 | 0 | fn(g->_last_context_remained_arg); |
426 | 0 | g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); |
427 | 0 | } |
428 | |
|
429 | | #ifndef NDEBUG |
430 | | --g->_sched_recursive_guard; |
431 | | #endif |
432 | 0 | } |
433 | |
|
434 | 0 | do { |
435 | | // A task can be stopped before it gets running, in which case |
436 | | // we may skip user function, but that may confuse user: |
437 | | // Most tasks have variables to remember running result of the task, |
438 | | // which is often initialized to values indicating success. If an |
439 | | // user function is never called, the variables will be unchanged |
440 | | // however they'd better reflect failures because the task is stopped |
441 | | // abnormally. |
442 | | |
443 | | // Meta and identifier of the task is persistent in this run. |
444 | 0 | TaskMeta* const m = g->_cur_meta; |
445 | |
|
446 | 0 | if (FLAGS_show_bthread_creation_in_vars) { |
447 | | // NOTE: the thread triggering exposure of pending time may spend |
448 | | // considerable time because a single bvar::LatencyRecorder |
449 | | // contains many bvar. |
450 | 0 | g->_control->exposed_pending_time() << |
451 | 0 | (butil::cpuwide_time_ns() - m->cpuwide_start_ns) / 1000L; |
452 | 0 | } |
453 | | |
454 | | // Not catch exceptions except ExitException which is for implementing |
455 | | // bthread_exit(). User code is intended to crash when an exception is |
456 | | // not caught explicitly. This is consistent with other threading |
457 | | // libraries. |
458 | 0 | void* thread_return; |
459 | 0 | try { |
460 | 0 | thread_return = m->fn(m->arg); |
461 | 0 | } catch (ExitException& e) { |
462 | 0 | thread_return = e.value(); |
463 | 0 | } |
464 | |
|
465 | 0 | if (m->attr.flags & BTHREAD_INHERIT_SPAN) { |
466 | 0 | if (g_end_bthread_span) { |
467 | 0 | g_end_bthread_span(); |
468 | 0 | } |
469 | 0 | } |
470 | | |
471 | | // TODO: Save thread_return |
472 | 0 | (void)thread_return; |
473 | | |
474 | | // Logging must be done before returning the keytable, since the logging lib |
475 | | // use bthread local storage internally, or will cause memory leak. |
476 | | // FIXME: the time from quiting fn to here is not counted into cputime |
477 | 0 | if (m->attr.flags & BTHREAD_LOG_START_AND_FINISH) { |
478 | 0 | LOG(INFO) << "Finished bthread " << m->tid << ", cputime=" |
479 | 0 | << m->stat.cputime_ns / 1000000.0 << "ms"; |
480 | 0 | } |
481 | | |
482 | | // Clean up span if it exists. This must be done before keytable cleanup |
483 | | // because span cleanup may use bthread local storage (e.g. logging, |
484 | | // which allocates bthread-local stream arrays via bthread_setspecific). |
485 | | // If span cleanup ran after keytable cleanup, such allocations would |
486 | | // re-populate the keytable and never be reclaimed, causing memory leak. |
487 | 0 | LocalStorage* tls_bls_ptr = bthread::tls_bls_ptr(); |
488 | 0 | if (tls_bls_ptr->rpcz_parent_span && g_rpcz_parent_span_dtor) { |
489 | 0 | g_rpcz_parent_span_dtor(tls_bls_ptr->rpcz_parent_span); |
490 | 0 | tls_bls_ptr = bthread::tls_bls_ptr(); |
491 | 0 | tls_bls_ptr->rpcz_parent_span = NULL; |
492 | 0 | m->local_storage.rpcz_parent_span = NULL; |
493 | 0 | } |
494 | | |
495 | | // Clean tls variables, must be done before changing version_butex |
496 | | // otherwise another thread just joined this thread may not see side |
497 | | // effects of destructing tls variables. |
498 | 0 | KeyTable* kt = tls_bls_ptr->keytable; |
499 | 0 | if (kt != NULL) { |
500 | 0 | return_keytable(m->attr.keytable_pool, kt); |
501 | | // After deletion: tls may be set during deletion. |
502 | 0 | tls_bls_ptr = bthread::tls_bls_ptr(); |
503 | 0 | tls_bls_ptr->keytable = NULL; |
504 | 0 | m->local_storage.keytable = NULL; // optional |
505 | 0 | } |
506 | | |
507 | | // During running the function in TaskMeta and deleting the KeyTable in |
508 | | // return_KeyTable, the group is probably changed. |
509 | 0 | g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); |
510 | | |
511 | | // Increase the version and wake up all joiners, if resulting version |
512 | | // is 0, change it to 1 to make bthread_t never be 0. Any access |
513 | | // or join to the bthread after changing version will be rejected. |
514 | | // The spinlock is for visibility of TaskGroup::get_attr. |
515 | | #ifdef BRPC_BTHREAD_TRACER |
516 | | bool tracing = false; |
517 | | #endif // BRPC_BTHREAD_TRACER |
518 | 0 | { |
519 | 0 | BAIDU_SCOPED_LOCK(m->version_lock); |
520 | | #ifdef BRPC_BTHREAD_TRACER |
521 | | tracing = TaskTracer::set_end_status_unsafe(m); |
522 | | #endif // BRPC_BTHREAD_TRACER |
523 | 0 | if (0 == ++*m->version_butex) { |
524 | 0 | ++*m->version_butex; |
525 | 0 | } |
526 | 0 | } |
527 | 0 | butex_wake_except(m->version_butex, 0); |
528 | |
|
529 | | #ifdef BRPC_BTHREAD_TRACER |
530 | | if (tracing) { |
531 | | // Wait for tracing completion. |
532 | | g->_control->_task_tracer.WaitForTracing(m); |
533 | | } |
534 | | g->_control->_task_tracer.set_status(TASK_STATUS_UNKNOWN, m); |
535 | | #endif // BRPC_BTHREAD_TRACER |
536 | |
|
537 | 0 | g->_control->_nbthreads << -1; |
538 | 0 | g->_control->tag_nbthreads(g->tag()) << -1; |
539 | 0 | g->set_remained(_release_last_context, m); |
540 | 0 | ending_sched(&g); |
541 | |
|
542 | 0 | } while (g->_cur_meta->tid != g->_main_tid); |
543 | | |
544 | | // Was called from a pthread and we don't have BTHREAD_STACKTYPE_PTHREAD |
545 | | // tasks to run, quit for more tasks. |
546 | 0 | } |
547 | | |
548 | 0 | void TaskGroup::_release_last_context(void* arg) { |
549 | 0 | TaskMeta* m = static_cast<TaskMeta*>(arg); |
550 | 0 | if (m->stack_type() != STACK_TYPE_PTHREAD) { |
551 | 0 | return_stack(m->release_stack()/*may be NULL*/); |
552 | 0 | } else { |
553 | | // it's _main_stack, don't return. |
554 | 0 | m->set_stack(NULL); |
555 | 0 | } |
556 | 0 | return_resource(get_slot(m->tid)); |
557 | 0 | } |
558 | | |
559 | | int TaskGroup::start_foreground(TaskGroup** pg, |
560 | | bthread_t* __restrict th, |
561 | | const bthread_attr_t* __restrict attr, |
562 | | void * (*fn)(void*), |
563 | 0 | void* __restrict arg) { |
564 | 0 | if (__builtin_expect(!fn, 0)) { |
565 | 0 | return EINVAL; |
566 | 0 | } |
567 | 0 | const int64_t start_ns = butil::cpuwide_time_ns(); |
568 | 0 | const bthread_attr_t using_attr = (attr ? *attr : BTHREAD_ATTR_NORMAL); |
569 | 0 | butil::ResourceId<TaskMeta> slot; |
570 | 0 | TaskMeta* m = butil::get_resource(&slot); |
571 | 0 | if (BAIDU_UNLIKELY(NULL == m)) { |
572 | 0 | return ENOMEM; |
573 | 0 | } |
574 | 0 | CHECK(m->current_waiter.load(butil::memory_order_relaxed) == NULL); |
575 | 0 | m->sleep_failed = false; |
576 | 0 | m->stop = false; |
577 | 0 | m->interrupted = false; |
578 | 0 | m->about_to_quit = false; |
579 | 0 | m->fn = fn; |
580 | 0 | m->arg = arg; |
581 | 0 | CHECK(m->stack == NULL); |
582 | 0 | m->attr = using_attr; |
583 | 0 | m->local_storage = LOCAL_STORAGE_INIT; |
584 | 0 | if (using_attr.flags & BTHREAD_INHERIT_SPAN) { |
585 | 0 | if (g_create_bthread_span) { |
586 | 0 | m->local_storage.rpcz_parent_span = g_create_bthread_span(); |
587 | 0 | } else { |
588 | 0 | m->local_storage.rpcz_parent_span = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls).rpcz_parent_span; |
589 | 0 | } |
590 | 0 | } |
591 | 0 | m->cpuwide_start_ns = start_ns; |
592 | 0 | m->stat = EMPTY_STAT; |
593 | 0 | m->tid = make_tid(*m->version_butex, slot); |
594 | |
|
595 | 0 | TaskGroup* g = *pg; |
596 | 0 | m->priority_index = g->_cur_meta->priority_index; |
597 | 0 | m->attr.tag = g->tag(); |
598 | 0 | *th = m->tid; |
599 | 0 | if (using_attr.flags & BTHREAD_LOG_START_AND_FINISH) { |
600 | 0 | LOG(INFO) << "Started bthread " << m->tid; |
601 | 0 | } |
602 | |
|
603 | 0 | g->_control->_nbthreads << 1; |
604 | 0 | g->_control->tag_nbthreads(g->tag()) << 1; |
605 | | #ifdef BRPC_BTHREAD_TRACER |
606 | | g->_control->_task_tracer.set_status(TASK_STATUS_CREATED, m); |
607 | | #endif // BRPC_BTHREAD_TRACER |
608 | 0 | if (g->is_current_pthread_task()) { |
609 | | // never create foreground task in pthread. |
610 | 0 | g->ready_to_run(m, using_attr.flags & BTHREAD_NOSIGNAL); |
611 | 0 | } else { |
612 | | // NOSIGNAL affects current task, not the new task. |
613 | 0 | RemainedFn fn = NULL; |
614 | 0 | auto& cur_attr = g->_cur_meta->attr; |
615 | 0 | if (g->_control->_enable_priority_queue && cur_attr.flags & BTHREAD_GLOBAL_PRIORITY) { |
616 | 0 | fn = priority_to_run; |
617 | 0 | } else if (g->current_task()->about_to_quit) { |
618 | 0 | fn = ready_to_run_in_worker_ignoresignal; |
619 | 0 | } else { |
620 | 0 | fn = ready_to_run_in_worker; |
621 | 0 | } |
622 | 0 | ReadyToRunArgs args = { |
623 | 0 | g->tag(), g->_cur_meta, (bool)(using_attr.flags & BTHREAD_NOSIGNAL) |
624 | 0 | }; |
625 | 0 | g->set_remained(fn, &args); |
626 | 0 | sched_to(pg, m->tid); |
627 | 0 | } |
628 | 0 | return 0; |
629 | 0 | } |
630 | | |
631 | | template <bool REMOTE> |
632 | | int TaskGroup::start_background(bthread_t* __restrict th, |
633 | | const bthread_attr_t* __restrict attr, |
634 | | void * (*fn)(void*), |
635 | 0 | void* __restrict arg) { |
636 | 0 | if (__builtin_expect(!fn, 0)) { |
637 | 0 | return EINVAL; |
638 | 0 | } |
639 | 0 | const int64_t start_ns = butil::cpuwide_time_ns(); |
640 | 0 | const bthread_attr_t using_attr = (attr ? *attr : BTHREAD_ATTR_NORMAL); |
641 | 0 | butil::ResourceId<TaskMeta> slot; |
642 | 0 | TaskMeta* m = butil::get_resource(&slot); |
643 | 0 | if (BAIDU_UNLIKELY(NULL == m)) { |
644 | 0 | return ENOMEM; |
645 | 0 | } |
646 | 0 | CHECK(m->current_waiter.load(butil::memory_order_relaxed) == NULL); |
647 | 0 | m->sleep_failed = false; |
648 | 0 | m->stop = false; |
649 | 0 | m->interrupted = false; |
650 | 0 | m->about_to_quit = false; |
651 | 0 | m->fn = fn; |
652 | 0 | m->arg = arg; |
653 | 0 | CHECK(m->stack == NULL); |
654 | 0 | m->attr = using_attr; |
655 | 0 | m->local_storage = LOCAL_STORAGE_INIT; |
656 | 0 | if (using_attr.flags & BTHREAD_INHERIT_SPAN) { |
657 | 0 | if (g_create_bthread_span) { |
658 | 0 | m->local_storage.rpcz_parent_span = g_create_bthread_span(); |
659 | 0 | } else { |
660 | 0 | m->local_storage.rpcz_parent_span = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls).rpcz_parent_span; |
661 | 0 | } |
662 | 0 | } |
663 | 0 | m->cpuwide_start_ns = start_ns; |
664 | 0 | m->stat = EMPTY_STAT; |
665 | 0 | m->tid = make_tid(*m->version_butex, slot); |
666 | 0 | m->priority_index = _cur_meta->priority_index; |
667 | 0 | *th = m->tid; |
668 | 0 | if (using_attr.flags & BTHREAD_LOG_START_AND_FINISH) { |
669 | 0 | LOG(INFO) << "Started bthread " << m->tid; |
670 | 0 | } |
671 | 0 | m->attr.tag = tag(); |
672 | 0 | _control->_nbthreads << 1; |
673 | 0 | _control->tag_nbthreads(tag()) << 1; |
674 | | #ifdef BRPC_BTHREAD_TRACER |
675 | | _control->_task_tracer.set_status(TASK_STATUS_CREATED, m); |
676 | | #endif // BRPC_BTHREAD_TRACER |
677 | 0 | if (REMOTE) { |
678 | 0 | ready_to_run_remote(m, (using_attr.flags & BTHREAD_NOSIGNAL)); |
679 | 0 | } else { |
680 | 0 | ready_to_run(m, (using_attr.flags & BTHREAD_NOSIGNAL)); |
681 | 0 | } |
682 | 0 | return 0; |
683 | 0 | } Unexecuted instantiation: int bthread::TaskGroup::start_background<true>(unsigned long*, bthread_attr_t const*, void* (*)(void*), void*) Unexecuted instantiation: int bthread::TaskGroup::start_background<false>(unsigned long*, bthread_attr_t const*, void* (*)(void*), void*) |
684 | | |
685 | | // Explicit instantiations. |
686 | | template int |
687 | | TaskGroup::start_background<true>(bthread_t* __restrict th, |
688 | | const bthread_attr_t* __restrict attr, |
689 | | void * (*fn)(void*), |
690 | | void* __restrict arg); |
691 | | template int |
692 | | TaskGroup::start_background<false>(bthread_t* __restrict th, |
693 | | const bthread_attr_t* __restrict attr, |
694 | | void * (*fn)(void*), |
695 | | void* __restrict arg); |
696 | | |
697 | 0 | int TaskGroup::join(bthread_t tid, void** return_value) { |
698 | 0 | if (__builtin_expect(!tid, 0)) { // tid of bthread is never 0. |
699 | 0 | return EINVAL; |
700 | 0 | } |
701 | 0 | TaskMeta* m = address_meta(tid); |
702 | 0 | if (BAIDU_UNLIKELY(NULL == m)) { |
703 | | // The bthread is not created yet, this join is definitely wrong. |
704 | 0 | return EINVAL; |
705 | 0 | } |
706 | 0 | TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); |
707 | 0 | if (g != NULL && g->current_tid() == tid) { |
708 | | // joining self causes indefinite waiting. |
709 | 0 | return EINVAL; |
710 | 0 | } |
711 | 0 | const uint32_t expected_version = get_version(tid); |
712 | 0 | while (*m->version_butex == expected_version) { |
713 | 0 | if (butex_wait(m->version_butex, expected_version, NULL) < 0 && |
714 | 0 | errno != EWOULDBLOCK && errno != EINTR) { |
715 | 0 | return errno; |
716 | 0 | } |
717 | 0 | } |
718 | | // Ensure all memory writes made by the joined bthread are visible to |
719 | | // the joining thread after join returns. This matches the semantic |
720 | | // guarantee provided by pthread_join() across supported architectures. |
721 | 0 | butil::atomic_thread_fence(butil::memory_order_acquire); |
722 | 0 | if (return_value) { |
723 | 0 | *return_value = NULL; |
724 | 0 | } |
725 | 0 | return 0; |
726 | 0 | } |
727 | | |
728 | 0 | bool TaskGroup::exists(bthread_t tid) { |
729 | 0 | if (tid != 0) { // tid of bthread is never 0. |
730 | 0 | TaskMeta* m = address_meta(tid); |
731 | 0 | if (m != NULL) { |
732 | 0 | return (*m->version_butex == get_version(tid)); |
733 | 0 | } |
734 | 0 | } |
735 | 0 | return false; |
736 | 0 | } |
737 | | |
738 | 0 | TaskStatistics TaskGroup::main_stat() const { |
739 | 0 | TaskMeta* m = address_meta(_main_tid); |
740 | 0 | return m ? m->stat : EMPTY_STAT; |
741 | 0 | } |
742 | | |
743 | 0 | void TaskGroup::ending_sched(TaskGroup** pg) { |
744 | 0 | TaskGroup* g = *pg; |
745 | 0 | bthread_t next_tid = 0; |
746 | | // Find next task to run, if none, switch to idle thread of the group. |
747 | |
|
748 | 0 | #ifndef BTHREAD_FAIR_WSQ |
749 | | // When BTHREAD_FAIR_WSQ is defined, profiling shows that cpu cost of |
750 | | // WSQ::steal() in example/multi_threaded_echo_c++ changes from 1.9% |
751 | | // to 2.9% |
752 | 0 | const bool popped = g->_rq.pop(&next_tid); |
753 | | #else |
754 | | const bool popped = g->_rq.steal(&next_tid); |
755 | | #endif |
756 | 0 | if (!popped && !g->steal_task(&next_tid)) { |
757 | | // Jump to main task if there's no task to run. |
758 | 0 | next_tid = g->_main_tid; |
759 | 0 | } |
760 | |
|
761 | 0 | TaskMeta* const cur_meta = g->_cur_meta; |
762 | 0 | TaskMeta* next_meta = address_meta(next_tid); |
763 | 0 | if (next_meta->stack == NULL) { |
764 | 0 | if (next_meta->stack_type() == cur_meta->stack_type()) { |
765 | | // Reuse the stack of the current ending task. |
766 | | // |
767 | | // also works with pthread_task scheduling to pthread_task, the |
768 | | // transfered stack is just _main_stack. |
769 | 0 | next_meta->set_stack(cur_meta->release_stack()); |
770 | 0 | } else { |
771 | | #ifdef BUTIL_USE_ASAN |
772 | | ContextualStack* stk = get_stack( |
773 | | next_meta->stack_type(), asan_task_runner); |
774 | | #else |
775 | 0 | ContextualStack* stk = get_stack(next_meta->stack_type(), task_runner); |
776 | 0 | #endif // BUTIL_USE_ASAN |
777 | 0 | if (stk) { |
778 | 0 | next_meta->set_stack(stk); |
779 | 0 | } else { |
780 | | // stack_type is BTHREAD_STACKTYPE_PTHREAD or out of memory, |
781 | | // In latter case, attr is forced to be BTHREAD_STACKTYPE_PTHREAD. |
782 | | // This basically means that if we can't allocate stack, run |
783 | | // the task in pthread directly. |
784 | 0 | next_meta->attr.stack_type = BTHREAD_STACKTYPE_PTHREAD; |
785 | 0 | next_meta->set_stack(g->_main_stack); |
786 | 0 | } |
787 | 0 | } |
788 | 0 | } |
789 | 0 | sched_to(pg, next_meta); |
790 | 0 | } |
791 | | |
792 | 0 | void TaskGroup::sched(TaskGroup** pg) { |
793 | 0 | TaskGroup* g = *pg; |
794 | 0 | bthread_t next_tid = 0; |
795 | | // Find next task to run, if none, switch to idle thread of the group. |
796 | 0 | #ifndef BTHREAD_FAIR_WSQ |
797 | 0 | const bool popped = g->_rq.pop(&next_tid); |
798 | | #else |
799 | | const bool popped = g->_rq.steal(&next_tid); |
800 | | #endif |
801 | 0 | if (!popped && !g->steal_task(&next_tid)) { |
802 | | // Jump to main task if there's no task to run. |
803 | 0 | next_tid = g->_main_tid; |
804 | 0 | } |
805 | 0 | sched_to(pg, next_tid); |
806 | 0 | } |
807 | | |
808 | | extern void CheckBthreadScheSafety(); |
809 | | |
810 | 0 | void TaskGroup::sched_to(TaskGroup** pg, TaskMeta* next_meta) { |
811 | 0 | TaskGroup* g = *pg; |
812 | | #ifndef NDEBUG |
813 | | if ((++g->_sched_recursive_guard) > 1) { |
814 | | LOG(FATAL) << "Recursively(" << g->_sched_recursive_guard - 1 |
815 | | << ") call sched_to(" << g << ")"; |
816 | | } |
817 | | #endif |
818 | | // Save errno so that errno is bthread-specific. |
819 | 0 | int saved_errno = errno; |
820 | 0 | void* saved_unique_user_ptr = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_unique_user_ptr); |
821 | |
|
822 | 0 | TaskMeta* const cur_meta = g->_cur_meta; |
823 | 0 | int64_t now = butil::cpuwide_time_ns(); |
824 | 0 | CPUTimeStat cpu_time_stat = g->_cpu_time_stat.load_unsafe(); |
825 | 0 | int64_t elp_ns = now - cpu_time_stat.last_run_ns(); |
826 | 0 | cur_meta->stat.cputime_ns += elp_ns; |
827 | | // Update cpu_time_stat. |
828 | 0 | cpu_time_stat.set_last_run_ns(now, is_main_task(g, next_meta->tid)); |
829 | 0 | cpu_time_stat.add_cumulated_cputime_ns(elp_ns, is_main_task(g, cur_meta->tid)); |
830 | 0 | g->_cpu_time_stat.store(cpu_time_stat); |
831 | |
|
832 | 0 | if (FLAGS_bthread_enable_cpu_clock_stat) { |
833 | 0 | const int64_t cpu_thread_time = butil::cputhread_time_ns(); |
834 | 0 | if (g->_last_cpu_clock_ns != 0) { |
835 | 0 | cur_meta->stat.cpu_usage_ns += cpu_thread_time - g->_last_cpu_clock_ns; |
836 | 0 | } |
837 | 0 | g->_last_cpu_clock_ns = cpu_thread_time; |
838 | 0 | } else { |
839 | 0 | g->_last_cpu_clock_ns = 0; |
840 | 0 | } |
841 | |
|
842 | 0 | ++cur_meta->stat.nswitch; |
843 | 0 | ++ g->_nswitch; |
844 | | // Switch to the task |
845 | 0 | if (__builtin_expect(next_meta != cur_meta, 1)) { |
846 | 0 | g->_cur_meta = next_meta; |
847 | | // Switch tls_bls |
848 | 0 | cur_meta->local_storage = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_bls); |
849 | 0 | BAIDU_SET_VOLATILE_THREAD_LOCAL(tls_bls, next_meta->local_storage); |
850 | | |
851 | | // Logging must be done after switching the local storage, since the logging lib |
852 | | // use bthread local storage internally, or will cause memory leak. |
853 | 0 | if ((cur_meta->attr.flags & BTHREAD_LOG_CONTEXT_SWITCH) || |
854 | 0 | (next_meta->attr.flags & BTHREAD_LOG_CONTEXT_SWITCH)) { |
855 | 0 | LOG(INFO) << "Switch bthread: " << cur_meta->tid << " -> " |
856 | 0 | << next_meta->tid; |
857 | 0 | } |
858 | |
|
859 | 0 | if (cur_meta->stack != NULL) { |
860 | 0 | if (next_meta->stack != cur_meta->stack) { |
861 | 0 | CheckBthreadScheSafety(); |
862 | | #ifdef BRPC_BTHREAD_TRACER |
863 | | g->_control->_task_tracer.set_status(TASK_STATUS_JUMPING, cur_meta); |
864 | | g->_control->_task_tracer.set_status(TASK_STATUS_JUMPING, next_meta); |
865 | | #endif // BRPC_BTHREAD_TRACER |
866 | 0 | { |
867 | 0 | BTHREAD_SCOPED_ASAN_FIBER_SWITCHER(next_meta->stack->storage); |
868 | 0 | jump_stack(cur_meta->stack, next_meta->stack); |
869 | 0 | } |
870 | | // probably went to another group, need to assign g again. |
871 | 0 | g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); |
872 | | #ifdef BRPC_BTHREAD_TRACER |
873 | | TaskTracer::set_running_status(g->tid(), g->_cur_meta); |
874 | | #endif // BRPC_BTHREAD_TRACER |
875 | 0 | } |
876 | | #ifndef NDEBUG |
877 | | else { |
878 | | // else pthread_task is switching to another pthread_task, sc |
879 | | // can only equal when they're both _main_stack |
880 | | CHECK(cur_meta->stack == g->_main_stack); |
881 | | } |
882 | | #endif |
883 | 0 | } /* else because of ending_sched(including pthread_task->pthread_task). */ |
884 | | #ifdef BRPC_BTHREAD_TRACER |
885 | | else { |
886 | | // _cur_meta: TASK_STATUS_FIRST_READY -> TASK_STATUS_RUNNING. |
887 | | TaskTracer::set_running_status(g->tid(), g->_cur_meta); |
888 | | } |
889 | | #endif // BRPC_BTHREAD_TRACER |
890 | 0 | } else { |
891 | 0 | LOG(FATAL) << "bthread=" << g->current_tid() << " sched_to itself!"; |
892 | 0 | } |
893 | |
|
894 | 0 | while (g->_last_context_remained) { |
895 | 0 | RemainedFn fn = g->_last_context_remained; |
896 | 0 | g->_last_context_remained = NULL; |
897 | 0 | fn(g->_last_context_remained_arg); |
898 | 0 | g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); |
899 | 0 | } |
900 | | |
901 | | // Restore errno |
902 | 0 | errno = saved_errno; |
903 | | // tls_unique_user_ptr probably changed. |
904 | 0 | BAIDU_SET_VOLATILE_THREAD_LOCAL(tls_unique_user_ptr, saved_unique_user_ptr); |
905 | |
|
906 | | #ifndef NDEBUG |
907 | | --g->_sched_recursive_guard; |
908 | | #endif |
909 | 0 | *pg = g; |
910 | 0 | } |
911 | | |
912 | 0 | void TaskGroup::destroy_self() { |
913 | 0 | if (_control) { |
914 | 0 | _control->_destroy_group(this); |
915 | 0 | _control = NULL; |
916 | 0 | } else { |
917 | 0 | CHECK(false); |
918 | 0 | } |
919 | 0 | } |
920 | | |
921 | | |
922 | 0 | void TaskGroup::ready_to_run(TaskMeta* meta, bool nosignal) { |
923 | | #ifdef BRPC_BTHREAD_TRACER |
924 | | _control->_task_tracer.set_status(TASK_STATUS_READY, meta); |
925 | | #endif // BRPC_BTHREAD_TRACER |
926 | 0 | push_rq(meta->tid); |
927 | 0 | if (nosignal) { |
928 | 0 | ++_num_nosignal; |
929 | 0 | } else { |
930 | 0 | const int additional_signal = _num_nosignal; |
931 | 0 | _num_nosignal = 0; |
932 | 0 | _nsignaled += 1 + additional_signal; |
933 | 0 | _control->signal_task(1 + additional_signal, _tag); |
934 | 0 | } |
935 | 0 | } |
936 | | |
937 | 0 | void TaskGroup::flush_nosignal_tasks() { |
938 | 0 | const int val = _num_nosignal; |
939 | 0 | if (val) { |
940 | 0 | _num_nosignal = 0; |
941 | 0 | _nsignaled += val; |
942 | 0 | _control->signal_task(val, _tag); |
943 | 0 | } |
944 | 0 | } |
945 | | |
946 | 0 | void TaskGroup::ready_to_run_remote(TaskMeta* meta, bool nosignal) { |
947 | | #ifdef BRPC_BTHREAD_TRACER |
948 | | _control->_task_tracer.set_status(TASK_STATUS_READY, meta); |
949 | | #endif // BRPC_BTHREAD_TRACER |
950 | 0 | _remote_rq._mutex.lock(); |
951 | 0 | while (!_remote_rq.push_locked(meta->tid)) { |
952 | 0 | flush_nosignal_tasks_remote_locked(_remote_rq._mutex); |
953 | 0 | LOG_EVERY_SECOND(ERROR) << "_remote_rq is full, capacity=" |
954 | 0 | << _remote_rq.capacity(); |
955 | 0 | ::usleep(1000); |
956 | 0 | _remote_rq._mutex.lock(); |
957 | 0 | } |
958 | 0 | if (nosignal) { |
959 | 0 | ++_remote_num_nosignal; |
960 | 0 | _remote_rq._mutex.unlock(); |
961 | 0 | } else { |
962 | 0 | const int additional_signal = _remote_num_nosignal; |
963 | 0 | _remote_num_nosignal = 0; |
964 | 0 | _remote_nsignaled += 1 + additional_signal; |
965 | 0 | _remote_rq._mutex.unlock(); |
966 | 0 | _control->signal_task(1 + additional_signal, _tag); |
967 | 0 | } |
968 | 0 | } |
969 | | |
970 | 0 | void TaskGroup::flush_nosignal_tasks_remote_locked(butil::Mutex& locked_mutex) { |
971 | 0 | const int val = _remote_num_nosignal; |
972 | 0 | if (!val) { |
973 | 0 | locked_mutex.unlock(); |
974 | 0 | return; |
975 | 0 | } |
976 | 0 | _remote_num_nosignal = 0; |
977 | 0 | _remote_nsignaled += val; |
978 | 0 | locked_mutex.unlock(); |
979 | 0 | _control->signal_task(val, _tag); |
980 | 0 | } |
981 | | |
982 | 0 | void TaskGroup::ready_to_run_general(TaskMeta* meta, bool nosignal) { |
983 | 0 | if (BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group) == this) { |
984 | 0 | return ready_to_run(meta, nosignal); |
985 | 0 | } |
986 | 0 | return ready_to_run_remote(meta, nosignal); |
987 | 0 | } |
988 | | |
989 | 0 | void TaskGroup::flush_nosignal_tasks_general() { |
990 | 0 | if (BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group) == this) { |
991 | 0 | return flush_nosignal_tasks(); |
992 | 0 | } |
993 | 0 | return flush_nosignal_tasks_remote(); |
994 | 0 | } |
995 | | |
996 | 0 | void TaskGroup::ready_to_run_in_worker(void* args_in) { |
997 | 0 | ReadyToRunArgs* args = static_cast<ReadyToRunArgs*>(args_in); |
998 | 0 | return BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group)-> |
999 | 0 | ready_to_run(args->meta, args->nosignal); |
1000 | 0 | } |
1001 | | |
1002 | 0 | void TaskGroup::ready_to_run_in_worker_ignoresignal(void* args_in) { |
1003 | 0 | ReadyToRunArgs* args = static_cast<ReadyToRunArgs*>(args_in); |
1004 | 0 | TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); |
1005 | |
|
1006 | | #ifdef BRPC_BTHREAD_TRACER |
1007 | | g->_control->_task_tracer.set_status(TASK_STATUS_READY, args->meta); |
1008 | | #endif // BRPC_BTHREAD_TRACER |
1009 | 0 | return g->push_rq(args->meta->tid); |
1010 | 0 | } |
1011 | | |
1012 | 0 | void TaskGroup::priority_to_run(void* args_in) { |
1013 | 0 | ReadyToRunArgs* args = static_cast<ReadyToRunArgs*>(args_in); |
1014 | 0 | TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); |
1015 | | #ifdef BRPC_BTHREAD_TRACER |
1016 | | g->_control->_task_tracer.set_status(TASK_STATUS_READY, args->meta); |
1017 | | #endif // BRPC_BTHREAD_TRACER |
1018 | 0 | if (args->meta->priority_index < 0) { |
1019 | 0 | return g->push_rq(args->meta->tid); |
1020 | 0 | } |
1021 | 0 | return g->control()->push_ed_priority_queue( |
1022 | 0 | args->tag, args->meta->priority_index, args->meta->tid); |
1023 | 0 | } |
1024 | | |
1025 | | struct SleepArgs { |
1026 | | uint64_t timeout_us; |
1027 | | bthread_t tid; |
1028 | | TaskMeta* meta; |
1029 | | TaskGroup* group; |
1030 | | }; |
1031 | | |
1032 | 0 | static void ready_to_run_from_timer_thread(void* arg) { |
1033 | 0 | CHECK(BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group) == NULL); |
1034 | 0 | const SleepArgs* e = static_cast<const SleepArgs*>(arg); |
1035 | 0 | TaskGroup* g = e->group; |
1036 | 0 | bthread_tag_t tag = g->tag(); |
1037 | 0 | g->control()->choose_one_group(tag)->ready_to_run_remote(e->meta); |
1038 | 0 | } |
1039 | | |
1040 | 0 | void TaskGroup::_add_sleep_event(void* void_args) { |
1041 | | // Must copy SleepArgs. After calling TimerThread::schedule(), previous |
1042 | | // thread may be stolen by a worker immediately and the on-stack SleepArgs |
1043 | | // will be gone. |
1044 | 0 | SleepArgs e = *static_cast<SleepArgs*>(void_args); |
1045 | 0 | TaskGroup* g = e.group; |
1046 | | #ifdef BRPC_BTHREAD_TRACER |
1047 | | g->_control->_task_tracer.set_status(TASK_STATUS_SUSPENDED, e.meta); |
1048 | | #endif // BRPC_BTHREAD_TRACER |
1049 | |
|
1050 | 0 | TimerThread::TaskId sleep_id; |
1051 | 0 | sleep_id = get_global_timer_thread()->schedule( |
1052 | 0 | ready_to_run_from_timer_thread, void_args, |
1053 | 0 | butil::microseconds_from_now(e.timeout_us)); |
1054 | |
|
1055 | 0 | if (!sleep_id) { |
1056 | 0 | e.meta->sleep_failed = true; |
1057 | | // Fail to schedule timer, go back to previous thread. |
1058 | 0 | g->ready_to_run(e.meta); |
1059 | 0 | return; |
1060 | 0 | } |
1061 | | |
1062 | | // Set TaskMeta::current_sleep which is for interruption. |
1063 | 0 | const uint32_t given_ver = get_version(e.tid); |
1064 | 0 | { |
1065 | 0 | BAIDU_SCOPED_LOCK(e.meta->version_lock); |
1066 | 0 | if (given_ver == *e.meta->version_butex && !e.meta->interrupted) { |
1067 | 0 | e.meta->current_sleep = sleep_id; |
1068 | 0 | return; |
1069 | 0 | } |
1070 | 0 | } |
1071 | | // The thread is stopped or interrupted. |
1072 | | // interrupt() always sees that current_sleep == 0. It will not schedule |
1073 | | // the calling thread. The race is between current thread and timer thread. |
1074 | 0 | if (get_global_timer_thread()->unschedule(sleep_id) == 0) { |
1075 | | // added to timer, previous thread may be already woken up by timer and |
1076 | | // even stopped. It's safe to schedule previous thread when unschedule() |
1077 | | // returns 0 which means "the not-run-yet sleep_id is removed". If the |
1078 | | // sleep_id is running(returns 1), ready_to_run_in_worker() will |
1079 | | // schedule previous thread as well. If sleep_id does not exist, |
1080 | | // previous thread is scheduled by timer thread before and we don't |
1081 | | // have to do it again. |
1082 | 0 | g->ready_to_run(e.meta); |
1083 | 0 | } |
1084 | 0 | } |
1085 | | |
1086 | | // To be consistent with sys_usleep, set errno and return -1 on error. |
1087 | 0 | int TaskGroup::usleep(TaskGroup** pg, uint64_t timeout_us) { |
1088 | 0 | if (0 == timeout_us) { |
1089 | 0 | yield(pg); |
1090 | 0 | return 0; |
1091 | 0 | } |
1092 | 0 | TaskGroup* g = *pg; |
1093 | | // We have to schedule timer after we switched to next bthread otherwise |
1094 | | // the timer may wake up(jump to) current still-running context. |
1095 | 0 | SleepArgs e = { timeout_us, g->current_tid(), g->current_task(), g }; |
1096 | 0 | g->set_remained(_add_sleep_event, &e); |
1097 | 0 | sched(pg); |
1098 | 0 | g = *pg; |
1099 | 0 | if (e.meta->sleep_failed) { |
1100 | | // Fail to schedule timer, return error. |
1101 | 0 | e.meta->sleep_failed = false; |
1102 | 0 | errno = ESTOP; |
1103 | 0 | return -1; |
1104 | 0 | } |
1105 | 0 | e.meta->current_sleep = 0; |
1106 | 0 | if (e.meta->interrupted) { |
1107 | | // Race with set and may consume multiple interruptions, which are OK. |
1108 | 0 | e.meta->interrupted = false; |
1109 | | // NOTE: setting errno to ESTOP is not necessary from bthread's |
1110 | | // pespective, however many RPC code expects bthread_usleep to set |
1111 | | // errno to ESTOP when the thread is stopping, and print FATAL |
1112 | | // otherwise. To make smooth transitions, ESTOP is still set instead |
1113 | | // of EINTR when the thread is stopping. |
1114 | 0 | errno = (e.meta->stop ? ESTOP : EINTR); |
1115 | 0 | return -1; |
1116 | 0 | } |
1117 | 0 | return 0; |
1118 | 0 | } |
1119 | | |
1120 | | // Defined in butex.cpp |
1121 | | bool erase_from_butex_because_of_interruption(ButexWaiter* bw); |
1122 | | |
1123 | | static int interrupt_and_consume_waiters( |
1124 | 0 | bthread_t tid, ButexWaiter** pw, uint64_t* sleep_id) { |
1125 | 0 | TaskMeta* const m = TaskGroup::address_meta(tid); |
1126 | 0 | if (m == NULL) { |
1127 | 0 | return EINVAL; |
1128 | 0 | } |
1129 | 0 | const uint32_t given_ver = get_version(tid); |
1130 | 0 | BAIDU_SCOPED_LOCK(m->version_lock); |
1131 | 0 | if (given_ver == *m->version_butex) { |
1132 | 0 | *pw = m->current_waiter.exchange(NULL, butil::memory_order_acquire); |
1133 | 0 | *sleep_id = m->current_sleep; |
1134 | 0 | m->current_sleep = 0; // only one stopper gets the sleep_id |
1135 | 0 | m->interrupted = true; |
1136 | 0 | return 0; |
1137 | 0 | } |
1138 | 0 | return EINVAL; |
1139 | 0 | } |
1140 | | |
1141 | 0 | static int set_butex_waiter(bthread_t tid, ButexWaiter* w) { |
1142 | 0 | TaskMeta* const m = TaskGroup::address_meta(tid); |
1143 | 0 | if (m != NULL) { |
1144 | 0 | const uint32_t given_ver = get_version(tid); |
1145 | 0 | BAIDU_SCOPED_LOCK(m->version_lock); |
1146 | 0 | if (given_ver == *m->version_butex) { |
1147 | | // Release fence makes m->interrupted visible to butex_wait |
1148 | 0 | m->current_waiter.store(w, butil::memory_order_release); |
1149 | 0 | return 0; |
1150 | 0 | } |
1151 | 0 | } |
1152 | 0 | return EINVAL; |
1153 | 0 | } |
1154 | | |
1155 | | // The interruption is "persistent" compared to the ones caused by signals, |
1156 | | // namely if a bthread is interrupted when it's not blocked, the interruption |
1157 | | // is still remembered and will be checked at next blocking. This designing |
1158 | | // choice simplifies the implementation and reduces notification loss caused |
1159 | | // by race conditions. |
1160 | | // TODO: bthreads created by BTHREAD_ATTR_PTHREAD blocking on bthread_usleep() |
1161 | | // can't be interrupted. |
1162 | 0 | int TaskGroup::interrupt(bthread_t tid, TaskControl* c) { |
1163 | | // Consume current_waiter in the TaskMeta, wake it up then set it back. |
1164 | 0 | ButexWaiter* w = NULL; |
1165 | 0 | uint64_t sleep_id = 0; |
1166 | 0 | int rc = interrupt_and_consume_waiters(tid, &w, &sleep_id); |
1167 | 0 | if (rc) { |
1168 | 0 | return rc; |
1169 | 0 | } |
1170 | | // a bthread cannot wait on a butex and be sleepy at the same time. |
1171 | 0 | CHECK(!sleep_id || !w); |
1172 | 0 | if (w != NULL) { |
1173 | 0 | erase_from_butex_because_of_interruption(w); |
1174 | | // If butex_wait() already wakes up before we set current_waiter back, |
1175 | | // the function will spin until current_waiter becomes non-NULL. |
1176 | 0 | rc = set_butex_waiter(tid, w); |
1177 | 0 | if (rc) { |
1178 | 0 | LOG(FATAL) << "butex_wait should spin until setting back waiter"; |
1179 | 0 | return rc; |
1180 | 0 | } |
1181 | 0 | } else if (sleep_id != 0) { |
1182 | 0 | if (get_global_timer_thread()->unschedule(sleep_id) == 0) { |
1183 | 0 | TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); |
1184 | 0 | TaskMeta* m = address_meta(tid); |
1185 | 0 | if (g) { |
1186 | 0 | g->ready_to_run(m); |
1187 | 0 | } else { |
1188 | 0 | if (!c) { |
1189 | 0 | return EINVAL; |
1190 | 0 | } |
1191 | 0 | c->choose_one_group(m->attr.tag)->ready_to_run_remote(m); |
1192 | 0 | } |
1193 | 0 | } |
1194 | 0 | } |
1195 | 0 | return 0; |
1196 | 0 | } |
1197 | | |
1198 | 0 | void TaskGroup::yield(TaskGroup** pg) { |
1199 | 0 | TaskGroup* g = *pg; |
1200 | 0 | ReadyToRunArgs args = { g->tag(), g->_cur_meta, false }; |
1201 | 0 | g->set_remained(ready_to_run_in_worker, &args); |
1202 | 0 | sched(pg); |
1203 | 0 | } |
1204 | | |
1205 | | void print_task(std::ostream& os, bthread_t tid, bool enable_trace, |
1206 | 0 | bool ignore_not_matched = false) { |
1207 | 0 | TaskMeta* const m = TaskGroup::address_meta(tid); |
1208 | 0 | if (m == NULL) { |
1209 | 0 | os << "bthread=" << tid << " : never existed\n"; |
1210 | 0 | return; |
1211 | 0 | } |
1212 | 0 | const uint32_t given_ver = get_version(tid); |
1213 | 0 | bool matched = false; |
1214 | 0 | bool stop = false; |
1215 | 0 | bool interrupted = false; |
1216 | 0 | bool about_to_quit = false; |
1217 | 0 | void* (*fn)(void*) = NULL; |
1218 | 0 | void* arg = NULL; |
1219 | 0 | bthread_attr_t attr = BTHREAD_ATTR_NORMAL; |
1220 | 0 | bool has_tls = false; |
1221 | 0 | int64_t cpuwide_start_ns = 0; |
1222 | 0 | TaskStatistics stat = {0, 0, 0}; |
1223 | 0 | TaskStatus status = TASK_STATUS_UNKNOWN; |
1224 | 0 | bool traced = false; |
1225 | 0 | pthread_t worker_tid{}; |
1226 | 0 | { |
1227 | 0 | BAIDU_SCOPED_LOCK(m->version_lock); |
1228 | 0 | if (given_ver == *m->version_butex) { |
1229 | 0 | matched = true; |
1230 | 0 | stop = m->stop; |
1231 | 0 | interrupted = m->interrupted; |
1232 | 0 | about_to_quit = m->about_to_quit; |
1233 | 0 | fn = m->fn; |
1234 | 0 | arg = m->arg; |
1235 | 0 | attr = m->attr; |
1236 | 0 | has_tls = m->local_storage.keytable; |
1237 | 0 | cpuwide_start_ns = m->cpuwide_start_ns; |
1238 | 0 | stat = m->stat; |
1239 | 0 | status = m->status; |
1240 | 0 | traced = m->traced; |
1241 | 0 | worker_tid = m->worker_tid; |
1242 | 0 | } |
1243 | 0 | } |
1244 | 0 | if (!matched) { |
1245 | 0 | if (!ignore_not_matched) { |
1246 | 0 | os << "bthread=" << tid << " : not exist now\n"; |
1247 | 0 | } |
1248 | 0 | } else { |
1249 | 0 | os << "bthread=" << tid << " :\nstop=" << stop |
1250 | 0 | << "\ninterrupted=" << interrupted |
1251 | 0 | << "\nabout_to_quit=" << about_to_quit |
1252 | 0 | << "\nfn=" << (void*)fn |
1253 | 0 | << "\narg=" << (void*)arg |
1254 | 0 | << "\nattr={stack_type=" << attr.stack_type |
1255 | 0 | << " flags=" << attr.flags |
1256 | 0 | << " specified_tag=" << attr.tag |
1257 | 0 | << " name=" << attr.name |
1258 | 0 | << " keytable_pool=" << attr.keytable_pool |
1259 | 0 | << "}\nhas_tls=" << has_tls |
1260 | 0 | << "\nuptime_ns=" << butil::cpuwide_time_ns() - cpuwide_start_ns |
1261 | 0 | << "\ncputime_ns=" << stat.cputime_ns |
1262 | 0 | << "\nnswitch=" << stat.nswitch |
1263 | | #ifdef BRPC_BTHREAD_TRACER |
1264 | | << "\nstatus=" << status |
1265 | | << "\ntraced=" << traced |
1266 | | << "\nworker_tid=" << worker_tid; |
1267 | | if (enable_trace) { |
1268 | | os << "\nbthread call stack:\n"; |
1269 | | stack_trace(os, tid); |
1270 | | } |
1271 | | os << "\n\n"; |
1272 | | #else |
1273 | 0 | << "\n\n"; |
1274 | 0 | (void)status;(void)traced;(void)worker_tid; |
1275 | 0 | #endif // BRPC_BTHREAD_TRACER |
1276 | 0 | } |
1277 | 0 | } |
1278 | | |
1279 | | } // namespace bthread |