Roux.GC (roux v0.2.2)

Copy Markdown View Source

Garbage collection of stale memo entries and dead entities.

Without GC, a long-running server accumulates unbounded dead state in ETS tables over hundreds of edits. Elixir's garbage collector handles process heap data, but ETS entries persist until explicitly deleted.

Entry points

  • sweep/1 — periodic sweep deleting zero-refcount entities and orphaned memo entries. Run on idle or explicit trigger, never during active query execution.
  • sweep_query/4 — called by Runtime after a query re-executes to diff output entities and adjust refcounts.
  • mark_input_removed/3 — called when an input is removed (e.g. file deleted). Deletes the memo entry and advances the revision counter.

Summary

Functions

Marks an input as removed (e.g. file deleted).

Performs a garbage collection sweep.

Diffs a query's output entities after re-execution.

Types

sweep_result()

@type sweep_result() :: %{
  memo_entries_removed: non_neg_integer(),
  entities_removed: non_neg_integer(),
  duration_us: non_neg_integer()
}

Functions

mark_input_removed(db, input_name, key)

@spec mark_input_removed(Roux.Database.t(), atom(), term()) :: :ok

Marks an input as removed (e.g. file deleted).

Deletes the input's memo entry and advances the revision counter so downstream queries detect the change on next validation. No-op if the input was never set.

sweep(db)

@spec sweep(Roux.Database.t()) :: sweep_result()

Performs a garbage collection sweep.

Deletes all entities with refcount == 0, then cascades to delete orphaned memo entries (entries where any dependency's memo entry is missing).

Returns statistics about what was cleaned. Emits [:roux, :gc, :sweep] telemetry.

Must not run while any query is in a fixed-point iteration loop.

sweep_query(db, query_key, opts)

@spec sweep_query(
  Roux.Database.t(),
  Roux.Memo.query_key(),
  keyword()
) :: :ok

Diffs a query's output entities after re-execution.

Decrements refcounts for entities in old but not new. Increments refcounts for entities in new but not old.

Resilient to entities that have already been deleted by a prior sweep — Entity.decrement_refcount/3 raises ArgumentError on missing entities, which is rescued here.

Options

  • :old — entities from the previous execution (required)
  • :new — entities from the current execution (required)