Skip to content

Distinguish fetch timeouts from database query timeouts - #2268

Merged
kmcginnes merged 2 commits into
mainfrom
distinguish-timeout-errors
Sep 25, 2026
Merged

kmcginnes merged 2 commits into
mainfrom
distinguish-timeout-errors

Conversation

@kmcginnes

@kmcginnes kmcginnes commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Description

A timeout reached the user as one of two things, and neither said who gave up. The connection's own fetch timeout surfaced as a bare DOMException named TimeoutError. A database-side query timeout surfaced as a NetworkError whose body happened to carry TimeLimitExceededException. They have opposite fixes: one is a setting on the connection, the other is the database's configuration. Anything that wanted to give specific advice had to sniff error names and body codes on its own.

This adds two errors, thrown from fetchDatabaseRequest, which every connector (Gremlin, openCypher, SPARQL) already goes through:

  • FetchTimeoutError carries timeoutMs. It's thrown when the fetch timeout signal fired and the caller's signal didn't, so a user cancel stays an AbortError even if both have fired by the time it's caught. It now also covers a timeout that fires while the body is being read, which used to escape as a raw DOMException.
  • DatabaseTimeoutError extends NetworkError and carries the database's databaseCode. Staying a NetworkError keeps the retry policy, the error details dialog, and the requestId in the details unchanged. It matches on the body, never on status alone, since Neptune also returns 500 for memory limits, throttling, and cancellation:
    • Neptune: code: "TimeLimitExceededException"
    • Gremlin Server: "Exception-Class": "java.util.concurrent.TimeoutException"

createDisplayError renders each with its own advice and drops the old name and code sniffing. The cancellation message no longer claims it might have been a timeout, since a timeout can't reach that branch any more. The troubleshooting guide explains the two messages.

Cancelling a Query tab query no longer restores the previous query's error. cancelQueries reverted to the last cached state by default, so a cancel right after a failure showed that failure again, which with this change could read "Database query timed out". Cancel now passes revert: false, so the cancel lands on the cancellation branch.

CONTEXT.md names the two timeouts, and docs/agents/connectors.md records that fetchDatabaseRequest is the only place they are classified.

MemoryLimitExceededException and the ETIMEDOUT errno that #2249 now carries are not query timeouts, and stay plain NetworkErrors.

Not changed: timeouts are still retried 3 times by the query client.

Validation

  • The Gremlin Server body is a real capture from tinkerpop/gremlin-server:3.8. A request-level evaluationTimeout field is ignored over HTTP, but g.with("evaluationTimeout", N) in the script works, and both the script limit and the server's 30s default produce the same body. Tests use that exact body.
  • The Neptune shape matches the existing TimeLimitExceededException handling.
  • fetchDatabaseRequest tests cover:
    • the timer firing
    • the caller aborting first, then the timeout, and the reverse, with both signals aborted before the catch runs
    • the timeout firing while an error body is read, which keeps the database's error
    • a timeout during response.json()
    • Neptune bodies with and without the proxy's error wrapper
    • MemoryLimitExceededException and ETIMEDOUT staying NetworkError
  • Each explorer has a test for both errors through rawQuery.
  • Live captures matched the classification on Neptune 1.2.1.0, 1.3.5.0, 1.4.5.1 and 1.4.7.0 (Gremlin), 1.4.5.1 and 1.4.7.0 (SPARQL), and 1.4.7.0 (openCypher), each returning HTTP 500 with TimeLimitExceededException, directly and through the proxy. Earlier versions reject a per-query openCypher timeout.
  • In the browser, both messages showed through the proxy on Gremlin Server and Neptune, and a cancel with a fetch timeout set stayed a cancel.
  • pnpm checks and pnpm test are clean: 227 files, 2779 tests.

Related Issues

None. #2244 will adopt these so edge connection discovery can tell the user which timeout to raise.

Check List

  • I confirm that my contribution is made under the terms of the Apache 2.0 license.
  • I have verified pnpm checks passes with no errors.
  • I have verified pnpm test passes with no failures.
  • I have covered new added functionality with unit tests if necessary.
  • I have updated documentation if necessary.

Adds FetchTimeoutError (client-side fetch timeout) and DatabaseTimeoutError
(database-side query timeout) so all three connectors can tell users which
side stopped the request instead of a generic "deadline exceeded" or
"TimeoutError" message.

fetchDatabaseRequest now keeps the fetch-timeout signal separate from the
caller's abort signal and classifies a caught error by which signal actually
fired, so a user cancellation is never reported as a timeout. Response body
reads are covered by the same classification, since a timeout can also fire
while streaming the body. databaseTimeoutCode() recognizes Neptune's
TimeLimitExceededException and a Gremlin Server evaluation timeout, captured
from a local tinkerpop/gremlin-server container.
Reclassify a caught error as a fetch timeout by comparing the combined
signal's reason to the fetch-timeout signal's reason, rather than checking
both signals' aborted flags after the fact, which cannot tell who fired
first once both end up aborted. Skip a NetworkError/DatabaseTimeoutError
already built from a received response body, even if the timeout also
fired while that body was being read.

Cancel a running query with revert: false so a cancelled query lands in
the cancellation branch instead of reverting to an earlier failed query's
error and re-showing it as current.

Also: pin the FetchTimeoutError message format (comma-grouped ms, trailing
period), extract the duplicated abort-aware fetch mock into
utils/testing/abortableFetch, and update the CONTEXT.md glossary and
connector/troubleshooting docs for Fetch Timeout vs Database Query Timeout.
@kmcginnes
kmcginnes merged commit 119e51d into main Sep 25, 2026
10 checks passed
@kmcginnes
kmcginnes deleted the distinguish-timeout-errors branch September 25, 2026 19:58
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.

1 participant