Skip to content

fix(harness): thread-spawn failure and worker panics deadlocked the search start barrier - #263

Merged
fcostaoliveira merged 4 commits into
masterfrom
fix/barrier-spawn-deadlock-214
Aug 8, 2026
Merged

fix(harness): thread-spawn failure and worker panics deadlocked the search start barrier#263
fcostaoliveira merged 4 commits into
masterfrom
fix/barrier-spawn-deadlock-214

Conversation

@fcostaoliveira

@fcostaoliveira fcostaoliveira commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

The bug

Every engine's search() synchronized its measured-window start with Barrier::new(parallel + 1) — a participant count fixed before the workers exist. Two ordinary failures make that count unmeetable, and both produce a permanent hang with no output rather than an error:

  1. The OS refuses a thread. Scope::spawn panics on EAGAIN (ulimit -u, cgroup pids.max — i.e. any CI container with a large parallel). The panic unwinds into thread::scope's drop, which joins the workers already spawned; they are parked in ready.wait() waiting for parallel + 1 arrivals that can never happen.
  2. A worker panics before the barrier — e.g. an out-of-bounds index in the "prime" query on a short query set. The coordinator then parks in ready.wait() forever.

--search-timeout defaults to 0.0 (disabled), so no watchdog breaks it.

The second half: a published number whose name lies

The barrier deadlock is the headline, but the same code had a quieter defect that needed no fault injection at all. Both cases below are a shipped config against a stock server, reproduced during review:

pgvector parallel: 100 (25 such points in pgvector-single-node.json) against Postgres 17 with max_connections=64:

master this branch
exit code 0 1
published parallel: 100, rps: 23.5 (nothing)
server log 36 × FATAL: sorry, too many clients already same
stderr (silent) only 64 of 100 pgvector-search workers reached the start gate — 36 failed setup: pgvector-search worker connect failed: FATAL: sorry, too many clients already

A 64-wide run labelled 100. The 36 failed workers crossed both barriers, returned empty, and had their errors discarded with no log line.

Redis maxclients=24, parallel: 64: master exits 0 and publishes parallel: 64, rps: 299.5, failed_queries: 0, mean_recall: 1.0 — measured by 24 workers. This branch exits 1 with only 24 of 64 redis-search workers reached the start gate — 40 failed setup … Refusing to report a run at parallel=64 that measured fewer workers.

Blast radius (independently derived)

The issue named 6 engines; its own follow-up comment had already raised that to 13. grep -rn 'Barrier::new' over src/ gives 15 harnesses across 14 engines — the extra being vertex, whose two harnesses spell it Barrier::new(workers + 1):

file harness shape
chroma, dragonfly, elasticsearch, kividb, milvus, mongodb, opensearch, pgvector, qdrant, redis, valkey, vectorsets search ready + go + OnceLock start cell
weaviate search gRPC and GraphQL one barrier pair shared by both branches
vertex search and mixed one barrier + OnceLock + spin

Checked for the same shape in other fixed-count primitives and found none: no mpsc channel awaiting N messages (the only channel is experiment.rs's watchdog, which uses recv_timeout and is disconnect-safe), no Condvar/latch counters, no WaitGroup. src/redisearch/ and src/vectorsets/ also contain barrier-free scopes but are not declared in lib.rs and are not compiled (confirmed against cargo's own dep-info).

23 s.spawn sites remain, in the upload / filter-only / mixed / turbopuffer paths. None has a fixed-count wait, so a refused spawn there panics loudly rather than hanging: 8 rely on an explicit h.join().unwrap() and 15 on thread::scope's implicit join, and either way the panic reaches the main thread. One of them — turbopuffer.rs:586 — is nonetheless inside a timed search harness, so it measures connection setup inside the window; that and seven other timed harnesses are #266.

The fix

New vector_db_benchmark::start_gate replaces both barriers and the OnceLock start cell with a gate whose wait is satisfied by ticket outcomes rather than a count:

  • WorkerPool::spawn goes through thread::Builder::spawn_scoped, which returns io::Result instead of panicking, and mints the worker's ticket itself, handing it to the closure. A worker cannot exist without a ticket, or a ticket without a worker — that pairing is the whole deadlock-freedom argument, so it is an API property, not a convention. WorkerPool::new also takes the planned worker count, and start refuses a pool that spawned fewer than it plans.
  • A ticket settles by arriving, by reporting a setup failure, or — via Drop — by being lost to a panic or to a thread the OS never started. Any terminal outcome satisfies the coordinator, so no arrival count is ever left unmet.
  • Drop for WorkerPool aborts the gate, so an early ? anywhere in the scope closure releases parked workers. Weaviate's gRPC path fans out tokio tasks rather than scoped threads and drives a bare StartGate, so it holds an AbortGateOnDrop declared after the runtime — without it, a coordinator-future panic left tasks parked on a condvar and Runtime::drop joined them forever, which is thread-spawn failure deadlocks the fixed-count Barrier in 6 engines' search harness #214's own shape. INV-4c now requires that guard.

Vertex's open-loop path keeps its 100 ms scheduling lead via start_with. Every other engine's diff is mechanical.

Failure semantics

Per the settled policy — anything that changes the reported number is a hard error. A worker that never started, died before the gate, failed setup, or panicked mid-run means the run measured fewer workers than the parallel it reports, so WorkerPool::start returns Err naming what happened.

Scope of the "silent degradation" half. This closes it for the closed-loop search() of 14 engines only. search_mixed (4 engines), search_filter_only (3) and turbopuffer::search still publish a parallel from config with no guard — tracked in #266.

Mid-run panics are a diagnostic improvement, not a correctness fix. Master does not publish a wrong result here: all 14 engines used h.join().unwrap(), so a worker panic re-panics on the main thread and the process exits 101. The gain is a clean exit 1 with 1 of 8 redis-search workers panicked mid-run; discarding the run rather than reporting partial results, and a named thread (redis-search-4) where master prints thread '<unnamed>'.

Reproduction — before / after

Real RLIMIT_NPROC (ulimit -u set 8 above the current task count, parallel=64), against the pre-fix shape extracted verbatim into a standalone binary:

  • unfixed: panics failed to spawn thread: Os { code: 11, kind: WouldBlock }, then hangs. SIGKILLed at 30 s in 2 of 3 runs; the third could not start because timeout itself could not fork. 30 s is the timeout bound — a floor, not a measured hang duration.
  • fixed: errors in ~1–2 ms (1.04 / 1.78 / 2.20 ms over three clean runs) with could not start redis-search worker 7 of 64: Resource temporarily unavailable (os error 11). The OS refused the thread — lower parallel, or raise the thread/process limit (ulimit -u, cgroup pids.max). Single-machine numbers, and the extraction harness is not in this diff; an independent reviewer measured 142 µs–9.3 ms on other boxes, and one noisier box 17–27 ms. The claim reproduces qualitatively, not to the decimal.

In CI, deterministically, via a #[cfg(test)] thread-local spawn-failure seam (absent from a release build by both strings and nm — a test seam, not a runtime backdoor), plus real worker panics and real dropped tickets. Reverting the gate to the pre-fix semantics — fixed arrival count, no Drop settling, Scope::spawn's panic, failed workers crossing both barriers, no planned-vs-spawned check — turns 6 of the 13 start-gate tests red:

running 13 tests
test start_gate::tests::early_return_from_the_scope_closure_does_not_strand_parked_workers ... ok
test start_gate::tests::start_with_hands_every_worker_the_caller_chosen_instant ... ok
test start_gate::tests::worker_panic_after_the_gate_is_reported_not_swallowed ... ok
test start_gate::tests::zero_workers_is_not_a_hang ... ok
test start_gate::tests::all_workers_observe_one_shared_start_after_every_worker_warmed ... ok
test start_gate::tests::legacy_fixed_count_barrier_hangs_when_a_spawn_fails ... ok
test start_gate::tests::legacy_fixed_count_barrier_hangs_when_a_worker_panics ... ok
test start_gate::tests::spawn_failure_is_a_prompt_error_not_a_hang ... FAILED
test start_gate::tests::a_pool_that_spawns_fewer_workers_than_it_plans_is_an_error ... FAILED
test start_gate::tests::a_worker_that_settles_nothing_is_lost_not_hung ... FAILED
test start_gate::tests::abort_releases_workers_already_parked_at_the_gate ... FAILED
test start_gate::tests::worker_setup_failure_is_a_hard_error_not_a_quieter_run ... FAILED
test start_gate::tests::worker_panic_before_the_gate_is_a_prompt_error_not_a_hang ... FAILED

test result: FAILED. 7 passed; 6 failed; 0 ignored; 0 measured; 98 filtered out; finished in 5.01s

Those six only report rather than hanging forever because each wraps its call in a 5 s watchdog. With the watchdog raised to an hour, four of them are SIGKILLed at the 30 s timeout boundspawn_failure_…, worker_panic_before_the_gate_…, worker_setup_failure_…, a_worker_that_settles_nothing_…. On this branch all 13 pass; the suite takes 2.00 s, which is the two legacy_* hang proofs deliberately watching a known-deadlocked shape for 2 s each. Every other test completes well under the 5 s bound it asserts (per-test timing is nightly-only, so no finer figure is quoted).

Tests and guards

  • src/start_gate.rs — 13 unit tests. Two legacy_* tests replicate the pre-fix Barrier shape under both failure modes and assert it never completes, so "the new code returns Err" is evidence of something rather than a tautology. Reviewers confirmed they are non-tautological: neutering Drop for WorkerTicket alone produces exactly one failure, a full pre-fix revert produces six.
  • tests/overhead_invariants.rsthis file had never run in CI. Every cargo test in the workflows is --lib --bins --release (which does not build tests/*.rs) or a named --test integration_<engine>, so INV-2 and INV-3 have been unenforced since they landed, and INV-4 would have been unfailable on day one. This PR adds cargo test --test overhead_invariants --release to the unit-test job.
  • All guards now scan comment- and string-stripped source. This was a live hazard, not hygiene: INV-4 bans the Barrier type, and all 14 engines used to carry a comment describing the barrier it removes, so the guard forbade documenting its own subject.
  • INV-4 bans the Barrier type anywhere under src/, not one spelling of the call — closing the alias, type-alias, UFCS and other-directory evasions at once — with a per-line // INV-4-ALLOW: <reason> opt-out so a future legitimate barrier is possible but reviewable.
  • INV-4b derives its engine list from engine/mod.rs (new engines opted in by default; each exclusion carries a reason), requires one park per harness so ungating one of vertex's or weaviate's two is caught, bans let _ = ticket.arrive_and_wait(), and requires the park to sit below the first setup-failure arm — which catches hoisting it above client construction, i.e. connection setup back inside the measured window.
  • INV-4c requires an AbortGateOnDrop wherever a bare StartGate is driven.

Mutation campaign — 10/10 as intended (8 realistic misses killed, 2 false-positive probes left alone):

mutant verdict
let _ = ticket.arrive_and_wait() (redis) KILLED
park hoisted above client construction (chroma) KILLED
one of vertex's two harnesses un-gated KILLED
use std::sync::Barrier as Rendezvous (kividb) KILLED
type Gate = std::sync::Barrier + UFCS (milvus) KILLED
AbortGateOnDrop removed (weaviate) KILLED
verdict discarded at a second site (pgvector) KILLED
brand-new engine file with Barrier::new KILLED
Barrier::new in a comment survived (correct)
legitimate barrier with INV-4-ALLOW survived (correct)

The three API-level mutants reviewers used to break the ticket/spawn pairing are now unrepresentable: pool.spawn(\|\| {}) and pool.ticket() fail to compile (E0593, E0599), and minting N-1 for N planned fails at runtime with test spawned 3 workers but the run is labelled parallel=4.

Other review fixes

  • WorkerPool::spawn's error read "worker k of k" (both operands were index + 1); it now names the planned parallelworker 7 of 64.
  • StartGate::wait_ready takes the harness label, so "N never reached the start gate" says which engine.
  • pgvector's setup-failure message went through postgres::Error's Display, which is literally "db error"; it now reads as_db_error() and the source() chain, so the likeliest real trigger reads FATAL: sorry, too many clients already.
  • A failing search point discarded the points that already succeeded. experiment.rs flushed pending_saves only after the whole phase, so a parallel 1, 2, 4 sweep in which only 4 fails wrote zero files. That contradicts the policy stated for --fail-on-dropped-queries ("results files are still written before the run fails, so the evidence survives"). The hard failure is now recorded and raised after the flush.

Behaviour change to be aware of

The shipped pgvector configs have 25 points at parallel: 100, and Postgres defaults to max_connections = 100 with 3 reserved for superusers — so on a stock server those points are one stray session away from a hard failure that master papered over. Mitigations here, none of which change a published parallel value:

  • tests/docker-compose.test.yml starts pgvector with max_connections=200.
  • pgvector warns before fanning out when parallel exceeds the live budget, with the arithmetic (max_connections=, superuser_reserved=, in use, available). Advisory only — it is racy by construction and meaningless behind a pooler; the gate stays the authority.
  • --repetitions (default 3) already absorbs transient failures: a run whose rep 1 was refused but whose reps 2–3 succeeded still exits 0 with a valid result. Only deterministic failures go red.

Follow-ups filed (not fixed here)

Note for whoever sequences this with #246

engine/opensearch.rs was flagged as off-limits for PR #246, but it is one of the six engines the issue names and leaving it would keep an identical hang alive next door. The escape hatch is not "drop one hunk" — it is 7 hunks; dropping only the harness leaves an unused use …WorkerPool and fails clippy -D warnings, and dropping the file entirely makes this PR's own tests fail (INV-4 flags opensearch.rs:1516/1517, INV-4b derives it from engine/mod.rs). My earlier non-overlap claim also missed my own import hunk at @@ -16,6, which falls inside #246's @@ -8,12; git merges it because the insertion points differ by two lines — resolved by margin, not by design. git merge-tree is clean for +#251, +#246 and all three together; if a conflict ever appears at that import, keep all three use lines.

Closes #214

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

…earch start barrier

Every engine's `search()` synchronized its measured-window start with
`Barrier::new(parallel + 1)`: a participant count fixed *before* the
workers exist. Two ordinary failures make that count unmeetable, and both
produce a permanent hang with no output rather than an error (#214):

  1. The OS refuses a thread. `Scope::spawn` panics on EAGAIN (`ulimit -u`,
     cgroup `pids.max` — i.e. any CI container with a large `parallel`).
     The panic unwinds into `thread::scope`'s drop, which joins the workers
     already spawned; they are parked in `ready.wait()` waiting for
     `parallel + 1` arrivals that can never happen.
  2. A worker panics before the barrier — e.g. an out-of-bounds index in
     the "prime" query on a short query set. The coordinator then parks in
     `ready.wait()` forever.

`--search-timeout` defaults to `0.0` (disabled), so no watchdog breaks it.

Reproduced end to end against the unmodified pre-fix shape, under a real
lowered `RLIMIT_NPROC`: it panics on spawn and then hangs (SIGKILLed at
30s). The same harness on this branch fails in 1.4ms with

    could not start redis-search worker 4 of 4: Resource temporarily
    unavailable (os error 11). The OS refused the thread — lower
    `parallel`, or raise the thread/process limit (ulimit -u,
    cgroup pids.max)

New `vector_db_benchmark::start_gate` replaces both barriers and the
`OnceLock` start cell with a gate whose wait is satisfied by ticket
*outcomes* rather than a count:

  * `WorkerPool::spawn` goes through `thread::Builder::spawn_scoped`, which
    returns `io::Result` instead of panicking.
  * Each worker is issued a `WorkerTicket` BEFORE it is spawned. A ticket
    settles by arriving, by reporting a setup failure, or — via `Drop` —
    by being lost to a panic or to a thread the OS never started. Any
    terminal outcome satisfies the coordinator, so there is no arrival
    count left unmet.
  * `Drop for WorkerPool` aborts the gate, so an early `?` anywhere in the
    scope closure releases parked workers instead of handing control to the
    same deadlock.

Failure semantics follow the settled policy — anything that changes the
reported number is a hard error. A worker that never started, died before
the gate, failed setup, or panicked mid-run means the run measured fewer
workers than the `parallel` it reports, so `WorkerPool::start` returns
`Err` naming what happened. It no longer silently proceeds at
`parallel - k` (previously a failed connect crossed both barriers, returned
empty, and had its error discarded with no log line), and it no longer
hangs.

Applied to all 15 affected harnesses across 14 engines: chroma, dragonfly,
elasticsearch, kividb, milvus, mongodb, opensearch, pgvector, qdrant,
redis, valkey, vectorsets, weaviate (gRPC + GraphQL) and vertex
(search + mixed). Weaviate's gRPC path drives tokio tasks rather than
scoped threads, so it coordinates the same `StartGate` by hand; Vertex's
open-loop path keeps its 100ms scheduling lead via `start_with`.

Tests: `src/start_gate.rs` carries two `legacy_*` tests that replicate the
pre-fix `Barrier` shape under both failure modes and assert it NEVER
completes (watchdog, 2s), so "the new code returns Err" is evidence of
something. The matching `WorkerPool` tests drive a real injected spawn
failure (`#[cfg(test)]` thread-local seam — no runtime backdoor) and a real
worker panic, and assert a prompt, informative error. Against a reverted
gate both hang indefinitely (SIGKILLed at 30s); on this branch both pass in
under 10ms. `tests/overhead_invariants.rs` gains INV-4 / INV-4b: no
`Barrier::new(...)` may reappear in an engine, and every fan-out harness
must still park at the gate.

Closes #214

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Docker Build Validation

Platforms tested:

  • linux/amd64 (built and tested)
  • linux/arm64 (build validated)

Tests performed:

  • --help command
  • --describe datasets command
  • Integration test (random-100 dataset + Redis)
  • Multi-platform build validation (amd64 + arm64)

…d make the guards real

Round-2 review fixes on top of the #214 start-gate replacement.

MUST-FIX items:

* Weaviate's gRPC branch was the one harness driving a bare `StartGate`
  instead of `WorkerPool`, so it had no equivalent of
  `Drop for WorkerPool` → `gate.abort()`. A coordinator-future panic
  between the first `ticket()` and `wait_ready` unwound out of
  `rt.block_on`, and `Runtime::drop` then joined tasks parked on a condvar
  nobody would notify — #214's own shape surviving on the branch that
  fixes #214. New `AbortGateOnDrop`, declared AFTER `rt` so drop order
  aborts the gate before the workers are joined, plus INV-4c to require it.
  (Its `Builder::build()` panics rather than returning `Err` on EAGAIN, so
  that harness reports a refused thread as a panic, not the friendly
  message. Loud, not hung — documented in place.)

* `tests/overhead_invariants.rs` never ran in CI: every `cargo test` in
  the workflows is `--lib --bins --release` (which excludes `tests/*.rs`)
  or a named `--test integration_<engine>`. INV-2/INV-3 had therefore
  never been enforced, and INV-4/INV-4b would have landed unfailable.
  Added `cargo test --test overhead_invariants --release` to the unit-test
  job.

* `WorkerPool::spawn` now mints the ticket and passes it to the worker
  closure (`spawn(|ticket| …)`), and `WorkerPool::ticket` is gone. The 1:1
  ticket/worker pairing was convention only, and three reviewers broke it
  through the public API — spawn with no ticket, `mem::forget(ticket)`,
  mint N-1 for N workers — each reintroducing the #214 deadlock with no
  `Barrier` anywhere, compiling clean and passing both guards. The first
  two are now unrepresentable (they fail to compile); for the third,
  `WorkerPool::new` takes the planned worker count and `start` refuses a
  pool that spawned fewer than it plans.

* The guards were 24% effective with four false positives. All searches now
  run over comment- and string-stripped source, which also fixes the live
  hazard that INV-4 forbade *documenting* the bug it removes — every engine
  used to carry exactly such a comment. INV-4 bans the `Barrier` TYPE
  anywhere under `src/` rather than one spelling of the call, closing the
  alias / type-alias / UFCS / other-directory evasions, with a per-line
  `INV-4-ALLOW: <reason>` opt-out for a future legitimate barrier. INV-4b
  derives its engine list from `engine/mod.rs` (new engines opted in by
  default, exclusions carry a reason), requires one park per harness so
  ungating one of vertex's or weaviate's two is caught, bans
  `let _ = ticket.arrive_and_wait()`, and requires the park to sit below
  the first setup-failure arm so hoisting it above client construction —
  putting connection setup back inside the measured window — is caught.

* `WorkerPool::spawn`'s error read "worker k of k" because it passed
  `index + 1` for both numerator and denominator; it now names the planned
  `parallel`. `StartGate::wait_ready` takes the harness label, so "N never
  reached the start gate" says which engine. pgvector's setup-failure
  message walked into `postgres::Error`'s `Display`, which is literally
  "db error" — it now reads `as_db_error()` / the `source()` chain, so the
  likeliest real trigger reads `FATAL: sorry, too many clients already`
  instead.

Addendum blockers:

* A failing search point discarded the points that already succeeded:
  `experiment.rs` flushed `pending_saves` only after the whole phase, so a
  sweep whose last point fails wrote zero files. That contradicts the
  policy stated for `--fail-on-dropped-queries` ("results files are still
  written before the run fails, so the evidence survives"). The hard
  failure is now recorded and raised after the flush.

* The shipped pgvector configs have 25 points at `parallel: 100`, and
  Postgres defaults to `max_connections = 100` with 3 reserved — so a
  stock server is one stray session away from a hard failure now that a
  short-staffed pool is an error. The repo's own compose service now sets
  `max_connections=200`, and pgvector warns before fanning out when
  `parallel` exceeds the live budget, with the arithmetic. The published
  `parallel` values are unchanged.

Tests: 13 start-gate unit tests (adds the planned-vs-spawned mismatch and
the dropped-ticket cases). Against a reverted gate, 6 of the 13 fail and
four hang unboundedly (SIGKILLed at 30s); on this branch all 13 pass in
2.00s, which is the two `legacy_*` hang proofs. A 10-mutant campaign over
the guards kills all 8 realistic misses and leaves both false-positive
probes alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Docker Build Validation

Platforms tested:

  • linux/amd64 (built and tested)
  • linux/arm64 (build validated)

Tests performed:

  • --help command
  • --describe datasets command
  • Integration test (random-100 dataset + Redis)
  • Multi-platform build validation (amd64 + arm64)

…4 opt-out actually work

`strip_comments_and_strings` treated every escape as two consumed
characters, so a `\`-plus-newline string continuation swallowed its
newline and the stripped text ended up shorter than the file. There are
219 such continuations under `src/`.

Both INV-4 behaviours depend on that alignment, and both were broken:

  * violations print `path:line` from the stripped text, so any barrier
    below the first continuation in a file was reported — and quoted —
    against the wrong source line (verified live: a probe on redis.rs
    raw line 3836 was reported as 3833);
  * the `INV-4-ALLOW:` opt-out is read from the RAW line at that index,
    so a marker on the real line was ignored, and a marker three lines
    ABOVE an unrelated barrier silently exempted it.

Latent — no `INV-4-ALLOW:` marker exists anywhere under `src/` yet, so
nothing was masked — but the file's own doc comment claimed "newlines are
preserved so reported line numbers stay true", which is exactly the class
of shipped false claim about a guard that this cycle has been clearing.
The escape branch now emits `\n` when the escaped character is a newline,
making the doc comment true.

Tightened the opt-out while pinning it. A marker may TRAIL the offending
line, or sit on the line above as a STANDALONE comment (a `use` reads
better with its reason above it) — but a marker trailing line N no longer
exempts line N+1, which would let one annotated barrier quietly cover its
unannotated neighbour.

Three tests pin it: a fixture asserting per-line width and that only the
code barrier (not the one in a comment) is reported; a whole-tree oracle
asserting stripped line count == raw line count for every file under
`src/`, plus that the tree still contains the `\`-continuations the case
depends on; and a marker test with a continuation above two adjacent
barriers, one annotated. All three fail against the unfixed stripper.

Also documents the two residual ways to defeat the ticket coupling.
`WorkerPool::spawn` minting the ticket prevents ACCIDENTAL omission — you
cannot forget one, mint too few, or pair the wrong ticket with the wrong
worker, and the compiler enforces all three — but `mem::forget(ticket)`
and sending it out of the closure over a channel both leave it unsettled
and hang `wait_ready`. Says so plainly rather than claiming the mistake is
unrepresentable.

Follow-ups filed, not fixed here: #276 (INV-4c is a per-file existence
check, so a second bare `StartGate` in `weaviate.rs` slips through) and
#277 (the pgvector connection-budget warning counts background workers
and subtracts superuser-reserved slots from a superuser connection, so it
warns spuriously).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fcostaoliveira
fcostaoliveira merged commit 01145d4 into master Aug 8, 2026
18 checks passed
fcostaoliveira added a commit that referenced this pull request Aug 8, 2026
Merged rather than rebased: the branch is already pushed and this repo's
standing rule is to add a commit, never to force-push.
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Docker Build Validation

Platforms tested:

  • linux/amd64 (built and tested)
  • linux/arm64 (build validated)

Tests performed:

  • --help command
  • --describe datasets command
  • Integration test (random-100 dataset + Redis)
  • Multi-platform build validation (amd64 + arm64)

fcostaoliveira added a commit that referenced this pull request Aug 8, 2026
Merging rather than rebasing: the branch is already published and rebasing it
once produced a non-fast-forward that must not be resolved by force-push.

#263 replaced the fixed-count `Barrier` in all 14 engine search harnesses with
`start_gate`, touching the same worker-spawn and error-propagation paths this
branch adds `corpus_row_count()` and the pre-search reuse guard to, so the merge
is exercised rather than assumed: see the commit that follows for the gate
results on this tree.
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Docker Build Validation

Platforms tested:

  • linux/amd64 (built and tested)
  • linux/arm64 (build validated)

Tests performed:

  • --help command
  • --describe datasets command
  • Integration test (random-100 dataset + Redis)
  • Multi-platform build validation (amd64 + arm64)

fcostaoliveira added a commit that referenced this pull request Aug 8, 2026
…t an engine (#223)

INV-4b (added by #263, and first actually RUN by CI this afternoon) derives its
engine list from `engine/mod.rs`'s `mod X;` lines minus a hand-maintained
EXCUSED list, then requires each survivor to hold a `WorkerPool::new` /
`StartGate::new`. `engine/geo.rs` is 359 lines of spherical geometry with zero
`impl Engine for` blocks, so it has no timed search to gate and belongs with the
five helpers already listed (index_naming, redis_utils, vertex_grpc,
weaviate_grpc, filter_guard).

The message it printed — "the synchronized start is gone" — reads as a
regression, but a file that never had a gate cannot have lost one. Not touching
that message here: it is #263's guard, and geo work should not be entangled with
harness work. The message, and the fact that EXCUSED must be hand-fed for every
future non-engine module, are filed as #287.

`cargo test --test overhead_invariants`: 9 passed, 0 failed.

Co-Authored-By: Claude Opus 5 (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.

thread-spawn failure deadlocks the fixed-count Barrier in 6 engines' search harness

1 participant