Skip to content

Commit c9b34f2

Browse files
authored
bthread: use macros for safer thread-local access on ARM (#3380)
Route accesses to tls_task_group, tls_task_group_nosignal, tls_bls and several mutex-profiling thread-locals (tls_inside_lock, tls_warn_up, tls_pthread_lock_count, tls_csites, tls_ever_created_keytable) through the BAIDU_(GET|SET|GET_PTR)_VOLATILE_THREAD_LOCAL macros instead of touching the raw __thread variables directly. On aarch64/clang the compiler can incorrectly cache the address of a thread_local variable across a suspend point (e.g. when a bthread is rescheduled onto another worker), so raw accesses can silently read a stale TaskGroup*/LocalStorage. Going through the accessor macros everywhere forces a fresh, non-cached load/store on those platforms while staying a plain variable access elsewhere. Add bthread::tls_bls_ptr() as the single helper for obtaining the current LocalStorage*, and update all call sites (bthread.cpp, butex.cpp, fd.cpp, key.cpp, mutex.cpp, task_control.cpp, brpc/controller.cpp, test/bthread_unittest.cpp) to use it instead of declaring their own stale/mistyped extern references to tls_task_group or tls_bls. Also hardens borrow_keytable()/return_keytable() in key.cpp: the KeyTableList pointer obtained under the read lock is no longer reused after re-acquiring the write lock, since pool->list/pool->destroyed may change concurrently via bthread_keytable_pool_destroy(); the thread-local list is drained first before falling back to the pool's global free list.
1 parent 97c8771 commit c9b34f2

11 files changed

Lines changed: 125 additions & 89 deletions

File tree

src/brpc/controller.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
#include "bthread/task_group.h"
5252

5353
namespace bthread {
54-
extern BAIDU_THREAD_LOCAL TaskGroup* tls_task_group;
54+
EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group);
5555
}
5656

5757
// This is the only place that both client/server must link, so we put
@@ -714,7 +714,7 @@ void Controller::OnVersionedRPCReturned(const CompletionInfo& info,
714714
response_attachment().clear();
715715

716716
// Retry backoff.
717-
bthread::TaskGroup* g = bthread::tls_task_group;
717+
bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group);
718718
int64_t backoff_time_us = retry_policy->GetBackoffTimeMs(this) * 1000L;
719719
if (backoff_time_us > 0 &&
720720
backoff_time_us < _deadline_us - butil::gettimeofday_us()) {

src/brpc/span.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@
3636
#include "brpc/options.pb.h" // ProtocolType
3737
#include "brpc/span.pb.h"
3838

39-
namespace bthread {
40-
extern __thread bthread::LocalStorage tls_bls;
41-
}
42-
4339
namespace brpc {
4440

4541
class Span;

src/bthread/bthread.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ pthread_mutex_t g_task_control_mutex = PTHREAD_MUTEX_INITIALIZER;
8686
// are not constructed before main().
8787
TaskControl* g_task_control = NULL;
8888

89-
extern BAIDU_THREAD_LOCAL TaskGroup* tls_task_group;
9089
EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group);
9190
extern void (*g_worker_startfn)();
9291
extern void (*g_tagged_worker_startfn)(bthread_tag_t);
@@ -263,7 +262,7 @@ static bool validate_bthread_concurrency_by_tag(const char*, int32_t val) {
263262
return bthread_setconcurrency_by_tag(val, FLAGS_bthread_current_tag) == 0;
264263
}
265264

266-
__thread TaskGroup* tls_task_group_nosignal = NULL;
265+
BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group_nosignal, NULL);
267266

268267
BUTIL_FORCE_INLINE int
269268
start_from_non_worker(bthread_t* __restrict tid,
@@ -283,10 +282,18 @@ start_from_non_worker(bthread_t* __restrict tid,
283282
// 1. NOSIGNAL is often for creating many bthreads in batch,
284283
// inserting into the same TaskGroup maximizes the batch.
285284
// 2. bthread_flush() needs to know which TaskGroup to flush.
286-
TaskGroup* g = tls_task_group_nosignal;
285+
auto g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group_nosignal);
287286
if (NULL == g) {
288287
g = c->choose_one_group(tag);
289-
tls_task_group_nosignal = g;
288+
BAIDU_SET_VOLATILE_THREAD_LOCAL(tls_task_group_nosignal, g);
289+
} else {
290+
// tls_task_group_nosignal is not tag-aware: mixing different
291+
// tags with BTHREAD_NOSIGNAL on the same non-worker thread is
292+
// not supported, otherwise bthreads would be silently inserted
293+
// into a TaskGroup of the wrong tag.
294+
CHECK_EQ(g->tag(), tag) << "Mixing different tags with "
295+
"BTHREAD_NOSIGNAL on the same thread is not supported, "
296+
"expected tag=" << g->tag() << " but got tag=" << tag;
290297
}
291298
return g->start_background<true>(tid, attr, fn, arg);
292299
}
@@ -298,7 +305,8 @@ start_from_non_worker(bthread_t* __restrict tid,
298305
// tag equal to thread local
299306
// tag equal to BTHREAD_TAG_INVALID
300307
BUTIL_FORCE_INLINE bool can_run_thread_local(const bthread_attr_t* __restrict attr) {
301-
return attr == nullptr || attr->tag == tls_task_group->tag() ||
308+
return attr == nullptr ||
309+
attr->tag == BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group)->tag() ||
302310
attr->tag == BTHREAD_TAG_INVALID;
303311
}
304312

@@ -360,10 +368,10 @@ void bthread_flush() {
360368
if (g) {
361369
return g->flush_nosignal_tasks();
362370
}
363-
g = bthread::tls_task_group_nosignal;
371+
g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group_nosignal);
364372
if (g) {
365373
// NOSIGNAL tasks were created in this non-worker.
366-
bthread::tls_task_group_nosignal = NULL;
374+
bthread::BAIDU_SET_VOLATILE_THREAD_LOCAL(tls_task_group_nosignal, NULL);
367375
return g->flush_nosignal_tasks_remote();
368376
}
369377
}

src/bthread/butex.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ int wait_pthread(ButexPthreadWaiter& pw, const timespec* abstime) {
187187
}
188188
}
189189

190-
extern BAIDU_THREAD_LOCAL TaskGroup* tls_task_group;
190+
EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group);
191191

192192
// Returns 0 when no need to unschedule or successfully unscheduled,
193193
// -1 otherwise.
@@ -280,7 +280,8 @@ void butex_destroy(void* butex) {
280280

281281
// if TaskGroup tls_task_group is belong to tag
282282
inline bool is_same_tag(bthread_tag_t tag) {
283-
return tls_task_group && tls_task_group->tag() == tag;
283+
auto g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group);
284+
return g && g->tag() == tag;
284285
}
285286

286287
// nosignal is true & tag is same can return true
@@ -290,7 +291,8 @@ inline bool check_nosignal(bool nosignal, bthread_tag_t tag) {
290291

291292
// if tag is same return tls_task_group else choose one group with tag
292293
inline TaskGroup* get_task_group(TaskControl* c, bthread_tag_t tag) {
293-
return is_same_tag(tag) ? tls_task_group : c->choose_one_group(tag);
294+
return is_same_tag(tag) ? BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group)
295+
: c->choose_one_group(tag);
294296
}
295297

296298
inline void run_in_local_task_group(TaskGroup* g, TaskMeta* next_meta, bool nosignal) {
@@ -320,7 +322,7 @@ int butex_wake(void* arg, bool nosignal) {
320322
ButexBthreadWaiter* bbw = static_cast<ButexBthreadWaiter*>(front);
321323
unsleep_if_necessary(bbw, get_global_timer_thread());
322324
TaskGroup* g = get_task_group(bbw->control, bbw->task_meta->attr.tag);
323-
if (g == tls_task_group) {
325+
if (g == BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group)) {
324326
run_in_local_task_group(g, bbw->task_meta, nosignal);
325327
} else {
326328
g->ready_to_run_remote(bbw->task_meta, check_nosignal(nosignal, g->tag()));
@@ -384,7 +386,7 @@ int butex_wake_n(void* arg, size_t n, bool nosignal) {
384386
}
385387
}
386388
auto g = get_task_group(next->control, next->task_meta->attr.tag);
387-
if (g == tls_task_group) {
389+
if (g == BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group)) {
388390
run_in_local_task_group(g, next->task_meta, nosignal);
389391
} else {
390392
g->ready_to_run_remote(next->task_meta, check_nosignal(nosignal, g->tag()));
@@ -488,7 +490,9 @@ int butex_requeue(void* arg, void* arg2) {
488490
}
489491
ButexBthreadWaiter* bbw = static_cast<ButexBthreadWaiter*>(front);
490492
unsleep_if_necessary(bbw, get_global_timer_thread());
491-
auto g = is_same_tag(bbw->task_meta->attr.tag) ? tls_task_group : NULL;
493+
auto g = is_same_tag(bbw->task_meta->attr.tag)
494+
? BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group)
495+
: NULL;
492496
if (g) {
493497
TaskGroup::exchange(&g, bbw->task_meta);
494498
} else {
@@ -597,7 +601,7 @@ void wait_for_butex(void* arg) {
597601
// the two functions. The on-stack ButexBthreadWaiter is safe to use and
598602
// bw->waiter_state will not change again.
599603
// unsleep_if_necessary(bw, get_global_timer_thread());
600-
tls_task_group->ready_to_run(bw->task_meta);
604+
BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group)->ready_to_run(bw->task_meta);
601605
// FIXME: jump back to original thread is buggy.
602606

603607
// // Value unmatched or waiter is already woken up by TimerThread, jump
@@ -677,7 +681,7 @@ int butex_wait(void* arg, int expected_value, const timespec* abstime, bool prep
677681
butil::atomic_thread_fence(butil::memory_order_acquire);
678682
return -1;
679683
}
680-
TaskGroup* g = tls_task_group;
684+
TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group);
681685
if (NULL == g || g->is_current_pthread_task()) {
682686
return butex_wait_from_pthread(g, b, expected_value, abstime, prepend);
683687
}

src/bthread/fd.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ extern int pthread_fd_wait(int fd, unsigned events, const timespec* abstime);
4444

4545
namespace bthread {
4646

47-
extern BAIDU_THREAD_LOCAL TaskGroup* tls_task_group;
47+
EXTERN_BAIDU_VOLATILE_THREAD_LOCAL(TaskGroup*, tls_task_group);
4848

4949
template <typename T, size_t NBLOCK, size_t BLOCK_SIZE>
5050
class LazyArray {
@@ -446,7 +446,7 @@ int bthread_fd_wait(int fd, unsigned events) {
446446
errno = EINVAL;
447447
return -1;
448448
}
449-
bthread::TaskGroup* g = bthread::tls_task_group;
449+
bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group);
450450
if (NULL != g && !g->is_current_pthread_task()) {
451451
return bthread::get_epoll_thread(fd).fd_wait(
452452
fd, events, NULL);
@@ -463,7 +463,7 @@ int bthread_fd_timedwait(int fd, unsigned events,
463463
errno = EINVAL;
464464
return -1;
465465
}
466-
bthread::TaskGroup* g = bthread::tls_task_group;
466+
bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group);
467467
if (NULL != g && !g->is_current_pthread_task()) {
468468
return bthread::get_epoll_thread(fd).fd_wait(
469469
fd, events, abstime);
@@ -473,7 +473,7 @@ int bthread_fd_timedwait(int fd, unsigned events,
473473

474474
int bthread_connect(int sockfd, const sockaddr* serv_addr,
475475
socklen_t addrlen) {
476-
bthread::TaskGroup* g = bthread::tls_task_group;
476+
bthread::TaskGroup* g = bthread::BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group);
477477
if (NULL == g || g->is_current_pthread_task()) {
478478
return ::connect(sockfd, serv_addr, addrlen);
479479
}

0 commit comments

Comments
 (0)