Jest: 跨进程缓存写竞争条件

创建于 2017-09-07  ·  79评论  ·  资料来源: facebook/jest

您是否要请求功能或报告错误
虫子

目前的行为是什么?
在与新的原子高速缓存写入并行运行测试时,由于多个进程尝试写入同一文件,因此会出现重命名错误。 即使设置了--no-cache选项,它仍然会遇到重命名错误,因为它仍在尝试写入文件。

预期的行为是什么?

  1. 我认为--no-cache不应该写入缓存文件
  2. 跨多个进程的缓存应该不会冲突,或者应该能够重新启动测试。

请提供准确的Jest配置,并提及您的Jest,节点,yarn / npm版本和操作系统。

{
    "clearMocks": true,
    "collectCoverageFrom": [
        "packages/**/src/**/*.{ts,tsx}",
        "!packages/sf-lint/**",
        "!**/*.d.ts"
    ],
    "coverageReporters": [
        "text-summary"
    ],
    "moduleFileExtensions": [
        "ts",
        "tsx",
        "js",
        "json"
    ],
    "setupTestFrameworkScriptFile": "<rootDir>/jestEnvironment.js",
    "transform": {
        "\\.(ts|tsx)$": "<rootDir>/scripts/preprocessTypescript.js",
        "\\.(less|css|svg|gif|png|jpg|jpeg)$": "<rootDir>/scripts/preprocessText.js"
    },
    "testRegex": "(Spec|.spec).tsx?$"
}

开玩笑21.0.1
节点6.9.2
纱线0.27.x / 1.0.0
操作系统Windows

Help Wanted Windows

最有用的评论

只是要插手-我在Windows Jenkins CI服务器上看到的笑话是23.6。

  • --runInBand确实可以工作,但是会使测试时间加倍,所以它不是很好,而且由于我们在推送之前进行了测试,因此如果不让我的团队成员感到非常沮丧就无法启用它
  • @jsheetzati (https://github.com/facebook/jest/issues/4444#issuecomment-370533355)提到的package.json的graceful-fs重写可以工作,但这有点麻烦

由于graceful-fs在此方面做得并不多(https://github.com/isaacs/node-graceful-fs/pull/131自去年7月以来就没有采取行动),也许是时候分叉了? 我在那儿添加了一个小小的评论,但我不希望这会使任何人突然跳出来进行整理)):

所有79条评论

我不相信我相信我们在回购中看到的情况是,针对两个不同的进程(并行运行)模拟了完全相同的文件,这会导致高速缓存写操作失败,因为一个进程已锁定文件。 该票证看起来更多是关于具有相同内容的不同文件。 在我们托管的存储库中,没有任何遇到此问题的案例。

我们的测试基本上遇到了相同的问题。 一种简单的重现方法是删除jest cacheDirectory以在下次运行时强制生成高速缓存。
测试套件无法运行

jest: failed to cache transform results in:

C:/ myniceproject / src / jest-cache / jest-transform-cache-b2e8f1f700b9bd266a0d27bb01b47a2b-34a7e3d71d38ff01f65fdb5abdf5126b / 3f / settingsProvider_3f1439e55275a95ecfdb7dcb4327
失败消息:EPERM:不允许操作,重命名
'C:\ myniceproject \ src \ jest-cache \ jest-transform-cache-b2e8f1f700b9bd266a0d27bb01b47a2b-34a7e3d71d38ff01f65fdb5abdf5126b \ 3f \ settingsProvider_3f1439e55275a95ecfdb7dcb432
->
'C:\ myniceproject \ src \ jest-cache \ jest-transform-cache-b2e8f1f700b9bd266a0d27bb01b47a2b-34a7e3d71d38ff01f65fdb5abdf5126b \ 3f \ settingsProvider_3f1439e55275a95ecfdb7dcb432

有同样的问题,找不到解决方法。 这样的笑话对我们基本上是无法使用的。

我们正在尝试从20.0.4更新到21.2.0,现在在构建服务器上出现以下错误:

Test suite failed to run
[13:46:50]
[13:46:50]    jest: failed to cache transform results in: C:/TeamCity/buildAgent/temp/buildTmp/jest/jest-transform-cache-c60bb8ad55f1dbc29115038800657f2f-4895fc34da3cd9ad1e120af622bca745/3b/fe-selectors_3b56db772e798e2e4d0c9fc7c9e4a770
[13:46:50]    Failure message: EPERM: operation not permitted, rename '...\jest\jest-transform-cache-c60bb8ad55f1dbc29115038800657f2f-4895fc34da3cd9ad1e120af622bca745\3b\fe-selectors_3b56db772e798e2e4d0c9fc7c9e4a770.1701848979' -> '...\jest\jest-transform-cache-c60bb8ad55f1dbc29115038800657f2f-4895fc34da3cd9ad1e120af622bca745\3b\fe-selectors_3b56db772e798e2e4d0c9fc7c9e4a770'
[13:46:50]      
[13:46:50]      at Object.fs.renameSync (fs.js:772:18)
[13:46:50]      at Function.writeFileSync [as sync] (node_modules/write-file-atomic/index.js:192:8)

我现在有相同的问题测试被随机破坏。

如果我使用--runInBand标志运行测试,则按预期进行就可以了。

我可以相当一致地看到相同的问题:

  ● Test suite failed to run

    jest: failed to cache transform results in: .../jest-transform-cache-...
    Failure message: EPERM: operation not permitted, rename '...' -> '...'
        at Error (native)

      at Object.fs.renameSync (fs.js:810:18)
      at Function.writeFileSync [as sync] (node_modules/write-file-atomic/index.js:192:8)

开玩笑21.2.1
节点6.11.1
操作系统Windows

--no-cache无效,仍在写入jest-transform-cache 。 唯一有帮助的是--runInBand ,这对于大型项目几乎是不可接受的。

我们能做些什么来帮助诊断问题? 我应该创建一个复制案例吗?

这个错误严重吗? 可以将其视为警告,而不是取消整个测试套件吗? 是否有退后再试的方法?

有一个小的复制品会很棒

这是复制品: https :
它有效地通过babel-jest运行lodash-es来填充转换缓存。
在两个不同的计算机(Win8.1和Win10)上,这对我来说80%的时间失败。
如果删除--no-cache它会在100%的时间内失败。 添加--runInBand将其降低到0%。

(出于好奇,尝试在Win10上的WSL中运行它,并且使用Posix API无法重现该问题)

这只是在Windows上发生吗? 我无法访问虚拟机以外的Windows机器,因此对我来说不是最容易调试的...

@jeanlauliac您在#4088中添加了write-file-atomic ,您能提供帮助吗?

只是运行了procmon跟踪,这是问题的一个示例:

一天中的时间| 流程名称| PID | 操作| 路径结果详情
-| -| -| -| -| -| -
16:54:43.2304011 | node.exe | 7624 | SetRenameInformationFile | ... \ constant_ee286bbcf367680ce61a90e506522f92.82986661 | 成功| ReplaceIfExists:正确,文件名:... \ constant_ee286bbcf367680ce61a90e506522f92
16:54:43.2305499 | node.exe | 8208 | SetRenameInformationFile | ... \ constant_ee286bbcf367680ce61a90e506522f92.103872574 | 拒绝访问| ReplaceIfExists:正确,文件名:... \ constant_ee286bbcf367680ce61a90e506522f92

如您所见,两个进程试图在1ms之内重命名同一文件,第二个进程失败。

我认为npm / write-file-atomic#22解决了writeFile()的异步版本,但是writeFileSync()仍然受到影响。

是否有可能创建一个复制说明,表明仅对同一文件使用worker-farm中的write-file-atomic以某种方式失败? 针对该项目提出一个问题将是很棒的,因为我认为这应该是解决方法。

或者,如果您可以在开玩笑的情况下编写一个测试,该测试显示出可能也是一个开始的相同错误(我们有Appveyor CI)?

我什至不确定这种错误情况下我们想要什么行为。 重试写入? 重新运行测试? 整个测试文件?

好的,我将尝试创建另一个副本。 不确定是否可以创建一个开玩笑的测试,因为它将需要产生多个进程,禁用/清理缓存并保持运行直到失败。

我什至不确定这种错误情况下我们想要什么行为。

好吧,首先,当--no-cache打开时,甚至不应该发生此问题,因为不应填充缓存。
其次,我不确定是否可以正确地重试同步操作-是否可以使用writeFile()代替writeFileSync() ? 这样, write-file-atomic应该会自动重试(我将创建一个测试以确认)。

好吧,首先,当--no-cache打开时,甚至不应该发生此问题,因为不应填充缓存。

这是个好主意,应单独修复。 这样, --no-cache至少可以解决。

其次,我不确定是否可以正确地重试同步操作-是否可以使用writeFile()代替writeFileSync()

@cpojer关于使其不同步的想法? 不确定如何缩放。 或者,如果您对如何解决此问题有其他想法

  • --no-cache实际上更像--reset-cache 。 这意味着它不会使用现有的缓存,但仍会写入缓存。 我想保留那个。
  • 这些操作必须同步,因为它们发生在用户代码中的require调用期间,所以我们不能更改它。

这是带有worker-farmwrite-file-atomic的另一个repro: https :

到目前为止的发现:同步版本失败,但令人惊讶的是异步版本也失败。 这意味着他们可能仅在重试队列在同一进程中运行时才实现重试队列,这在我们的案例中无济于事。

我想保留那个。

新国旗? 这是一个极具误导性的名称。 而在例如CI上,您无论如何都很少想要缓存,因此它只是浪费资源。 还是在--no-cache期间使用了一次测试运行中生成的缓存,而只忽略了现有的缓存?

这是另一个带有worker-farmwrite-file-atomic复制品

太棒了! 您能针对写文件原子性提出一个问题吗? 感觉应该在这里进行修复,如果不行(他们不想支持多个进程同时编写),我们可以在我们这儿进行访问。 WDYT?

我在本地尝试的似乎有效的补丁程序会忽略该错误,如果它来自尝试重命名为具有相同内容的文件。 由于这只是意味着另一个过程“赢得了比赛”,我们可以忽略它并继续前进。

const cacheWriteErrorSafeToIgnore = (
  e: Error,
  cachePath: Path,
  fileData: string,
) => {
  if (
    e.message &&
    e.message.indexOf('EPERM: operation not permitted, rename') > -1
  ) {
    try {
      const currentContent = fs.readFileSync(cachePath, 'utf8');
      return fileData === currentContent;
    } catch (e) {
    }
  }
  return false;
};

@SimenB ,当然,我会提出问题。

@cpojer ,这个错误是否可以被吞没/忽略并视为警告? 这意味着该文件已被写入,不应丢失任何数据。

上游问题:npm / write-file-atomic#28

我认为这意味着“重命名”不是Windows上的原子操作,因此它打破了write-file-atomic的假设。 除非有一个可以在Windows API级别启用的标志,否则这意味着不可能在Windows上完全进行原子写入/重命名。

@jwbay您的解决方案对我来说看起来很合理! however但是,我将使用e.code === 'EPERM' (而不是使用indexOf e.code === 'EPERM' (更可靠,不取决于特定消息)。 我认为我们不应该再次读取文件来检查该值,因为这可能会带来其他并发问题(例如,如果文件是同时由另一个进程写入的)。 您介意发送PR吗?

我正准备以“ write-file-atomic ”的名义开始PR的工作,“如果要求我们写文件同步,但是已经在异步写入队列中,请保释”(也许有一个选择开启行为)。 但是,如果我们乐于在开玩笑的水平上解决这个问题,我不会着急。 cc @jeanlauliac

我正准备按照“如果我们被要求写文件同步,但已经在异步写入队列中,请保释”来进行写文件原子的PR的工作(也许可以选择开启行为)。

我认为添加此逻辑(本地队列)不会解决此问题,因为大多数情况下,当不同的进程尝试写入(重命名)同一文件时会发生这种情况。

为了一劳永逸地解决并发问题,我们可能必须重新考虑我们如何进行缓存,例如,有一个访问缓存的进程,我们通过某种IPC与之通信。 现有的键/值存储系统可能很方便,例如memcached

我认为添加此逻辑(本地队列)不会解决此问题,因为大多数情况下,当不同的进程尝试写入(重命名)同一文件时会发生这种情况。

嗯,那我可能误会了这个问题。 按照我的阅读方式,该库已经具有一种排队机制,该机制可以很好地处理异步请求,但是如果您将同步请求混合使用(也可以)会发生冲突。

我上面引用的拉取请求应该可以解决此问题。 至少它为我做到了!

@mekwall ,我认为他们在writeFile()rename() writeFile() ,但在我的测试中仍然失败: https

@asapach您尝试了我的更改吗? 因为我尝试了几次,所以每次更改都不会得到EPERM: operation not permitted, rename更改。

@mekwall ,是的,您

确切地说,从技术上讲它不会失败(因为同步流不会中断),但是控制台仍然充满EPERM错误。

@asapach我发现了您遇到的问题。 在graceful-fs polyfill中。 我已经在此PR中发布了修复程序: https :

@mekwall ,是的,这似乎确实解决了这个问题-同步版本和异步版本中都没有更多错误。
现在的问题是临时文件没有被删除,因为从未调用fs.unlinkSync(tmpfile)https :

@asapach我添加了取消链接到graceful-fs重命名的功能,但是我不确定这是否是正确的方法。 Afaik fs.rename使用MoveFile函数,并且不应将源复制到目标。 源只应更改名称,而源和目的地不应同时存在。

@mekwall确实有帮助,但是在某些情况下,如果工作器提前终止(因为所有工作都已完成),则不会清理某些文件,因为它不等待清理。 异步版本似乎工作正常。

@asapach根本没有按预期工作。 我需要深入研究节点的内部知识,以弄清它实际上是如何工作的以及预期的行为应该是什么。 我相信graceful-fs的全部目的是使它在每个平台上均能正常工作,因此我将对此进行更深入的研究。 至少我们找到了罪魁祸首:)

@asapach我意识到我无法使用write-file-atomic PR,所以我采取了另一种方法,在graceful-fs添加fs.renameSync graceful-fs ,其解决方法与fs.rename但阻塞。 这使您的测试按预期进行!

@mekwall ,谢谢,我已经验证了您对两个
我认为不利的一面是同步处理会占用更多的CPU和磁盘,但是这可能是预期的。

感谢许多人对此进行整理并帮助修复它。 非常感激! ❤️希望graceful-fs中的修复程序是正确的,并且它会被发布。

@SimenB不客气! 我们在工作中为此感到痛苦,所以我有一些时间由团队进行调查。 更改将影响很多程序包,因此它们很可能需要一些时间才能接受它:/

知道什么时候可以解决该问题吗?

@cpojer您能提供一些更多信息为什么关闭它吗? 是否提供修复程序? 我们仍然有这个问题

抱歉,此修补程序似乎尚未落入graceful-fs中:(

是否可以有多个人确认使用https://github.com/isaacs/node-graceful-fs/pull/119可以解决他们的问题?

您可以通过使用纱线分辨率来使用前叉,请参阅https://yarnpkg.com/en/docs/selective-version-resolutions ,它应允许您将修订部署到CI等。

例如

{
  "resolutions": {
    "graceful-fs": "mekwall/node-graceful-fs#a10aa576f771d7cf3dfaee523f2e02d6eb11a89f"
  }
}

@SimenB它为我解决了问题,至少least

我也+1。

@SimenB还解决了我的问题,现在我可以在Windows上使用Jest 22。 (在此之前,我们停留在20点)。

编辑:实际上,它可以在我的开发笔记本电脑上使用,但不能在构建服务器上使用。 它运行的是纱线1.2.1,也许那是为什么?

[16:47:55][Step 5/8]     jest: failed to read cache file: D:/TeamCity/buildAgent2/temp/buildTmp/jest/jest-transform-cache-c39254d365b4bcb2c90f133d4b359d91-56a1804d8d831b3401a35a7e81857f3b/7e/rafPolyfill_7e7a83ed3f2680ba9aec0e45f59ade5d
[16:47:55][Step 5/8]     Failure message: EPERM: operation not permitted, open 'D:\TeamCity\buildAgent2\temp\buildTmp\jest\jest-transform-cache-c39254d365b4bcb2c90f133d4b359d91-56a1804d8d831b3401a35a7e81857f3b\7e\rafPolyfill_7e7a83ed3f2680ba9aec0e45f59ade5d'
[16:47:55][Step 5/8]       
[16:47:55][Step 5/8]       at readCacheFile (node_modules/jest-runtime/build/script_transformer.js:465:60)

纱线1.0.0应该足够了,但是值得尝试升级

只是试图提出决议,但对我来说仍然失败。 但是我同时违反了ENOENT和EPERM:

    jest: failed to read cache file: C:/Users/dev/AppData/Local/Temp/jest/jest-transform-cache-857f905b2da01d52a9d1d17b6772ea4a-3a91587e29d4fef23c6e0cf16b2f5679/7d/index_7d0afc82f0b29ec31c4b5f296cbdee74
    Failure message: ENOENT: no such file or directory, open 'C:\Users\dev\AppData\Local\Temp\jest\jest-transform-cache-857f905b2da01d52a9d1d17b6772ea4a-3a91587e29d4fef23c6e0cf16b2f5679\7d\index_7d0afc82f0b29ec31c4b5f296cbdee74'

      at Object.fs.openSync (../fs.js:653:18)
      at Object.fs.readFileSync (../fs.js:554:33)

    jest: failed to read cache file: C:/Users/dev/AppData/Local/Temp/jest/jest-transform-cache-857f905b2da01d52a9d1d17b6772ea4a-3a91587e29d4fef23c6e0cf16b2f5679/c4/std_pb_c403e6e7645c904896b66f44a3e43606
    Failure message: EPERM: operation not permitted, open 'C:\Users\dev\AppData\Local\Temp\jest\jest-transform-cache-857f905b2da01d52a9d1d17b6772ea4a-3a91587e29d4fef23c6e0cf16b2f5679\c4\std_pb_c403e6e7645c904896b66f44a3e43606'

      at Object.fs.openSync (../fs.js:653:18)
      at Object.fs.readFileSync (../fs.js:554:33)

@mreishus您的构建服务器是否运行Windows? 因为graceful-fs的修复程序仅适用于Windows,但不应在基于Linux的操作系统上进行。

@mekwall是的,

这对我来说是个主要问题,自2016年11月以来,我看到的graceful-fs没有任何反应。 因此,我感到非常悲观,因为提供的修复程序-i标志和解决方法之外,是否有任何暂时可用的解决方案?

--runInBand对您@frenic无效吗?

-i ,是的,它可以工作。 但是不幸的是,从长远来看,对于大型项目而言,这是不可持续的。

我想我们可以分叉并发布自己的,但似乎该修复程序并不适合所有人

我也遇到同样的情况,但就我而言,--runInBand不起作用。

我已经用最新版本的Jest检查了graceful-fs替代,但是不幸的是,自从我上次测试以来,它似乎不再像以前那样可靠。 在大型测试套件中,它仍然有非零的机会进入竞争状态。

滚动浏览此线程后,我找到了使用yarn的分辨率。 是否有使用npm的分辨率?

到目前为止,我们已经很幸运了,只是将graceful-fs的修补版本添加到我们的package.json中。 为我们使用npm和yarn。

"graceful-fs": "https://github.com/mekwall/node-graceful-fs.git#patch-1",

你好

由于某些原因,我们仅在从Jenkins运行时才收到此错误,而在本地运行时(即使在同一台计算机/文件夹等上)也不会出现此错误。
@jsheetzati的解决方案也适用于我们(使用npm),但这毕竟是一个补丁。 是否有ETA可以永久解决此问题?

谢谢,

从詹金斯(Jenkins)开玩笑时,我们也遇到这个问题。 --runInBand选项有助于避免在单个作业执行期间失败,但在并行运行多个构建时,玩笑仍然会失败。
作为一种解决方法,我们使用可锁定资源插件来确保仅执行一个jest进程,同时保留--runInBand选项。
希望此评论对某人有用。

@nyrkovalex为了避免您正在描述的问题,我们要做的是使用Jest的缓存目录选项来确保不在工作空间之间共享缓存。

为此,我们发布了一个设置cacheDirectory: '<rootDir>/.jest-cache'的Jest预设,并确保所有程序包都使用它。 如果这样做,请确保将.jest-cache.gitignore

在添加该选项之前,由于每个Jenkins代理在16个执行程序之间共享全局Jest缓存,因此会遇到几个问题。 使用可锁定的资源还可以防止出现您提到的问题,但由于您没有充分利用Jenkins代理的潜力,因此非常浪费(因为Jest测试成为瓶颈)。

@ anescobar1991该选项绝对是一个更好的解决方案,我们将考虑使用它。
多谢小费!

你好

我们使用gradle运行npm(不要问为什么:)),而将其与Jenkins结合使用将是一个杀手。
我们尝试了:

  1. 将缓存设置为本地目录,而不是全局缓存
  2. 使用--runInBand
  3. 在代理上仅运行单个作业-没有并行作业
  4. 运行gradle测试--max-workers 1(并且不使用--parallel)

所有失败均出现相同的错误。
唯一适用于我们的解决方案是@jsheetzati提出的解决方案-我希望此问题能得到正式解决。

我们可能可以用该修复程序进行分叉和发布

那将是真棒...

我有很多这个问题,优美的fs补丁对我有用。 所以,我将不胜感激。

作为解决graceful-fs的一种解决方法,您是否不能简单地将每个工作进程/线程赋予它自己的缓存目录以避免竞争情况?

它可能很慢,但是我们必须在CI服务器上使用--runInBand,这更糟。

如果有人可以指出要查看的正确文件,我什至可以尝试编写补丁。 我很难开玩笑的来源。

我不确定这是什么,但是已经过了几周,可能是几个月,而且我再也没有观察到失败了。 我们使用jest 22.4.2已有一段时间,最近又升级到22.4.4。 我们还更新了其他各种软件包。

只是要插手-我在Windows Jenkins CI服务器上看到的笑话是23.6。

  • --runInBand确实可以工作,但是会使测试时间加倍,所以它不是很好,而且由于我们在推送之前进行了测试,因此如果不让我的团队成员感到非常沮丧就无法启用它
  • @jsheetzati (https://github.com/facebook/jest/issues/4444#issuecomment-370533355)提到的package.json的graceful-fs重写可以工作,但这有点麻烦

由于graceful-fs在此方面做得并不多(https://github.com/isaacs/node-graceful-fs/pull/131自去年7月以来就没有采取行动),也许是时候分叉了? 我在那儿添加了一个小小的评论,但我不希望这会使任何人突然跳出来进行整理)):

我遇到相同的问题,但错误消息不同
Failure message: EBADF: bad file descriptor, close

jest: failed to cache transform results in: C:/agent/_work/_temp/jest/jest-transform-cache-2a12bf9796741cb06fb97a276aa09712-7d718cda2d798ae78eb741b3feff799e/7b/test-setup_7bdb1937d8dbbf5088142bc21e74a530
2019-01-24T13:44:55.5496091Z     Failure message: EBADF: bad file descriptor, close

似乎用--runInBand运行玩笑不会第一次解决问题,但只有在再次运行后,它才会正确执行。

作为TFS Build的一部分在Windows 10 Enterprise VM上运行。

@EthanSankin您也可以使用链接的graceful-fs PR进行测试吗?

我正在为graceful-fs的替代品解决这些问题。 它目前处于Alpha状态,但希望能有一些早期采用者会很棒: https

恢复为旧版本的write-file-atomic解决了该问题。

@moizgh从什么版本到什么版本?

@moizgh从什么版本到什么版本?

2.4.2至2.3.0

@iarna似乎引入了som回归。

同样遇到此问题,对更好/永久性修复的任何见解?

在过去的几个月中,对于我们来说,这又是又一次开始了-Windows-非常间歇。

write-file-atomic不再使用graceful-fs-也许与此有关吗?

此页面是否有帮助?
0 / 5 - 0 等级