Class LoomCacheService

java.lang.Object
net.fabricmc.loom.util.gradle.LoomCacheService
All Implemented Interfaces:
org.gradle.api.services.BuildService<org.gradle.api.services.BuildServiceParameters.None>

public abstract class LoomCacheService extends Object implements org.gradle.api.services.BuildService<org.gradle.api.services.BuildServiceParameters.None>
build 作用域的共享缓存编排服务.

在 JVM 内按 key 去重,并叠加跨进程文件锁,使同一缓存条目的生产在「单 daemon --parallel」与 「多 daemon」下都串行化。读已发布缓存的路径由调用方自身的存在性检查无锁完成,无需经过此服务。

  • Field Details

  • Constructor Details

    • LoomCacheService

      public LoomCacheService()
  • Method Details

    • getAsyncCache

      public <T> AsyncCache<T> getAsyncCache(String cacheName)
      返回当前 Loom classloader 作用域内按名称共享的异步缓存.

      调用方必须保证同一名称始终使用同一种值类型。服务注册名包含 classloader 身份, 因而不会读取另一插件 classloader 创建的强类型缓存实例。

    • runExclusive

      public void runExclusive(Path lockRoot, String key, Duration timeout, Callable<Void> action) throws Exception
      在按 key 取得的「JVM 监视器 + 跨进程文件锁」保护下执行一个互斥动作.

      读路径应由调用方自身的存在性检查无锁完成;仅在判定需要生产时才调用本方法取锁。 各 provider 以 shouldRefreshOutputs / Files.exists 作为无锁快路径, 配合产物的原子发布(写入即完整),保证读方不会看到半成品。

      Parameters:
      lockRoot - 锁文件目录
      key - 互斥 key
      timeout - 等待跨进程锁的超时时间
      action - 取得锁后执行的动作
      Throws:
      Exception
    • runExclusiveChecking

      public void runExclusiveChecking(Path lockRoot, String key, Duration timeout, Callable<Boolean> upToDate, Callable<Void> onUpToDate, Callable<Void> produce) throws Exception
      双检锁形式执行「先检查、再生产」:已就绪时连锁都不取.

      为什么需要它:runExclusive(java.nio.file.Path, java.lang.String, java.time.Duration, java.util.concurrent.Callable<java.lang.Void>) 是纯串行——后到的调用方必须先拿到锁, 才能进入临界区做 isUpToDate() 检查,然后立刻退出。干净环境(CI 首次构建)下 N 个项目配置同一份 Minecraft 时,它们算出同一个 key,于是第 2..N 个项目各自白等 「第一个项目做完整条 download→remap」的时间,而这个时间远超锁的 1 分钟超时。

      双检锁把等待变成「只等第一个项目生产完」,而不是「每个都排一遍队」:

      1. 锁外检查:已就绪 → 直接跑 action 之外的回退分支,不取锁;
      2. 未就绪 → 取锁(可能等前一个项目生产完);
      3. 锁内重检:若前一个项目已经把产物做好,就跳过生产。这是关键一步: 没有它,第 2..N 个项目拿到锁后会把同一份产物再重建一遍。

      两次检查都必须是纯只读的存在性/脏标志判断(调用方保证),不得在锁外写共享缓存。

      Parameters:
      lockRoot - 锁文件目录
      key - 互斥 key
      timeout - 等待跨进程锁的超时时间
      upToDate - 只读就绪判定;锁内会被再次调用
      onUpToDate - 已就绪时的动作(跑在锁外,应做「读已发布产物」这类无副作用的事)
      produce - 需要生产时的动作(跑在锁内)
      Throws:
      Exception
    • defaultTimeout

      public static Duration defaultTimeout()
      共享缓存锁的默认等待超时.

      可通过系统属性 fabric.loom.lock.timeout.minutes 覆盖 (在 gradle.properties 中写 systemProp.fabric.loom.lock.timeout.minutes=10)。 未配置时:CI 上 10 分钟(干净环境首次构建要下载、解压、重映射整份 MC, 实测在 CI runner 上远超 1 分钟;原先按「CI 上不太可能解锁」取的 1 分钟正是 多项目并发配置时超时的直接原因),本地 30 分钟(容忍首次冷构建的慢速下载, 但避免在异常持有者上无限等待——配合等待日志中的持有者 PID 可手动处置)。

    • modDepsKey

      public static String modDepsKey(File rootDir, String mappingsIdentifier)
      mod 依赖重映射写 LOCAL 共享 remapped_mods 缓存时的互斥 key(按根项目目录 + mappings 标识分).

      同一 checkout 的多个并发构建(如同时跑 client 与 server)共享该 key、串行化写入; 不同 checkout 根目录不同 → key 不同 → 互不阻塞。当前唯一的取锁点是 SourceRemapper.remapAll(sources 缓存);二进制产出已改由 L3 任务持有产出目录、 由任务图串行化,不再使用本 key。key 由本助手统一生成,避免两处写法漂移。

    • get

      public static org.gradle.api.provider.Provider<LoomCacheService> get(org.gradle.api.Project project)
      返回当前 Loom classloader 对应的缓存服务.

      同一 Gradle root 可能通过约定插件和直接插件加载多份 Loom。注册名包含当前 classloader 身份,避免从另一 loader 取得不可强转的服务实现;同一 loader 的多个 子项目仍共享服务和内存缓存。跨 daemon 正确性继续由 CacheEntryLock 保证。