Class ExternalFailures
java.lang.Object
org.elasticsearch.xpack.esql.datasources.ExternalFailures
Classifies a failure raised while reading an external data source into the exception the
AsyncExternalSourceOperator should surface, so that it maps to the right HTTP status. The
companion surface(java.lang.Throwable, java.lang.String) helper is used at the worker rethrow sites inside parallel coordinators and
page iterators to pre-type the failure: it wraps a raw IOException in an already-classified
ExternalClientException (400) so the read boundary's classify(java.lang.Throwable) sees a status-typed
exception. surface cannot rescue a status signal already buried under a status-neutral
RuntimeException wrapper; callers must therefore pass the raw stored throwable, not a
pre-wrapped one.
classify(java.lang.Throwable) is the single boundary where external-source reads turn into a user-visible error,
and it is reached only for external-source queries (the operator exists only for them), so index queries
are unaffected. It runs co-located with the throw, on the node that reads the external source, before
the failure is serialized back to the coordinator — so classification relies on the concrete
exception type while it is still available, and only the resulting status() needs to cross
the wire (see ExternalException). The policy:
Error(assertion failures, OOM, …) is rethrown — a JVM/programming fault must stay fatal, never be downgraded to a request error.- An
ElasticsearchExceptionalready carries its own status and is returned unchanged: this covers theExternalExceptionfamily (400/500/503) raised at the reader/storage boundary, as well asCircuitBreakingException(429) andTaskCancelledException(400). - An
EsRejectedExecutionException— a thread pool refusing the task (e.g. the node shutting down) — is client-actionable backpressure, not a server fault. It already maps to 429 (TOO_MANY_REQUESTS) viaExceptionsHelper.status, so it is returned unchanged rather than mistaken for a broken invariant and reported as 500. - An
IllegalArgumentExceptionalready maps to 400; it is returned as-is. - An
IOException/UncheckedIOException, or one of the specific third-party decoding exceptions inMALFORMED_DATA_EXCEPTIONS, means we could not read or interpret the resource — a client-classExternalClientException(400). Retryable transport failures never reach here as plain I/O errors: the storage layer raises them asExternalException(503) first. - Anything else (
IllegalStateException,NullPointerException, an unrecognizedRuntimeException) indicates a broken invariant in our own code rather than bad input, so it surfaces as anExternalServerException(500) to keep the bug visible.
TaskCancelledException (handled by the
ElasticsearchException branch, 400), and a read interrupted while blocking surfaces as an
IOException subclass (so, 400). A bare InterruptedException would fall through to 500;
the interrupt flag is intentionally left untouched, since this runs on the thread surfacing the stored
failure, not the worker thread that was interrupted.-
Method Summary
Modifier and TypeMethodDescriptionstatic RuntimeExceptionReturns theRuntimeExceptionto throw for the given read failure.static RuntimeExceptionSurfaces a worker-side stored failure as a typed ES|QL exception that already carries the right HTTP status, instead of a status-neutralRuntimeExceptionwrapper.
-
Method Details
-
classify
Returns theRuntimeExceptionto throw for the given read failure. May instead throw iftis anError, which must propagate unchanged. -
surface
Surfaces a worker-side stored failure as a typed ES|QL exception that already carries the right HTTP status, instead of a status-neutralRuntimeExceptionwrapper. Called at the throw site inside parallel parsing coordinators and page iterators (the boundary between the worker that stored the failure and the consumer pulling from the iterator). Companion toclassify(java.lang.Throwable): whereclassifyruns at the read boundary and maps a freshly raised failure to a status-typed exception,surfaceruns at the worker rethrow site and pins the status carried by the underlying type while preserving a context prefix (fallbackMessage, e.g."Streaming parallel parsing failed") so the coordinator/iterator origin stays visible in logs and the user-facing message:- An
Erroris rethrown unchanged — a JVM/programming fault must stay fatal. - A
RuntimeExceptionis returned as-is. This covers status carriers (ElasticsearchExceptionfamily,IllegalArgumentException) which already pin their own status, and any other unchecked cause raised by the worker. Note: a bareRuntimeExceptionthat buries anIOExceptioncause is not rescued here — the wrapper has already destroyed the type signal. Callers must therefore pass the raw stored throwable, not a pre-wrapped one. - An
IOExceptionorUncheckedIOExceptionbecomes anExternalClientException(400) — undecodable input is a client-class error, not a server fault. ThefallbackMessageprefix is kept either way, so the context survives whether the worker raised checked or unchecked I/O. - Anything else (a checked, non-IO exception — typically
InterruptedExceptionstored after a worker thread was interrupted) becomes anExternalServerException(500): we have no evidence it is the caller's fault, so we keep the bug visible.
surfaceat the worker rethrow site letsAsyncExternalSourceOperator'sclassify(java.lang.Throwable)compose cleanly on the result: anExternalExceptionreturned here passes straight throughclassifyunchanged; a non-classifiedRuntimeExceptionis the only shapeclassifystill actively re-wraps (intoExternalServerException, the same statussurfacewould have produced for an unrecognized worker fault).- Parameters:
failure- the raw stored worker-side throwable; not a status-neutral wrapper around itfallbackMessage- non-null context prefix (e.g. the coordinator/iterator name); included in every wrapped result so the throw-site origin survives classification
- An
-