Skip to main content
Functions

Function runtime

Instance lifecycle, concurrency, streaming, filesystem, networking, and shutdown behavior for Easel Functions.

The function runtime provides the execution environment for server-side application code.

This page describes instance lifecycle, concurrency, reuse, streaming, filesystem behavior, networking, and shutdown semantics.

Runtime environment

Easel Functions run application code produced by supported frameworks or application-defined handlers.

A runtime environment includes:

  • The application bundle
  • Runtime dependencies
  • Configured environment variables
  • Allocated CPU and memory
  • Temporary filesystem space
  • Network access
  • Request and observability integrations

Supported runtime versions are listed in Function configuration.

Instance lifecycle

A function instance moves through a lifecycle similar to:

Instances may be reused across requests, then shut down.

Easel may create, reuse, suspend, or terminate instances based on traffic and platform conditions.

The application must not depend on a specific instance remaining available.

Initialization

Initialization happens before an instance begins handling application traffic.

Typical initialization work includes:

  • Loading application modules
  • Initializing framework code
  • Creating database clients
  • Reading configuration
  • Preparing reusable application state

Module-level initialization can reduce repeated work across requests:

const database = createDatabaseClient({
  url: process.env.DATABASE_URL,
});

export async function handler(request: Request) {
  const users = await database.query("SELECT * FROM users");

  return Response.json(users);
}

Initialization code must remain reasonably fast. Large dependency graphs, expensive synchronous work, and unnecessary network requests increase startup latency.

Instance reuse

An initialized instance may handle multiple requests over its lifetime.

Reuse allows applications to preserve:

  • Loaded modules
  • Database connection pools
  • HTTP clients
  • Parsed configuration
  • Read-only lookup data
  • In-memory caches used only as optional optimizations

Reuse is not guaranteed.

Code must continue working when an instance starts with no previous in-memory state.

A warm Fluid instance may handle more than one request during a single platform wake, but only while enough wake time remains for the route’s maxDuration. Customer maxDuration bounds each request (including streaming and post-response work). It is not the AWS session Timeout; that is a platform setting that keeps the wake open long enough to multiplex eligible requests.

The platform aborts a request that makes no progress well before the full duration budget: headers must start within 60 seconds, and body chunks must arrive no more than 30 seconds apart (see Function limits). The per-request wait tracks maxDuration rather than the longer wake Timeout.

In-function concurrency

An instance may process overlapping requests.

This differs from a model in which each instance handles exactly one request at a time.

Concurrency is especially useful for workloads that spend significant time waiting on:

  • Databases
  • External APIs
  • Object storage
  • Network streams
  • Other asynchronous services

Concurrency safety

Do not place request-specific state in shared mutable variables.

Avoid:

const requestContext: {
  organizationId?: string;
} = {};

export async function handler(request: Request) {
  requestContext.organizationId =
    request.headers.get("x-organization-id") ?? undefined;

  return renderOrganization(requestContext.organizationId);
}

Instead, keep request state local:

export async function handler(request: Request) {
  const organizationId =
    request.headers.get("x-organization-id") ?? undefined;

  return renderOrganization(organizationId);
}

Libraries used as shared clients must support concurrent requests.

CPU and asynchronous work

Concurrency does not create unlimited CPU.

CPU-intensive JavaScript or native work can delay other requests handled by the same instance.

Examples include:

  • Large JSON transformations
  • Image or video processing
  • Compression
  • Cryptographic operations
  • Synchronous filesystem access
  • Large template compilation
  • Machine-learning inference

Move unusually expensive tasks to an appropriate worker or service when they interfere with request latency.

Request lifecycle

An invocation begins when Easel assigns a request to the function runtime.

It ends when:

  • Application execution completes
  • The response and registered post-response work complete
  • The application fails
  • The client disconnect causes execution to terminate, where applicable
  • The maximum duration is reached
  • The platform terminates the instance

Billing and duration measurement follow the function invocation rather than only the time until the first response byte.

Streaming responses

A function can return a streaming response when supported by the runtime.

export async function GET() {
  const encoder = new TextEncoder();

  const stream = new ReadableStream({
    async start(controller) {
      controller.enqueue(encoder.encode("First chunk\n"));

      await performAsyncWork();

      controller.enqueue(encoder.encode("Second chunk\n"));
      controller.close();
    },
  });

  return new Response(stream, {
    headers: {
      "Content-Type": "text/plain",
    },
  });
}

Easel forwards response headers as soon as they are available, then forwards body chunks as the runtime produces them.

The invocation remains active while the response stream is open and until registered post-response work finishes. Streaming and waitUntil / after() work count toward the route’s maximum duration (default 30s, max 800s).

Applications must:

  • Handle cancellation where supported
  • Close streams reliably
  • Avoid retaining unbounded buffers
  • Respect the maximum duration
  • Send periodic data only when appropriate for the protocol

Some paths that need a complete body for CDN or ISR admit still buffer before caching. Client delivery for ordinary dynamic responses streams.

Client disconnection

A client can disconnect before the function finishes.

Applications must not assume that a successfully started request will retain an active client connection until completion.

Use abort signals when supported:

export async function handler(request: Request) {
  const result = await performWork({
    signal: request.signal,
  });

  return Response.json(result);
}

Whether execution continues after a client disconnect may depend on the runtime, framework, and response state.

Do not use client-connected requests as a substitute for durable job processing.

Work after the response

Framework integrations may support post-response APIs.

Registered work:

  • Runs within the original invocation
  • Counts toward duration
  • Uses the function’s CPU and memory
  • Can be interrupted if the invocation ends
  • Does not provide durable retries by itself

Use this capability for short, non-critical work such as:

  • Best-effort analytics
  • Log enrichment
  • Small cache updates
  • Non-critical notifications

Do not use it for:

  • Financial transactions
  • Durable workflow steps
  • Long-running imports
  • Tasks requiring retries
  • Jobs that must complete exactly once

Filesystem

The function filesystem is ephemeral.

Applications can use temporary storage during an invocation where supported, but files are not guaranteed to remain available:

  • Across instances
  • Across deployments
  • After an instance shuts down
  • For any specific retention period

Do not use the local filesystem for durable uploads, application databases, or persistent user data.

Use object storage or another durable service.

Read-only application files

Files packaged into the application bundle can be read at runtime where supported.

Avoid modifying packaged application files.

Temporary files

Temporary files may be useful for:

  • Parsing uploads
  • Generating intermediate output
  • Working with libraries that require file paths
  • Buffering bounded data

Delete temporary files when possible and remain within storage limits.

Networking

Functions can make outbound network requests to databases, APIs, and other services.

const response = await fetch("https://api.example.com/data");

Application latency depends on the network distance between the function region (US East) and its dependencies.

Prefer persistent, reusable clients where supported.

Connections and pooling

An active instance can reuse outbound connections across requests.

Examples include:

  • Database pools
  • HTTP keep-alive connections
  • TLS sessions
  • SDK clients

Create reusable clients outside the request handler:

const client = createApiClient({
  apiKey: process.env.API_KEY,
});

export async function handler() {
  return Response.json(await client.listItems());
}

Client libraries must tolerate:

  • Concurrent requests
  • Idle connection closure
  • Instance shutdown
  • Network interruption
  • Reconnection

Do not assume that a connection remains open for the lifetime of the deployment.

Incoming connections

Functions handle HTTP requests through Easel’s request interface.

Long-lived incoming WebSocket connections are not supported.

Streaming HTTP and Server-Sent Events are separate from WebSockets and remain subject to function duration and connection limits.

Signals and shutdown

Easel may notify the runtime before termination where supported, but applications must not depend on receiving enough time to complete important work.

Shutdown cleanup must be best effort.

Examples include:

  • Flushing buffered telemetry
  • Closing clients
  • Releasing temporary resources
  • Stopping internal timers

Durable state must be committed before the response or through a durable external system.

Timers and background loops

Do not start unbounded background loops from a request handler.

Avoid:

setInterval(() => {
  refreshSomething();
}, 60_000);

The instance may stop at any time, and repeated initialization can create multiple overlapping loops.

Use a scheduled job or durable task system for recurring work.

Short timers used during an active invocation are acceptable when they complete within the invocation lifecycle.

Environment variables

Environment variables are available to application code according to the deployment environment.

Do not mutate process.env as a way to share request state.

Treat secret variables as sensitive and avoid writing them to logs.

Time and locale

Do not assume that the runtime’s system timezone matches the user or project location.

Store and process timestamps in UTC unless application requirements specify otherwise.

Format dates and times explicitly using the appropriate user locale and timezone.

Error handling

Unhandled errors fail the invocation and may return a platform or framework error response.

Applications must:

  • Catch expected failures
  • Return appropriate HTTP status codes
  • Avoid exposing stack traces or secrets
  • Include request IDs in support workflows
  • Use structured application logs

Example:

export async function GET() {
  try {
    const data = await loadData();

    return Response.json(data);
  } catch (error) {
    console.error("Failed to load data", {
      error,
    });

    return Response.json(
      {
        error: "Unable to load data",
      },
      {
        status: 500,
      },
    );
  }
}

Retries

Do not assume an HTTP invocation will be retried automatically.

Clients, proxies, queues, or framework integrations may retry some requests independently.

Application mutations must use idempotency keys when duplicate execution would be harmful.

Runtime observability

Runtime telemetry can include:

  • Initialization duration
  • Invocation duration
  • Active requests
  • Memory usage
  • CPU usage
  • Errors
  • Region
  • Instance identifiers
  • Downstream spans

Instance identifiers are diagnostic and must not be used for application routing or durable state.

See Observability and Function observability.