Skip to content

Add serial_default_lang option for parallel-unsafe plugins - #310

Open
rathboma wants to merge 2 commits into
untra:mainfrom
rathboma:serial-default-lang
Open

Add serial_default_lang option for parallel-unsafe plugins#310
rathboma wants to merge 2 commits into
untra:mainfrom
rathboma:serial-default-lang

Conversation

@rathboma

Copy link
Copy Markdown
Contributor

What this does

Adds a new config option, serial_default_lang (default false). When enabled alongside parallel_localization: true, Polyglot processes the default language first, on its own, in the parent process — and only then forks for the remaining languages.

Why

With parallel_localization: true, Polyglot forks one process per language and runs them all at once. That breaks plugins that do expensive one-time setup and share state across the whole site.

The case I hit is jekyll-assets: each fork independently initializes its Sprockets cache, and on startup it clears the shared .jekyll-cache/assets/ directory. When several forks race to clear the same directory at the same time, all but one die mid-traversal with:

Errno::ENOENT: No such file or directory @ apply2files - .jekyll-cache/assets/proxied/<hash>

…and the whole build fails. The only workaround today is parallel_localization: false, which means giving up parallelism entirely.

With serial_default_lang: true, the expensive setup happens once during the default-language pass in the parent. The language forks then inherit the finished state via fork(2) copy-on-write, so there's nothing left to race over. It isn't jekyll-assets specific — any plugin with the same "shared cache, cleared on init" shape benefits.

Performance

Tested on a real site — the Beekeeper Studio website: ~770 markdown source files, 10 languages, jekyll-assets in the pipeline.

Build Time
parallel_localization: false (serial — previously the only thing that worked here) ~310s
parallel_localization: true + serial_default_lang: true ~85s

Roughly a 3.6x speedup, and more to the point it makes parallel_localization usable on this site at all. I diffed the parallel output against a serial build and confirmed it is content-identical: same file count, and every byte-level difference is ordinary build nondeterminism (timestamps, asset content-hashes, randomized DOM ids) that shows up between any two builds regardless of this change.

The cost is one fewer concurrent fork — the default language no longer overlaps with the others. On a multi-language site that is a small fraction of what parallelism buys back.

Compatibility

Defaults to false, so behavior is unchanged unless you explicitly opt in.

Tests

  • New spec covers the parent-runs-default-lang path.
  • The existing fork-count spec is unchanged, since default behavior is preserved.
  • bash test.sh (RuboCop + RSpec) passes locally.

rathboma added 2 commits May 19, 2026 14:46
When parallel_localization is on, polyglot forks one Ruby process per
language. Each fork independently initializes jekyll-assets, whose
Sprockets::Cache calls FileUtils.rm_r on the shared .jekyll-cache/assets/
directory on first use. Multiple forks racing on the same clear leaves
all but one with `Errno::ENOENT @ apply2files` and the build fails.

The new serial_default_lang option (default false) tells polyglot to
process the default language synchronously in the parent before forking
the rest. That lets jekyll-assets initialize and write its manifest
exactly once; the language forks then inherit the populated @sprockets
via fork(2) copy-on-write, so the destructive clear never runs in any
fork.

No behavior change unless the option is explicitly enabled.
Reframe the docs around parallel-unsafe plugins in general rather than
the jekyll-assets/sprockets internals. Keeps jekyll-assets as the named
example but drops the sprockets-specific traceback and implementation
detail that won't age well.
Comment on lines +13 to +19
# When true (and parallel_localization is also true), the default
# language is processed synchronously in the parent before any forks
# are spawned for the other languages. This makes parallel builds
# safe for plugins that do expensive one-time setup and share state
# across the site (e.g. jekyll-assets), which otherwise race when
# every fork runs that setup at once. See README for details.
@serial_default_lang = config.fetch('serial_default_lang', false)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something I've noticed and don't like with claude made code contributions, is it writes a lot of comments.
Code comments rot rapidly, and then become lies that downstream AI reads, and then comments that are misleading cause problems.

can you remove some of these extraneous excessive code comments in the site.rb and spec ? they stand out like a sore thumb. Otherwise this PR is good, and I'm inclined to merge it soon with the next release, after I can adjust some verbiage and make the upcoming blogpost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants