Make a COTTAS query killable, and make a timeout report as one - #16
Merged
Merged
Conversation
06_equivalence hung twice on the same query: 41 hours, then 10 hours WITH --validation-query-timeout 1800 and --validation-time-budget 14400 on the command line. Neither bound could fire, for two independent reasons. CottasEngine queried in-process through rdflib, so nothing could interrupt it. The call sits inside pycottas and no timeout reaches it; the time budget is checked between queries, so it never gets a turn either. And no engine ever produced exit 124, which is what the query loop keys on. The HTTP engines caught every exception and returned 1, so a timeout was indistinguishable from a crash, and --stop-after-query-timeout was unreachable in any configuration. run_query_process -- which kills the whole process group and returns 124 -- existed in the module with no callers at all. So: each COTTAS query now runs in its own process through run_query_process, and both HTTP engines classify a timeout as 124 via a new is_timeout_error rather than lumping it in with genuine faults. Measured before choosing the design, on a 39 MB artifact: opening the store takes 3-15 ms, `LIMIT 1` takes 12.7 s, and COUNT(*) does not finish in two minutes. Opening is free, so a process per query costs nothing and the load-once property the other engines need does not apply here -- for COTTAS the artifact is the index. A long-lived worker with a query protocol would have been complexity bought with an assumption the measurement contradicts. The numbers also say the hang is not a q05 bug: rdflib evaluates SPARQL over a custom Store by pulling triples through the Store API in Python, so this is what COTTAS costs at this scale. The fix bounds it; it does not make it fast. Tests cover the mechanism rather than the report: that run_query_process returns 124 and that a forked grandchild does not outlive the group kill, that CottasEngine no longer queries in-process, that timeouts classify correctly, and that something in the module still produces the 124 the loop depends on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
The pycottas path could not answer a genotype query at benchmark scale. rdflib evaluates SPARQL over a custom Store by pulling triples through the Store API in Python: on a 39 MB artifact, opening the store took 3-15 ms while `SELECT ?s ?p ?o LIMIT 1` took 12.7 s and COUNT(*) did not finish in two minutes. 06_equivalence hung for 41 hours, then for 10 more once bounded -- a timeout stops a query without making the engine able to answer it. @elias.crum/query-sparql-cottas answers the same artifact through DuckDB over the Parquet, and ships comunica-sparql-cottas-http. That means CottasEngine becomes a sibling of HdtEngine rather than a special case: the same ComunicaHttpEndpointMixin, one long-lived endpoint, the graph opened once, each query charged only for itself, and the mixin's existing timeout handling inherited rather than reimplemented. Building the artifact still goes through pycottas' rdf2cottas; only querying moved. The binary check runs before NativeArtifactEngine.start so a dead engine does not first pay for rdf2cottas, which is the expensive step. Indexing is SPO only for now. The engine finds sibling .posg/.ospg files by naming convention if they exist and prunes patterns accordingly; none are built here, so every pattern is answered from the one order. Discovery is silent, so this is recorded in describe() rather than left to be inferred. The image installs the package globally beside the other comunica engines, and the build now fails if the binary will not run -- the DuckDB addon is a native prebuild with no musl build and per-architecture artifacts, and that failure would otherwise surface inside a benchmark cell hours later. Verified against the published 0.1.0 inside the current image on the benchmark host: it installs and answers a query on a real test-1k.cottas under Node 22.22.1, one patch below the version the integration guide states. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first guard ran `comunica-sparql-cottas --version`, which prints its version table and then exits 1 -- so a working engine failed the build. The addon cannot be probed with require() either, because @duckdb/node-api is ESM. Both checks report a healthy engine as broken. Build a one-triple .cottas with pycottas and query it instead. That settles the question the guard is actually asking, and covers rdf2cottas as well, which is the half of this engine that did not move to comunica. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
What was wrong
06_equivalencehung twice on the same query — 41 hours, then 10 hours with--validation-query-timeout 1800and--validation-time-budget 14400on the command line. Two independent causes, either of which alone would have been enough:1. The query was not interruptible.
CottasEnginequeried in-process throughrdflib.Graph(store=pycottas.COTTASStore(...)). The call sits inside pycottas, so no timeout can reach it, and the time budget is checked between queries so it never gets a turn either.2. Nothing ever produced exit 124. That is the code the query loop keys on (
executions[query_id].get("exitCode") == 124). Both HTTP engines caught every exception and returned1, making a timeout indistinguishable from a crash — so--stop-after-query-timeoutwas unreachable in any configuration, and timeouts were reported asEXECUTION_FAILED. Meanwhilerun_query_process, which kills the whole process group and returns 124, was defined in the module with no callers at all.The measurement that chose the design
I was about to build a long-lived worker subprocess with a query protocol, to preserve the load-once property the suite deliberately has. Measuring first, on a 39 MB artifact:
Opening is free. For COTTAS the artifact is the index — there is no load to amortise, so load-once does not apply to this engine and a process per query costs ~3 ms. The worker design would have been complexity bought with an assumption the measurement contradicts.
The same numbers say the hang is not a
q05bug: rdflib evaluates SPARQL over a custom Store by pulling triples through the Store API in Python. This fix bounds COTTAS; it does not make it fast.Changes
CottasEngine.executeruns each query viarun_query_process— its own process group, killed on timeout, exit 124.start()keeps the dependency check;stop()has nothing to close.is_timeout_errordistinguishes a genuine timeout (direct or wrapped inURLError) from a fault, and both HTTP engines now return 124 for one.Verification
382 unit tests pass. The new ones cover the mechanism, not the report: that
run_query_processreturns 124 and that a forked grandchild does not outlive the group kill (the orphan case its docstring exists for), thatCottasEngineno longer queries in-process, that timeouts classify correctly, and that something still produces the 124 the loop depends on.End to end on the benchmark VM, against the exact artifact and query shape that hung:
Note for the benchmark
With this,
06_equivalencecompletes and recordsq05as a timeout for cottas while the other engines answer it. That is the honest cross-engine result rather than a hung run — but it means the COTTAS column will contain timeouts at this scale, which belongs in the limitations rather than being read as disagreement.🤖 Generated with Claude Code