Roux.Cycle (roux v0.2.2)

Copy Markdown View Source

Detects cycles in the query dependency graph at runtime.

When query A calls query B which calls query A, the framework must detect this and respond rather than deadlocking or stack-overflowing. Current behavior: abort with a clear Roux.Cycle.Error.

Detection is O(n) in the query stack depth, which is bounded by the query DAG depth. The query stack is process-local (in the Context struct), so no synchronization is needed.

Designing for fixed-point iteration

The data structures support future fixed-point iteration without structural changes. When implemented, check!/2 would return a provisional value instead of raising, and the runtime would re-execute the cycle until the result stabilizes. See D7 for rationale.

Summary

Functions

Non-raising variant of check!/2.

Checks if the given query is already on the active stack.

Functions

check(context, query_key)

@spec check(Roux.Runtime.Context.t(), Roux.Memo.query_key()) ::
  :ok | {:cycle, [Roux.Memo.query_key()]}

Non-raising variant of check!/2.

Returns :ok if no cycle, or {:cycle, path} where path is the list of query keys forming the cycle (starting and ending with the repeated key).

check!(context, query_key)

@spec check!(Roux.Runtime.Context.t(), Roux.Memo.query_key()) :: :ok

Checks if the given query is already on the active stack.

Returns :ok if no cycle exists. Raises Roux.Cycle.Error with the cycle path if one is detected.

Called by Runtime before pushing a query onto the stack.