Skip to content

[rush-daemon][WS2] Add warm workspace session foundation - #5936

Open
Mo Jazayeri (mojaza) wants to merge 8 commits into
microsoft:mainfrom
mojaza:mojazayeri-microsoft-rushd-warm-workspace-session
Open

[rush-daemon][WS2] Add warm workspace session foundation#5936
Mo Jazayeri (mojaza) wants to merge 8 commits into
microsoft:mainfrom
mojaza:mojazayeri-microsoft-rushd-warm-workspace-session

Conversation

@mojaza

Copy link
Copy Markdown
Contributor

Summary

Adds the next reviewable WS2 increment for #5897: a single warm WorkspaceSession owned by each RushDaemonHost lifecycle. The daemon now loads stable Rush workspace identity once, retains headless file invalidations while no client is attached, and disposes session resources deterministically without changing normal Rush CLI behavior.

Details

  • Initializes one concurrency-safe WorkspaceSession before publishing daemon readiness and reuses it for the host lifetime.
  • Loads RushConfiguration once and exposes stable repository, Rush version, project count, and project-name metadata for future routing.
  • Adds typed dependency-injection seams for a reusable operation graph, Rush plugin session, input snapshot, and project watcher.
  • Adds a headless watcher plus sequence-based invalidation tracker that coalesces paths, preserves changes until acknowledged, and remains conservatively dirty after watcher errors.
  • Handles concurrent initialization, synchronous/asynchronous failure retry, startup cleanup, listener-bind cleanup, and shutdown ordering.
  • Keeps the implementation within @rushstack/rush-daemon; no apps/rush or rush-lib launcher integration is added.

A real all-projects operation graph is intentionally not constructed here. Its graph shape and runner lifetime remain command-dependent, and the reusable per-iteration runner lifetime tracked by open WS0 issue #5895 is incomplete. The component factory is the integration seam for that follow-up rather than duplicating PhasedScriptAction internals or landing placeholder routing behavior.

How it was tested

  • rush build --to @rushstack/rush-daemon
  • rush test --only @rushstack/rush-daemon
  • rush update --subspace default --recheck
  • rush change --verify

The focused tests cover one-time initialization and reuse, concurrent initialization, synchronous/asynchronous retry semantics, retained headless invalidations, watcher-error dirty state, component cleanup after failed startup, listener-bind cleanup, protocol preservation, and deterministic shutdown ordering.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a lifecycle-owned warm workspace session to the Rush daemon.

Changes:

  • Loads and reuses workspace metadata and injectable engine components.
  • Tracks headless filesystem invalidations with deterministic cleanup.
  • Adds lifecycle tests, documentation, API updates, and dependencies.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
libraries/rush-daemon/src/WorkspaceSessionProvider.ts Coordinates session initialization and disposal.
libraries/rush-daemon/src/WorkspaceSessionFileWatcher.ts Watches workspace paths for invalidations.
libraries/rush-daemon/src/WorkspaceSession.ts Defines and implements warm sessions.
libraries/rush-daemon/src/WorkspaceInvalidationTracker.ts Retains sequence-based invalidations.
libraries/rush-daemon/src/test/WorkspaceSessionProvider.test.ts Tests initialization concurrency and retries.
libraries/rush-daemon/src/test/WorkspaceSession.test.ts Tests metadata, invalidations, and cleanup.
libraries/rush-daemon/src/test/TestWorkspaceSession.ts Provides a session test double.
libraries/rush-daemon/src/test/RushDaemonHost.test.ts Tests host/session lifecycle integration.
libraries/rush-daemon/src/RushDaemonHost.ts Owns the session for the host lifecycle.
libraries/rush-daemon/src/index.ts Exports session APIs.
libraries/rush-daemon/README.md Documents warm-session behavior.
libraries/rush-daemon/package.json Adds the Rush library dependency.
common/reviews/api/rush-daemon.api.md Records the new public API surface.
common/config/subspaces/default/pnpm-lock.yaml Locks the new workspace dependency.
common/changes/@rushstack/rush-daemon/mojazayeri-warm-workspace-session_2026-08-18-22-30.json Adds the release change entry.
Files not reviewed (1)
  • common/config/subspaces/default/pnpm-lock.yaml: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread libraries/rush-daemon/src/WorkspaceSessionFileWatcher.ts
Mo Jazayeri (mojaza) and others added 2 commits August 19, 2026 10:36
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Files not reviewed (1)
  • common/config/subspaces/default/pnpm-lock.yaml: Generated file
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

libraries/rush-daemon/src/WorkspaceSessionProvider.ts:53

  • A cleanup failure is suppressed here during concurrent initialization and shutdown. If the factory resolves after _disposed is set and session.disposeAsync() rejects in _initializeAsync(), that rejection is converted to undefined, so this disposeAsync() resolves even though the session was not cleaned up. Preserve and propagate initialization-time disposal errors (while still handling ordinary factory rejection), and cover the rejecting-disposer case.
      (await this._initializationPromise?.then(
        (initializedSession: IWorkspaceSession) => initializedSession,
        () => undefined
      ));
  • Files reviewed: 15/16 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Comment thread libraries/rush-daemon/src/WorkspaceSession.ts Outdated
Comment thread libraries/rush-daemon/src/WorkspaceSession.ts Outdated
Comment thread libraries/rush-daemon/src/WorkspaceSession.ts Outdated
Comment thread libraries/rush-daemon/src/WorkspaceSession.ts Outdated
Comment thread libraries/rush-daemon/src/WorkspaceSession.ts
Comment thread libraries/rush-daemon/src/WorkspaceSession.ts Outdated
Comment thread libraries/rush-daemon/src/WorkspaceSession.ts Outdated
Comment thread libraries/rush-daemon/src/WorkspaceSessionFileWatcher.ts Outdated
Comment thread libraries/rush-daemon/src/WorkspaceSessionFileWatcher.ts Outdated
mojazayeri and others added 2 commits August 19, 2026 20:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* @beta
*/
export class WorkspaceInvalidationTracker {
private readonly _sequenceByPath: Map<string, number> = new Map();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heads up we'll have this lint rule enforced in the next couple days for ecmascript private class members

you don't necessarily need to refactor yet but up to you

readonly #sequenceByPath instead of private readonly _sequenceByPath.

return false;
}
return filename
.split(/[\\/]/)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

store regex in a var outside instead of rebuilding it on every call to this function

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should consider making this into a lint rule for the repo

} catch (error) {
const cleanupErrors: unknown[] = [];
try {
await projectWatcher?.[Symbol.asyncDispose]();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is projectWatcher not being cleaned up with the rest of the components later?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WorkspaceSession first disposes projectWatcher, then disposes components.... but the watcher came from components.projectWatcher, so the component disposer may already dispose it. right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs triage

Development

Successfully merging this pull request may close these issues.

5 participants