I am a long-lived wrapper around OTP's :xref cross-reference server.
The previous code in GtBridge.Analysis started a fresh :xref server,
added the relevant app's ebin directory, ran one query, and stopped the
server — for every call to function_references/3 and
module_graph/1. The add_directory step is the expensive part; xref
parses every .beam file in the directory to build its edge index.
Doing that per-query made interactive paths (C-n, the |> expander)
perceptibly slow.
I keep one xref server running for the lifetime of the BEAM. Indexing
runs in a background :low-priority Task so it yields to any
normal-priority work — eager add_directory on every loaded app
contends with the OTP code server enough to cascade GT's startup
module_details fan-out into 5s eval timeouts at normal priority.
Queries arriving before indexing completes return {:ok, []} rather
than blocking. When BeamModuleRecompiled fires (from hot_reload/2
via the event bus), I call :xref.replace_module/3 on just that
module — so the index stays consistent with the live BEAM without
rebuilding from scratch.
Public API
q/1— run an xref query string and return its:xref.q/2resultreplace/1— explicitly refresh one module's edges (also called automatically onBeamModuleRecompiled)
Subscriptions
I subscribe to GtBridge.Events.AnyModuleEvent and react to
:recompiled events by calling replace/1 on the affected module.
Summary
Functions
Returns a specification to start this module under a supervisor.
I am true once the initial indexing pass has completed. Until then
every q/1 returns {:ok, []} per the moduledoc contract.
Block the caller until indexing finishes (or timeout_ms elapses).
Useful from test setup so assertions about xref data don't race
the background indexer. Returns :ok when ready, exits with
:timeout like any other GenServer.call/3 if the deadline lapses.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec ready?() :: boolean()
I am true once the initial indexing pass has completed. Until then
every q/1 returns {:ok, []} per the moduledoc contract.
@spec replace(module()) :: :ok
@spec start_link(term()) :: GenServer.on_start()
@spec wait_until_ready(timeout()) :: :ok
Block the caller until indexing finishes (or timeout_ms elapses).
Useful from test setup so assertions about xref data don't race
the background indexer. Returns :ok when ready, exits with
:timeout like any other GenServer.call/3 if the deadline lapses.
Already-ready replies immediately; otherwise the caller is parked
on a deferred reply and woken from handle_cast(:indexing_done, _).