[ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync#153
Merged
jodavis merged 5 commits intofeature/ADR-162-client-side-layout-updatesfrom Apr 22, 2026
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces signal-driven DI scope recycling in the Lifecycle subsystem (including Blazor scope reload support), and adds Playwright browser setup guidance/scripts for restricted sandbox environments.
Changes:
- Convert
ApplicationLifecycle.ExecuteAsyncinto a recycle loop that reacts to anIApplicationRecycleSignal. - Implement
BlazorAppScope.RecycleAsync()via JSlocation.reloadand register a concreteApplicationRecycleSignalin DI. - Add/expand lifecycle documentation + unit tests for recycle behavior, and add a sandbox Playwright browser setup script + updated E2E instructions.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| test/AdaptiveRemote.App.Tests/Services/Lifecycle/ApplicationLifecycleTests.cs | Adds recycle-signal unit tests and updates constructor usage to include the signal. |
| src/AdaptiveRemote.App/Services/Lifecycle/_doc_Lifecycle.md | Documents the new recycle loop, pre-initializer behavior, and recycle signal contract. |
| src/AdaptiveRemote.App/Services/Lifecycle/IApplicationRecycleSignal.cs | Adds a lifecycle-internal interface to request and reset recycle signaling. |
| src/AdaptiveRemote.App/Services/Lifecycle/ApplicationRecycleSignal.cs | Adds the concrete recycle signal implementation (CTS-based). |
| src/AdaptiveRemote.App/Services/Lifecycle/ApplicationLifecycle.cs | Implements the recycle loop, linking stoppingToken with the recycle signal token. |
| src/AdaptiveRemote.App/Logging/MessageLogger.cs | Adds a new log event for “Recycling application scope”. |
| src/AdaptiveRemote.App/Configuration/HostBuilderExtensions.cs | Registers IApplicationRecycleSignal as a singleton. |
| src/AdaptiveRemote.App/Components/BlazorAppScope.cs | Implements scope recycle via JS runtime reload. |
| scripts/setup-playwright-sandbox.sh | Adds a sandbox helper to symlink preinstalled Playwright browsers to expected revisions. |
| CLAUDE.md | Updates E2E Playwright setup instructions for dev machines vs sandbox environments. |
…eAsync
Implements the scope recycle loop in ApplicationLifecycle.ExecuteAsync:
- IApplicationRecycleSignal / ApplicationRecycleSignal: new cross-service
interface backed by a CancellationTokenSource; RequestRecycle() cancels it,
Token is linked into the scope work item, Reset() creates a fresh CTS
- ExecuteAsync refactored to a while loop; linked token from stoppingToken +
signal.Token passed into InvokeInScopeAsync each iteration
- Steady-state path: signal fires during Task.Delay(Infinite) → OCE → cleanup
→ log RecyclingScope → RecycleScopeAsync() → signal.Reset() → loop
- Init-phase path: signal fires during InitializeAllAsync → cancel → cleanup
→ signal.Reset() → loop without RecycleScopeAsync (scope TCS still valid)
- IPreScopeInitializer services awaited only before the first scope (before
the while loop), not re-awaited on recycles
- BlazorAppScope.RecycleAsync: now calls IJSRuntime.InvokeVoidAsync("location.reload")
- Registered IApplicationRecycleSignal as singleton in DI
- Added EventId 712 ApplicationLifecycle_RecyclingScope log message
- Added 5 unit tests covering both recycle paths, loop continuation,
pre-initializer not re-awaited, and signal.Reset() called after recycle
- Updated _doc_Lifecycle.md: removed "Future Plans" stub, documented the
implemented recycle loop, signal, and two recycle paths
https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s
…sers Claude Code cloud sandbox environments block cdn.playwright.dev, so the standard `playwright.ps1 install` fails. Browsers are pre-installed at /opt/pw-browsers but under a different revision (1194) than the current Playwright package expects (1208). - scripts/setup-playwright-sandbox.sh: auto-detects the expected version from the Playwright registry JS, finds the highest installed version in /opt/pw-browsers, and creates symlinks + INSTALLATION_COMPLETE markers - CLAUDE.md: updated E2E test instructions to document both the developer machine path and the sandbox workaround, with PLAYWRIGHT_BROWSERS_PATH https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s
49676e4 to
b7b6252
Compare
143c6db to
eccef82
Compare
… add tests - ApplicationLifecycle.ExecuteAsync: replace Task.Delay(Timeout.Infinite) / exception-based control flow with WaitForCancelledAsync (returns normally, no OCE for steady-state transitions) - Add ScopeReady log message (EventId 713); ScopeReleased now only logged on init/construction failure - Cleanup now runs inside the loop before ShuttingDown, rather than after the loop - BlazorAppScope.RecycleAsync: use GetRequiredService<IJSRuntime>(); remove unused using - ApplicationRecycleSignal: full lock-based thread safety; implement IDisposable - IApplicationRecycleSignal: add XML doc comments to all interface methods - HostBuilderExtensions: restore missing using AdaptiveRemote.Services.CloudAssets - CLAUDE.md: update cloud sandbox E2E guidance — don't fall back to setup script; report broken environment - _doc_Lifecycle.md: update recycle loop description to match current WaitForCancelledAsync design - ApplicationLifecycleTests: fix 3 tests for new log ordering; add 6 recycle scenario tests (second signal during cleanup, blocks until cleanup, delay during init after recycle, error during cleanup continues recycle, error during init exits loop, signal during pre-init is no-op) https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s
8102476
into
feature/ADR-162-client-side-layout-updates
2 checks passed
jodavis
added a commit
that referenced
this pull request
Apr 23, 2026
…leAsync (#153) * [ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync Implements the scope recycle loop in ApplicationLifecycle.ExecuteAsync: - IApplicationRecycleSignal / ApplicationRecycleSignal: new cross-service interface backed by a CancellationTokenSource; RequestRecycle() cancels it, Token is linked into the scope work item, Reset() creates a fresh CTS - ExecuteAsync refactored to a while loop; linked token from stoppingToken + signal.Token passed into InvokeInScopeAsync each iteration - Steady-state path: signal fires during Task.Delay(Infinite) → OCE → cleanup → log RecyclingScope → RecycleScopeAsync() → signal.Reset() → loop - Init-phase path: signal fires during InitializeAllAsync → cancel → cleanup → signal.Reset() → loop without RecycleScopeAsync (scope TCS still valid) - IPreScopeInitializer services awaited only before the first scope (before the while loop), not re-awaited on recycles - BlazorAppScope.RecycleAsync: now calls IJSRuntime.InvokeVoidAsync("location.reload") - Registered IApplicationRecycleSignal as singleton in DI - Added EventId 712 ApplicationLifecycle_RecyclingScope log message - Added 5 unit tests covering both recycle paths, loop continuation, pre-initializer not re-awaited, and signal.Reset() called after recycle - Updated _doc_Lifecycle.md: removed "Future Plans" stub, documented the implemented recycle loop, signal, and two recycle paths https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Add sandbox setup script and update E2E test docs for Playwright browsers Claude Code cloud sandbox environments block cdn.playwright.dev, so the standard `playwright.ps1 install` fails. Browsers are pre-installed at /opt/pw-browsers but under a different revision (1194) than the current Playwright package expects (1208). - scripts/setup-playwright-sandbox.sh: auto-detects the expected version from the Playwright registry JS, finds the highest installed version in /opt/pw-browsers, and creates symlinks + INSTALLATION_COMPLETE markers - CLAUDE.md: updated E2E test instructions to document both the developer machine path and the sandbox workaround, with PLAYWRIGHT_BROWSERS_PATH https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Address PR review comments: refactor recycle loop, fix thread safety, add tests - ApplicationLifecycle.ExecuteAsync: replace Task.Delay(Timeout.Infinite) / exception-based control flow with WaitForCancelledAsync (returns normally, no OCE for steady-state transitions) - Add ScopeReady log message (EventId 713); ScopeReleased now only logged on init/construction failure - Cleanup now runs inside the loop before ShuttingDown, rather than after the loop - BlazorAppScope.RecycleAsync: use GetRequiredService<IJSRuntime>(); remove unused using - ApplicationRecycleSignal: full lock-based thread safety; implement IDisposable - IApplicationRecycleSignal: add XML doc comments to all interface methods - HostBuilderExtensions: restore missing using AdaptiveRemote.Services.CloudAssets - CLAUDE.md: update cloud sandbox E2E guidance — don't fall back to setup script; report broken environment - _doc_Lifecycle.md: update recycle loop description to match current WaitForCancelledAsync design - ApplicationLifecycleTests: fix 3 tests for new log ordering; add 6 recycle scenario tests (second signal during cleanup, blocks until cleanup, delay during init after recycle, error during cleanup continues recycle, error during init exits loop, signal during pre-init is no-op) https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Improve ApplicationLifecycle unit tests, behavior, and logging. * Delete the playwright workaround script since it shouldn't be necessary --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Joe Davis <ElwoodMoves@hotmail.com>
jodavis
added a commit
that referenced
this pull request
Apr 23, 2026
…leAsync (#153) * [ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync Implements the scope recycle loop in ApplicationLifecycle.ExecuteAsync: - IApplicationRecycleSignal / ApplicationRecycleSignal: new cross-service interface backed by a CancellationTokenSource; RequestRecycle() cancels it, Token is linked into the scope work item, Reset() creates a fresh CTS - ExecuteAsync refactored to a while loop; linked token from stoppingToken + signal.Token passed into InvokeInScopeAsync each iteration - Steady-state path: signal fires during Task.Delay(Infinite) → OCE → cleanup → log RecyclingScope → RecycleScopeAsync() → signal.Reset() → loop - Init-phase path: signal fires during InitializeAllAsync → cancel → cleanup → signal.Reset() → loop without RecycleScopeAsync (scope TCS still valid) - IPreScopeInitializer services awaited only before the first scope (before the while loop), not re-awaited on recycles - BlazorAppScope.RecycleAsync: now calls IJSRuntime.InvokeVoidAsync("location.reload") - Registered IApplicationRecycleSignal as singleton in DI - Added EventId 712 ApplicationLifecycle_RecyclingScope log message - Added 5 unit tests covering both recycle paths, loop continuation, pre-initializer not re-awaited, and signal.Reset() called after recycle - Updated _doc_Lifecycle.md: removed "Future Plans" stub, documented the implemented recycle loop, signal, and two recycle paths https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Add sandbox setup script and update E2E test docs for Playwright browsers Claude Code cloud sandbox environments block cdn.playwright.dev, so the standard `playwright.ps1 install` fails. Browsers are pre-installed at /opt/pw-browsers but under a different revision (1194) than the current Playwright package expects (1208). - scripts/setup-playwright-sandbox.sh: auto-detects the expected version from the Playwright registry JS, finds the highest installed version in /opt/pw-browsers, and creates symlinks + INSTALLATION_COMPLETE markers - CLAUDE.md: updated E2E test instructions to document both the developer machine path and the sandbox workaround, with PLAYWRIGHT_BROWSERS_PATH https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Address PR review comments: refactor recycle loop, fix thread safety, add tests - ApplicationLifecycle.ExecuteAsync: replace Task.Delay(Timeout.Infinite) / exception-based control flow with WaitForCancelledAsync (returns normally, no OCE for steady-state transitions) - Add ScopeReady log message (EventId 713); ScopeReleased now only logged on init/construction failure - Cleanup now runs inside the loop before ShuttingDown, rather than after the loop - BlazorAppScope.RecycleAsync: use GetRequiredService<IJSRuntime>(); remove unused using - ApplicationRecycleSignal: full lock-based thread safety; implement IDisposable - IApplicationRecycleSignal: add XML doc comments to all interface methods - HostBuilderExtensions: restore missing using AdaptiveRemote.Services.CloudAssets - CLAUDE.md: update cloud sandbox E2E guidance — don't fall back to setup script; report broken environment - _doc_Lifecycle.md: update recycle loop description to match current WaitForCancelledAsync design - ApplicationLifecycleTests: fix 3 tests for new log ordering; add 6 recycle scenario tests (second signal during cleanup, blocks until cleanup, delay during init after recycle, error during cleanup continues recycle, error during init exits loop, signal during pre-init is no-op) https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Improve ApplicationLifecycle unit tests, behavior, and logging. * Delete the playwright workaround script since it shouldn't be necessary --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Joe Davis <ElwoodMoves@hotmail.com>
jodavis
added a commit
that referenced
this pull request
Apr 29, 2026
…leAsync (#153) * [ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync Implements the scope recycle loop in ApplicationLifecycle.ExecuteAsync: - IApplicationRecycleSignal / ApplicationRecycleSignal: new cross-service interface backed by a CancellationTokenSource; RequestRecycle() cancels it, Token is linked into the scope work item, Reset() creates a fresh CTS - ExecuteAsync refactored to a while loop; linked token from stoppingToken + signal.Token passed into InvokeInScopeAsync each iteration - Steady-state path: signal fires during Task.Delay(Infinite) → OCE → cleanup → log RecyclingScope → RecycleScopeAsync() → signal.Reset() → loop - Init-phase path: signal fires during InitializeAllAsync → cancel → cleanup → signal.Reset() → loop without RecycleScopeAsync (scope TCS still valid) - IPreScopeInitializer services awaited only before the first scope (before the while loop), not re-awaited on recycles - BlazorAppScope.RecycleAsync: now calls IJSRuntime.InvokeVoidAsync("location.reload") - Registered IApplicationRecycleSignal as singleton in DI - Added EventId 712 ApplicationLifecycle_RecyclingScope log message - Added 5 unit tests covering both recycle paths, loop continuation, pre-initializer not re-awaited, and signal.Reset() called after recycle - Updated _doc_Lifecycle.md: removed "Future Plans" stub, documented the implemented recycle loop, signal, and two recycle paths https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Add sandbox setup script and update E2E test docs for Playwright browsers Claude Code cloud sandbox environments block cdn.playwright.dev, so the standard `playwright.ps1 install` fails. Browsers are pre-installed at /opt/pw-browsers but under a different revision (1194) than the current Playwright package expects (1208). - scripts/setup-playwright-sandbox.sh: auto-detects the expected version from the Playwright registry JS, finds the highest installed version in /opt/pw-browsers, and creates symlinks + INSTALLATION_COMPLETE markers - CLAUDE.md: updated E2E test instructions to document both the developer machine path and the sandbox workaround, with PLAYWRIGHT_BROWSERS_PATH https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Address PR review comments: refactor recycle loop, fix thread safety, add tests - ApplicationLifecycle.ExecuteAsync: replace Task.Delay(Timeout.Infinite) / exception-based control flow with WaitForCancelledAsync (returns normally, no OCE for steady-state transitions) - Add ScopeReady log message (EventId 713); ScopeReleased now only logged on init/construction failure - Cleanup now runs inside the loop before ShuttingDown, rather than after the loop - BlazorAppScope.RecycleAsync: use GetRequiredService<IJSRuntime>(); remove unused using - ApplicationRecycleSignal: full lock-based thread safety; implement IDisposable - IApplicationRecycleSignal: add XML doc comments to all interface methods - HostBuilderExtensions: restore missing using AdaptiveRemote.Services.CloudAssets - CLAUDE.md: update cloud sandbox E2E guidance — don't fall back to setup script; report broken environment - _doc_Lifecycle.md: update recycle loop description to match current WaitForCancelledAsync design - ApplicationLifecycleTests: fix 3 tests for new log ordering; add 6 recycle scenario tests (second signal during cleanup, blocks until cleanup, delay during init after recycle, error during cleanup continues recycle, error during init exits loop, signal during pre-init is no-op) https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Improve ApplicationLifecycle unit tests, behavior, and logging. * Delete the playwright workaround script since it shouldn't be necessary --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Joe Davis <ElwoodMoves@hotmail.com>
jodavis
added a commit
that referenced
this pull request
Apr 29, 2026
…leAsync (#153) * [ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync Implements the scope recycle loop in ApplicationLifecycle.ExecuteAsync: - IApplicationRecycleSignal / ApplicationRecycleSignal: new cross-service interface backed by a CancellationTokenSource; RequestRecycle() cancels it, Token is linked into the scope work item, Reset() creates a fresh CTS - ExecuteAsync refactored to a while loop; linked token from stoppingToken + signal.Token passed into InvokeInScopeAsync each iteration - Steady-state path: signal fires during Task.Delay(Infinite) → OCE → cleanup → log RecyclingScope → RecycleScopeAsync() → signal.Reset() → loop - Init-phase path: signal fires during InitializeAllAsync → cancel → cleanup → signal.Reset() → loop without RecycleScopeAsync (scope TCS still valid) - IPreScopeInitializer services awaited only before the first scope (before the while loop), not re-awaited on recycles - BlazorAppScope.RecycleAsync: now calls IJSRuntime.InvokeVoidAsync("location.reload") - Registered IApplicationRecycleSignal as singleton in DI - Added EventId 712 ApplicationLifecycle_RecyclingScope log message - Added 5 unit tests covering both recycle paths, loop continuation, pre-initializer not re-awaited, and signal.Reset() called after recycle - Updated _doc_Lifecycle.md: removed "Future Plans" stub, documented the implemented recycle loop, signal, and two recycle paths https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Add sandbox setup script and update E2E test docs for Playwright browsers Claude Code cloud sandbox environments block cdn.playwright.dev, so the standard `playwright.ps1 install` fails. Browsers are pre-installed at /opt/pw-browsers but under a different revision (1194) than the current Playwright package expects (1208). - scripts/setup-playwright-sandbox.sh: auto-detects the expected version from the Playwright registry JS, finds the highest installed version in /opt/pw-browsers, and creates symlinks + INSTALLATION_COMPLETE markers - CLAUDE.md: updated E2E test instructions to document both the developer machine path and the sandbox workaround, with PLAYWRIGHT_BROWSERS_PATH https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Address PR review comments: refactor recycle loop, fix thread safety, add tests - ApplicationLifecycle.ExecuteAsync: replace Task.Delay(Timeout.Infinite) / exception-based control flow with WaitForCancelledAsync (returns normally, no OCE for steady-state transitions) - Add ScopeReady log message (EventId 713); ScopeReleased now only logged on init/construction failure - Cleanup now runs inside the loop before ShuttingDown, rather than after the loop - BlazorAppScope.RecycleAsync: use GetRequiredService<IJSRuntime>(); remove unused using - ApplicationRecycleSignal: full lock-based thread safety; implement IDisposable - IApplicationRecycleSignal: add XML doc comments to all interface methods - HostBuilderExtensions: restore missing using AdaptiveRemote.Services.CloudAssets - CLAUDE.md: update cloud sandbox E2E guidance — don't fall back to setup script; report broken environment - _doc_Lifecycle.md: update recycle loop description to match current WaitForCancelledAsync design - ApplicationLifecycleTests: fix 3 tests for new log ordering; add 6 recycle scenario tests (second signal during cleanup, blocks until cleanup, delay during init after recycle, error during cleanup continues recycle, error during init exits loop, signal during pre-init is no-op) https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s * Improve ApplicationLifecycle unit tests, behavior, and logging. * Delete the playwright workaround script since it shouldn't be necessary --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Joe Davis <ElwoodMoves@hotmail.com>
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.
Convert ApplicationLifecycle.ExecuteAsync to a recycle loop and implement BlazorAppScope.RecycleAsync().