Skip to content

in the sync api, queries are not cancelled when the calling thread is interrupted #1195

@lovasoa

Description

@lovasoa

Java API client version

main at commit 056ed9c30

Java version

OpenJDK 25.0.2

Elasticsearch Version

N/A. This is a client-side transport issue and reproduces against all elasticsearch versions.

Problem description

Hi!

Synchronous Java API client calls keep the underlying HTTP request running after the calling thread is interrupted. The result is discarded on the client side, but this prevents ElasticSearch from cancelling expensive searches that have been interrupted by the caller.

I initially reported this issue to Trino here: trinodb/trino#28927. But Trino is probably not the only one affected by this, and I think a fix in the ElasticSearch client would be nice.

Expected behavior: Interrupting a thread blocked in a synchronous client call should abort the in-flight HTTP request.

Actual behavior: The calling thread returns after interruption, but the underlying request continues running in the background.

Relevant code:

Steps to reproduce

  1. Create a synchronous ElasticsearchClient
  2. Run an expensive elasticsearch operation in one thread
  3. Wait until the request reaches the server, then interrupt the client thread.
  4. Observe whether the server cancels the task associated to the request

Here is a minimal repro: https://github.com/lovasoa/elasticsearch-java/blob/a5598215482233ceee4c44d6dc6642c6b83e0f23/java-client/src/test/java/co/elastic/clients/transport/InterruptSyncRequestTest.java

Reproduction.java
CountDownLatch requestArrived = new CountDownLatch(1);
CompletableFuture<IOException> serverException = new CompletableFuture<>();

HttpServer httpServer = createSlowServer(requestArrived, serverException);
httpServer.start();
try {
    ElasticsearchClient client = ElasticsearchTestClient.createClient(httpServer, null);

    Thread clientThread = new Thread(() -> {
        try {
            client.count();
        } catch (IOException | ElasticsearchException e) {
            // ignored for repro
        }
    });
    clientThread.start();

    assertTrue(requestArrived.await(5, TimeUnit.SECONDS));
    clientThread.interrupt();

    // Expected if the sync request is really cancelled:
    assertInstanceOf(IOException.class, serverException.get(5, TimeUnit.SECONDS));
} finally {
    httpServer.stop(0);
}

For comparison, cancelling the async future does close the connection in the companion test:

CompletableFuture<?> future = asyncClient.count();
assertTrue(requestArrived.await(5, TimeUnit.SECONDS));
future.cancel(true);
assertInstanceOf(IOException.class, serverException.get(5, TimeUnit.SECONDS));

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions