Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WASM bytecode cache concurrent access #10065

Open
walkerburgin opened this issue Feb 20, 2025 · 3 comments
Open

WASM bytecode cache concurrent access #10065

walkerburgin opened this issue Feb 20, 2025 · 3 comments
Labels

Comments

@walkerburgin
Copy link

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

@kdy1 kdy1 added this to the Planned milestone Feb 21, 2025
@kdy1 kdy1 assigned kdy1 and unassigned kdy1 Feb 27, 2025
@kdy1 kdy1 removed this from the Planned milestone Feb 27, 2025
@kdy1
Copy link
Member

kdy1 commented Mar 3, 2025

Expected usage is to invoke swc_cli once and parallelism is handled by SWC itself

@kdy1 kdy1 closed this as not planned Won't fix, can't repro, duplicate, stale Mar 3, 2025
@walkerburgin
Copy link
Author

@kdy1 I understand that running swc once per file within a package is unusual and I can take that up with @alexeagle & @jbedard (the maintainers of aspect_rules_swc)

I think this is still be an issue for us, albeit at a slightly smaller scale, when there are multiple swc processes running across different packages at the same time. That feels like a pretty common scenario for any moderately sized project?

Would you be open to a contribution of some sort?

@kdy1
Copy link
Member

kdy1 commented Mar 3, 2025

Yeap, of course

@kdy1 kdy1 reopened this Mar 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants