0.2.0: fix lost responses and close handling, publish to Gitea #2

Merged
selimaj-dev merged 1 commits from fix-pending-requests into master 2026-09-25 12:31:51 +00:00
Owner

Fixes #1.

Summary

Fixes

  • Lost responses (#1): request() registered its pending future only after sending, so a response that arrived first was dropped and the future never completed. It now registers before sending.
  • Close handling: pending requests now fail with SessionClosedException when the peer closes or the connection errors. Before, they hung forever.
  • Concurrent sends: java.net.http.WebSocket allows only one outstanding text send. request, respond and notify called sendText without waiting, so concurrent use could throw IllegalStateException. Sends are now chained one after another.
  • Split messages: onText ignored the last flag, so a message delivered in several parts was parsed piece by piece and failed. Parts are now buffered until last.
  • SessionResult.error() built results with isError = false, so errors from request handlers were sent as response instead of errorresponse.
  • close() called sendClose(0, ...). 0 isn't a valid close code, so it threw IllegalArgumentException and never closed. It now sends NORMAL_CLOSURE (1000).
  • Unknown methods: requests for an unknown method now get an errorresponse, matching session-rs 0.2, instead of no reply. A handler that throws also produces an error response.

API additions

These are source-compatible, except that the send methods now return futures.

  • Session.onClose(Runnable): runs exactly once, whether the session closed locally, by the peer, or on a network error. It runs immediately if the session is already closed.
  • Session.isOpen().
  • Session.request(method, req, Duration timeout): completes with a TimeoutException when the timeout passes.
  • SessionErrorException (with getError() returning the error payload) and SessionClosedException, both RuntimeExceptions. Before, errors completed with a plain RuntimeException(error.toString()).
  • send, respond, respondError and notify return CompletableFuture<Void> instead of void throws Exception.

Build and distribution

  • JitPack can't build this repo any more, because the GitHub repo is gone. Publishing moves to the Gitea package registry:
    • coordinates: dev.selimaj:session-java:0.2.0
    • publish with GITEA_TOKEN=<package:write token> ./gradlew publish
  • Jackson becomes an api dependency, since JsonNode is part of the public API.
  • The unused jakarta.websocket-api and tyrus-client dependencies are removed.
  • Sources jar added. The version was stuck at 0.1.3, and is now 0.2.0.
  • README: installation, usage, semantics and publishing; the protocol section now documents errorresponse and the real notification shape.

Testing

The empty SessionTest is replaced by 9 tests against an in-process Java-WebSocket server:

  • request/response
  • error responses
  • 800 concurrent requests from 8 threads against a server that answers immediately, covering both the race and the concurrent sends
  • pending requests failing and onClose running once when the server closes
  • request timeout
  • a response split into 3 frames
  • handler results, error results and unknown methods being answered
  • notifications
  • local close() sending 1000

All pass, 6 of 6 runs.

The Saturn client (1.21.11) was also run against this build, installed with publishToMavenLocal. It authenticated, noticed when the server was stopped, retried with backoff, and reconnected when it came back.

After merging

  1. Publish: GITEA_TOKEN=... ./gradlew publish.
  2. Check https://git.selimaj.dev/api/packages/selimaj-dev/maven/dev/selimaj/session-java/0.2.0/session-java-0.2.0.pom downloads without auth.
  3. The Saturn client PRs (reconnect and timeouts) depend on it.

🤖 Generated with Claude Code

Fixes #1. ## Summary ### Fixes - **Lost responses (#1):** `request()` registered its pending future only *after* sending, so a response that arrived first was dropped and the future never completed. It now registers before sending. - **Close handling:** pending requests now fail with `SessionClosedException` when the peer closes or the connection errors. Before, they hung forever. - **Concurrent sends:** `java.net.http.WebSocket` allows only one outstanding text send. `request`, `respond` and `notify` called `sendText` without waiting, so concurrent use could throw `IllegalStateException`. Sends are now chained one after another. - **Split messages:** `onText` ignored the `last` flag, so a message delivered in several parts was parsed piece by piece and failed. Parts are now buffered until `last`. - **`SessionResult.error()`** built results with `isError = false`, so errors from request handlers were sent as `response` instead of `errorresponse`. - **`close()`** called `sendClose(0, ...)`. 0 isn't a valid close code, so it threw `IllegalArgumentException` and never closed. It now sends `NORMAL_CLOSURE` (1000). - **Unknown methods:** requests for an unknown method now get an `errorresponse`, matching session-rs 0.2, instead of no reply. A handler that throws also produces an error response. ### API additions These are source-compatible, except that the send methods now return futures. - `Session.onClose(Runnable)`: runs exactly once, whether the session closed locally, by the peer, or on a network error. It runs immediately if the session is already closed. - `Session.isOpen()`. - `Session.request(method, req, Duration timeout)`: completes with a `TimeoutException` when the timeout passes. - `SessionErrorException` (with `getError()` returning the error payload) and `SessionClosedException`, both `RuntimeException`s. Before, errors completed with a plain `RuntimeException(error.toString())`. - `send`, `respond`, `respondError` and `notify` return `CompletableFuture<Void>` instead of `void throws Exception`. ### Build and distribution - JitPack can't build this repo any more, because the GitHub repo is gone. **Publishing moves to the Gitea package registry**: - coordinates: `dev.selimaj:session-java:0.2.0` - publish with `GITEA_TOKEN=<package:write token> ./gradlew publish` - Jackson becomes an `api` dependency, since `JsonNode` is part of the public API. - The unused `jakarta.websocket-api` and `tyrus-client` dependencies are removed. - Sources jar added. The version was stuck at `0.1.3`, and is now `0.2.0`. - README: installation, usage, semantics and publishing; the protocol section now documents `errorresponse` and the real notification shape. ## Testing The empty `SessionTest` is replaced by **9 tests** against an in-process Java-WebSocket server: - request/response - error responses - 800 concurrent requests from 8 threads against a server that answers immediately, covering both the race and the concurrent sends - pending requests failing and `onClose` running once when the server closes - request timeout - a response split into 3 frames - handler results, error results and unknown methods being answered - notifications - local `close()` sending 1000 All pass, 6 of 6 runs. The Saturn client (1.21.11) was also run against this build, installed with `publishToMavenLocal`. It authenticated, noticed when the server was stopped, retried with backoff, and reconnected when it came back. ## After merging 1. Publish: `GITEA_TOKEN=... ./gradlew publish`. 2. Check `https://git.selimaj.dev/api/packages/selimaj-dev/maven/dev/selimaj/session-java/0.2.0/session-java-0.2.0.pom` downloads without auth. 3. The Saturn client PRs (reconnect and timeouts) depend on it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
selimaj-dev added 1 commit 2026-09-25 12:28:05 +00:00
Fix lost responses and close handling; publish to Gitea as 0.2.0
Gradle Build / build (pull_request) Failing after 6s
97eab5f814
- Register pending requests before sending so fast responses aren't lost
- Fail pending requests with SessionClosedException when the connection
  closes or errors, and add onClose(), isOpen() and a request overload
  with a timeout
- Serialize text sends: java.net.http.WebSocket allows only one
  outstanding send, so concurrent requests could throw
- Reassemble text messages delivered in parts before parsing
- Complete error responses with SessionErrorException carrying the payload
- Answer requests for unknown methods with an error response
- Fix SessionResult.error() producing success responses
- Fix close() using invalid close code 0; send 1000
- Publish as dev.selimaj:session-java to the Gitea package registry;
  make Jackson an api dependency and drop unused Tyrus/Jakarta deps
- Replace the empty test with 9 tests against an in-process server

Fixes #1

Co-Authored-By: Claude Opus 5.5 <[email protected]>
selimaj-dev merged commit 03307f4e21 into master 2026-09-25 12:31:51 +00:00
selimaj-dev deleted branch fix-pending-requests 2026-09-25 12:31:56 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: selimaj-dev/session-java#2