rivet-dev / rivet-dev/actors

(Issue)- unbounded memory growth during actors cycling.

Open
#5,607 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
6.1k
Forks
250
Avg merge
2d 8h
Merged PRs (30d)
96

Description

Summary:-
ActorContextShared::reset_runtime_state() in rivetkit-napi uses std::mem::forget to avoid a panic when dropping a napi::Ref on a Tokio worker thread (where no Node.js Env is available). This leaks one JS object reference slot per actor wake cycle. Over time in long-running processes with many actors cycling through sleep/wake, this is unbounded memory growth.

Problem:-
When an actor wakes from sleep, reset_runtime_state() is called to clear the previous JS state object. The napi::Ref type requires calling .unref(env) with a valid Node.js Env handle to properly release the reference. But reset_runtime_state() runs on a Tokio worker thread where no Env exists.

The current code uses std::mem::forget(old) to suppress the destructor. This avoids a debug_assert panic in napi-rs, but permanently leaks the underlying V8 reference slot.

The same pattern appears in the Drop impl for ActorContextShared.

File: rivetkit-typescript/packages/rivetkit-napi/src/actor_context.rs

if let Some(old) = self.runtime_state.lock().take() {
    std::mem::forget(old);
}

Impact:-

  • Rivet Actors are designed to sleep and wake frequently. An actor that wakes 100 times leaks 100 JS reference slots.
  • The leak is per-actor, per-wake. A deployment with thousands of actors cycling through sleep/wake accumulates leaked references without bound.
  • The only relief is process restart.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in rivetkit-typescript/packages/rivetkit-napi/src/actor_context.rs, reading ActorContextShared::reset_runtime_state() and its Drop implementation together. Trace how napi::Ref is released when a valid Node.js Env is available and how actor wake cycles reach these paths. Done means repeated sleep/wake cycles release JS references without panic or unbounded growth.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, rust
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.