Skip to content

Timeouts and Errors

Mario edited this page Sep 23, 2026 · 1 revision

Timeouts & error handling

Vyra follows a strict rule: no silent failures.

When a remote operation fails, times out or encounters a serialization issue, Vyra surfaces explicit, typed exceptions rather than returning null or hanging indefinitely.


The exception hierarchy

All framework exceptions inherit from the unchecked VyraException:

classDiagram
    direction TB
    class VyraException {
        base class for all Vyra errors
    }
    class VyraTimeoutException {
        request exceeded its timeout
    }
    class VyraTransportException {
        network failure / instance closed
    }
    class VyraSerializationException {
        payload conversion failed / type not registered
    }
    class VyraRemoteException {
        remote handler threw / server busy
    }
    VyraException <|-- VyraTimeoutException
    VyraException <|-- VyraTransportException
    VyraException <|-- VyraSerializationException
    VyraException <|-- VyraRemoteException
Loading

Every failure is a typed exception. Catch VyraException at your top-level boundary, or handle each subtype separately.

Exception When it occurs Recommended action
VyraTimeoutException The remote service did not return a response before the specified timeout expired. Retry if the operation is idempotent; otherwise alert or fail with a fallback.
VyraTransportException Network-level failure while sending bytes to the underlying transport (e.g. broker disconnected). Retry with exponential backoff; check transport connectivity.
VyraSerializationException A message type was not registered via register(...), or JSON/binary encoding/decoding failed. Fix missing type registrations or incompatible class schemas.
VyraRemoteException The remote service received the request, but the handler threw an exception or the server rejected the task. Inspect remote.getCode() to determine cause.

Timeout semantics

Every request(...) invocation requires a mandatory Duration timeout:

client.request("payment-service", req, PaymentResponse.class, Duration.ofSeconds(3));

What happens when a request times out

  1. Vyra’s timeout scheduler removes the pending request future from the correlation table.
  2. The caller’s CompletionStage completes exceptionally with VyraTimeoutException.
  3. Safe late discard: if the remote server eventually finishes and sends a response later, Vyra finds no matching correlation record and silently drops the late response. No memory is leaked.

Inspecting remote errors

When a remote service encounters an error, it returns a structured RemoteError envelope. Locally, this is unwrapped into a VyraRemoteException.

Instead of parsing exception text, check the machine-readable error code:

client.request("billing", req, InvoiceResponse.class, Duration.ofSeconds(3))
    .whenComplete((res, throwable) -> {
        if (throwable != null) {
            Throwable cause = throwable instanceof CompletionException ? throwable.getCause() : throwable;
            
            if (cause instanceof VyraRemoteException remote) {
                switch (remote.getCode()) {
                    case HANDLER_ERROR -> 
                        System.err.println("Handler logic threw an exception: " + remote.getMessage());
                    case SERVER_BUSY -> 
                        System.err.println("Remote worker thread pool was full. Safe to retry with backoff.");
                    case UNKNOWN_MESSAGE_TYPE -> 
                        System.err.println("Remote service does not know this message type!");
                    case SERIALIZATION_ERROR -> 
                        System.err.println("Remote service could not deserialize payload.");
                }
            } else if (cause instanceof VyraTimeoutException) {
                System.err.println("Request timed out after 3 seconds.");
            }
        } else {
            System.out.println("Success: " + res);
        }
    });

Smart retry guidelines

Because distributed systems can experience network partitions and worker restarts, design your retry strategies deliberately:

1. Safe-to-retry operations (idempotent reads)

Read queries (GetPlayerRequest, FetchAccountBalance) can be safely retried upon VyraTimeoutException or VyraTransportException.

2. State mutations (require idempotency keys)

State-modifying actions (ChargeCreditCardRequest, TransferFundsRequest) must not be blindly retried on timeout, because the remote handler may have processed the request just before the timeout fired.

  • Best practice: include a unique requestId or idempotencyKey inside your request record so the receiver can deduplicate operations.

3. Handling SERVER_BUSY

RemoteError.Code.SERVER_BUSY indicates the remote server is alive, but its handler executor queue was temporarily full. This is a prime candidate for retrying after a brief exponential backoff delay.

4. Events are never retried

Events sent via publish(...) are strictly at-most-once. If a subscriber fails while processing an event, the framework logs the failure and continues.


Next steps

Vyra documentation

Getting started and messaging patterns

Transports and formats

Inside the framework

Reference

Clone this wiki locally