Skip to content

Commit 2bb7154

Browse files
committed
chore: move rivetkit to task model
1 parent daf7aa8 commit 2bb7154

1 file changed

Lines changed: 27 additions & 0 deletions

File tree

.agent/notes/user-complaints.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,3 +634,30 @@ For each candidate found, classify as:
634634

635635
- CLAUDE.md already has the rule. Add a supplementary rule: "For every shared counter that has an awaiter, the decrement-to-zero site must ping a paired `Notify` / `watch` / release-permit. Waiters must arm the permit before re-checking the counter (to avoid lost wakeups)."
636636
- Add a clippy-style lint or review checklist item so this gets caught in review rather than re-emerging.
637+
## 21. WebSocket close callbacks should be async to match prior TS behavior
638+
639+
`rivetkit-rust/packages/rivetkit-core/src/websocket.rs:10-17` defines all four WebSocket callbacks as sync closures returning `Result<()>`:
640+
641+
- `WebSocketSendCallback = Arc<dyn Fn(WsMessage) -> Result<()> + Send + Sync>`
642+
- `WebSocketCloseCallback = Arc<dyn Fn(Option<u16>, Option<String>) -> Result<()> + Send + Sync>`
643+
- `WebSocketMessageEventCallback = Arc<dyn Fn(WsMessage, Option<u16>) -> Result<()> + Send + Sync>`
644+
- `WebSocketCloseEventCallback = Arc<dyn Fn(u16, String, bool) -> Result<()> + Send + Sync>`
645+
646+
This breaks parity with the TypeScript implementation, which allowed async work in WebSocket cleanup so cleanup gated sleep — the actor would not be allowed to sleep while close handlers were still running.
647+
648+
Inconsistency in the current Rust code itself: `rivetkit-rust/packages/rivetkit-core/src/actor/connection.rs:29-30` defines `DisconnectCallback` as `BoxFuture<'static, Result<()>>` — async — for the connection-level disconnect path. So at the connection layer, async cleanup is supported. At the WebSocket layer it's not. There's no architectural reason for the asymmetry.
649+
650+
Also relevant: CLAUDE.md guidance "rivetkit-core sleep readiness should stay centralized in `SleepController`, with queue waits, scheduled internal work, disconnect callbacks, and websocket callbacks reporting activity through `ActorContext` hooks so the idle timer stays accurate." That requirement is hard to meet with sync close callbacks — there's no future to await against, no point at which the close handler's work has "completed" from the sleep controller's perspective.
651+
652+
Fix: change the close callbacks to async (`BoxFuture<'static, Result<()>>`), consistent with `DisconnectCallback` and the broader CLAUDE.md guidance "rivetkit-core boxed callback APIs should use `futures::future::BoxFuture<'static, ...>`":
653+
654+
```rust
655+
pub(crate) type WebSocketCloseCallback =
656+
Arc<dyn Fn(Option<u16>, Option<String>) -> BoxFuture<'static, Result<()>> + Send + Sync>;
657+
pub(crate) type WebSocketCloseEventCallback =
658+
Arc<dyn Fn(u16, String, bool) -> BoxFuture<'static, Result<()>> + Send + Sync>;
659+
```
660+
661+
Wire each invocation through a `WebSocketCallbackGuard` (already exists at `actor/context.rs`) so the in-flight close work counts toward sleep readiness — the actor stays awake until cleanup completes, matching the prior TS contract.
662+
663+
Send and message-event callbacks could stay sync if they're truly fire-and-forget on the network path, but if they ever need async cleanup (e.g., persist hibernation state on send), they should also become async for consistency.

0 commit comments

Comments
 (0)