Skip to content

WASM bytecode cache concurrent access #10065

@walkerburgin

Description

@walkerburgin

Describe the bug

Context

We're using the @swc/plugin-formatjs plugin as part of our internationalization system.

Our build system (Bazel + aspect_rules_swc) spawns many, many SWC processes concurrently. Under most circumstances this works great! SWC starts up so fast.

We're currently seeing two different issues related to the WASM bytecode cache.

Issue 1)

SWC won't use the bytecode cache if the directory is read only.

aspect_rules_swc tries to pre-populate the bytecode cache and pass it as as an explicit input to SWC actions. Unfortunately the build system marks this directory as read-only when it's used as an input. From what I can tell SWC is using wasmer's FileSystemCache (here), which fails if the directory is read only (here) and falls back to an in-memory cache every time (which doesn't help us when we're spawning many small, granular actions). The net effect is that we re-prepare the plugin bytecode many thousands of times. The cache is pre-populated, so we know that it won't be written to. It would be nice in this scenario if the cache failed on write instead of failing to initialize altogether.

We've tried to mitigate this by setting jsc.experimental.cacheRoot to a writable dirctory outside of the build system's control (e.g. /tmp/swc) but…

Issue 2)

SWC fails intermittently when multiple processes try to write to the SWC cache at the same time.

It looks like the wasmer FileSystemCache isn't safe for many processes to run concurrently. I put together a standalone repro here that runs swc directly here: https://github.com/walkerburgin/swc-plugin-cache-repro

This fails reliably on my machine:

➜  rm -rf .swc && rm -rf build && seq 0 499 | xargs -P 25 -I{} ./bin/swc-darwin-arm64-v1.10.18 compile --config-json '{"jsc":{"experimental":{ "cacheRoot": ".swc", "plugins":[["./node_modules/@swc/plugin-formatjs", { }]]}}}' --out-file ./build/Component{}.js src/Component{}.tsx
xargs: ./bin/swc-darwin-arm64: terminated with signal 10; aborting

And this succeeds:

# Pre-populate the plugin bytecode cache
➜  rm -rf .swc && ./bin/swc-darwin-arm64-v1.10.18 compile --config-json '{"jsc":{"experimental":{ "cacheRoot": ".swc", "plugins":[["./node_modules/@swc/plugin-formatjs", { }]]}}}' --out-file /dev/null /dev/null

# Then we can run many actions in parallel successfully (and fast!)
➜  rm -rf build && seq 0 499 | xargs -P 25 -I{} ./bin/swc-darwin-arm64-v1.10.18 compile --config-json '{"jsc":{"experimental":{ "cacheRoot": ".swc", "plugins":[["./node_modules/@swc/plugin-formatjs", { }]]}}}' --out-file ./build/Component{}.js src/Component{}.tsx

Trying to watch the filesystem access to the .swc cache directory while the failing case is running:

➜  fswatch -x .swc
.swc Created IsDir AttributeModified
.swc/plugins Created IsDir AttributeModified
.swc/plugins/v7_macos_aarch64_7.0.0 Created IsDir AttributeModified
.swc/plugins/v7_macos_aarch64_7.0.0/73d7dfbc154ced25d4c677fe2162e5aa0280fb947e891799719e0f23d786d2dc Created AttributeModified IsFile Updated Removed AttributeModified
.swc/plugins/v7_macos_aarch64_7.0.0/73d7dfbc154ced25d4c677fe2162e5aa0280fb947e891799719e0f23d786d2dc Created IsFile Updated Removed AttributeModified
.swc/plugins/v7_macos_aarch64_7.0.0/73d7dfbc154ced25d4c677fe2162e5aa0280fb947e891799719e0f23d786d2dc Created AttributeModified IsFile Updated AttributeModified

Misc

Input code

See: https://github.com/walkerburgin/swc-plugin-cache-repro/tree/master

Config

{
  "sourceMaps": "inline",
  "module": {
    "type": "es6",
    "strictMode": true,
    "noInterop": false
  },
  "jsc": {
    "externalHelpers": false,
    "target": "es2022",
    "parser": {
      "syntax": "typescript",
      "tsx": true,
      "decorators": false,
      "dynamicImport": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": false,
      "react": {
        "throwIfNamespace": false,
        "useBuiltins": false,
        "pragma": "React.createElement",
        "pragmaFrag": "React.Fragment",
        "importSource": "react"
      }
    },
    "keepClassNames": true
  }
}

Playground link (or link to the minimal reproduction)

https://github.com/walkerburgin/swc-plugin-cache-repro

SWC Info output

Operating System:
    Platform: darwin
    Arch: arm64
    Machine Type: arm64
    Version: Darwin Kernel Version 24.2.0: Fri Dec  6 19:02:41 PST 2024; root:xnu-11215.61.5~2/RELEASE_ARM64_T6030
    CPU: (12 cores)
        Models: Apple M3 Pro

Binaries:
    Node: 22.14.0
    npm: 10.9.2
    Yarn: N/A
    pnpm: 9.10.0

Relevant Packages:
    @swc/core: 1.10.18
    @swc/helpers: N/A
    @swc/types: N/A


SWC Config:
    output: N/A
    .swcrc path: N/A

Next.js info:
    output: N/A

Expected behavior

  • SWC can read from a read-only plugin cache directory
  • Concurrent SWC processes can write safely to a shared plugin cache directory

Actual behavior

  • SWC does not read from plugin cache directories that are read only
  • Concurrent writes to a shared plugin cache directory fail intermittently

Version

v1.10.18

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions