Skip to content

RebindGoMethodReceivers hangs for 2+ hours on the gortex repo itself (holds global writeMu the whole time) #651

Description

@timkjr

Summary

Store.RebindGoMethodReceivers("") (internal/graph/store_sqlite/method_receiver_rebind.go:85), the SQLite-native fast path for the resolver's rebind_go_method_receivers attribution sub-pass, hung for 2+ hours reindexing the gortex repository itself, and never completed before I gave up and restarted the daemon.

Evidence (confirmed from logs)

Every other occurrence of this exact sub-pass in the same daemon's log history completed in well under a second:

{"ts":..., "msg":"resolver: attribution sub-pass starting","pass":"rebind_go_method_receivers"}
{"ts":..., "msg":"resolver: attribution sub-passes","rebind_go_method_receivers":0.000966664, ...}

(4 separate occurrences across different sessions, all sub-second, one example above)

This occurrence, on a fresh gortex track --wait reindex of the gortex repo itself:

09:07:02  "resolver: attribution sub-pass starting","pass":"rebind_go_method_receivers"

— never logged a completion line. From 09:08:48 through 11:33:48 (2h26m), the log shows a wal checkpoint deferred mode=PASSIVE reason=writer_gate message every 5 minutes without interruption, consistent with something continuously holding the store's write role. I restarted the daemon at 11:35:02 without it ever finishing.

Repo being indexed at the time: gortex itself — 1,997 files, 162,498 nodes, 726,118 edges, 6,251 methods (per gortex status / the repo's own generated CLAUDE.md stats). Every prior fast completion was presumably on a smaller/less method-dense repo — I don't have a log line proving which repo those ran against, so I can't rule out repo-size as the sole variable, but the ~10,000x+ slowdown on this specific run stood out enough to investigate.

Hypothesis (not confirmed — I did not attach a debugger or get an EXPLAIN QUERY PLAN before restarting)

RebindGoMethodReceivers holds s.writeMu for its entire duration (method_receiver_rebind.go:86-87), across:

  1. The candidate-collection query — a 3-way JOIN (edges × nodes × nodes, using nodes_go_receiver_type and edges_by_kind indexes) with GROUP BY e.id HAVING COUNT(*) = 1 AND MIN(c.id) <> e.to_id (goMethodReceiverCandidatesGlobalSQL, lines 20-40).
  2. A 3-statement mutating transaction (conflict-delete, dedup-delete, update).

On a package/dir layout with many same-named Go types clustering under nodes_go_receiver_type (repo_prefix, file_dir, name) — plausible in a codebase this large with common receiver/type names repeated across many packages — the pre-GROUP BY intermediate row set from the join could blow up combinatorially before collapsing, turning what's normally a cheap indexed lookup into a very expensive scan. I have not verified this against EXPLAIN QUERY PLAN — it's the most plausible mechanism I could identify from reading the query, not a confirmed root cause.

Whatever the actual query-plan behavior, holding the single global writeMu for the full duration means every other write-dependent operation (and, per the daemon's own log, hits/subsequent analysis passes queued behind the same store) stalls right along with it — worth decoupling the long-running candidate SELECT from the writer lock if it can safely run under a read/shared-read scope before the short mutating transaction.

Environment

  • gortex v0.63.8, linux/amd64
  • Repo under index: gortex's own source tree (self-hosted/dogfooding case)
  • Store: SQLite backend, default config

What I did

Restarted the daemon rather than continue waiting or attempt to intervene in the live process (didn't want to risk corrupting a stuck write transaction). No repro script yet — this was observed once, on this specific reindex. Happy to help gather EXPLAIN QUERY PLAN output or attach pprof if it recurs and someone wants a live capture before restart.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions