Rivet Actors 睡眠 API 深度解析:POST /actors/{actor_id}/sleep 端点原理与 Rust SDK 调用指南
【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors
Rivet Actors 为有状态工作负载(AI Agent、协作应用、持久化执行)提供了完整的生命周期管理能力,其中"睡眠(Sleep)"是控制 Actor 资源释放与状态持久化的关键原语。本文以engine/sdks/rust/api-full/rust/docs/ActorsSleepApi.md为骨架,完整解析POST /actors/{actor_id}/sleep端点的请求参数、认证方式、响应语义,并沿着 API 网关到引擎核心的调用链,讲解其底层实现原理。读完本文,你将掌握如何通过 Rust SDK 与原生 HTTP 调用主动休眠 Actor、理解 sleep 与 destroy 的差异,以及keepAwake/waitUntil等配套原语的使用场景。
一、API 概览
ActorsSleepApi是 Rivet API 中负责主动触发 Actor 睡眠的端点。当 Actor 处于空闲状态时,平台会依据空闲超时自动将其休眠以释放资源;而该端点则提供了一条显式、按需的休眠通道,让调用方可以在业务逻辑确认 Actor 不再需要驻留时立即触发睡眠流程。
| Method | HTTP request | Description |
|---|---|---|
| actors_sleep | POST/actors/{actor_id}/sleep | 主动请求指定 Actor 进入睡眠状态 |
该文档由 OpenAPI Generator 基于engine/artifacts/openapi.json生成(版本 2.3.14),所有 URI 均相对于http://localhost开发环境地址,生产环境应以实际部署的 API 网关地址为基准。除了 Rust SDK(api-full)外,仓库还提供了同构的 TypeScript SDK(engine/sdks/typescript/api-full/src/Client.ts)与 Go SDK(engine/sdks/go/api-full/client/client.go),三者共享同一份 OpenAPI 契约。
二、请求参数详解
actors_sleep方法签名如下:
pub async fn actors_sleep( configuration: &configuration::Configuration, actor_id: &str, namespace: &str, body: serde_json::Value, ) -> Result<serde_json::Value, Error<ActorsSleepError>>参数表
| Name | Type | Description | Required | Notes |
|---|---|---|---|---|
| actor_id | String | 目标 Actor 的 ID | [required] | 路径参数,位于/actors/{actor_id}/sleep的 URL 路径中 |
| namespace | String | Actor 所属的命名空间名称 | [required] | 查询参数,以?namespace=...形式附加在 URL 上 |
| body | serde_json::Value | 请求体 | [required] | JSON 请求体,Content-Type: application/json |
参数在请求中的实际位置
查看 SDK 源码 actors_sleep_api.rs 可以确认三个参数的组装方式:
let uri_str = format!("{}/actors/{actor_id}/sleep", configuration.base_path, actor_id = crate::apis::urlencode(p_actor_id)); let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str); req_builder = req_builder.query(&[("namespace", &p_namespace.to_string())]); // ... req_builder = req_builder.json(&p_body);actor_id经过urlencode后拼入 URL 路径,因此需要 URL 编码;namespace通过 reqwest 的query()以查询字符串追加;body通过json()序列化为请求体。
请求体的真实结构
尽管文档中body的类型标注为通用的serde_json::Value,但从服务端契约 api-types/src/actors/sleep.rs 可以看到,请求体实际是空对象:
#[derive(Serialize, Deserialize, ToSchema)] #[serde(deny_unknown_fields)] #[schema(as = ActorsSleepRequestBody)] pub struct SleepRequest {}该结构体启用了deny_unknown_fields,意味着请求体中不能携带任何额外字段。实际调用时传入serde_json::json!({})即可:
let result = actors_sleep(&config, &actor_id, "default", serde_json::json!({})).await?;若传入未知字段,服务端会因反序列化失败而拒绝请求。
三、认证与响应
认证方式
该端点使用Bearer Token认证(bearer_auth),参见 api-public 路由定义 中的security(("bearer_auth" = []))声明。Rust SDK 侧,认证令牌在Configuration中通过bearer_access_token字段注入,请求发出时自动附加Authorization: Bearer <token>头:
if let Some(ref token) = configuration.bearer_access_token { req_builder = req_builder.bearer_auth(token.to_owned()); };完整的认证说明可参考 engine/sdks/rust/api-full/rust/README.md。
返回类型与响应头
| 项目 | 值 |
|---|---|
| 成功响应 | 200 OK,返回serde_json::Value(实际为ActorsSleepResponse空对象{}) |
| Content-Type(请求) | application/json |
| Accept(响应) | application/json |
SDK 在收到响应后,会根据响应头的content-type决定解析策略(actors_sleep_api.rs):application/json会尝试反序列化为serde_json::Value;text/plain及未知类型会构造错误返回。4xx/5xx 响应则统一包装为Error::ResponseError,其中携带状态码、原始响应体与反序列化后的ActorsSleepError。
四、调用链:从 HTTP 端点到引擎核心
actors_sleep表面是一个简单 POST 端点,但背后贯穿了 API 网关、控制面与运行时三层。理解这条链路有助于排查问题与预估延迟。
第一步:API 网关路由与鉴权
服务端入口位于 engine/packages/api-public/src/actors/sleep.rs。sleep_inner首先执行ctx.auth().await?完成鉴权,然后判断目标 Actor 所在的数据中心:
- 若 Actor 属于当前数据中心(
path.actor_id.label() == ctx.config().dc_label()),直接调用本地的rivet_api_peer::actors::sleep::sleep; - 否则通过
request_remote_datacenter_raw将请求原样转发(包括查询参数与请求体)到 Actor 所在数据中心的/actors/{actor_id}/sleep端点。这种"按数据中心就近路由"的设计意味着调用方无需感知 Actor 部署在哪个区域。
第二步:控制面校验与信号下发
请求进入 engine/packages/api-peer/src/actors/sleep.rs 后依次完成三道校验:
- 通过
pegboard::ops::actor::get查询 Actor,不存在则返回Actor::NotFound; - 通过
namespace::ops::resolve_for_name_global解析namespace参数,命名空间不存在则返回Namespace::NotFound; - 校验 Actor 的
namespace_id与解析出的命名空间一致,不一致时同样返回Actor::NotFound——避免跨命名空间越权操作。
校验通过后,控制面发送pegboard::workflows::actor2::Sleep {}信号(定义于 engine/packages/pegboard/src/workflows/actor2/mod.rs),并指定tag("actor_id", ...)定向投递给对应 Actor 的 workflow 实例。若 Actor workflow 已不存在(例如刚刚停止),graceful_not_found()会静默返回,仅记录一条 warning 日志,不会报错。
第三步:运行时生命周期状态机
信号最终作用于 RivetKit 运行时核心的睡眠状态机。ActorContext::sleep()(rivetkit-rust/packages/rivetkit-core/src/actor/context.rs)内部执行了严格的生命周期守卫:
- 若生命周期尚未启动(
lifecycle_started == false):区分"尚未启动完成"(返回actor/starting错误)与"已在关闭中"(返回actor/stopping错误)两种诊断; - 通过
sleep_requested.swap(true, Ordering::SeqCst)原子交换保证每个生命周期代次(generation)内只能成功请求一次; - 成功后调用
cancel_sleep_timer()取消空闲定时器,避免与自动睡眠竞争。
从源码结构看,睡眠与销毁(destroy)共用同一套优雅关闭(grace)通道:睡眠进入SleepGrace阶段,销毁进入DestroyGrace阶段,二者都会触发abortSignal通知用户代码收尾(详见 docs-internal/engine/sleep-sequence.md)。
五、Rust SDK 完整调用示例
结合 Configuration 与 Error 类型,一个完整的最小可运行调用如下:
use rivet_api_full::apis::{actors_sleep_api, configuration::Configuration}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // 1. 构造配置(base_path 指向 API 网关,token 来自鉴权流程) let mut config = Configuration::new(); config.base_path = "https://api.example.com".to_string(); config.bearer_access_token = Some("your-token".to_string()); // 2. 主动触发 Actor 睡眠 let actor_id = "actor-uuid-here"; let namespace = "default"; let result = actors_sleep_api::actors_sleep( &config, actor_id, namespace, serde_json::json!({}), // 请求体必须是空对象 ) .await?; println!("sleep 请求已受理: {result:?}"); Ok(()) }actors_sleep是异步非阻塞的:它只负责提交睡眠意图并返回SleepResponse {},真正的状态持久化与进程回收在后台异步完成。业务侧如需确认 Actor 已进入睡眠,应轮询 Actor 的状态或等待下一次唤醒后观察其重启计数。
六、睡眠语义:触发场景、状态持久化与保持唤醒
何时会触发睡眠
从测试套件 actor-sleep.test.ts 可以归纳出以下触发与抑制行为:
- 空闲超时自动睡眠:Actor 在
SLEEP_TIMEOUT内无任何活动(无 RPC、无连接)即自动睡眠; - 显式 API 触发:即本文的
actors_sleep端点,以及 Actor 内部调用c.sleep()(TypeScript 侧实现在 registry/native.ts); - 保持唤醒的活动:进行中的 RPC 调用、活跃的 Raw WebSocket 连接、未完成的 Raw HTTP 请求、已设置的 alarm 定时器都会阻止睡眠;
noSleep配置:Actor 可通过配置完全禁用自动睡眠。
睡眠即状态持久化
睡眠并不是简单地"杀掉进程"。睡眠流程会先序列化 Actor 的 KV 状态与 SQLite 数据,再释放计算资源;下次有请求到达时,平台会以持久化的状态唤醒并重建 Actor 实例。测试"actor sleep persists state"验证了这一点:触发睡眠后,sleepCount从 0 变为 1、startCount从 1 变为 2,表明旧实例已回收、新实例基于持久化状态重启。这一机制是 Rivet Actors 支撑有状态工作负载的成本优势来源——空闲即休眠,按需唤醒。
keepAwake 与 waitUntil:控制优雅关闭窗口
睡眠信号到达后,Actor 不会立即停止,而是进入宽限期(grace period)等待进行中的收尾工作完成。开发者通过两个 Promise 原语控制这一窗口(详细不变量见 sleep-sequence.md):
| Method | 阻止空闲睡眠 | 阻止宽限期完成 | 说明 |
|---|---|---|---|
c.keepAwake(promise) | 是 | 是 | 返回同一个 Promise,用于必须保持 Actor 运行的工作(如 alarm、队列接收) |
c.waitUntil(promise) | 否 | 是 | 返回 void,用于尽力的 flush/清理工作,允许在宽限期内完成 |
二者均以 Promise 为粒度计数,Promise settle(无论 resolve 还是 reject)后计数器自动递减,不会像旧的setPreventSleep标志位那样因忘记复位而将 Actor 永久"卡醒"。setPreventSleep/preventSleep目前是保留兼容性的 no-op(调用时会打印 deprecation 警告),计划在 2.2.0 移除。
七、测试与验证
仓库为睡眠生命周期提供了多层次的测试保障:
- TypeScript 驱动测试:rivetkit-typescript/packages/rivetkit/tests/driver/actor-sleep.test.ts(约 1072 行),覆盖状态持久化、RPC/WebSocket/HTTP 保持唤醒、alarm 唤醒、
onSleep回调中发送消息、宽限期与 abortSignal 竞态等 20 余个场景;配套 fixture 位于 rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/,包含sleep.ts、sleepWithRawWebSocket.ts、sleepNestedWaitUntil.ts等大量可复用的测试 Actor; - Rust 集成测试:位于
rivetkit-core/tests/modules/sleep.rs,锁定睡眠谓词、宽限期选择与save_final_state兜底上限(SERIALIZE_STATE_SHUTDOWN_SANITY_CAP = 15s)等核心不变量; - 端到端脚本:scripts/tests/actor_sleep.ts 与 examples/kitchen-sink/scripts/sleep-close-fuzz.ts 提供面向真实部署的验证与模糊测试路径。
这些测试共同确认了actors_sleep端点在真实系统中的行为边界:RPC 会重置空闲定时器、长 RPC 期间不会睡眠、活跃连接关闭后按超时睡眠等。
八、总结
POST /actors/{actor_id}/sleep是 Rivet Actors 主动控制生命周期的一等公民 API:调用方通过actor_id(路径)、namespace(查询参数)与空对象请求体即可安全地提交睡眠意图,服务端以 Bearer Token 鉴权,并在跨数据中心场景下自动就近转发。其底层实现(api-public → api-peer → pegboard workflow 信号 → RivetKit 生命周期状态机)保证了校验的严谨与状态持久化的可靠。配合keepAwake/waitUntil原语,开发者可以精确编排"何时保持活跃、何时优雅让位",在成本与可用性之间取得平衡。
// 快速参考:提交睡眠请求所需的最少步骤 actors_sleep_api::actors_sleep(&config, actor_id, namespace, serde_json::json!({})).await?;【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考