跳转到内容
搜索文档

生命周期

最后更新 查看 MarkdownAgent 设置

生命周期、内存与资源管理

使用服务绑定通过 RPC 调用另一个 Worker 时,会占用被调用 Worker 的内存。考虑以下示例:

let user = await env.USER_SERVICE.findUser(id);

假设服务端 findUser() 返回继承 RpcTarget 的对象,因此客户端的 user 最终是指向该远程对象的 stub。

只要客户端上仍存在 stub,服务端上的对应对象就无法被垃圾回收。但每个 isolate 都有自己的垃圾回收器,无法查看其他 isolate。因此,为了让服务端的 isolate 知道对象可以被回收,调用方 isolate 必须发送明确的信号,称为「释放(disposing)」stub。

在许多情况下(如下所述),系统会自动识别 stub 何时不再需要并自动释放。但为获得最佳性能,代码应在不再使用 stub 时显式释放。

显式资源管理(Explicit Resource Management)

为确保资源得到正确释放,应使用显式资源管理(Explicit Resource Management) ↗,这是一项新的 JavaScript 语言特性,允许你明确指示何时可以释放资源。显式资源管理是 TC39 第 3 阶段提案——即将进入 V8 ↗。

显式资源管理添加了以下语言特性:

如果变量使用 using 声明,当变量超出作用域时,将调用该变量的释放器。例如:

function sendEmail(id, message) {
  using user = await env.USER_SERVICE.findUser(id);
  await user.sendEmail(message);

  // user[Symbol.dispose]() is implicitly called at the end of the scope.
}

using 声明有助于确保不会忘记释放 stub——即使代码被异常中断。

如何在 Worker 中使用 using 声明

Wrangler v4+ 原生支持 using 关键字。如果你使用的是较早版本的 Wrangler,则需要手动释放资源。

以下代码:

{
	using counter = await env.COUNTER_SERVICE.newCounter();
	await counter.increment(2);
	await counter.increment(4);
}

...等价于:

{
	const counter = await env.COUNTER_SERVICE.newCounter();
	try {
		await counter.increment(2);
		await counter.increment(4);
	} finally {
		counter[Symbol.dispose]();
	}
}

自动释放与执行上下文

RPC 系统在以下情况下会自动释放 stub:

事件处理程序 / 执行上下文结束

当事件处理程序「完成」时,作为该事件一部分创建的所有 stub 会自动释放。

例如,考虑处理传入 HTTP 事件的 fetch() 处理程序。处理程序可能在处理事件时发起出站 RPC,这些 RPC 可能返回 stub。当最终 HTTP 响应发送后,处理程序「完成」,所有 stub 会立即释放。

更精确地说,事件有一个「执行上下文(execution context)」,从处理程序首次调用时开始,到 HTTP 响应发送时结束。如果客户端在收到响应之前断开连接,执行上下文也可能提前结束;或者通过调用 ctx.waitUntil() 可以延长其正常结束点。

例如,下面的 Worker 未使用 using 声明,但一旦 fetch() 处理程序返回响应,stub 就会被释放:

export default {
	async fetch(request, env, ctx) {
		let authResult = await env.AUTH_SERVICE.checkCookie(
			req.headers.get("Cookie"),
		);
		if (!authResult.authorized) {
			return new Response("Not authorized", { status: 403 });
		}
		let profile = await authResult.user.getProfile();

		return new Response(`Hello, ${profile.name}!`);
	},
};

通过 RPC 调用的 Worker 也有执行上下文。当调用 WorkerEntrypoint 上的 RPC 方法时,上下文开始。如果此 RPC 的参数或结果中未传递任何 stub,则 RPC 返回时上下文结束(事件「完成」)。但是,如果传递了任何 stub,则执行上下文会隐式延长,直到所有此类 stub 都被释放(以及通过它们发起的所有调用都已返回)。与 HTTP 一样,如果客户端断开连接,服务端的执行上下文会立即取消,无论 stub 是否仍然存在。作为另一个 Worker 的客户端,在其自身执行上下文结束时被视为已断开连接。同样,可以通过 ctx.waitUntil() 延长上下文。

作为 RPC 调用参数接收的 stub

当 stub 作为 RPC 参数接收时,调用返回时会自动释放。如果你希望保留 stub 更长时间,必须对其调用 dup() 方法。

释放 RPC 对象会释放属于该对象的 stub

当 RPC 返回任何类型的对象时,系统会为该对象添加释放器。释放它会释放调用返回的所有 stub。例如,如果 RPC 返回包含四个 stub 的数组,数组本身会有一个释放器来释放全部四个 stub。RPC 返回值没有释放器的唯一情况是原始值,如数字或字符串。这些类型无法添加释放器,但由于它们本身不能包含 stub,因此在此情况下不需要释放器。

这意味着你几乎应始终将 RPC 的结果存储到 using 声明中:

using result = stub.foo();

这样,如果结果包含任何 stub,它们会被释放。即使你不期望 RPC 返回 stub,如果它返回任何类型的对象,将其存储到 using 声明中也是好主意。这样,如果将来 RPC 扩展为返回 stub,你的代码已做好准备。

如果你决定在 using 声明作用域之外保留返回的 stub,可以在作用域结束前对其调用 dup()。(请记住稍后显式释放副本。)

释放器与 RpcTarget 类

继承 RpcTarget 的类可以选择实现释放器:

class Foo extends RpcTarget {
	[Symbol.dispose]() {
		// ...
	}
}

RpcTarget 的释放器在最后一个 stub 被释放后运行。请注意,客户端对 stub 释放器的调用不会等待服务端释放器被调用;服务端释放器稍后才被调用。因此,释放器抛出的任何异常不会传播到客户端;而是作为未捕获异常报告。请注意,RpcTarget 的释放器必须声明为 Symbol.dispose。不支持 Symbol.asyncDispose。

dup() 方法

有时,你需要将 stub 传递给会在使用完毕后释放 stub 的函数,但你也想保留 stub 以供后续使用。为解决此问题,可以「dup」stub:

let stub = await env.SOME_SERVICE.getThing();

// Create a duplicate.
let stub2 = stub.dup();

// Call some function that will dispose the stub.
await func(stub);

// stub2 is still valid

你可以将 dup() 视为与同名 Unix 系统调用 ↗类似:它创建一个指向同一目标的新句柄,必须独立关闭(释放)。

如果 stub 指向的 RpcTarget 类实例具有释放器,则只有在所有副本都被释放后才会调用释放器。但这仅适用于源自同一 stub 的副本。如果同一 RpcTarget 实例多次通过 RPC 传递,每次都会创建新 stub,这些 stub 不被视为彼此的副本。因此,释放器会在 RpcTarget 每次被发送时调用一次。

为避免这种情况,你可以在本地手动创建 stub,然后多次通过 RPC 传递该 stub。通过 RPC 传递 stub 时,stub 的所有权会转移给接收方,因此每次发送时都必须 dup():

import { RpcTarget, RpcStub } from "cloudflare:workers";

class Foo extends RpcTarget {
	// ...
}

let obj = new Foo();
let stub = new RpcStub(obj);
await rpc1(stub.dup()); // sends a dup of `stub`
await rpc2(stub.dup()); // sends another dup of `stub`
stub[Symbol.dispose](); // disposes the original stub

// obj's disposer will be called when the other two stubs
// are disposed remotely.

这篇文档对您有帮助吗?