Class DownloadLock

java.lang.Object
net.fabricmc.loom.util.download.DownloadLock
All Implemented Interfaces:
AutoCloseable

public final class DownloadLock extends Object implements AutoCloseable
单个下载目标的跨进程互斥锁.

锁文件里记的是什么

锁文件 <name>.lock 用 Files.createFile(java.nio.file.Path, java.nio.file.attribute.FileAttribute<?>...) 独占创建,创建成功后立刻写入持有者信息 (PID、进程启动时刻、主机名、token)。判活全部基于这份信息:锁文件存在只说明「曾经有人持有」, 与「现在有人在写」是两件事——改造前把两者当成同一件事,于是崩溃残留会让下一次构建白下载一遍, 而真正并发的第二个写入者却直接被判失败。

残留与「正在写」怎么区分

  • 持有者在本机、PID 已不存在(或进程启动时刻与记录不符,说明 PID 被复用)→ 残留,立即接管;
  • 持有者在本机、PID 还活着且不是本 JVM → 正在写,等待;
  • 持有者是本 JVM:本 classloader 的持有表里有它 → 另一个线程在写,等待;表里没有、且锁文件已存在 超过 SAME_JVM_GRACE → 本 JVM 的遗留锁(收尾删除失败或另一份 Loom classloader 已放手),接管;
  • 跨主机(共享缓存盘 / NFS)或持有者信息还没写完 → 无法判活,按锁文件年龄推断: 刚创建视为正在写,超过 EMPTY_CONTENT_GRACE / FOREIGN_STALE_AFTER 视为残留.

为什么等待超时也接管

这把锁只负责去重与礼仪,正确性不依赖它:下载写入本次尝试专用的唯一临时文件, 写完整后原子落位(AtomicFiles),因此两个写入者同时存在 最多重复一次网络传输,不会产出半成品或损坏的文件。既然如此,「持有者挂死也不让构建失败」 就比「等不到锁就抛异常」更合适。

  • Method Details

    • acquire

      public static DownloadLock acquire(Path lockFile, Duration timeout) throws IOException
      取得 lockFile 的独占权,必要时接管残留锁.
      Parameters:
      lockFile - 锁文件路径,父目录会被创建
      timeout - 等待「正在写」的持有者释放锁的最长时间,超时后强制接管
      Returns:
      已持有的锁,调用方必须在 finally / try-with-resources 中关闭
      Throws:
      IOException - 无法创建或接管锁文件时
    • tookOverLiveLock

      public boolean tookOverLiveLock()
      Returns 本次取得锁是否属于「持有者看起来还活着但仍被接管」..

      调用方据此可以加一条提示;下载本身不依赖该信息。

      Returns:
      本次取得锁是否属于「持有者看起来还活着但仍被接管」
    • waitedForLock

      public boolean waitedForLock()
      Returns 取得锁的过程中是否碰过别人留下的锁文件(等过或接管过)..

      调用方据此判断「锁内重检」是否值得做:从未碰到过锁文件,就不可能有人在此期间把产物写好, 重检只是白白多一次 IO(并可能多算一次 hash)。

      Returns:
      取得锁的过程中是否碰过别人留下的锁文件(等过或接管过)
    • close

      public void close()
      Specified by:
      close in interface AutoCloseable