乐于分享
好东西不私藏

OpenCode 源码-Effect 运行时系统——Fiber、Runner 与服务编排

OpenCode 源码-Effect 运行时系统——Fiber、Runner 与服务编排

OpenCode 源码解析

第 16 讲:Effect 运行时系统——Fiber、Runner 与服务编排

基于 dev 分支源码 · 2026-07-24

一、Effect 运行时:OpenCode 的并发引擎

前十五讲覆盖了 OpenCode 从 CLI 到数据库的核心能力。这一讲进入Effect 运行时系统——OpenCode 基于 Effect TS 构建的并发引擎,管理 Fiber、Runner 和服务编排。Effect 模块横跨 11 个文件、约 767 行代码,是整个系统的并发基础设施。

📦 本讲核心文件

packages/opencode/src/effect/runner.ts(217 行)— Runner 并发控制器

packages/opencode/src/effect/app-runtime.ts(135 行)— 应用运行时

packages/opencode/src/effect/instance-state.ts(69 行)— 实例状态管理

packages/opencode/src/effect/run-service.ts(47 行)— 服务运行器

packages/opencode/src/effect/bridge.ts(84 行)— Effect/Promise 桥接

二、Runner:并发控制器

runner.ts(217 行)是 Effect 运行时的核心,实现了 Runner 模式——管理 Effect 任务的生命周期、并发控制和取消。

1. Runner 接口

📄 effect/runner.ts (第 3-9 行)

export interface Runner<A, E = never> {
  readonly state: State<A, E>
  readonly busy: boolean
  readonly ensureRunning: (work: Effect.Effect<A, E>)
    => Effect.Effect<A, E>
  readonly startShell: (work: Effect.Effect<A, E>,
    ready?: Latch.Latch) => Effect.Effect<A, E | Busy>
  readonly cancel: Effect.Effect<void>
}

设计亮点:Runner 封装了 Effect 任务的生命周期管理,提供 5 个核心方法:状态查询、忙碌检查、确保运行、启动 Shell 和取消任务。

2. 四种状态

📄 effect/runner.ts (第 33-37 行)

export type State<A, E> =
  | { readonly _tag: "Idle" }
  | { readonly _tag: "Running"; readonly run: RunHandle<A, E> }
  | { readonly _tag: "Shell"; readonly shell: ShellHandle<A, E> }
  | { readonly _tag: "ShellThenRun";
      readonly shell: ShellHandle<A, E>;
      readonly run: PendingHandle<A, E> }

状态机:Idle(空闲)→ Running(运行中)→ Shell(Shell 模式)→ ShellThenRun(Shell 后运行)。每个状态都有对应的处理逻辑。

三、Fiber 并发模型

Runner 基于 Effect 的 Fiber 模型实现并发控制,支持任务取消、超时和错误处理。

1. Fiber 生命周期

📄 effect/runner.ts (第 83-91 行)

const startRun = (work: Effect.Effect<A, E>,
  done: Deferred.Deferred<A, E | Cancelled>) =>
  Effect.gen(function* () {
    const id = next()
    const fiber = yield* work.pipe(
      Effect.onExit((exit) => finishRun(id, done, exit)),
      Effect.forkIn(scope),
    )
    return { id, done, fiber } satisfies RunHandle<A, E>
  })

Fiber 设计:每个任务 fork 到 Scope 中运行,通过 Deferred 跟踪完成状态。onExit 回调确保任务结束时正确更新状态。

2. 任务取消

📄 effect/runner.ts (第 171-202 行)

const cancel = SynchronizedRef.modify(ref, (st) => {
  switch (st._tag) {
    case "Idle":
      return [Effect.void, st] as const
    case "Running":
      return [
        Effect.gen(function* () {
          yield* Fiber.interrupt(st.run.fiber)
          yield* Deferred.fail(st.run.done,
            new Cancelled()).pipe(Effect.asVoid)
          yield* idleIfCurrent()
        }),
        { _tag: "Idle" } as const,
      ] as const
    case "Shell":
      return [
        Effect.gen(function* () {
          yield* stopShell(st.shell)
          yield* idleIfCurrent()
        }),
        { _tag: "Idle" } as const,
      ] as const
    // ... ShellThenRun 同理
  }
}).pipe(Effect.flatten)

取消逻辑:根据当前状态中断对应的 Fiber,更新 Deferred 为 Cancelled 状态,然后回到 Idle 状态。Shell 模式需要等待 ready 信号。

四、实例状态管理

instance-state.ts(69 行)实现了基于目录的实例状态管理,每个项目目录有独立的状态。

1. InstanceState 接口

📄 effect/instance-state.ts (第 15-40 行)

export type InstanceState<S> = {
  readonly make: (effect: Effect.Effect<S>)
    => Effect.Effect<void>
  readonly use: (f: (state: S) => A)
    => Effect.Effect<A>
  readonly useEffect: (f: (state: S)
    => Effect.Effect<A, E>)
    => Effect.Effect<A, E>
  readonly directory: string
}

设计亮点:InstanceState 基于 ScopedCache 实现,每个项目目录有独立状态。make 创建状态,use/useEffect 读取状态。状态在实例销毁时自动清理。

五、总结

🔹 Runner 模式管理 Effect 任务生命周期

🔹 四种状态:Idle/Running/Shell/ShellThenRun

🔹 Fiber 并发模型支持取消、超时和错误处理

🔹 InstanceState 基于目录的实例状态管理

🔹 EffectBridge 桥接 Promise 和 Effect

🔹 SynchronizedRef 保证状态修改的原子性

← 系列导航 →

← 第 15 讲:Env 与运行时标志 | 第 17 讲:Event 事件总线 →

关注公众号获取更多 OpenCode 源码解析干货

源码:https://github.com/opencode-ai/opencode