Skip to main content

Threading and failure handling

Many Reverse operations use CompletionStage or CompletableFuture. Let’s separate two questions: where do I call the operation? and where does its completion callback run?

Start on the right thread

Call recording start/stop and live playback/UI controls on the Bukkit main thread. Let the service move storage work off that thread.
This stops capture now and returns a stage for persistence. It does not require moving stopSession itself into an async task.

Handle both completion and failure

Inside a JavaPlugin callback, we can handle a stop result without touching player/world state:
An exceptional completion is not the only non-success outcome. Conversely, a missing/forbidden catalog entry is a normal result, not necessarily an exception. Notification coverage differs by operation: a metadata backend failure completes the stage exceptionally without ReplayMetadataUpdatedEvent; a deletion stage failure also emits ReplayDeletedEvent with its cause. A dispatch exception emits TRANSFER_FAILED and may leave the stage exceptional, while some failures are converted to a normal result. Synchronous validation/setup failures can occur before any notification hook is attached. See the event contracts, and retain stage handling wherever your integration must observe every outcome.

Schedule Bukkit work back to the main thread

Suppose viewerId identifies the player to notify after saving. Capture the ID before asynchronous work, then look up the current player on the main thread:
This snippet belongs inside your JavaPlugin class. It avoids Bukkit player lookup in the completion thread. A real request flow should also track whether this is still the viewer’s latest request and access policy. If disabling can race task submission in your integration, handle scheduler rejection during teardown too. There is no guarantee that a future finishes before your plugin begins disabling.

Do not wait for I/O in a normal server tick

Avoid this pattern in a command, match callback, or inventory action:
Attach a completion handler instead. Moving the backend wait to the main thread defeats the asynchronous API and can stall the server.

Catch synchronous validation failures where they happen

Not every error arrives through a future. For example, user-supplied metadata is validated synchronously:
Do that in your main-thread command/menu layer before calling start/update. Keep backend exceptions in the async failure path. Do not report every exception as “not found.”

Respect runtime ownership

Services belong to the Reverse runtime. Your plugin owns the match-to-session IDs, listeners, request guards, and custom registrations it creates.
  • Stop the recordings your matches own on end/cancellation/disable paths.
  • Do not call stopAll just to clean up your own match when other integrations may be recording.
  • Remove your menu actions/items and retire hotbar callbacks as described in UI extensions.
  • Do not close borrowed storage/availability or reinitialize its packet adapter.
For optional Reverse support, isolate role-specific classes and listeners until that role is available. Packaging another API copy does not make an absent runtime integration work.

Complete examples

The first recording tutorial handles all save outcomes. The playback example demonstrates main-thread completion handling, and the event example shows save-notification scheduling.