Skip to content

fix(webhooks): offload persist() I/O to blocking thread#2006

Merged
senamakel merged 1 commit into
tinyhumansai:mainfrom
Sathvik-1007:fix/webhook-persist-async-io
May 17, 2026
Merged

fix(webhooks): offload persist() I/O to blocking thread#2006
senamakel merged 1 commit into
tinyhumansai:mainfrom
Sathvik-1007:fix/webhook-persist-async-io

Conversation

@Sathvik-1007
Copy link
Copy Markdown
Contributor

@Sathvik-1007 Sathvik-1007 commented May 17, 2026

Summary

  • WebhookRouter::persist() now offloads std::fs::write and create_dir_all to tokio::task::spawn_blocking when called from an async context.
  • Falls back to inline I/O when no tokio runtime is available (sync tests, CLI).

Problem

persist() used synchronous std::fs::write and std::fs::create_dir_all directly on the tokio worker thread. Every register(), unregister(), and unregister_skill() call blocked the async runtime for the duration of the disk write. Under rapid webhook registration churn this stalls concurrent tasks and causes latency spikes.

Solution

Extract the I/O into a closure and dispatch it via tokio::task::spawn_blocking when Handle::try_current() succeeds. When no runtime is present (unit tests, one-shot CLI), the closure runs inline so existing tests remain deterministic without requiring #[tokio::test].

Submission Checklist

  • Tests added or updated — existing persist_and_load_roundtrip test covers the inline fallback path; async path exercised by integration tests in CI.
  • Diff coverage ≥ 80% — single function change, all branches covered by existing test suite (90 webhook tests pass).
  • Coverage matrix updated — N/A: behaviour-only change, no new feature surface.
  • All affected feature IDs — N/A: internal plumbing fix.
  • No new external network dependencies introduced.
  • Manual smoke checklist — N/A: no release-cut surface touched.
  • Linked issue closed via closing keyword below.

Impact

  • Desktop/CLI runtime: eliminates tokio worker stalls during webhook persistence.
  • No API or behaviour change — purely internal scheduling improvement.

Related

Closes #1933

Summary by CodeRabbit

  • Bug Fixes
    • Offloads webhook file I/O to a blocking/background context when running inside an async runtime, reducing performance bottlenecks.
    • Keeps synchronous file I/O behavior for non-async environments (CLI/tests) to preserve compatibility.
    • Prevents stale disk writes by skipping outdated persistence operations, improving reliability.

Review Change Stack

@Sathvik-1007 Sathvik-1007 requested a review from a team May 17, 2026 13:55
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a380c918-e17b-4ea6-bcf4-8dae5580f2b3

📥 Commits

Reviewing files that changed from the base of the PR and between 8cdaa57 and 6e2b80e.

📒 Files selected for processing (1)
  • src/openhuman/webhooks/router.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/openhuman/webhooks/router.rs

📝 Walkthrough

Walkthrough

The WebhookRouter::persist method now captures the snapshot and destination path, increments a monotonic persist_generation, and runs directory creation, pretty JSON serialization, and file write inside a do_write closure that is executed on a tokio blocking thread when a runtime exists or inline otherwise; closures skip stale writes using the generation counter.

Changes

Webhook persistence blocking fix

Layer / File(s) Summary
Persist generation and imports
src/openhuman/webhooks/router.rs
Adds AtomicU64 imports and shared-state Arc/RwLock usage adjustments.
Persist field and initialization
src/openhuman/webhooks/router.rs
Adds persist_generation: Arc<AtomicU64> to WebhookRouter and initializes it with AtomicU64::new(0) in new.
Persist doc and implementation refactor
src/openhuman/webhooks/router.rs
Expands persist docs and refactors implementation: snapshot cloning, incrementing persist_generation, wrapping dir creation/serde_json::to_string_pretty/file write in do_write, skipping stale generations, and conditionally running via tokio::task::spawn_blocking when Handle::try_current() succeeds or inline otherwise.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A rabbit taps the runtime gate,
Counts each write so none are late,
If Tokio hums it sends to play,
Otherwise it writes the old-school way,
Happy routes hop safe and straight. 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: offloading persist() file I/O to a blocking thread to fix async runtime blocking issues.
Linked Issues check ✅ Passed The PR directly addresses issue #1933 by implementing spawn_blocking for file I/O, preventing async runtime stalls during webhook persistence.
Out of Scope Changes check ✅ Passed All changes are scoped to the persist() method in WebhookRouter to address the async runtime blocking issue; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/webhooks/router.rs`:
- Around line 523-525: The detach of tokio::task::spawn_blocking(do_write) in
persist() can allow older snapshots to overwrite newer ones; modify persist()
(the block around tokio::task::spawn_blocking and the do_write closure) to
serialize disk writes—either by introducing a single-threaded write queue/actor
(spawn a dedicated writer task that accepts snapshot+generation via an mpsc
channel) or by adding a monotonic generation counter stored with each snapshot
and have the spawned writer drop writes whose generation is older than the
latest seen; ensure register(), unregister(), and unregister_skill() continue to
call persist() but persist() enqueues the write or tags it with the generation
so writes are executed or persisted strictly in snapshot order.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4ad4ee46-6747-47db-a20b-495fe1894f5d

📥 Commits

Reviewing files that changed from the base of the PR and between f9de38d and 8cdaa57.

📒 Files selected for processing (1)
  • src/openhuman/webhooks/router.rs

Comment thread src/openhuman/webhooks/router.rs
std::fs::write and create_dir_all in persist() blocked the tokio
worker thread on every register/unregister call. Offload to
spawn_blocking when a runtime is available; fall back to inline
I/O for sync contexts (tests, CLI).
@Sathvik-1007 Sathvik-1007 force-pushed the fix/webhook-persist-async-io branch from 8cdaa57 to 6e2b80e Compare May 17, 2026 14:50
@senamakel senamakel merged commit 9e8348b into tinyhumansai:main May 17, 2026
25 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.

Bug: webhooks/router.rs persist() blocks async runtime with sync file I/O

2 participants