Skip to content

Edge Functions worker timeouts and WebSocket drops

Last edited: 8/12/2026

背景 #

🌐 Background

Edge Functions 在由 edge-runtime 管理的 V8 隔离环境中运行。

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

主管会执行资源限制:

🌐 The supervisor enforces resource limits:

  • 挂钟时间(worker_timeout_ms
  • CPU 时间(软限制 + 硬限制)
  • 内存使用

如果隔离实例处于空闲状态,它可能会提前退出(EarlyDrop 事件)。当满足以下条件时,隔离实例被认为是空闲的:

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

  • HTTP 响应已经返回了。
  • 所有 EdgeRuntime.waitUntil() 的承诺都已完成。

如果在资源检查期间两者都为真,即使有打开的 WebSocket 连接,隔离也可以被终止。

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

场景 1:WebSocket 大约在一半的墙上时钟限制时掉线 #

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

症状 #

🌐 Symptoms

  • WebSocket 会在一个固定的间隔关闭(通常大约是配置的时钟限制的一半)。
  • 日志包含 EarlyDrop 或实时时钟警告事件。

根本原因 #

🌐 Root cause

Deno.upgradeWebSocket(req) 返回响应后,HTTP 请求就被认为已被确认。如果没有未解决的 waitUntil 工作,工作线程可能看起来空闲,并可能提前被回收。

🌐 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. 把连接使用时长和你的墙上时钟限制对比一下。
  2. 检查 EarlyDrop 在断开连接时间前后的日志。
  3. 确认没有未解决的与套接字生命周期相关的 EdgeRuntime.waitUntil() 承诺。

解决方法 #

🌐 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()可以防止提前退休,但它不会延长硬性退休年龄限制。

场景 2:功能在固定时间内被终止 #

🌐 Scenario 2: Function killed at a consistent duration

症状 #

🌐 Symptoms

  • 函数会在可预测的运行时间失败(例如,总是在差不多同一秒时)。
  • 日志包含墙钟关闭的原因。
  • 客户可能会收到状态 546 或取消错误。

根本原因 #

🌐 Root cause

函数超出了设置的实际时间预算。

🌐 The function exceeded the configured wall clock budget.

变通方法 #

🌐 Workarounds

  • 把工作分成更小的部分。
  • 把耗时的工作移到异步/后台处理,并尽早返回。
  • 尽可能使用上游 API 的流式传输。
  • 使用队列(pg_netpgmq 或 webhooks)进行分块处理。

场景3:函数被CPU限制杀死 #

🌐 Scenario 3: Function killed by CPU limit

症状 #

🌐 Symptoms

  • 在高计算任务中出错。
  • 日志包括 CPU 软/硬限制事件。

根本原因 #

🌐 Root cause

CPU 时间预算和实际时间预算是独立的。一个函数可能在实际时间耗尽之前就用完了 CPU 时间。

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

变通方法 #

🌐 Workarounds

  • 把大的同步循环拆成异步块。
  • 优化耗费高的路径,避免重复计算。
  • 把重负载计算转移到为长时间CPU密集型工作设计的系统上。

场景 4:SSE 或 AI 流在完成前结束 #

🌐 Scenario 4: SSE or AI streams end before completion

症状 #

🌐 Symptoms

  • 直播开始了,但很快就结束了。
  • 没有最终的 [DONE] 标记或正常的关闭标记。

根本原因 #

🌐 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
})

场景5:冷启动在第一次响应前失败 #

🌐 Scenario 5: Cold starts fail before first response

症状 #

🌐 Symptoms

  • 闲置后第一次请求失败(例如,504 或工作线程创建超时)。
  • 后续的请求可能会成功。

根本原因 #

🌐 Root cause

大型依赖树或昂贵的顶层初始化可能会超出启动预算。

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

变通方法 #

🌐 Workarounds

  • 避免慢速的顶层 await 工作。
  • 在请求处理器里懒加载重量级客户端。
  • 减少包和依赖的大小。
  • 如果需要的话,保持关键功能处于活跃状态。

主要区别 #

🌐 Key distinctions

  • EarlyDrop:当工人显得懒散时提前退休。
  • WallClockTime:达到硬性运行时间上限。
  • CPU 和挂钟时间限制是独立的。
  • 升级后,WebSockets 不会被主管追踪请求。

🌐 Related resources