---
title: Sandbox Execution Lifecycle
summary: null
url: >-
  https://www.fastly.com/documentation/guides/compute/developer-guides/sandbox-lifecycle
---

Compute runs request handlers in WebAssembly sandboxes.

In the default configuration, each request starts a new sandbox. This provides fast startup and per-request isolation. Compute also supports an opt-in mode in which a sandbox may process multiple requests. This can reduce repeated initialization overhead for applications with expensive startup work.

## Default behavior

By default, Compute starts a new [WebAssembly](https://webassembly.org/) sandbox for each request. This means request handling begins in a fresh execution environment rather than reusing sandbox state from a prior request.

This execution model is a good fit for most applications because it provides:

- Fast startup
- Per-request isolation
- A simple mental model for request handling

## Reusable sandboxes (opt-in)

Some applications perform expensive initialization when a sandbox starts. For example, an application may:

- parse a large JSON configuration blob
- build in-memory lookup tables
- initialize other data structures needed to serve requests

With the default behavior described above, this initialization work is repeated for each request because each request starts a new sandbox.

If repeated initialization is a significant performance cost for your workload, you can enable an opt-in mode in which a sandbox may process multiple requests. When reusable sandboxes are enabled, a single sandbox can process more than one incoming request. This can improve performance for workloads that benefit from reusing initialized in-memory state across requests.

> **IMPORTANT:** Enabling reusable sandboxes changes the request execution lifecycle. Because a single sandbox may process multiple requests, per-request isolation is not guaranteed. Sandbox state (e.g., global variables) may persist across requests handled by the same sandbox. Architect your application carefully to prevent sensitive data from leaking between requests. For example:
>
> - Do not store user-specific data, such as a password or token, in a global variable.
> - If you lazily initialize global state, ensure it is not based on per-request data that would be unsafe to share. State modified based on the contents or processing of one request (e.g., a user's session data) can be observed by a subsequent request.
>   - **Unsafe:** Initializing a global cache based on a user's ID or authentication token. This data could leak to a subsequent request from a different user.
>   - **Potentially safe:** Initializing a cache with public data, such as a mapping of country codes to country names looked up from the request's geolocation data.
> - Keep in mind that certain operations affect state that persists across requests. For example:
>   - Registering a [dynamic backend](https://www.fastly.com/documentation/guides/integrations/non-fastly-services/developer-guide-backends/#dynamic-backends): For efficiency, a backend name registered during one request remains available to subsequent requests when the sandbox is reused. While attempting to register the same name with identical properties will succeed, an attempt to use an existing name with different properties will fail. To ensure safety and maximize reuse, build your backend names deterministically based on their configuration. For further details, refer to [Dynamic Backends: Registration Scope and Reuse](https://www.fastly.com/documentation/guides/integrations/non-fastly-services/developer-guide-backends/#registration-scope-and-reuse).
> - Be mindful of [timing side-channels](https://en.wikipedia.org/wiki/Timing_attack). If one request's processing path and execution time depend on its sensitive data, a subsequent request in the same sandbox can infer information by observing a change in its own performance.

### Configuring reusable sandboxes

When reusable sandboxes are enabled, you can configure limits that control how long a reusable sandbox remains available and when it is stopped.

You can configure the following limits (desired maximums):

- the maximum lifetime of the sandbox
- the maximum amount of time to wait for another request before stopping
- the maximum amount of memory the sandbox may use before stopping
- the maximum number of requests to serve before stopping

These settings are not guarantees that a sandbox will run until a given limit is reached; a sandbox may stop earlier depending on factors like high memory use or whether other sandboxes for the service are idling while waiting for further requests. These limits help you balance efficiency gains from reusable sandboxes against resource usage and lifecycle control.

### Lifecycle and waiting for the next request

A Compute sandbox is not a general-purpose server and is designed not to wait indefinitely for new requests.

When reusable sandboxes are enabled, the sandbox's lifetime progresses as follows:

1. A WebAssembly sandbox is started in response to an incoming request.

2. The request handler runs against the current request.

   > **IMPORTANT:** When reusable sandboxes are enabled, request execution limits (such as CPU time and runtime limits) apply to each incoming request. For current request execution limits, refer to [Limitations and constraints](https://www.fastly.com/documentation/guides/compute/getting-started-with-compute/#limitations-and-constraints).

3. After the request handler has run to completion, the sandbox is checked for whether any of the following limits have been met:

   - the "maximum lifetime of the sandbox" limit, if set
   - the "maximum memory the sandbox may use" limit, if set
   - the "maximum number of requests to serve" limit, if set
   - a platform-defined maximum number of requests a sandbox can execute (this value is not publicly disclosed and may change)

   If any of the limits have been met, the sandbox exits.

4. The sandbox waits for an additional request. The time a sandbox spends waiting for another request is bounded by the lesser of the following values:

   - the "amount of time to wait for another request" limit, if set
   - a platform-defined maximum amount of time a sandbox is willing to wait for a next request (this value is not publicly disclosed and may change)

   If no request arrives in this time, the sandbox exits.

5. Repeat from step 2 against the newly-arrived request.

> **IMPORTANT:** Sandbox lifecycle checks are only performed between requests, not during active request handling.

### Concurrency

When reusable sandboxes are enabled, a single sandbox does not process multiple requests concurrently.

A sandbox is not reused while it is handling a request. Even if the request is temporarily idle (for example, waiting for asynchronous work), the sandbox remains dedicated to that request until execution completes. A sandbox becomes eligible for reuse only after the request handler has finished. If another request arrives while a sandbox is still handling a request, Compute starts a new sandbox to handle the incoming request.

For example, in JavaScript, the `addEventListener()` syntax and event-loop-based code can make it appear that another request might be handled in the same sandbox while an earlier request is awaiting asynchronous work. In practice, this does not occur. Each request runs to completion before that sandbox can be reused.

### Resource usage and billing considerations

Charges for compute services are determined by request volume and consumed vCPU time. Leveraging reusable sandboxes does not alter how requests are counted: they are tracked identically whether reusable sandboxes are used.

However, reusable sandboxes may offer a cost advantage: by bypassing expensive initialization routines on subsequent invocations, they minimize vCPU consumption and reduce total expenditure.

### Environment variables

Environment variables are associated with a sandbox rather than an individual request.

For example, `FASTLY_TRACE_ID` is generated for each sandbox. If a sandbox processes multiple incoming requests, use the provided [per-request identifier](https://www.fastly.com/documentation/guides/compute/developer-guides/sandbox-lifecycle#per-request-id) when you need request-level correlation (e.g., telemetry or logging).

### How to enable reusable sandboxes

Reusable sandboxes are enabled using language-specific configuration or APIs.

### Rust

To enable reusable sandboxes with the Rust SDK for Compute, use a `main()` function rather than the [`#[fastly::main]`](https://docs.rs/fastly/latest/fastly/attr.main.html) macro. Create an instance of [`Serve`](https://docs.rs/fastly/latest/fastly/http/serve/struct.Serve.html) from the [`fastly::http::serve`](https://docs.rs/fastly/latest/fastly/http/serve/index.html) module with the required options set for your reusable sandbox configuration, call `.run()` to configure it with your request handler, and return the result from your `main()` function.

The following example initializes the sandbox to handle at most 10 requests before shutting down the sandbox.

```rust
use fastly::{Error, Request, Response};
use fastly::http::serve::*;

fn main() -> Result<(), Error> {
    Serve::new()
        .with_max_requests(10)
        .run(handler)
        .into_result()
}

fn handler(_req: Request) -> Result {
    Ok(Response::from_body("hello")
        .with_header("hello", "world!")
        .with_status(200))
}
```

For more details, refer to the SDK documentation for [fastly::http::serve](https://docs.rs/fastly/latest/fastly/http/serve/index.html).

### Javascript

To enable reusable sandboxes with the JavaScript SDK for Compute, call [`setReusableSandboxOptions`](https://js-compute-reference-docs.edgecompute.app/docs/fastly:experimental/setReusableSandboxOptions) from `fastly:experimental` in your top-level initialization code with the required options set for your reusable sandbox configuration.

The following example initializes the sandbox to handle at most 10 requests before shutting down the sandbox.

```javascript

setReusableSandboxOptions({
  maxRequests: 10,
});

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

async function handleRequest(event: FetchEvent) {
    return new Response('hello', {
        status: 200,
        headers: {
            'hello': 'world!',
        }
    });
}
```

For more details, refer to the SDK documentation for [`setReusableSandboxOptions`](https://js-compute-reference-docs.edgecompute.app/docs/fastly:experimental/setReusableSandboxOptions).

> **NOTE:** As mentioned above, the `addEventListener()` syntax and event-loop-based code can make it appear that another request might be handled in the same sandbox while an earlier request is still in progress, but this does not occur.

### Go

To enable reusable sandboxes with the Go SDK for Compute, call [`ServeMany`](https://pkg.go.dev/github.com/fastly/compute-sdk-go/fsthttp#ServeMany) instead of `ServeFunc` from the fsthttp package. Pass in a [`ServeManyOptions` structure](https://pkg.go.dev/github.com/fastly/compute-sdk-go/fsthttp#ServeManyOptions) with the required options set for your reusable sandbox configuration.

The following example initializes the sandbox to handle at most 10 requests before shutting down the sandbox.

```go
package main

import (
	"context"
	"fmt"

	"github.com/fastly/compute-sdk-go/fsthttp"
)

func main() {
	opts := &fsthttp.ServeManyOptions{
		MaxRequests: 10,
	}

	handler := func(ctx context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) {
		w.Header().Set("hello", "world")
		fmt.Fprintf(w, "hello")
	}

	fsthttp.ServeMany(handler, opts)
}
```

For more details, refer to the SDK documentation for [`ServeMany` in fsthttp](https://pkg.go.dev/github.com/fastly/compute-sdk-go/fsthttp#ServeMany).

> **NOTE:** If you are using TinyGo (instead of the standard Go toolchain), keep in mind that TinyGo's garbage collection implementation is more limited than that of standard Go. Depending on your workload, heap usage may grow more quickly in a reusable sandbox than you expect. This may cause sandboxes to reach memory-related limits sooner.
>
> When evaluating sandbox reuse with TinyGo, monitor memory usage carefully and consider setting conservative sandbox memory and reuse limits.

### Cpp

Reusable sandboxes are not available in this version of the Fastly Compute C++ SDK.

### Per-request ID

As described above, `FASTLY_TRACE_ID` is generated for each sandbox. If a sandbox processes multiple incoming requests, use the provided per-request identifier when you need request-level correlation (telemetry, logging).

### Rust

The per-request identifier is available as [`get_client_request_id()`](https://docs.rs/fastly/latest/fastly/struct.Request.html#method.get_client_request_id) on the `Request` struct.

```rust compile_fail
let request_id = req.get_client_request_id().unwrap_or("");
println!("Request ID: {}", request_id);
```

### Javascript

The per-request identifier is available as [`client.requestId`](https://js-compute-reference-docs.edgecompute.app/docs/globals/FetchEvent/) on the `FetchEvent` object passed to the request handler.

```javascript
console.log(`Request ID: ${event.client.requestId}`);
```

### Go

The per-request identifier is available as [`RequestID` of the `FastlyMeta` struct](https://pkg.go.dev/github.com/fastly/compute-sdk-go/fsthttp#FastlyMeta), obtained by calling the [`FastlyMeta()` method of the `Request` struct](https://pkg.go.dev/github.com/fastly/compute-sdk-go/fsthttp#Request.FastlyMeta).

```go no-compile-test
meta, err := r.FastlyMeta()
if err != nil {
    fsthttp.Error(w, err.Error(), fsthttp.StatusInternalServerError)
    return
}
fmt.Printf("Request ID: %s\n", meta.requestID)
```

### Cpp

Reusable sandboxes are not available in this version of the Fastly Compute C++ SDK.

## See also

- [Getting started with Fastly Compute](https://www.fastly.com/documentation/guides/compute/getting-started-with-compute/)
- [`FASTLY_TRACE_ID` (Compute environment)](https://www.fastly.com/documentation/reference/compute/ecp-env/fastly-trace-id/)
