Skip to content

Add optional name resolver for per-process cache redirection#436

Open
pinetops wants to merge 1 commit into
whitfin:mainfrom
pinetops:feat/name-resolver
Open

Add optional name resolver for per-process cache redirection#436
pinetops wants to merge 1 commit into
whitfin:mainfrom
pinetops:feat/name-resolver

Conversation

@pinetops
Copy link
Copy Markdown

@pinetops pinetops commented Jun 1, 2026

Motivation

Every public Cachex.* call funnels through Cachex.Services.Overseer.lookup/1, which maps a cache name to its cache state via a single ETS read. There's currently no supported way to influence that resolution based on the calling process.

This matters for test isolation. A library that wants to give each async: true test its own isolated cache (so concurrent tests don't share state through a globally-named cache) has no hook to do so.

What this does

Adds an opt-in :name_resolver consulted at the single resolution chokepoint, via a new Overseer.resolve_name/1:

config :cachex, :name_resolver, &MyModule.resolve/1

The resolver is a (atom -> atom) function. lookup/1's atom clause passes the name through it before the ETS read, so a name can be transparently routed to a different registered cache — e.g. a per-test cache derived from the process dictionary or $callers.

  • Default is the identity function — when :name_resolver is unset, behaviour is unchanged and there's no measurable overhead (one Application.get_env returning nil).
  • Non-recursive: the resolver's result is used directly as the ETS key.
  • nil falls back to the original name.

This lets test-isolation (and similar per-process routing) work against vanilla Cachex, no fork or bytecode patching..

Every cache call funnels through `Overseer.lookup/1` to map a cache name
to its state. There is currently no supported way to redirect that
resolution based on the calling process, which forces test-isolation
libraries (e.g. for `async: true` tests) to runtime-patch Cachex's
bytecode to give each test its own cache.

Add an opt-in `:name_resolver` — a `(atom -> atom)` function consulted at
the single resolution chokepoint via `Overseer.resolve_name/1`. It
defaults to the identity function, so there is no behaviour change and no
measurable overhead unless configured. When set, a name can be
transparently routed to a different cache (e.g. a per-test cache derived
from the process dictionary or `$callers`), enabling sandboxing without
forking or patching Cachex.

Resolution is non-recursive (the resolver's result is the ETS key), and a
resolver returning nil falls back to the original name.
pinetops added a commit to u2i/sandbox_case that referenced this pull request Jun 1, 2026
…on coupling

Ecto/Mimic/Mox have first-class per-process hooks; Cachex and
FunWithFlags don't, so sandbox_case patches their compiled modules at
runtime (test env only) to route to isolated ETS storage. Document this
mechanism, the tested version ranges (Cachex 4.1.x, FWF 1.13.x), and the
graceful-degradation behavior (probe shape, warn + skip on mismatch
rather than crash). Note the upstream direction: whitfin/cachex#436 (a
per-process name resolver) and the no-patch FunWithFlags alternative
(custom Store.Persistent adapter + cache disabled).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant