Effect 的 Pool 怎么复用连接并限制并发?

我用 Pool.makeWithTTL 把上限设成 3,然后同时发起 6 个任务:最终只创建了 3 个连接,id 被复用。

用 Pool 复用同类资源并限制并发:固定大小用 Pool.make,要在 min 到 max 之间弹性伸缩用 Pool.makeWithTTL,借用用 Pool.use。 它不按 key 区分资源,池里的资源是可互换的。

Pool 解决的是另一类问题:同一种资源(数据库连接、worker)创建昂贵,需要一批复用。资源本身仍然由 Scope 和 acquireRelease 管理,没读过的话先看 Effect 怎么保证资源一定被释放?;如果资源需要按 key 区分,应该看 Effect 的 LayerMap、RcMap、Pool 该怎么选?。下面从代码开始。

Pool 怎么创建和使用?

import { Effect, Pool } from "effect"

let created = 0

const program = Effect.gen(function*() {
  const pool = yield* Pool.makeWithTTL({
    acquire: Effect.acquireRelease(
      Effect.sync(() => ({ id: ++created })),
      () => Effect.void
    ),
    min: 1,
    max: 3,
    timeToLive: "100 millis"
  })

  // 6 个并发任务,最多只会创建 3 个连接
  const ids = yield* Effect.all(
    [1, 2, 3, 4, 5, 6].map(() =>
      Pool.use(pool, (conn) => Effect.as(Effect.sleep("50 millis"), conn.id))
    ),
    { concurrency: "unbounded" }
  )
  console.log(ids, created)

  // 发现某个连接坏了:invalidate 之后池会惰性补一个新的
  yield* Effect.scoped(
    Effect.gen(function*() {
      const conn = yield* Pool.get(pool)
      yield* Pool.invalidate(pool, conn)
    })
  )
})

Effect.runPromise(Effect.scoped(program))

跑出来的结果:6 个并发任务只创建了 3 个连接,id 被复用;等待超过 TTL 后,创建过 3 个、关闭了 2 个,收缩回 min = 1;invalidate 之后下一次取到的是新建的第 4 个。

make 和 makeWithTTL 有什么区别?

Pool.make 是固定大小。Pool.makeWithTTL 是 min 到 max 弹性伸缩,超出 min 的空闲项在 timeToLive 后回收,这就是上面例子里「创建 3 个、关闭 2 个、收缩回 1 个」的来源。

Pool.use 和 Pool.get 用哪个?

单个借用用 Pool.use,它不需要额外分配 scope;需要把借用期绑定到某个 scope 才用 Pool.get,比如上面例子里在 Effect.scoped 内取出连接再 invalidate。

一个连接能同时给几个任务用?

默认不能:concurrency 默认是 1,即每个连接同一时刻只给一个 fiber。支持多路复用的客户端可以把它调大。

连接坏了怎么处理,TTL 多久回收?

连接坏了就 Pool.invalidate,池会惰性补一个新的。注意 invalidate 按严格相等匹配,传一个「长得一样」的对象不会生效,必须传从池里取出的那个引用。

timeToLive 默认按「最后一次归还」计时,且按 TTL 周期检查,所以实际回收最长可能拖到两倍 TTL,别拿它做精确的时间保证。

Pool 在 4.0.5 里是 stable,可以放心用在生产代码里。

版本是 effect@4.0.5。上面代码的结果(3 个连接、关闭 2 个、第 4 个新建)在这个版本上跑过;stable 标签来自扫描该版本的 dist/*.d.ts。默认并发为 1、invalidate 严格相等、回收最长两倍 TTL 这几条是对行为的描述,别当作跨版本的契约。