feat: deduplicate online license re-validation across plugin instances#11
Merged
Conversation
When a DAW project loads N plugin instances simultaneously, they each call validate_token_online. Previously the SDK's 5-min throttle only protected against repeats after one had written its result, and the function didn't persist the refreshed license at all — so siblings (including out-of-process siblings under Bitwig / Ableton 12.1 Sandbox / AU v3) couldn't observe each other and would all fire parallel network requests. Three coordinated layers now collapse this to a single network call per min_interval window per host: - An in-process std::mutex in moonbase::licensing for cheap fast-path serialization of same-process callers. - A cross-process file lock via license_store::lock_for_update() — a new optional virtual returning an RAII guard. file_license_store backs it with a flock/LockFileEx wrapper (detail/file_lock.hpp); the lock target is a dedicated <license>.lock sidecar so it survives delete_local_license unlinking the data file. memory_license_store backs it with an internal recursive_mutex so the default store is safe under concurrent clear/validate. - Reload-the-store-under-lock + persist-the-refresh-under-lock inside validate_token_online itself, so the second waiter sees the fresh validated_at the first writer just wrote and short-circuits via the throttle. validate_token_online gains an optional should_persist predicate, called inside both locks immediately before the store write. The JUCE bridge's async path uses it to veto the persist when the user's generation has moved on (clearLicense / revokeActivation / pollPendingActivation success), which otherwise could resurrect a cleared license on disk. Those bridge mutations now also hold the same file lock so persist and delete are ordered relative to each other. The bridge additionally caches moonbase::licensing instances process-wide so plugins sharing a (endpoint, product_id, public_key, account_id, store, fingerprint) configuration share a single SDK instance — and therefore a single in-process mutex — across all their MoonbaseUnlockStatus instances. Tests: 7 new unit tests plus a POSIX fork()-based cross-process integration test that spawns 8 children against a shared file store and asserts a single network call.
github-actions Bot
pushed a commit
that referenced
this pull request
May 18, 2026
# [2.2.0](v2.1.0...v2.2.0) (2026-05-18) ### Features * deduplicate online license re-validation across plugin instances ([#11](#11)) ([f095246](f095246))
|
🎉 This PR is included in version 2.2.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
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.
Summary
validate_token_onlinesimultaneously) to a single network call permin_intervalwindow per host — including across sandboxed sibling processes (Bitwig, Ableton 12.1 Sandbox, AU v3).std::mutexinmoonbase::licensing, a cross-process file lock via a newlicense_store::lock_for_update()returning an RAII guard (sidecar<license>.lockso it survivesdelete_local_licenseunlinking the data file;memory_license_storebacks it with an internalrecursive_mutex), and reload-then-persist-under-lock insidevalidate_token_online.should_persistpredicate onvalidate_token_online, called inside both locks immediately before the store write — the JUCE bridge's async path uses it to veto persists afterclearLicense/revokeActivation/pollPendingActivationso a stale refresh can't resurrect a cleared license on disk; those bridge mutations also hold the same file lock now. The bridge additionally sharesmoonbase::licensinginstances process-wide keyed on the full backend configuration.Test plan
ctest --output-on-failure— 48 tests pass, including 7 new unit tests and a POSIXfork()-based cross-process integration test that spawns 8 children against a shared file store and asserts a single network call.MOONBASE_BUILD_JUCE_EXAMPLE=ON) builds and runs.