Repository navigation
Take Task out of the mesh core: delete a dead startup TCS, end GitCli's sync-over-async, ratchet the IMeshService shim - #2531
Conversation
There was a problem hiding this comment.
Pull request overview
This PR continues the “tasks are evil” sweep by removing several Task/TaskCompletionSource surfaces from mesh-reachable framework code, and by hardening the remaining test-only bridges and process-leaf behavior so they don’t reintroduce async hazards.
Changes:
- Removed a dead
TaskCompletionSourcestartup gate fromMessageService. - Moved the
IMeshServiceawait-based CRUD extension shim out ofMeshWeaver.Mesh.Contractinto the test-onlyMeshWeaver.Fixture, and added a guard test to prevent reintroduction in shipped assemblies. - Rewrote
GitCli.Execto avoid sync-over-async while still draining stdout/stderr concurrently, with new tests pinning the drain contract.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/MeshWeaver.GitSync.Test/GitCliProcessDrainTest.cs | Adds regression tests covering stdout byte-exactness, large output drain, and stderr on failure. |
| test/MeshWeaver.Documentation.Test/MeshServiceHasNoTaskShimGuard.cs | Adds governance tests ensuring IMeshService exposes no Task-shaped surface in shipped assemblies. |
| src/MeshWeaver.Messaging.Hub/MessageService.cs | Deletes a dead startup TaskCompletionSource field and its disposal-time cancellation block. |
| src/MeshWeaver.Mesh.Contract/Services/MeshServiceExtensions.cs | Removes the Task-returning CRUD extension shim from the core contract assembly. |
| src/MeshWeaver.Mesh.Contract/Services/IMeshService.cs | Updates interface comments to reflect the removal/move of the await-shaped shim. |
| src/MeshWeaver.GitSync/GitCli.cs | Replaces sync-over-async process draining with stderr event pumping + stdout ReadToEnd. |
| src/MeshWeaver.Fixture/MeshServiceTestExtensions.cs | Reintroduces the await-shaped CRUD bridge in a test-only assembly for use at test boundaries. |
| src/MeshWeaver.Documentation/Data/Architecture/SatelliteNodePatterns.md | Updates docs to reflect that the Task shim no longer ships in production assemblies. |
| src/MeshWeaver.Documentation/Data/Architecture/AsynchronousCalls.md | Updates the “no async shims” rule to point at the test-only location. |
| src/MeshWeaver.Documentation/Data/AI/ExecuteScript.md | Updates scripting guidance to reflect the shim’s test-only location. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
cc522af to
0f0e5c4
Compare
ec93ab7 to
455b9c3
Compare
…'s sync-over-async, ratchet the IMeshService shim Two Task sites in framework code removed, and a third NOT removed — with a measured reason that is the more useful half of this PR. 1. MessageService.startupCompletionSource was DEAD. Declared, cancelled at dispose, never completed and never read — no TrySetResult anywhere, no reader in src/, test/, memex/ or samples/. Deleted with its dispose block; no behaviour to preserve. 2. GitCli.Exec started two ReadToEndAsync tasks and unwrapped them with GetAwaiter().GetResult() — sync-over-async inside the method whose entire job is to be the synchronous body IIoPool.InvokeBlocking takes. It now pumps stderr through BeginErrorReadLine (accumulating into a ConcurrentQueue, since the runtime raises that handler on its own thread) while reading stdout to EOF on the pool thread. Both pipes still drain concurrently; no Task remains in the assembly. stdout deliberately stays ReadToEnd() rather than moving to the line pump as well: `git show <rev>:<path>` returns FILE CONTENT (GitWorkingTreeService.ShowFile, GitPackageSource.ShowFile), and a line pump would silently rewrite its CRLFs. 🚨 Reading synchronously dropped a property ReadToEndAsync(ct) gave for free — a cancelled run raised OperationCanceledException. Without it the kill leaves git with a signal exit code, and returning THAT makes every Expect(...) in the assembly raise GitWorkingTreeException: an unsubscribe would masquerade as "git failed". ThrowIfCancellationRequested now guards both entry and exit. Caught in review; it was a real regression. 3. NOT done, and this is the finding: MeshServiceExtensions (CreateNodeAsync/UpdateNodeAsync/DeleteNodeAsync over a TaskCompletionSource) STAYS on MeshWeaver.Mesh.Contract. The move to MeshWeaver.Fixture was written, built green, and withdrawn. Its doc comment claims "~180 existing callers"; measured, that is ZERO in src/, samples/, memex/, content/ and the live mesh — every caller left in this repo is a test, and all four test projects already reference Fixture. What blocks it is a repo no compiler here can look at: MeshWeaver.Reinsurance has 58 call sites across 22 in-mesh Source/*.cs files (TreatySubmission, UwPortfolio, CatScenario, UWDeepfieldHome, UwLossWatch). In-mesh source compiles at RUNTIME in the portal, so deleting the shim turns 22 NodeTypes CompileError — and a CompileError NodeType refuses portal readiness. CI would have been green the whole way. Exit is two steps, in order: port those 58 sites to CreateNode(...).Subscribe(...) — a real fix, since each is an await on hub-reachable layout-area code — then move the shim. Tests - GitCliProcessDrainTest (4 new, green): stdout byte-exact through a CRLF blob; ~1 MB of stdout past any pipe buffer arriving whole (a regression there HANGS rather than fails, hence its own timeout); a non-zero exit still carrying its stderr; and a cancelled run raising cancellation instead of reporting a result. - MeshServiceHasNoTaskShimGuard (2 new, green) holds the shim where it is: IMeshService itself declares no Task member, and the bridge stays exactly one shipped assembly and three methods — a fourth verb or a second assembly fails. It loads MeshWeaver.*.dll from the test output rather than walking the live AppDomain (a lazily-unloaded assembly would make a domain walk pass having looked at nothing) and asserts it found a populated set and the known shim before trusting any zero. Docs: AsynchronousCalls.md and SatelliteNodePatterns.md now carry the measured caller count and the two-step exit, instead of describing the shim as ordinary back-compat. DocumentationLinkIntegrityTest green (it caught two '.md' sibling links on the first pass). No What's New entry: pure-internal refactor, no user-visible effect. Refs #2489 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
455b9c3 to
0271708
Compare
Test Results (shard 0)1 312 tests 1 312 ✅ 6m 10s ⏱️ Results for commit 0271708. |
Test Results (shard 2)1 087 tests 895 ✅ 5m 28s ⏱️ Results for commit 0271708. |
Test Results (shard 5)1 601 tests 1 600 ✅ 9m 44s ⏱️ Results for commit 0271708. |
Test Results (shard 4) 8 files 8 suites 11m 31s ⏱️ Results for commit 0271708. |
Test Results (shard 1)739 tests 735 ✅ 7m 21s ⏱️ Results for commit 0271708. |
Test Results 44 files 44 suites 58m 26s ⏱️ Results for commit 0271708. |
The Reinsurance side is ported — the shim can move nowSystemorph/MeshWeaver.Reinsurance#124 landed the last of it, closing MeshWeaver.Reinsurance#102. Measured on The 58 went in three passes: #106 (2 sites, the worked template), #112 (10 leaf handlers), and #124 So
Two findings from the port that are about the framework, not about UWDeepfield, in case they are
|
|
🚨 Caveat on the sweep this thread asks for before the shim moves: the Content indexing is inactive on both MCP-reachable portals, and Until #2741 is resolved, the honest basis for moving (Reinsurance's side is clear: #102 merged as |
Part of the "tasks are evil" sweep, framework half. Companion to #2535 (portal-API half).
Two
Tasksites removed, and a third not removed — with a measured reason that is the more useful half of this PR.1.
MessageService.startupCompletionSourcewas DEADDeclared, cancelled at dispose, never completed and never read — no
TrySetResultanywhere, no reader insrc/,test/,memex/orsamples/. Deleted with its dispose block. No behaviour to preserve.2.
GitCli.Execstopped doing sync-over-asyncIt started two
ReadToEndAsynctasks and unwrapped them withGetAwaiter().GetResult()— inside the method whose entire job is to be the synchronous bodyIIoPool.InvokeBlockingtakes. It now pumps stderr throughBeginErrorReadLine(into aConcurrentQueue, since the runtime raises that handler on its own thread) while reading stdout to EOF on the pool thread. Both pipes still drain concurrently; noTaskremains in the assembly.stdout deliberately stays
ReadToEnd()rather than moving to the line pump as well —git show <rev>:<path>returns FILE CONTENT (GitWorkingTreeService.ShowFile,GitPackageSource.ShowFile), and a line pump would silently rewrite its CRLFs.🚨 Reading synchronously dropped a property
ReadToEndAsync(ct)gave for free: a cancelled run raisedOperationCanceledException. Without it the kill leaves git with a signal exit code, and returning that makes everyExpect(...)in the assembly raiseGitWorkingTreeException— an unsubscribe would masquerade as "git failed".ThrowIfCancellationRequestednow guards both entry and exit. Caught in review; it was a real regression, and there is now a test for it.3. NOT done — and this is the finding
MeshServiceExtensions(CreateNodeAsync/UpdateNodeAsync/DeleteNodeAsyncover aTaskCompletionSource) stays onMeshWeaver.Mesh.Contract.The move to
MeshWeaver.Fixturewas written, built green, and withdrawn. Its doc comment claims "~180 existing callers"; measured, that is zero insrc/,samples/,memex/andcontent/— every caller left in this repo is a test, and all four consuming test projects already reference Fixture. So on this repo's evidence the move was free.Measured twice by different means — a tree grep here, and an org-wide GitHub code search, which is what reaches the satellite repos and their in-mesh source. That second measurement is what found the blocker, and the first one alone would have shipped the break.
What blocks it is a repo no compiler here can look at:
MeshWeaver.Reinsurancehas 58 call sites across 22 in-meshSource/*.csfiles (TreatySubmission,UwPortfolio,CatScenario,UWDeepfieldHome,UwLossWatch). In-mesh source compiles at RUNTIME in the portal, so deleting the shim turns 22 NodeTypesCompileError— and aCompileErrorNodeType refuses portal readiness. CI would have been green the whole way.Tracked as MeshWeaver.Reinsurance#102. Exit is two steps, in this order:
CreateNode(...).Subscribe(...)— a real fix, not paperwork: each is anawaiton hub-reachable layout-area code, i.e. already the deadlock shape.QueryAsyncbridge inMeshWeaver.Fixture.Until then
MeshServiceHasNoTaskShimGuardholds the line: exactly one shipped assembly, exactly three methods. A fourth verb or a second assembly fails.Tests
GitCliProcessDrainTest(4 new, green): stdout byte-exact through a CRLF blob; ~1 MB of stdout past any pipe buffer arriving whole (a regression there hangs rather than fails, hence its own timeout); a non-zero exit still carrying its stderr; a cancelled run raising cancellation instead of reporting a result.MeshServiceHasNoTaskShimGuard(2 new, green):IMeshServicedeclares noTaskmember, and the bridge stays one assembly / three methods. It loadsMeshWeaver.*.dllfrom the test output rather than walking the liveAppDomain— a lazily-unloaded assembly would make a domain walk pass having looked at nothing — and asserts it found a populated set and the known shim before trusting any zero.-c Release -warnaserror:Fixture,GitSync,PluginCatalog,Documentation,Documentation.Test,GitSync.Test— all0 Warning(s) 0 Error(s).DocumentationLinkIntegrityTestgreen (it caught two.mdsibling links on the first pass).Docs:
AsynchronousCalls.mdandSatelliteNodePatterns.mdnow carry the measured caller count and the two-step exit, instead of describing the shim as ordinary back-compat.Also reported, not done here
IMessageHub.Startedis a publicTaskreadiness gate — the twin of the disposal defect in #2488, where I have left the full consumer list and an assessment: #2488 comment. It is the right fix, it lands in the same files #2301/#2488 are already changing, and it wants its own PR.No What's New entry: pure-internal refactor, no user-visible effect.
Refs #2489
🤖 Generated with Claude Code