request() registers its pending future after sending, so fast responses are lost #1

Closed
opened 2026-09-25 11:43:29 +00:00 by selimaj-dev · 0 comments
Owner

Problem

SessionListener.request sends the request first and only registers its pending future afterwards:

return CompletableFuture
        .runAsync(() -> send(ws, new Message.Request(id, method, ...)))  // 1. send
        .thenCompose(v -> {
            CompletableFuture<JsonNode> fut = new CompletableFuture<>();
            pending.put(id, fut);                                        // 2. register
            return fut;
        })

If the response arrives between steps 1 and 2, onText finds no pending entry and drops it. The returned future then never completes. Callers that .join() or .get() without a timeout hang forever.

session-rs had the same race and fixed it in 0.2.0 (selimaj-dev/session-rs#1).

Fix

  • Create the future and pending.put(id, fut) before sending. If sending fails, remove the entry and complete the future exceptionally.
  • Complete every pending future exceptionally in onClose / onError, so callers fail fast instead of waiting forever after a disconnect.
  • Optional: a request(method, req, timeout) overload that removes the pending entry on timeout.

Related

  • onText ignores the last flag. java.net.http.WebSocket may deliver one text message in several parts, and each part is parsed as JSON on its own. Current messages are small, but large ones would fail to parse. Buffer the parts until last == true.
## Problem `SessionListener.request` sends the request first and only registers its pending future afterwards: ```java return CompletableFuture .runAsync(() -> send(ws, new Message.Request(id, method, ...))) // 1. send .thenCompose(v -> { CompletableFuture<JsonNode> fut = new CompletableFuture<>(); pending.put(id, fut); // 2. register return fut; }) ``` If the response arrives between steps 1 and 2, `onText` finds no pending entry and drops it. The returned future then never completes. Callers that `.join()` or `.get()` without a timeout hang forever. `session-rs` had the same race and fixed it in 0.2.0 (selimaj-dev/session-rs#1). ## Fix - Create the future and `pending.put(id, fut)` **before** sending. If sending fails, remove the entry and complete the future exceptionally. - Complete every pending future exceptionally in `onClose` / `onError`, so callers fail fast instead of waiting forever after a disconnect. - Optional: a `request(method, req, timeout)` overload that removes the pending entry on timeout. ## Related - `onText` ignores the `last` flag. `java.net.http.WebSocket` may deliver one text message in several parts, and each part is parsed as JSON on its own. Current messages are small, but large ones would fail to parse. Buffer the parts until `last == true`.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: selimaj-dev/session-java#1