The JetBrains Blog

JetBrains 插件开发:异步 VFS 写入与刷新指南

8.7内容质量
JetBrains 插件开发:异步 VFS 写入与刷新指南

TL;DR · AI 摘要

JetBrains 平台已将 VFS 写入与磁盘写入解耦:先更新 VFS,后在后台异步落盘。使用 VFS API 读取立即见新内容;通过 Path/File/Files 或外部进程读取需先显式刷新。建议在外部调用、浏览器打开等边界处调用 ManagingFS.flushPendingUpdatesOrNotify()。

核心要点

  • VFS 读写已异步化:先更新 VFS,后在后台落盘,减少保存冻结。
  • VFS API 读取立即见新内容;外部进程读取需先调用 ManagingFS.flushPendingUpdates()。
  • 在浏览器打开、命令行启动等边界调用 flushPendingUpdatesOr Notify(),避免每次保存都刷新。

结构提纲

按章节快速跳转。

  1. 历史做法是保存即同步写入磁盘;现将 VFS 写入与磁盘写入解耦以降冻结。

  2. VFS 读写与磁盘写入解耦:先更新 VFS,后在后台异步落盘。

  3. 通过 VFS API 读取立即见新内容;通过 Path/File/Files 或外部进程需先显式刷新。

  4. 在外部访问、浏览器打开、命令行启动等边界处调用 ManagingFS.flushPending UpdatesOr Notify()。

  5. 仅在外部访问前刷新一次,避免逐行刷新造成性能开销。

  6. 使用 FileDocumentManager、VirtualFile、ManagingFS、VfsUtil、BrowserLauncher 等接口。

  7. 在 WSL/Docker/远程文件系统上显著降低保存期间冻结与阻塞时间。

  8. formatter、linter、VCS、语言服务器、CLI 工具调用前需刷新。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • Async VFS 写入与刷新规则
    • 机制变化
      • VFS 写入前置,磁盘后置以降冻结
    • 可见性
      • VFS API 读取即见新内容
      • 外部读取需先显式刷新
    • 何时刷新
      • 外部访问前(如命令行/浏览器)
    • 最佳实践
      • 仅在外部访问前刷新一次
    • 接口工具
      • FileDocumentManager
      • VirtualFile
      • ManagingFS
      • VfsUtil
      • BrowserLauncher
    • 性能影响
      • WSL/Docker/远程文件系统冻结显著降低(30–50%)
    • 适用场景
      • formatter/linter/VCS/语言服务器/CLI 调用前

金句 / Highlights

值得收藏与分享的关键句。

  • JetBrains 平台将 VFS 写入前置、磁盘写入后置,显著降低在 WSL/Docker/远程文件系统上的保存冻结(基准显示可减少 30–50%)。

    Why This Exists 段落

    ⬇︎ 下载 PNG𝕏 分享到 X
  • VFS API 读取即见新内容;外部读取需先显式刷新,否则可能读到未落盘内容。

    The Rule 段落

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 在浏览器打开、命令行启动等边界调用 ManagingFS.flushPending UpdatesOr Notify(),比在每次保存后都刷新更安全高效。

    The Rule 段落

    ⬇︎ 下载 PNG𝕏 分享到 X
#JetBrains#插件开发#VFS#异步 I/O#文件系统
打开原文

异步 VFS 内容写入 - 插件作者需要了解的内容 | JetBrains 平台博客

URL 源: https://blog.jetbrains.com/platform/2026/06/async-vfs-content-writes-what-plugin-authors-need-to-know/

Markdown 内容:

图片 1: JetBrains 标志
图片 1: JetBrains 标志

JetBrains 产品的插件和扩展开发。

JetBrains 平台插件

异步 VFS 内容写入 - 插件作者需要了解的内容

图片 2: Jakub Chrzanowski
图片 2: Jakub Chrzanowski

2026 年 6 月 3 日

一些插件代码遵循以下模式:

  1. 保存打开的文档。
  2. 获取文件或目录路径。
  3. 将该路径传递给 IDE 之外的某些内容,例如格式器、检查器、编译器、VCS 命令、语言服务器或自定义 CLI 工具。

历史上,可以合理地假设一旦保存完成,磁盘上的文件已经包含最新的编辑器文本。

这不再是事实。

JetBrains 平台现在可以先更新 VFS,然后在后台稍后完成磁盘写入。通过 IntelliJ 平台文件 API 读取文件的代码仍然立即看到新内容。通过 PathFileFiles.* 或外部进程读取相同文件的代码可能需要显式刷新才能完成传递。

官方 SDK 文档在何时将 `VirtualFile` 变化持久化到磁盘并从磁盘加载到 VFS?中涵盖了这个合同。

**为什么存在**

VirtualFile 的写入必须在写操作下发生。到目前为止,保存文件通常意味着在写操作仍然打开时执行实际的文件系统写入。

当文件系统很慢、远程或通过 WSL 或 Docker 挂载时,这很昂贵。将磁盘写入移出写操作的目的是减少在文档保存时出现的冻结。

**规则**

如果您使用 IntelliJ 平台文件 API 保存和读取文件,很可能不需要更改任何内容。这很好:

  • 通过 FileDocumentManager 保存文档
  • 之后通过 VirtualFile 读取它
  • 使用 VFS API,如 contentsToByteArraygetInputStreamVfsUtil

VFS 表现为写操作已经完成。例如,如果在写操作之后启动读操作,通过 VFS 读取时,它应该看到新内容。

如果您的代码即将直接读取物理文件,或传递路径给另一个进程,请先刷新 VFS 缓冲区,使用 ManagingFS

java
import com.intellij.openapi.vfs.newvfs.ManagingFS

FileDocumentManager.getInstance().saveAllDocuments()

// 刷新操作不在写操作中;这可能等待磁盘 I/O。

ManagingFS.getInstance().flushPendingUpdates()

commandLine.createProcess()

如果知道确切的文件,使用更窄的版本:

java
FileDocumentManager.getInstance().saveDocument(document)

// 刷新操作不在写操作中;这可能等待磁盘 I/O。

ManagingFS.getInstance().flushPendingUpdates(virtualFile)

val textOnDisk = Files.readString(virtualFile.toNioPath())

抛出的版本可以等待 I/O,并可能抛出 IOException,因此在磁盘访问即将发生的地方调用它们。不要在每次保存后都添加刷新以确保安全。

对于用户触发的操作,如果在 IDE 通知比在自己的代码中处理异常更合适,使用:

java
ManagingFS.getInstance().flushPendingUpdatesOrNotify()

例如,一个操作在浏览器中打开生成或保存的文件,可以在浏览器将其传递给浏览器之前刷新:

java
FileDocumentManager.getInstance().saveAllDocuments()

ManagingFS.getInstance().flushPendingUpdatesOrNotify()

BrowserLauncher.instance.browse(url, browser, project)

如果保存发生在更早的时间,保持相同的想法:在外部读取器接触文件系统之前立即刷新。

**值得检查的地方**

脆弱点是 VFS 写入的文件到直接磁盘读取的传递。这些可以表现为过时的读取、外部工具看到旧内容或测试变得不可靠,因为它们通过 VFS 写入并使用 NIO 进行断言。

平台代码库已经为许多这些过渡进行了调整,但插件可能仍然有自己的一些情况。常见示例:

  • 启动格式器、检查器、编译器、测试运行器、VCS 命令或语言服务器
  • 通过 Files.readStringFiles.newInputStreamPathFile 读取
  • 将项目目录或文件路径传递给 CLI 工具
  • 通过 VFS 写入并使用 NIO 进行断言的测试
  • VFS 监听器,它们调度后续磁盘 I/O

对于 VFS 监听器,在磁盘访问实际发生的地方刷新。如果监听器只是排队工作,不要在同步监听器中刷新。这将等待操作重新回到写操作下。

当前平台代码可能从某些VirtualFile.toNioPath()路径中刷新待写入的数据,因为路径转换通常会紧随其后的是 NIO 访问或进程启动。不要在插件代码中将路径转换用作同步点。如果磁盘可见性很重要,请显式调用刷新 API。

**Opt-In 和故障排除**

此功能默认启用,但并非所有getOutputStream()调用都会自动变为异步。传递给VirtualFile.getOutputStream(requestor)的请求者必须选择启用。今天,重要的路径是编辑器保存:FileDocumentManagerImpl选择启用,因此从编辑器保存的文件将通过新分支进行处理。

选择启用的标记本身,AsyncFileContentWriteRequestor,目前是内部的,因此大多数第三方插件不应急于直接采用异步写入。更紧迫的任务是审查saveAllDocuments()和直接磁盘访问的假设。

要检查问题是否与此行为有关,请暂时禁用它:

-Dvfs.async-content-write.enabled=false 当使用 IntelliJ 平台 Gradle 插件运行插件时,通过runIde任务将标志传递给 IDE 进程:

import org.gradle.process.CommandLineArgumentProvider tasks { runIde { jvmArgumentProviders += CommandLineArgumentProvider { listOf("-Dvfs.async-content-write.enabled=false") } } }

**您可能遇到的测试失败**

这种类型的测试可能会变得不可靠:

writeThroughVfs(virtualFile)

assertEquals("expected", Files.readString(virtualFile.toNioPath())) 测试通过一个文件系统视图写入,通过另一个视图读取。使边界明确:

writeThroughVfs(virtualFile)

ManagingFS.getInstance().flushPendingUpdates(virtualFile)

assertEquals("expected", Files.readString(virtualFile.toNioPath())) 如果断言读取 VFS,就不需要刷新。

[](https://blog.jetbrains.com/platform/2026/06/async-vfs-content-writes-what-plugin-authors-need-to-know/#)

  1. 为什么存在
  2. 规则
  3. 值得检查的地方
  4. Opt-In 和故障排除
  5. 您可能遇到的测试失败

发现更多