Skip to content

fix(qdrant): keep GitHub collection discovery pending after a validation interrupt - #243

Open
detail-app[bot] wants to merge 1 commit into
devfrom
detail/bug-fix/fix-qdrant-keep-github-collection-discovery-pendin-681f53
Open

fix(qdrant): keep GitHub collection discovery pending after a validation interrupt#243
detail-app[bot] wants to merge 1 commit into
devfrom
detail/bug-fix/fix-qdrant-keep-github-collection-discovery-pendin-681f53

Conversation

@detail-app

@detail-app detail-app Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Detail bug report: View on Detail

Closes #230

Bug

QdrantGitHubCollectionDiscovery.validateGitHubCollection caught InterruptedException and rethrew it as a plain IllegalStateException, which fell through attemptDiscovery's catch (RuntimeException) arm and permanently parked the discovery state machine at FAILED. The listing path already propagated the same interrupt as a checked exception and stayed PENDING, so the two halves of loadValidatedGitHubCollections handled one transient event in two non-equivalent ways. Once FAILED, the non-PENDING guard short-circuited every @Scheduled retry, leaving the retrieval fan-out (getDiscoveredCollections()) and readiness (discoveryHealth()) stuck empty/DOWN for the bean's lifetime and emitting a spurious ERROR under the "schema validation failed" banner. This was a regression introduced when the PENDING/READY/FAILED state machine was added.

Fix

Declare throws InterruptedException on validateGitHubCollection and delete the local catch (InterruptedException) that rethrew it as IllegalStateException. The interrupt now propagates as a checked exception — exactly like the listing path — into attemptDiscovery's existing catch (InterruptedException) arm, which restores the interrupt status and keeps the state PENDING so the @Scheduled retry (or next startup) can recover. This is the idiomatic fix because it restores symmetry between the listing and validation halves and reuses the existing transient-interrupt handler rather than adding a new one.

Testing

Three committed unit tests pin the fix against regression:

  • a validation interrupt leaves discovery PENDING (not FAILED), cancels the in-flight getCollectionInfoAsync future, and reports DOWN/"pending";
  • after a validation interrupt, the @Scheduled retry entry point recovers and reaches READY once Qdrant returns valid collection info — the key guard against the stuck-FAILED no-recovery path;
  • the thread's interrupt status is restored after the interrupt, so upper layers (scheduler, shutdown hooks) still observe it.

Existing tests still pass unchanged, including schema-mismatch → FAILED (fail-closed preserved), transient gRPC failure → PENDING + recover-on-retry, listing timeout → PENDING, the inventory refresh path (independent of discoveryState), and the happy-path discovery of a valid collection.

Routine checks all pass: full JVM unit-test suite (no new failures, no errors), compileJava/compileTestJava, SpotBugs and PMD (main + test), Spotless/Palantir formatting, ast-grep Java rules, the Ruby chat-model SSOT guard, and make build, make lint, and make test (16 shell contracts + the JVM suite).

End-to-end: booted the packaged jar on Java 25 (BellSoft Liberica). With Qdrant down, discovery logged the transient WARN ... deferred: Qdrant is unavailable (not a schema-failure ERROR) and readiness reported DOWN/pending. With Qdrant 1.18.3 up via docker compose, discovery reached READY (the "No GitHub collections found" INFO line fires only after discoveryState.set(READY)), and the shutdown log contained no schema validation failed / validation interrupted line. The Qdrant 1.18.3 integration contract script also passed, exercising the same listCollectionsAsync + getCollectionInfoAsync path against a real Qdrant.

A real Thread.interrupt() landing mid-Future.get() in a live scheduler thread is timing-dependent, so the committed tests stub get(...) to throw InterruptedException to exercise that code arm deterministically; the live trigger is realistically only a context-close shutdownNow() during an in-flight @Scheduled retry, where the JVM is exiting.


Automatic Fixes PRs can be configured here.

…ion interrupt

An InterruptedException raised while validating a GitHub collection was
converted to an IllegalStateException and fell through attemptDiscovery's
catch (RuntimeException) arm, permanently parking the state machine at FAILED.
The listing path already propagated the same interrupt as a checked exception
and stayed PENDING, so the two halves of loadValidatedGitHubCollections handled
one transient event in two non-equivalent ways, and the non-PENDING guard then
short-circuited every @scheduled retry with no in-bean recovery.

- declare throws InterruptedException on validateGitHubCollection and drop the
  local catch that rethrew it as IllegalStateException, so the interrupt reaches
  attemptDiscovery's existing catch (InterruptedException) and keeps state PENDING
  like the listing path, with the interrupt status still restored
@detail-app
detail-app Bot requested a review from WilliamAGH September 6, 2026 14:01
@detail-app detail-app Bot added the bug Something isn't working label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Detail Bug] Qdrant discovery: validation thread interrupt permanently marks GitHub collections discovery as FAILED

1 participant