-
Notifications
You must be signed in to change notification settings - Fork 0
Bundle Client
Package com.evento.application.client.* in evento-bundle. This is the bundle side of the
connection: it dials the server, performs the handshake, registers handlers, tracks in-flight
requests, and survives reconnects without losing work.
| Class | Role |
|---|---|
BundleClient |
Public façade: Builder, start / stop, request(payloadType, byte[], Duration), notify(...), enable / disable, registerRequestHandler / registerNotificationHandler
|
BundleClientConfig |
Record + Builder: host/port list, identity, auth token, capabilities, timeouts, transport config, autoEnable, maxInFlightRequests
|
BundleClientState |
INITIAL → CONNECTING → HANDSHAKING → REGISTERING → READY → RECONNECTING → CLOSING → CLOSED |
ConnectionSupervisor |
Owns the Netty transport and the reconnect loop. The start() future resolves at the first READY
|
BundleCorrelationTracker |
UUID → CompletableFuture<Response>, scheduler-driven expiry |
ProcessedRequestCache |
Inbound dedup: resolveOrClaim → Claimed / InFlight / Replay. LRU + TTL |
HandlerRegistry |
payloadType → RequestHandler / NotificationHandler (pure byte arrays) |
HelloFactory |
Builds Hello from BundleClientConfig
|
InboundDispatcher |
Pattern-matches: Response → tracker; Request → dedup, handler, reply; Notification → handler. All on a virtual-thread executor |
EventoServerAdapter |
Implements the EventoServer gateway SPI over BundleClient
|
BundleInboundDispatcher |
Bridges HandlerRegistry.RequestHandler to the v2 CBOR byte-array contract for @CommandHandler / @QueryHandler classes |
BundleAdminRequestHandler |
Handles evento:server-admin-request — decodes EventoRequest, dispatches consumer operations, encodes EventoResponse
|
BundleCorrelationTracker.failAll() runs on shutdown only, never on disconnect. A request that
was in flight when the socket dropped keeps its future pending; the server's reconnect buffer
(see Server Bus § 4) replays the response after re-handshake, and the future completes
normally. Failing futures on every disconnect would turn a transient network blip into an application
error for work that in fact completed.
A failed request no longer collapses into a generic IllegalStateException.
EventoServerAdapter.request reconstructs the specific exception from
ResponseError.exceptionClassName(). Two types in com.evento.transport matter operationally:
| Exception | Meaning | Safe to retry? |
|---|---|---|
RequestTimeoutException |
The caller stopped waiting. Not a rejection — the handler may have applied the work in full | Only if the operation is idempotent. Report it as indeterminate (HTTP 504), not failed (500) |
TooManyPendingRequestsException |
maxInFlightRequests was already outstanding, so nothing was transmitted |
Always — the refusal is definite |
This distinction came out of a real incident: roughly 20 commands reported as failures had in fact been fully applied, and retrying them would have double-applied. See Throughput and Capacity.
BundleClientConfig.Builder.maxInFlightRequests(n) (default 2048, 0 disables) makes a bundle refuse
a request outright once n are outstanding, rather than queueing work that will expire unsent.
Frame → InboundDispatcher
├── Response → BundleCorrelationTracker.complete(correlationId)
├── Request → ProcessedRequestCache.resolveOrClaim
│ ├── Claimed → run handler, reply, cache response
│ ├── InFlight → drop (a reply is already coming)
│ └── Replay → re-send the cached response, do NOT re-run the handler
└── Notification → HandlerRegistry notification handler
Everything dispatches on a virtual-thread executor — the Netty EventLoop is never blocked by handler code.
Since 2.1.0, ConfinementScanner performs an ASM sweep at registration time over every class in the
scanned packages that is not a registered component, and reports each CommandGateway.send /
sendAndWait / QueryGateway.query call site it finds there — with class, method, line and kind.
The problem it solves: such "gateway leaks" (say, an injected helper class issuing commands on a
handler's behalf) are invisible to static interaction-graph extraction, so the extracted emit set
was silently under-approximated and the GUI's interaction graph quietly wrong.
AsmInvocationScanner additionally reports gateway calls whose payload is typed as the abstract
Command / Query base, since the concrete payload type is then statically unresolvable.
Findings are logged as warnings by default. EventoBundle.Builder.strictConfinement (default
false) turns them into an IllegalStateException at registration.
- Wire Protocol § 6 — the handshake this client performs
- Consumer Engines — what runs alongside the client inside a bundle
- Throughput and Capacity — bounding a bundle's own concurrency
Evento Framework — Copyright 2020–2026 © Gabor Galazzo. Dual-licensed under AGPL-3.0 and a commercial licence.
This wiki documents the implementation; the repository is authoritative where the two disagree. Found something out of date? Open an issue.
Getting oriented
Internals
Operations
- Server Configuration
- Throughput and Capacity
- Observability
- Security Model
- Server REST API
- Troubleshooting
Project