Skip to main content

Module cycle_gc

Module cycle_gc 

Source
Expand description

参照カウントでは回収できない循環ゴミの回収(試行削除 / trial deletion)。

【2026-10-19】JS の値は Rc<RefCell<…>> の参照カウントで管理しており、 「関数呼び出しのスコープ → 中で作った関数 → そのスコープ」のような循環は ページを閉じるまで解放されなかった。YouTube の視聴ページ(kevlar_base 10.8MB)の 実行中に 512MB のヒープを使い切っていた。

§方式

生きている Obj と Scope をすべて台帳(Registry)に登録しておき、回収時に

  1. 台帳の全節点について、節点どうしの参照(辺)の本数を数える(内部参照数)。
  2. 強参照数 − 内部参照数 > 0 の節点は、台帳の外(Rust のスタック・大域・DOM 側の リスナ等)から参照されている「根」。
  3. 根から辺をたどって届く節点に印を付ける。印の無い節点は外から届かない循環ゴミ。
  4. ゴミの中身(プロパティ・変数・原型など)を空にして循環を断ち、参照カウントで 解放させる。

§安全性

  • 数える辺は「実在する強参照」だけ(数え漏れは安全側: その先の節点が根に見える)。 共有され得る中間の Rc(Promise の状態など)の先は数えない。
  • 借用中で中身を読めない節点は根として扱い、その先も数えない(同じく安全側)。
  • Rust のスタックが保持している値は強参照数に表れるので、実行の途中でも回収できる。
  • JS は 1 つのスレッド(ブラウザのアプリのプロセス)でしか動かない前提。台帳自体は spin ロックで守るが、回収は台帳の全節点を触るので、別スレッドで同時に JS が動く 構成になったら台帳をスレッドごとに分ける必要がある。
  • Obj・Scope を Rc から取り出したり、中身ごと入れ替えたりしてはならない (台帳の番地と中身の番号がずれる)。

Structs§

GcStats
回収の結果。
Registry 🔒
生きている Obj・Scope の台帳(Rc::as_ptr の番地。空きは 0)。

Enums§

Node 🔒
台帳の節点(一時的に強参照を 1 本持つ)。

Constants§

MIN_INTERVAL 🔒
回収の最小間隔(登録数)。
NO_SLOT
未登録を表す台帳の番号。

Statics§

MADE 🔒
前回の回収から登録した数。
NEXT_AT 🔒
次に回収する登録数(前回の生存数に比例させ、回収の手間を償却で O(1) にする)。
REGISTRY 🔒

Functions§

children 🔒
節点が直接持つ強参照の先(節点番号)を out に積む。読めなければ false。 節点番号は Obj が台帳の番号、Scope が n_objs + 台帳の番号。 番号を読めない(借用中・未登録の)先は積まない(安全側)。
collect
循環ゴミを探す。free が真なら回収もする(偽なら数えるだけ)。
maybe_collect
頃合いなら回収する。
put 🔒
should_collect
回収する頃合いか(呼び出し経路から頻繁に呼ぶので原子変数だけ見る)。
track_obj
作った Obj を台帳に載せる(value.rs の構築関数が呼ぶ)。
track_scope
作った Scope を台帳に載せる。
untrack_obj
Obj の解放時に台帳から外す。
untrack_scope
Scope の解放時に台帳から外す。