Skip to content

Commit c6d1418

Browse files
jpnurmiclaude
andcommitted
fix(retry): make trigger one-shot to prevent rapid retry exhaustion
retry_trigger_task recursively re-triggered itself on network failure, bypassing exponential backoff (UINT64_MAX skips the backoff check) and burning through all 5 retry attempts in milliseconds. Since sentry__retry_send already processes all cached envelopes in a single call, the re-trigger is only ever reached on network failure — exactly the case where it's harmful. Make the trigger one-shot; failed items are left for the regular poll task which respects backoff. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bb86cbc commit c6d1418

3 files changed

Lines changed: 43 additions & 4 deletions

File tree

src/sentry_retry.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,10 +360,7 @@ retry_trigger_task(void *_retry, void *_state)
360360
{
361361
(void)_state;
362362
sentry_retry_t *retry = _retry;
363-
if (sentry__retry_send(
364-
retry, UINT64_MAX, retry->send_cb, retry->send_data)) {
365-
sentry__retry_trigger(retry);
366-
}
363+
sentry__retry_send(retry, UINT64_MAX, retry->send_cb, retry->send_data);
367364
}
368365

369366
void

tests/unit/test_retry.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,44 @@ SENTRY_TEST(retry_backoff)
357357
sentry__retry_free(retry);
358358
sentry_close();
359359
}
360+
361+
SENTRY_TEST(retry_trigger)
362+
{
363+
SENTRY_TEST_OPTIONS_NEW(options);
364+
sentry_options_set_dsn(options, "https://foo@sentry.invalid/42");
365+
sentry_options_set_http_retry(options, true);
366+
sentry_init(options);
367+
368+
sentry_retry_t *retry = sentry__retry_new(options);
369+
TEST_ASSERT(!!retry);
370+
371+
const sentry_path_t *cache_path = options->run->cache_path;
372+
sentry__path_remove_all(cache_path);
373+
sentry__path_create_dir_all(cache_path);
374+
375+
uint64_t old_ts
376+
= sentry__usec_time() / 1000 - 10 * sentry__retry_backoff(0);
377+
sentry_uuid_t event_id = sentry_uuid_new_v4();
378+
write_retry_file(retry, old_ts, 0, &event_id);
379+
380+
// UINT64_MAX (trigger mode) bypasses backoff: bumps count
381+
retry_test_ctx_t ctx = { -1, 0 };
382+
sentry__retry_send(retry, UINT64_MAX, test_send_cb, &ctx);
383+
TEST_CHECK_INT_EQUAL(ctx.count, 1);
384+
TEST_CHECK_INT_EQUAL(find_envelope_attempt(cache_path), 1);
385+
386+
// second call: bumps again because UINT64_MAX skips backoff
387+
ctx = (retry_test_ctx_t) { -1, 0 };
388+
sentry__retry_send(retry, UINT64_MAX, test_send_cb, &ctx);
389+
TEST_CHECK_INT_EQUAL(ctx.count, 1);
390+
TEST_CHECK_INT_EQUAL(find_envelope_attempt(cache_path), 2);
391+
392+
// before=0 (poll mode) respects backoff: item is skipped
393+
ctx = (retry_test_ctx_t) { -1, 0 };
394+
sentry__retry_send(retry, 0, test_send_cb, &ctx);
395+
TEST_CHECK_INT_EQUAL(ctx.count, 0);
396+
TEST_CHECK_INT_EQUAL(find_envelope_attempt(cache_path), 2);
397+
398+
sentry__retry_free(retry);
399+
sentry_close();
400+
}

tests/unit/tests.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ XX(retry_cache)
169169
XX(retry_result)
170170
XX(retry_session)
171171
XX(retry_throttle)
172+
XX(retry_trigger)
172173
XX(ringbuffer_append)
173174
XX(ringbuffer_append_invalid_decref_value)
174175
XX(ringbuffer_append_null_decref_value)

0 commit comments

Comments
 (0)