Effect 的 ScopedCache 过期后,资源什么时候被释放?

给 ScopedCache 设了 1 分钟 TTL,快进两分钟,日志里却没有出现 close。条目过期了,资源还开着。

条目过期后,只有下一次访问发现它过期,才会先关旧的、再开新的;没有人访问,finalizer 就不会执行。 ScopedCache 的过期是惰性的,不是定时器。

ScopedCache 用在缓存的值本身是需要释放的资源时(连接、文件句柄、子进程)。lookup 在每个条目自己的 scope 里运行,条目被淘汰、失效、过期,或缓存所在的 scope 关闭时,那个条目的 scope 被关闭,finalizer 随之执行。Effect 里资源释放的基本思路见 Effect 怎么保证资源一定被释放?。

怎么看到每个条目的开和关?

用 Effect.acquireRelease 当 lookup,往日志里记 open 与 close:

import { Effect, ScopedCache } from "effect"
import { TestClock } from "effect/testing"

const log: Array<string> = []

const program = Effect.gen(function*() {
  const cache = yield* ScopedCache.make({
    capacity: 10,
    timeToLive: "1 minute",
    lookup: (key: string) =>
      Effect.acquireRelease(
        Effect.sync(() => {
          log.push(`open ${key}`)
          return key
        }),
        () => Effect.sync(() => void log.push(`close ${key}`))
      )
  })

  yield* ScopedCache.get(cache, "c")
  yield* TestClock.adjust("2 minutes")
  log.push("-- expired, untouched")
  yield* ScopedCache.get(cache, "c")
  log.push("-- touched")
  yield* ScopedCache.invalidate(cache, "c")
  log.push("-- invalidated")
  yield* ScopedCache.get(cache, "d")
  log.push("-- leaving scope")
}).pipe(Effect.scoped, Effect.provide(TestClock.layer()))

Effect.runPromise(program).then(() => console.log(log.join("\n")))
// open c
// -- expired, untouched
// close c
// open c
// -- touched
// close c
// -- invalidated
// open d
// -- leaving scope
// close d

过期之后 finalizer 什么时候执行?

看输出:-- expired, untouched 之后没有 close c。快进两分钟,条目早已过期,但 finalizer 没有执行;要等下一次 ScopedCache.get 发现过期,才先关旧的 c,再开新的 c。

之后 invalidate 会立刻关闭那个条目(close c 出现在 -- invalidated 之前),离开外层 scope 时,剩下的条目 d 一并关闭。

对连接数有上限的资源,该怎么用?

不要指望它像定时器那样准点释放。对外部有计费或连接数上限的资源,有两种办法:配合容量上限,或者在你知道不再需要时显式 invalidate。

为什么 ScopedCache 要求外层有 Scope?

它的类型签名要求外层有 Scope,用 Effect.scoped 或 layer 的 scope 提供即可,缓存所在 scope 关闭时所有条目一并释放。layer 里关闭资源的写法,见 Effect 的 Layer 怎么在退出时关闭连接池?。

如果缓存的值不是资源,只是需要按结果设 TTL,用普通的 Cache,见 Effect 的 Cache 怎么让成功和失败用不同的 TTL?。

版本是 effect 4.0.5。上面的 open、close 顺序在 TestClock 下编译并运行过,输出与注释一致。没有验证真实时钟下的行为,也没有验证对真实连接池的效果。