Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(gateway): reduce plugin registry lock duration during load/unload - #25

Merged
joshuajbouw merged 1 commit into
mainfrom
fix/plugin-load-lock-duration
Feb 16, 2026
Merged

joshuajbouw merged 1 commit into
mainfrom
fix/plugin-load-lock-duration

Conversation

@joshuajbouw

@joshuajbouw joshuajbouw commented Feb 16, 2026

Copy link
Copy Markdown
Member

Summary

  • The write lock on the plugin registry was held across expensive async operations: plugin.load() (MCP handshake + subprocess spawn) and plugin.unload() (child process teardown)
  • This blocked all other registry readers/writers for the entire duration — potentially seconds for MCP plugins
  • Restructured to a take-out/put-back pattern: briefly lock to unregister the plugin, perform the async operation without the lock, then briefly lock again to register it back
  • Applied to: RPC load_plugin, RPC unload_plugin, and the startup auto-load background task

Test plan

  • cargo check -p astrid-gateway -p astrid-cli — clean
  • cargo clippy — no errors
  • cargo fmt --check — no issues
  • Manual: load an MCP plugin while simultaneously listing plugins — verify no blocking

The write lock on the plugin registry was held across expensive async
operations: plugin.load() (MCP handshake + subprocess spawn) and
plugin.unload() (child process teardown). This blocked all other
registry readers/writers for the entire duration.

Restructure to take-out/put-back pattern: briefly lock to unregister
the plugin, perform the async operation without the lock, then briefly
lock again to re-register. Applies to:
- RPC load_plugin handler
- RPC unload_plugin handler
- Startup auto-load background task
@joshuajbouw

Copy link
Copy Markdown
Member Author

Confirmed not blocking when running a plugin install and a plugin list at the same time with two different terminals.

@joshuajbouw
joshuajbouw merged commit 97fd299 into main Feb 16, 2026
5 checks passed
@joshuajbouw
joshuajbouw deleted the fix/plugin-load-lock-duration branch February 16, 2026 20:54
@joshuajbouw joshuajbouw self-assigned this Feb 22, 2026
joshuajbouw added a commit that referenced this pull request Jun 10, 2026
…livery (#883)

## Linked Issue

Closes #882

## Summary

The publish/subscribe **ACL authorization** used the strict
`topic::topic_matches` (a `*` is exactly one segment) while event
**delivery** uses `astrid_events::TopicMatcher` (a trailing `*` is a
subtree wildcard — one or more segments at any depth). That divergence
forced capsule manifests to enumerate wildcard depth
(`astrid.v1.admin.*` / `*.*` / `*.*.*`) just to authorize publishing
topics whose depth varies or is unknown (uuid suffixes, variable
sub-paths), and it's the same matcher asymmetry that made the cli
run-loop crash confusing. This authorizes via the route-layer matcher so
a declared trailing `*` covers the whole subtree, and makes
`TopicMatcher::matches_topic` the single source of truth shared by
delivery and ACL — they can never silently diverge again.

## Changes

- `astrid-events`: extract `TopicMatcher::matches_topic(&str)` from the
existing subtree logic; `matches(&event)` delegates to it. Added a
direct subtree test.
- `astrid-capsule`: the two ACL checks (`publish_inner`,
`check_subscribe_acl`) authorize via
`TopicMatcher::new(pattern).matches_topic(topic)` instead of strict
`topic_matches`.
- Scope: only the two ACL sites change. Interceptor dispatch
(`dispatcher.rs`) keeps strict `topic_matches`; the runtime "wildcard
must be terminal" subscribe gate is unchanged.

Permissive change — authorizes more, denies nothing previously allowed;
delivery was already subtree, so nothing that worked stops working.
Adversarial pass: ACL is per-capsule declared intent (no
cross-principal/escalation path); no match-all hole (a bare `*` stays
single-segment; only `prefix.*` is subtree-under-prefix); subscribe
pattern-vs-pattern is sound (a broad ACL authorizes a narrower request,
an exact ACL does not authorize a broader wildcard request). Breadth is
the operator's decision at install (the manifest declares intent;
capabilities + install review are the boundary).

## Test Plan

### Automated

- [x] `cargo test --workspace` passes
- [x] No new clippy warnings

`cargo test -p astrid-events` (matcher, incl. new
`matches_topic_subtree_for_acl`) and `-p astrid-capsule --lib` (ACL /
topic / audit-scope) — green, no regressions.

### Manual

Rebuilt the daemon, collapsed the cli manifest to `astrid.v1.request.*`
+ `astrid.v1.admin.*` (dropping the depth enumeration and the mcp
front-door exacts), reinstalled. `astrid agent list` (a 6-segment
`astrid.v1.admin.response.agent.list` round-trip) and `astrid mcp serve`
`tools/list` (8 tools, a 6-segment `astrid.v1.request.mcp.tools.list`
publish) both work — authorized by the single subtree patterns.

Follow-up (separate, capsule-cli): collapse the cli manifest once this
lands — it currently enumerates depth + lists the mcp exacts
(capsule-cli #25) because it must work against today's strict kernel.

## Checklist

- [x] Linked to an issue
- [x] CHANGELOG.md updated under `[Unreleased]`
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.

1 participant