feat(schema-renderer): abort in-flight findAll on cleanup and re-run#71
Closed
HexaField wants to merge 1 commit into
Closed
feat(schema-renderer): abort in-flight findAll on cleanup and re-run#71HexaField wants to merge 1 commit into
HexaField wants to merge 1 commit into
Conversation
SchemaRenderer's `createQuerySignal` and `$single` paths both run
`ModelClass.findAll` inside a `createEffect`. The effect re-runs on any
reactive dependency change (perspective swap, params token update) and
the component can unmount mid-query. Without cancellation the stale
findAll keeps grinding through SPARQL serialisation, network round-trip,
and client-side deserialisation — work that's discarded the moment a
fresher result arrives.
Wires an `AbortController` scoped to each effect iteration:
- `onCleanup` aborts the controller before the effect re-runs or the
component unmounts.
- The `findAll(p, queryOptions, { signal })` call forwards the signal
to ad4m's `Ad4mModel.findAll` (extended in
coasys/ad4m#855 to thread the signal through
`modelQuery` → `apiClient.call` → the executor's `request.cancel`
WebSocket message).
- `.then` guards on `controller.signal.aborted` to ignore the stale
result if the controller fired before the promise resolved.
- `.catch` swallows `DOMException('Aborted', 'AbortError')` so
cancellation isn't surfaced to the UI as an error — the new effect
run (or unmount) handles state instead.
The subscribe path already cleans up via `builder.dispose()` and isn't
changed.
Caveat: the executor's Oxigraph SPARQL engine can't be interrupted
mid-evaluation; what's saved is the JSON serialise + WebSocket reply +
client deserialise tax, which dominates for any non-trivial result set.
Tests (queryToken.test.tsx):
- findAll receives an AbortSignal in `options.signal`.
- Unmount aborts the controller.
- Re-running the effect (perspective signal change) aborts the prior
controller and gives the new run a fresh, un-aborted signal.
- AbortError rejection is swallowed without surfacing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
✅ Deploy Preview for coasys-we ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
Author
|
Re-opening from coasys/we branch directly (not from fork) — see successor PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wires an
AbortControllerintoSchemaRendererso the in-flightAd4mModel.findAllis cancelled the moment the rendering effect re-runs (perspective swap, params change) or the component unmounts.Why it matters
createQuerySignaland the$singlepath both runfindAllinsidecreateEffect. Without a signal, every reactive dep change leaks an in-flight query that keeps grinding through SPARQL eval → JSON serialise → WebSocket reply → client deserialise, all of which is discarded the moment the new result arrives. On chatty perspectives that adds up fast — a perspective swap can leave 4–5 dead queries shipping megabytes of JSON before the new one even starts.How
const controller = new AbortController()inside the effect.onCleanup(() => controller.abort())— Solid fires this before the effect re-runs and on unmount.ModelClass.findAll(p, queryOptions, { signal: controller.signal })— the structural{ signal }shape matchesAd4mModel.findAll's new third argument added in coasys/ad4m#855, which forwards the signal throughmodelQuery→apiClient.call→ the executor'srequest.cancelWebSocket message..thenguards oncontroller.signal.abortedso a stale result that arrives after cancellation doesn't overwrite the new effect's state..catchswallowsDOMException('Aborted', 'AbortError')so cancellation isn't surfaced as a UI error — the new effect run (or unmount) handles state.The
subscribepath already cleans up viabuilder.dispose(); not changed.Caveat
The executor's Oxigraph SPARQL engine has no internal interrupt hook, so the blocking thread keeps running until the query returns. What's saved is the JSON serialise + WebSocket reply + client deserialise tax, which is the dominant cost for any non-trivial result set. The API is forward-compatible with a future Oxigraph interrupt.
Files changed
packages/schema-system/frameworks/solid/src/SchemaRenderer.tsx— twofindAllcall sites (createQuerySignal list path + $single one-shot path) now scope an AbortController per effect iteration.packages/schema-system/frameworks/solid/tests/queryToken.test.tsx— 3 new tests for the new behaviour.Test plan
findAllreceives anAbortSignalinoptions.signal.aborted: false→true).AbortErrorrejection is swallowed without surfacing.Coordination
Ad4mModel.findAll/modelQueryto thread the signal.Ad4mModel.findAll(3rd arg is dropped) — no breakage, just no early termination.🤖 Generated with Claude Code