You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala使用os.lib.watch监听目录改名触发ClosedByInterruptException如何解决

问题原因
  • 回调线程限制:os.watch的事件回调默认运行在JNA底层调用的线程中,该线程对执行时长有限制,不允许执行阻塞IO类操作,运行超时会被系统主动中断,触发ClosedByInterruptException异常
  • 文件操作时序问题:文件名变更事件触发时,操作系统尚未完成重命名的收尾操作,文件句柄还处于锁定状态,此时直接读取会失败
  • 依赖版本不兼容:你当前使用的os-lib 0.7.8和os-lib-watch 0.4.2版本不匹配,底层API调用逻辑不一致也会引发异常
解决方案

1. 修正依赖

新版本os-lib已经内置目录监听能力,不需要单独引入os-lib-watch,sbt依赖修改为:

"com.lihaoyi" %% "os-lib" % "0.9.1"

如果要保留单独的os-lib-watch依赖,可将os-lib版本调整为0.4.2,保持两者版本一致即可。

2. 优化代码逻辑

  • 单独启动线程池处理文件读取逻辑,不阻塞回调线程
  • 读取文件前加短暂延迟,确保文件操作完成
  • 增加异常捕获逻辑,兼容偶发的文件占用场景

修复后参考代码:

import java.util.concurrent.Executors
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try

object Main extends App {
  // 单独创建线程池处理文件IO操作
  private val ioPool = Executors.newCachedThreadPool()
  private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(ioPool)

  def readFileContent(file: os.Path): Unit = {
    println("Reading Input Json..")
    // 加100ms延迟,等待操作系统完成文件操作
    Thread.sleep(100)
    Try {
      val jsonString = os.read(file)
      val data = ujson.read(jsonString)
      println(data)
    }.recover {
      case e: Exception => println(s"读取文件失败: ${e.getMessage}, 路径: $file")
    }
  }

  def processFile(filePath: os.Path): Unit = {
    println("FileName:" + filePath)
    // 异步提交到IO线程池执行,不阻塞回调线程
    Future(readFileContent(filePath))
  }

  os.watch.watch(
    roots = Seq(os.pwd / "output"),
    onEvent = events => events.foreach { event =>
      processFile(event.path)
    }
  )

  // 保持主线程不退出
  Thread.currentThread().join()
}
补充说明
  • 如果只需要监听文件名变更事件,可以在回调中增加事件类型判断,过滤不需要处理的其他事件
  • 生产环境使用建议给线程池设置合理的线程数和拒绝策略,避免短时间大量事件触发导致OOM

内容的提问来源于stack exchange,提问作者uxke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 18:27:03