Effect fork 的 uninterruptible 选项怎么用?

我给一个 sleep 30 毫秒的子 fiber 设了 uninterruptible: true,5 毫秒后去中断它,Fiber.interrupt 等了 31 毫秒才返回;不设的话,6 毫秒就回来了。

uninterruptible: true 让子 fiber 整个不可中断,中断请求被推迟到它自然结束,调用方会被阻塞到那时。它适合「必须做完的收尾任务」,不能用在可能永远不结束的任务上。

这个选项和 startImmediately 一样,是 fork 系列函数的第二个选项,类型是 boolean | "inherit"。中断在 Effect 里是一等公民,它与失败的区别见 Effect 里中断和失败有什么区别?。下面对比三种取值。

三种取值有什么区别?

我用一个会 sleep 的子 fiber,让父 fiber 5 毫秒后调用 Fiber.interrupt,对比三种取值:

import { Effect, Fiber } from "effect"

const probe = async (label: string, options?: { readonly uninterruptible?: boolean | "inherit" }) => {
  const log: Array<string> = []
  const body = Effect.gen(function*() {
    log.push("start")
    yield* Effect.sleep(30)
    log.push("finished")
  }).pipe(Effect.onInterrupt(() => Effect.sync(() => { log.push("interrupted") })))
  const t0 = performance.now()
  await Effect.runPromise(Effect.gen(function*() {
    const fiber = yield* Effect.forkChild(body, { startImmediately: true, ...options })
    yield* Effect.sleep(5)
    yield* Fiber.interrupt(fiber)
  }))
  console.log(label.padEnd(26), log, `interrupt returned after ${Math.round(performance.now() - t0)}ms`)
}

await probe("default")
await probe("uninterruptible: true", { uninterruptible: true })
await probe("uninterruptible: inherit", { uninterruptible: "inherit" })

输出(耗时每次略有浮动):

default                    [ 'start', 'interrupted' ] interrupt returned after 6ms
uninterruptible: true      [ 'start', 'finished' ] interrupt returned after 31ms
uninterruptible: inherit   [ 'start', 'interrupted' ] interrupt returned after 6ms
  • 默认和 "inherit" 在这个场景下一致,子 fiber 被中断。
  • true 让子 fiber 整个不可中断:中断请求被推迟,Fiber.interrupt 要等它自然结束才返回(31 毫秒,而不是 6 毫秒)。
  • "inherit" 是继承父 fiber 当前的可中断状态。我另外在 Effect.uninterruptible(...) 包裹的父 fiber 里 fork,结果子 fiber 同样没有被中断,输出的是 finished。

什么任务该设 uninterruptible: true?

适合「必须做完的收尾任务」,比如落盘、提交 offset。代价很直接:它会让中断调用方被阻塞。对「可能永远不结束」的任务,千万不要这么设,否则关闭流程会卡住。

Scope 关闭时,它也会推迟中断吗?

我不知道。我只验证了通过 Fiber.interrupt 触发的中断;它对 Scope 关闭时的中断是否同样推迟,我没有跑过,不写成结论。如果你的子 fiber 是用 forkScoped 或 forkIn 挂在 Scope 上的,先自己跑一遍确认。

四个 fork 各自挂在哪、什么时候该用哪个,见 Effect 的四个 fork 该怎么选?。

验证环境是 effect 4.0.5。三种取值的对比输出,以及 "inherit" 在 Effect.uninterruptible 包裹的父 fiber 里的表现,都是我在这个版本上跑出来的,耗时每次略有浮动。Scope 关闭时的中断是否同样被推迟,我没有验证;落盘、提交 offset 这类用途是我的建议,不是跑出来的结论。