Skip to content

feat(sync): add delayed task submission for throttling#1506

Draft
jpnurmi wants to merge 11 commits intomasterfrom
jpnurmi/feat/delayed-task
Draft

feat(sync): add delayed task submission for throttling#1506
jpnurmi wants to merge 11 commits intomasterfrom
jpnurmi/feat/delayed-task

Conversation

@jpnurmi
Copy link
Collaborator

@jpnurmi jpnurmi commented Feb 6, 2026

Add sentry__bgworker_submit_delayed() to allow deferring task execution by a given number of milliseconds. This paves the road for HTTP retries that should be first throttled on startup (similar to Cocoa/iOS and Java/Android SDKs that throttle HTTP retries by 100ms) and then scheduled with exponential backoff (15min, 30min, 1h, 2h, ...).

// delay 100ms
sentry__bgworker_submit_delayed(bgworker, http_retry_task, sentry_envelope_free, envelope, 100);

Required for:

#skip-changelog (internal)

@github-actions
Copy link

github-actions bot commented Feb 6, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against bfbfe36

@jpnurmi
Copy link
Collaborator Author

jpnurmi commented Feb 6, 2026

@sentry review

@jpnurmi
Copy link
Collaborator Author

jpnurmi commented Feb 6, 2026

@cursor review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

jpnurmi and others added 5 commits February 13, 2026 18:32
Add sentry__bgworker_submit_delayed() to defer task execution by a
given number of milliseconds. Tasks are sorted by readiness time using
monotonic timestamps, so a ready delayed task is not bypassed by a
later-submitted immediate task. On shutdown, tasks that exceed the
deadline (started + timeout) are pruned while the rest execute normally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The flush marker now uses the last task's deadline (capped at the flush
timeout) so it sorts after all current tasks including delayed ones.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract the core submission logic into submit_at which takes an absolute
execute_after time. submit and submit_delayed become thin wrappers.

Use submit_at in bgworker_delayed_tasks to pin all tasks to a single
base timestamp, making the test ordering deterministic regardless of OS
preemption between submissions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move delayed task cleanup from the shutdown pre-prune pass into the
worker thread itself. When the worker encounters a delayed task that
is not yet ready and shutdown has been signaled, it discards all
remaining tasks. This catches tasks submitted during execution that
the one-time pre-prune in shutdown could not see.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of explicitly discarding delayed tasks in worker_thread,
teach sentry__bgworker_is_done to treat pending delayed tasks as
done when !running. Remaining tasks are cleaned up by decref.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jpnurmi and others added 2 commits February 15, 2026 13:19
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a delayed task's deadline exceeds the flush timeout, don't use it
to delay the flush sentinel. Such tasks cannot complete within the
timeout anyway, and capping to the timeout caused the sentinel to race
with the caller's deadline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jpnurmi
Copy link
Collaborator Author

jpnurmi commented Feb 16, 2026

@sentry review

@jpnurmi
Copy link
Collaborator Author

jpnurmi commented Feb 16, 2026

@cursor review

jpnurmi and others added 2 commits February 16, 2026 20:31
The previous fix only checked last_task, so a far-future tail caused
delay_ms=0 which skipped eligible delayed tasks earlier in the queue.
Walk from first_task to find the last task due within the timeout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Large delay_ms values could wrap execute_after into the past, causing
immediate execution. Use saturating addition capped at UINT64_MAX.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jpnurmi
Copy link
Collaborator Author

jpnurmi commented Feb 16, 2026

@cursor review

jpnurmi and others added 2 commits February 16, 2026 21:09
submit_at sorted insertion could insert before first_task while it was
executing without the lock held, causing the worker loop to skip the
pop and re-execute the task on the next iteration. Track current_task
so sorted insertion starts after it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
submit_delayed reads the clock again internally, so the sentinel ended
up scheduled later than intended. Use submit_at with the absolute
timestamp to eliminate drift between queue scan and submit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jpnurmi
Copy link
Collaborator Author

jpnurmi commented Feb 16, 2026

@cursor review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

// insert sorted by execute_after; skip past current_task which
// may be executing without the lock held
sentry_bgworker_task_t *prev = bgw->current_task;
sentry_bgworker_task_t *cur = prev ? prev->next_task : bgw->first_task;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use-after-free accessing freed task pointer

High Severity

The insertion logic at line 513 accesses current_task->next_task, but that next task may have been freed by sentry__bgworker_foreach_matching. When foreach_matching removes and frees the task pointed to by next_task, the pointer becomes stale. Subsequent insertions dereference this freed memory, causing a use-after-free that can crash the program. This occurs because foreach_matching decrements the task's refcount (potentially freeing it), but never updates the next_task pointer in current_task.

Fix in Cursor Fix in Web

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.

1 participant