lsp-max 26.7.3

Law-state LSP runtime: max LSP 3.18 coverage, process-mining conformance, receipt-chain admission
Documentation
# Cancellation Safety

In Rust, futures are cancelled by being dropped. This is a fundamental aspect of the `Future` trait and the async ecosystem. When a future is dropped, it is no longer polled, and its state is cleaned up.

## Cancellation in `lsp-max`

In `lsp-max`, server-side handling of client requests is tracked in the `Pending` map. When a request is started, it is wrapped in a `future::abortable` wrapper, which provides an `AbortHandle`.

1.  A client sends a request.
2.  `lsp-max` starts an asynchronous task to handle the request.
3.  The request ID and its `AbortHandle` are stored in the `Pending` map.
4.  If the client sends a `$/cancelRequest` notification with that ID:
    - The `AbortHandle` is used to abort the future.
    - The future is immediately dropped.
    - The task resolves to a "request cancelled" error response.

## Why Cancellation Safety Matters

Because cancellation is just "dropping the future," any logic that follows an `.await` point might never execute if the future is cancelled at that point.

Consider this example:

```rust
async fn handle_request() -> Result<(), Error> {
    let temp_file = create_temp_file().await?;
    
    // If the future is cancelled during this .await, 
    // the code below will NEVER run.
    do_some_work().await?;
    
    // This cleanup might be skipped!
    remove_temp_file(temp_file).await?;
    Ok(())
}
```

If `do_some_work().await` is cancelled, `remove_temp_file` is never called.

## Ensuring Cleanup with `CancellationGuard`

To ensure resources are released even when a future is cancelled (dropped), use the RAII (Resource Acquisition Is Initialization) pattern. In Rust, this means implementing the `Drop` trait.

`lsp-max` provides a `CancellationGuard` helper to make this easier.

### Example: Basic Cleanup

```rust
use lsp_max::service::CancellationGuard;

async fn handle_request() -> Result<(), Error> {
    let temp_file = create_temp_file().await?;
    
    // This guard will run its closure when dropped,
    // unless it is explicitly disarmed.
    let guard = CancellationGuard::new(|| {
        println!("Cleaning up temp file...");
        let _ = std::fs::remove_file("temp.txt");
    });

    do_some_work().await?;

    // If we reached here, we might want to "disarm" the guard
    // if the cleanup should only happen on failure/cancellation.
    // Or just let it drop naturally if it should always run.
    guard.disarm(); 
    
    Ok(())
}
```

### Example: Using the `OnDrop` helper

The `OnDrop` trait provides a convenient `.on_drop()` method for any type.

```rust
use lsp_max::service::OnDrop;

async fn handle_request() -> Result<(), Error> {
    let (temp_file, _guard) = create_temp_file().await?.on_drop(|| {
        let _ = std::fs::remove_file("temp.txt");
    });

    do_some_work().await?;

    // The temp_file and its guard will be dropped at the end of the scope.
    Ok(())
}
```

## Best Practices

1.  **Prefer RAII:** Wrap resources in types that implement `Drop`.
2.  **Keep Drop Simple:** `Drop::drop` is synchronous and should not block for long periods.
3.  **Async Cleanup:** If you need to perform asynchronous cleanup on drop, consider spawning a new background task:
    ```rust
    let guard = CancellationGuard::new(|| {
        tokio::spawn(async move {
            cleanup_async().await;
        });
    });
    ```
4.  **Idempotence:** Ensure your cleanup logic is idempotent, as it might be called in various states of completion.