# How to Implement Workspace Disposal in a Durable Object for Resource Cleanup

> Learn how to implement workspace disposal in a Durable Object for efficient resource cleanup. Call workspace.close() in your alarm handler or teardown method to release connections and clear caches.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: how-to-guide
- Published: 2026-08-14

---

**Call `workspace.close()` in your Durable Object's `alarm()` handler or explicit teardown method to release all backend connections, clear caches, and reset internal state.**

Durable Objects on Cloudflare Workers provide stateful compute with persistent storage, but they require explicit cleanup to prevent resource leaks. When building applications with the `cloudflare/computer` repository, the `Workspace` class manages local SQLite storage, execution backends, and RPC connections to remote processes. Proper disposal ensures graceful shutdowns and prevents dangling handles when a Durable Object terminates or encounters fatal errors.

## What Workspace.close() Does Under the Hood

The `close()` method in [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) performs a four-step teardown sequence designed to make the instance safe for garbage collection:

### 1. Increment Connection Generation

The method first bumps `this.#connectionGeneration` to prevent any in-flight RPC connection from being reused after shutdown:

```typescript
this.#connectionGeneration += 1;

```

Source: [`workspace.ts#L665`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts#L665)

This acts as a circuit breaker—any pending connection attempts will see the generation mismatch and abort rather than establishing stale links.

### 2. Clear All Caches

All handle maps are purged so subsequent calls trigger reconnection rather than returning dead references:

```typescript
this.#handles.clear();
this.#moduleHandles.clear();
this.#shells.clear();
this.#commandHandles.clear();
this.#connecting.clear();

```

Source: [`workspace.ts#L666-L680`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts#L666-L680)

### 3. Close Backend Handles

Each cached handle may own a network socket or remote process. `close()` awaits their individual `close()` methods in parallel:

```typescript
await Promise.all([...handles, ...moduleHandles].map(async (h) => {
  await h.close?.();
}));

```

Source: [`workspace.ts#L688-L690`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts#L688-L690)

Handles without a `close` method are skipped gracefully via optional chaining.

### 4. Reset Ready Promise

The ready promise is cleared so future `ready()` calls re-initialize mounts and backends:

```typescript
this.#readyPromise = undefined;

```

Source: [`workspace.ts#L681`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts#L681)

After these steps complete, the `Workspace` holds no cached RPC connections, no pending mutation queues, and all local resources are eligible for garbage collection.

## When to Dispose a Workspace in Your Durable Object

Three scenarios require explicit disposal:

- **Durable Object termination** – Cloudflare invokes `alarm()` during graceful shutdown; this is your only hook for cleanup.
- **Explicit session teardown** – User-initiated "stop" commands or admin operations that must free resources immediately.
- **Error-path cleanup** – Fatal errors that render the DO unusable; call `close()` before returning error responses to prevent leaks.

## Complete Implementation Example

Below is a production-ready Durable Object that properly initializes and disposes its `Workspace`:

```typescript
import { DurableObject } from "cloudflare:workers";
import { Workspace } from "@cloudflare/computer";

export interface Env {
  // Your environment bindings
}

export class ComputeDO extends DurableObject {
  #workspace: Workspace;

  constructor(state: DurableObjectState, env: Env) {
    super(state, env);
    
    // Initialize with DO storage as persistence layer
    this.#workspace = new Workspace({
      storage: state.storage,
      backends: [{ 
        id: "default", 
        type: "shell", 
        connect: () => this.#createBackendConnection(env) 
      }],
    });
  }

  async fetch(request: Request): Promise<Response> {
    // Ensure workspace is ready before use
    await this.#workspace.ready();
    
    const path = new URL(request.url).pathname;
    const content = await this.#workspace.fs.readFile(path);
    
    return new Response(content ?? "Not found", {
      status: content ? 200 : 404
    });
  }

  // Called by Cloudflare during DO shutdown or alarm trigger
  async alarm(): Promise<void> {
    await this.#workspace.close();
  }

  // Optional: expose explicit disposal for admin operations
  async dispose(): Promise<{ success: boolean }> {
    await this.#workspace.close();
    return { success: true };
  }

  #createBackendConnection(env: Env): any {
    // Implementation depends on your backend configuration
    return env.BACKEND_SERVICE.connect();
  }
}

```

## Handling Disposal Outside of alarm()

For scenarios requiring cleanup outside the shutdown lifecycle, expose a dedicated disposal method:

```typescript
async fetch(request: Request): Promise<Response> {
  const url = new URL(request.url);
  
  if (url.pathname === "/dispose" && request.method === "POST") {
    await this.#workspace.close();
    return new Response("Workspace disposed", { status: 200 });
  }
  
  // Normal operation...
}

```

Note that after `close()`, subsequent operations will trigger re-initialization via `ready()` if you continue using the same instance.

## Integration with Related Components

Understanding how disposal interacts with other `cloudflare/computer` components prevents subtle bugs:

| Component | Interaction with `close()` |
|-----------|---------------------------|
| **WorkspaceStub** | The stub at [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts) is a stateless RPC façade—it does not own resources, so disposing the underlying `Workspace` is sufficient. |
| **CommandExecutor** | Each executor holds cached backend handles. When `close()` clears `#shells` and `#commandHandles`, in-flight commands either finish gracefully or abort via `#invalidateHandle` transport-error handling. |
| **SyncRetryScheduler** | Pending retry intents are abandoned when the DO's storage clears; no explicit cleanup needed. |

## Real-World Pattern from the Repository

The `think-compare-runtimes` example demonstrates production cleanup with `bestEffortCleanup`:

```typescript
// From examples/think-compare-runtimes/worker/think/agents.ts
await bestEffortCleanup(
  "Workspace session close",
  () => workspace.close(),
);

```

Source: [`agents.ts#L362-L364`](https://github.com/cloudflare/computer/blob/main/examples/think-compare-runtimes/worker/think/agents.ts#L362-L364)

The `bestEffortCleanup` wrapper logs failures without throwing, ensuring shutdown proceeds even if individual handles fail to close.

## Key Source Files

| File | Purpose |
|------|---------|
| [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) | Core `Workspace` class and `close()` implementation |
| [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts) | Lightweight `WorkspaceStub` RPC forwarding |
| [`examples/think-compare-runtimes/worker/think/agents.ts`](https://github.com/cloudflare/computer/blob/main/examples/think-compare-runtimes/worker/think/agents.ts) | Production cleanup pattern with `bestEffortCleanup` |
| [`packages/computer/tests/workspace.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/tests/workspace.test.ts) | Unit tests verifying cache drops and handle closure |

## Summary

- **Always call `await workspace.close()`** in your Durable Object's `alarm()` handler to prevent resource leaks during shutdown.
- The `close()` method **increments connection generation**, **clears all handle caches**, **closes backend connections**, and **resets the ready promise** for clean re-initialization.
- For **explicit teardown**, expose a `dispose()` method that callers can invoke via HTTP or other triggers.
- The **`WorkspaceStub`** requires no separate disposal; cleaning the underlying `Workspace` covers all resources.
- Reference **`bestEffortCleanup`** from the repository examples for resilient production patterns that handle partial failures gracefully.

## Frequently Asked Questions

### What happens if I don't call close() on my Workspace?

Without `close()`, cached backend handles remain open, potentially leaking network sockets and remote process connections. The Durable Object may also retain in-memory caches that prevent garbage collection, increasing memory usage across invocations. While Cloudflare eventually evicts the DO, explicit cleanup ensures deterministic resource release.

### Can I reuse a Workspace after calling close()?

Yes, but it reinitializes. Subsequent calls to `ready()` will re-index mounts and reconnect backends because `close()` resets `#readyPromise` to `undefined`. However, for clarity, most applications should treat `close()` as terminal and instantiate a fresh `Workspace` if needed.

### How does handle closure work if a backend is unresponsive?

The `close()` implementation uses `Promise.all` with optional chaining (`h.close?.()`), so unresponsive backends time out individually without blocking other cleanup. The `bestEffortCleanup` wrapper in the examples captures and logs these failures while allowing shutdown to proceed.

### Should I dispose WorkspaceStub separately?

No. The `WorkspaceStub` at [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts) is a thin RPC client with no owned resources. Disposing the underlying `Workspace` instance that the stub targets is sufficient for complete cleanup.