Edge Functions worker timeouts and WebSocket drops

Last edited: 5/29/2026

Background#

Edge Functions run inside V8 isolates managed by a supervisor in edge-runtime.

The supervisor enforces resource limits:

  • Wall clock time (worker_timeout_ms)
  • CPU time (soft + hard limits)
  • Memory usage

It may retire early (EarlyDrop event) if the isolate is idle Isolate is considered idle if following conditions are met:

  • The HTTP response has already been returned.
  • All EdgeRuntime.waitUntil() promises have resolved.

If both are true during a resource check, the isolate can be terminated even with open WebSocket connections.

Scenario 1: WebSocket drops around half of the wall clock limit#

Symptoms#

  • WebSocket closes at a consistent interval (often around half of the configured wall clock limit).
  • Logs include EarlyDrop or wall clock warning events.

Root cause#

After Deno.upgradeWebSocket(req) returns a response, the HTTP request is considered acknowledged. If there is no unresolved waitUntil work, the worker may look idle and be retired early.

What to check#

  1. Compare connection lifetime to your wall clock limit.
  2. Inspect logs for EarlyDrop around disconnect time.
  3. Confirm there is no unresolved EdgeRuntime.waitUntil() promise tied to socket lifecycle.

Workaround#

Keep a promise pending until the socket closes.

1
Deno.serve((req) => {
2
const { socket, response } = Deno.upgradeWebSocket(req)
3
4
const socketClosedPromise = new Promise<void>((resolve) => {
5
socket.onclose = () => resolve()
6
})
7
8
EdgeRuntime.waitUntil(socketClosedPromise)
9
10
socket.onmessage = (event) => {
11
socket.send(event.data)
12
}
13
14
return response
15
})

EdgeRuntime.waitUntil() prevents early retirement, but it does not extend the hard wall clock limit.

Scenario 2: Function killed at a consistent duration#

Symptoms#

  • Function fails at a predictable runtime (for example, always around the same second mark).
  • Logs include wall clock shutdown reasons.
  • Clients may receive status 546 or cancellation errors.

Root cause#

The function exceeded the configured wall clock budget.

Workarounds#

  • Split work into smaller units.
  • Move long work to async/background processing and return early.
  • Use streaming from upstream APIs where possible.
  • Use queues (pg_net, pgmq, or webhooks) for chunked processing.

Scenario 3: Function killed by CPU limit#

Symptoms#

  • Failures during compute-heavy tasks.
  • Logs include CPU soft/hard limit events.

Root cause#

CPU budget and wall clock budget are independent. A function can run out of CPU time long before wall clock is exhausted.

Workarounds#

  • Break large synchronous loops into async chunks.
  • Optimize expensive paths and avoid repeated recalculation.
  • Move heavy compute to systems designed for long CPU-bound workloads.

Scenario 4: SSE or AI streams end before completion#

Symptoms#

  • Streaming starts but ends prematurely.
  • No final [DONE] token or normal close marker.

Root cause#

The worker hits wall clock or early retirement conditions while forwarding a long stream.

Workaround#

Keep the isolate alive for the stream piping lifecycle:

1
Deno.serve(async (_req) => {
2
const upstream = await fetch('https://api.openai.com/v1/chat/completions', {
3
method: 'POST',
4
headers: {
5
'Content-Type': 'application/json',
6
Authorization: `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
7
},
8
body: JSON.stringify({ stream: true }),
9
})
10
11
const { readable, writable } = new TransformStream()
12
13
EdgeRuntime.waitUntil(upstream.body!.pipeTo(writable))
14
15
return new Response(readable, {
16
headers: { 'Content-Type': 'text/event-stream' },
17
})
18
})

Scenario 5: Cold starts fail before first response#

Symptoms#

  • First request after idle fails (for example, 504 or worker creation timeout).
  • Subsequent requests may succeed.

Root cause#

Large dependency trees or expensive top-level initialization can exceed startup budget.

Workarounds#

  • Avoid slow top-level await work.
  • Lazy-initialize heavy clients inside request handlers.
  • Reduce bundle and dependency size.
  • Keep critical functions warm if needed.

Key distinctions#

  • EarlyDrop: early retirement when worker appears idle.
  • WallClockTime: hard runtime ceiling reached.
  • CPU and wall clock limits are independent.
  • WebSockets are not request-tracked by the supervisor after upgrade.