Repository navigation
Plug-ins: windows and processes take turns on the Plug-ins folder - #7460
Merged
bflatastic merged 3 commits intoNov 14, 2026
Merged
Conversation
Two windows or processes using one Plug-ins folder could interleave their writes: an install from one could leave its module with the other's grant, a grant written for one plug-in could drop one written for another, and plug-in settings and cache changes could lose each other's keys or pass the cache caps together. Each change to the folder now runs in one turn on disabled.lock, the file the disabled list was already locked with: - plugin.install takes the turn before it reads the stored grants and holds it through the module, grant, put-back and disabled-list writes; plugin.remove holds it through grant removal and storage deletion. - Grant writes, the disabled list, loading the folder, and the plug-ins' settings and cache changes read and write in a turn. - When disabled.lock can't be locked, plugin.install and plugin.remove fail and change nothing, plugin.setEnabled keeps its change in the process, and loading the folder writes nothing to it. Docs: docs/plugins.md, docs/mcp.md, the plugin.install and plugin.remove params text, docs/target-app-parity.md.
Contributor
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 49 seconds. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
This was referenced Nov 14, 2026
Member
|
Merged, thanks @dotCooCoo! It passed its CI checks. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
PLUGIN_CHANGESkeeps installs and removals in one process in order, but two processes that use the samePlug-insfolder (the app andvectorcraft-cli, or two app instances) wrote it without any lock. Two faults followed:plugin.installcommitted a module, it wrote the module, thengrants.json, then the disabled list, unlocked. A second process installing the same id between those writes could leave one install's module next to the other install's grant, so the folder held a grant for bytes it was never given for, or skipped an approved module at the next start.store_grantreadgrants.json, changed one entry and replaced the whole file. Two processes granting different ids could both read the old list, and the later write dropped the earlier grant while both calls reported success. Plug-in settings and cache files had the same read-change-write pattern, guarded only by the in-processPLUGIN_DATA_LOCK.This change makes every window and process lock one file per folder, the existing
disabled.lock, for the whole of each change. These are VectorCraft's own rules: a stored module keeps its own grant, and one write does not undo another.crates/engine/src/store.rs:lock_disabledbecomeslock_store, which opensdisabled.lockfor reading and writing without truncating it and takes an exclusiveFile::lock(stable since Rust 1.89; the workspace minimum is 1.95). The held lock is aStoreTurn, and the store writers (module, put-back, removal, grant, disabled list, moving kept settings back, removing plug-in storage) take it as an argument and write nothing without it, so no caller takes the lock twice. Plug-in settings and cache changes take the folder lock beforePLUGIN_DATA_LOCK. Loading the folder at startup takes it once across reading the grants and modules and removing stale grants. Onwasm32there is no file lock, and the web build attaches no folder, as before.crates/engine/src/cmd/plugin.rs:plugin.installandplugin.removetake the lock right afterPLUGIN_CHANGES, before reading the stored approval, and hold it through the module, grant, put-back and disabled-list writes.plugin.setEnabledtakes it before changing the list in memory.PLUGIN_CHANGES, then the folder lock, then the registry locks, thenPLUGIN_DATA_LOCK.disabled.lockcan't be locked,plugin.installandplugin.removenow fail with an error and change nothing; before, they wrote the folder without a lock.plugin.setEnabledkeeps answeringsaved: falsein that case, and loading the folder writes nothing to it.plugin.installandplugin.remove,docs/plugins.md,docs/mcp.mdand one line indocs/target-app-parity.mddescribe the lock.How it was tested
New tests in
crates/engine/src/tests/plugins.rs. The two install and grant races use a second session with its ownplugin_dirand no load, so it does not takePLUGIN_CHANGES;File::locklocks per handle, so the threads exercise the file lock. The install and grant tests wait on channels and a test hook that reports when a writer waits for the lock. The settings test waits on lines that the child process prints. None of the tests sleeps.installs_from_two_processes_into_one_store_keep_each_module_with_its_grant: one install stops between writing its module and its grant while a second process writes another module and its grant for the same id; the grant stored for the id matches the hash of the module stored for it.grants_written_by_two_processes_into_one_store_keep_both: one grant write stops after readinggrants.jsonwhile another id is granted; both grants are kept.settings_changed_by_two_processes_keep_both: a child test process changes one setting of a plug-in while the parent changes another; both are kept.a_store_that_cant_be_locked_refuses_installs_and_removals: withdisabled.lockreplaced by a folder,plugin.installandplugin.removereturn an error and leave the registry and the folder as they were.The three race tests fail without the change (with
lock_storereturning before it locks: the stored grant's hash differs from the module's, one grant is lost, one setting is lost), and the last one fails when the install ignores the lock error. Thestore_fileshelper in the same file no longer countsdisabled.lock, andhostile_storage_arguments_never_crashincrates/engine/tests/plugin_fuzz.rsallows the lock file in the folder.Focused checks on Linux, on the final tree, on
mainat1e4e3534f5(the branch merges cleanly with it):cargo test -p vectorcraft-engine --lib tests::plugins::: 130 passed, 0 failed.cargo test -p vectorcraft-engine --test plugin_safe_mode: 17 passed;--test plugin_clear_cache: 1 passed.PROPTEST_CASES=1 cargo test -p vectorcraft-engine --test plugin_fuzz hostile_storage_arguments_never_crash: 1 passed.cargo clippy -p vectorcraft-engine --all-targets -- -D warnings: no warnings.cargo check --workspace --testsandcargo check -p vectorcraft-engine --target wasm32-unknown-unknown: no errors.cargo xtask brands,layers(30 crates) andassets(337 files): OK.cargo fmt --all -- --checkreports onlycrates/svg/src/lib.rs:491, which this branch does not change;mainat1e4e3534f5has the same diff.cargo xtask ciruns in this pull request's CI.No Windows-only code changed. On Windows,
File::lockusesLockFileEx. This pull request's CI runs the tests on Linux only, so it does not exercise the lock on Windows.Checklist
cargo xtask cipasses (fmt, clippy, tests, assets, brands, layers, wasm).unwrap(),expect(),panic!,unreachable!,todo!orunimplemented!outside tests, and no unchecked indexing on data from files, parameters ormessages. Errors are returned as
Resultand shown to the user(why and how).
ASSETS.md.