> For the complete documentation index, see [llms.txt](https://docs.marioded.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.marioded.com/api-how-to-guides/threading-and-failures.md).

# Threading and failure handling

Handle asynchronous completion, missing data, and runtime lifecycle without blocking Bukkit.

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.

```java
CompletionStage<StopResult> saving = recorder.getRecorderService().stopSession(sessionId);
```

This stops capture now and returns a stage for persistence. It does not require moving `stopSession` itself into an async task.

| Operation                                             | Rule                                                                                                    |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Recorder start/stop                                   | Invoke on Bukkit's main thread.                                                                         |
| Loaded playback start, seek, pause, speed, leave/stop | Main thread.                                                                                            |
| `play(UUID, ...)`                                     | Loads asynchronously and starts on the main thread; completion callbacks have no thread guarantee.      |
| Catalog/storage stages                                | No main-thread completion guarantee.                                                                    |
| Layout service and inventory action callbacks         | Main thread; do not block.                                                                              |
| Save-completion events                                | Can run on the save-completion thread.                                                                  |
| Explicit recorder observation APIs                    | Designed for capture/network threads; obey their contract and do not query Bukkit positions there.      |
| Chunk payload transfer                                | Provider is thread-safe and asynchronous; world-source snapshot setup has its own main-thread contract. |

## Handle both completion and failure

Inside a JavaPlugin callback, we can handle a stop result without touching player/world state:

```java
saving.whenComplete((result, failure) -> {
    if (failure != null) {
        getLogger().log(Level.SEVERE, "Save failed for " + sessionId, failure);
        return;
    }
    if (result.replayId() == null) {
        getLogger().warning("The session was no longer recording");
        return;
    }
    if (result.degraded()) {
        getLogger().log(Level.WARNING, "Replay saved with terrain failure", result.terrainFailure());
        return;
    }
    getLogger().info("Replay ready: " + result.replayId());
});
```

An exceptional completion is not the only non-success outcome. Conversely, a missing/forbidden catalog entry is a normal result, not necessarily an exception.

## 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:

```java
saving.whenComplete((result, failure) -> {
    if (!isEnabled()) return;
    getServer().getScheduler().runTask(this, () -> {
        Player current = getServer().getPlayer(viewerId);
        if (current == null || !current.isOnline()) return;
        if (failure != null || result.replayId() == null || result.degraded()) {
            current.sendMessage("This recording is not ready for normal playback.");
            return;
        }
        current.sendMessage("Replay ready: " + result.replayId());
    });
});
```

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:

```java
// Do not use in a normal main-thread callback: it waits for backend I/O.
saving.toCompletableFuture().join();
```

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:

```java
try {
    ReplayCatalog.validateMetadata(name, description);
} catch (IllegalArgumentException invalid) {
    viewer.sendMessage("Choose a shorter plain-text name and description.");
    return;
}
```

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](/api-how-to-guides/ui-extensions.md).
* 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](/api-introduction/first-recording.md) handles all save outcomes. The [playback example](/api-how-to-guides/examples/playback-integration.md) demonstrates main-thread completion handling, and the [event example](/api-how-to-guides/examples/replay-notifications.md) shows save-notification scheduling.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.marioded.com/api-how-to-guides/threading-and-failures.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
