Skip to content

Commit e4bf6ba

Browse files
jpnurmiclaude
andcommitted
fix bgworker draining queue after on_timeout
Replace the boolean `running` flag with a tri-state `status` field (STOPPED, RUNNING, STOPPING) so the worker stops after its current task when a timeout occurs, leaving remaining tasks in the queue for dump_queue to save to disk. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 06e319b commit e4bf6ba

1 file changed

Lines changed: 28 additions & 15 deletions

File tree

src/sentry_sync.c

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ sentry__task_decref(sentry_bgworker_task_t *task)
157157
}
158158
}
159159

160+
enum {
161+
SENTRY_BGW_STOPPED = 0,
162+
SENTRY_BGW_RUNNING = 1,
163+
SENTRY_BGW_STOPPING = 2,
164+
};
165+
160166
struct sentry_bgworker_s {
161167
sentry_threadid_t thread_id;
162168
char *thread_name;
@@ -169,7 +175,7 @@ struct sentry_bgworker_s {
169175
void *state;
170176
void (*free_state)(void *state);
171177
long refcount;
172-
long running;
178+
long status; // SENTRY_BGW_*
173179
};
174180

175181
sentry_bgworker_t *
@@ -236,9 +242,14 @@ sentry__bgworker_get_state(sentry_bgworker_t *bgw)
236242
static bool
237243
sentry__bgworker_is_done(sentry_bgworker_t *bgw)
238244
{
239-
return (!bgw->first_task
240-
|| sentry__monotonic_time() < bgw->first_task->execute_after)
241-
&& !sentry__atomic_fetch(&bgw->running);
245+
long status = sentry__atomic_fetch(&bgw->status);
246+
if (status == SENTRY_BGW_STOPPING) {
247+
// stop immediately to leave remaining tasks in the queue
248+
return true;
249+
}
250+
return status != SENTRY_BGW_RUNNING
251+
&& (!bgw->first_task
252+
|| sentry__monotonic_time() < bgw->first_task->execute_after);
242253
}
243254

244255
SENTRY_THREAD_FN
@@ -317,11 +328,11 @@ int
317328
sentry__bgworker_start(sentry_bgworker_t *bgw)
318329
{
319330
SENTRY_DEBUG("starting background worker thread");
320-
sentry__atomic_store(&bgw->running, 1);
331+
sentry__atomic_store(&bgw->status, SENTRY_BGW_RUNNING);
321332
// this incref moves the reference into the background thread
322333
sentry__bgworker_incref(bgw);
323334
if (sentry__thread_spawn(&bgw->thread_id, &worker_thread, bgw) != 0) {
324-
sentry__atomic_store(&bgw->running, 0);
335+
sentry__atomic_store(&bgw->status, SENTRY_BGW_STOPPED);
325336
sentry__bgworker_decref(bgw);
326337
return 1;
327338
}
@@ -358,7 +369,7 @@ sentry__flush_task_decref(sentry_flush_task_t *task)
358369
int
359370
sentry__bgworker_flush(sentry_bgworker_t *bgw, uint64_t timeout)
360371
{
361-
if (!sentry__atomic_fetch(&bgw->running)) {
372+
if (sentry__atomic_fetch(&bgw->status) != SENTRY_BGW_RUNNING) {
362373
SENTRY_WARN("trying to flush non-running thread");
363374
return 0;
364375
}
@@ -421,14 +432,14 @@ static void
421432
shutdown_task(void *task_data, void *UNUSED(state))
422433
{
423434
sentry_bgworker_t *bgw = task_data;
424-
sentry__atomic_store(&bgw->running, 0);
435+
sentry__atomic_store(&bgw->status, SENTRY_BGW_STOPPED);
425436
}
426437

427438
int
428439
sentry__bgworker_shutdown_cb(sentry_bgworker_t *bgw, uint64_t timeout,
429440
void (*on_timeout)(void *), void *on_timeout_data)
430441
{
431-
if (!sentry__atomic_fetch(&bgw->running)) {
442+
if (sentry__atomic_fetch(&bgw->status) != SENTRY_BGW_RUNNING) {
432443
SENTRY_WARN("trying to shut down non-running thread");
433444
return 0;
434445
}
@@ -443,16 +454,18 @@ sentry__bgworker_shutdown_cb(sentry_bgworker_t *bgw, uint64_t timeout,
443454
uint64_t now = sentry__monotonic_time();
444455
if (now > started && now - started > timeout) {
445456
if (on_timeout) {
446-
// fire on_timeout to cancel the ongoing task, and give the
447-
// worker an extra loop cycle up to 250ms to handle the
448-
// cancellation
457+
// fire on_timeout to cancel the ongoing task, and tell
458+
// the worker to stop so remaining tasks stay in the
459+
// queue for dumping. the worker gets up to 250ms for
460+
// graceful cancellation before the thread is detached.
449461
sentry__mutex_unlock(&bgw->task_lock);
450462
on_timeout(on_timeout_data);
463+
sentry__atomic_store(&bgw->status, SENTRY_BGW_STOPPING);
451464
on_timeout = NULL;
452465
sentry__mutex_lock(&bgw->task_lock);
453-
// fall through to !running check below
466+
// fall through to detach on next timeout
454467
} else {
455-
sentry__atomic_store(&bgw->running, 0);
468+
sentry__atomic_store(&bgw->status, SENTRY_BGW_STOPPING);
456469
sentry__thread_detach(bgw->thread_id);
457470
sentry__mutex_unlock(&bgw->task_lock);
458471
SENTRY_WARN("background thread failed to shut down cleanly "
@@ -461,7 +474,7 @@ sentry__bgworker_shutdown_cb(sentry_bgworker_t *bgw, uint64_t timeout,
461474
}
462475
}
463476

464-
if (!sentry__atomic_fetch(&bgw->running)) {
477+
if (sentry__atomic_fetch(&bgw->status) == SENTRY_BGW_STOPPED) {
465478
sentry__mutex_unlock(&bgw->task_lock);
466479
sentry__thread_join(bgw->thread_id);
467480
return 0;

0 commit comments

Comments
 (0)