Skip to content

Ubshm transport fix 2 - #74

Merged
zchuango merged 2 commits into
LinQuickDev:ubshm_transport_fix_2from
Sherlock0203:ubshm_transport_fix_2
Oct 12, 2026
Merged

zchuango merged 2 commits into
LinQuickDev:ubshm_transport_fix_2from
Sherlock0203:ubshm_transport_fix_2

Conversation

@Sherlock0203

Copy link
Copy Markdown

No description provided.

g_cleanup_queue was a file-scope MPSCQueue with a destructor, while the
cleanup worker is a bthread that is never stopped: the bthread runtime
itself is not destructed at exit (see the note in task_control.cpp), so
the worker can still be inside Dequeue() when static destructors run.
MPSCQueue's destructor drains the queue, turning that destruction into a
second concurrent consumer of a single-consumer queue: _cur_dequeue_node
is a plain pointer, so the destructor and the worker could pick and free
the same node (double free) or interleave their advances (lost nodes). A
timer callback firing during the destructor phase would additionally
enqueue into a destroyed queue.

No orderly teardown drains the queue at exit -- GlobalRelease only runs
on init failure and in UT teardown -- so the residue the destructor would
drain is really possible.

Allocate the queue with new and never delete it, following the brpc
convention for globals entangled with threads that never stop
(g_task_control itself); the kernel reclaims it at exit. DrainAndWait
observes g_cleanup_inflight, not the queue, so teardown semantics are
unchanged. g_cleanup_inflight (trivially destructible atomics) and
g_worker_mtx (a static initializer, nothing to destroy) keep their
storage.

Tested: cmake --build build --target brpc_ubring_unittest, and
./build/test/brpc_ubring_unittest (12/12 passed).
A failed bthread_start_background in Post() latched g_worker_unavailable
forever, so every later cleanup fell back to running inline: the SDK
calls went back to the process-wide timer thread (UbsShmDrainNode from
UbsShmCallback, the delayed trx cleanups from their callbacks), delaying
unrelated RPC timeouts, backup requests and bthread wakeups -- exactly
what the worker exists to avoid. bthread creation fails transiently
(ENOMEM), so the degradation correlated with memory pressure, the moment
where blocking the timer thread hurts most, and it never self-healed.

Drop the terminal flag and retry the lazy start on every post made while
the worker is not started. A failed attempt leaves g_worker_started
false and fails only that post; the caller runs it inline as the safety
net, and the next post retries the start. The posting mutex already
serializes the start attempt with the enqueue, so a job is still only
ever queued behind a start that succeeded -- the flag was redundant
double insurance for orphaned jobs. The error is logged with
LOG_EVERY_SECOND so a failure window cannot spam.

Add the g_ub_test_worker_start_rc hook so the new UT can inject a start
failure deterministically (no real bthread is created while it is set),
and cover both review cases: repeated failures fail only their own post
and queue nothing, and the first post after the injection is cleared
retries the start, runs the job exactly once and lets DrainAndWait
observe quiescence. The suite must observe the worker in its unstarted
state, so it is declared ahead of UBShmEndpointTest, whose teardown
posts cleanups, and probes for an already-started worker to skip rather
than assert on a start that is never attempted.

Tested: cmake --build build --target brpc_ubring_unittest, and
./build/test/brpc_ubring_unittest (13/13 passed). Reintroducing the
latch locally makes the new UT fail, confirming it guards the retry.
@zchuango
zchuango merged commit bb7bf7f into LinQuickDev:ubshm_transport_fix_2 Oct 12, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants