Skip to content

Commit 27e5181

Browse files
authored
Merge pull request #4328 from dmichon-msft/rush-serve-socket
[rush-serve-plugin] Add build status monitoring via WebSocket
2 parents 4775ea7 + 668f032 commit 27e5181

18 files changed

+574
-73
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush",
5+
"comment": "Add \"Waiting\" operation status for operations that have one or more dependencies still pending. Ensure that the `onOperationStatusChanged` hook fires for every status change.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush",
5+
"comment": "Add support for optional build status notifications over a web socket connection to `@rushstack/rush-serve-plugin`.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}

common/config/rush/nonbrowser-approved-packages.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,14 +406,14 @@
406406
"name": "compression",
407407
"allowedCategories": [ "libraries" ]
408408
},
409-
{
410-
"name": "cors",
411-
"allowedCategories": [ "libraries" ]
412-
},
413409
{
414410
"name": "constructs",
415411
"allowedCategories": [ "tests" ]
416412
},
413+
{
414+
"name": "cors",
415+
"allowedCategories": [ "libraries" ]
416+
},
417417
{
418418
"name": "css-loader",
419419
"allowedCategories": [ "libraries", "tests" ]
@@ -826,6 +826,10 @@
826826
"name": "wordwrap",
827827
"allowedCategories": [ "libraries" ]
828828
},
829+
{
830+
"name": "ws",
831+
"allowedCategories": [ "libraries" ]
832+
},
829833
{
830834
"name": "xmldoc",
831835
"allowedCategories": [ "libraries" ]

common/config/rush/pnpm-lock.yaml

Lines changed: 16 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/config/rush/repo-state.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
22
{
3-
"pnpmShrinkwrapHash": "5ee5e5b7fa262ef5096e77b7d86ef24ade887c5b",
3+
"pnpmShrinkwrapHash": "af97ead68ee0632cb19c93ef203e84fc0c1e1375",
44
"preferredVersionsHash": "1926a5b12ac8f4ab41e76503a0d1d0dccc9c0e06"
55
}

common/reviews/api/rush-lib.api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,8 @@ export enum OperationStatus {
877877
RemoteExecuting = "REMOTE EXECUTING",
878878
Skipped = "SKIPPED",
879879
Success = "SUCCESS",
880-
SuccessWithWarning = "SUCCESS WITH WARNINGS"
880+
SuccessWithWarning = "SUCCESS WITH WARNINGS",
881+
Waiting = "WAITING"
881882
}
882883

883884
// @public (undocumented)

libraries/rush-lib/src/logic/operations/AsyncOperationQueue.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ export class AsyncOperationQueue
7474
*/
7575
public complete(record: OperationExecutionRecord): void {
7676
this._completedOperations.add(record);
77+
78+
// Apply status changes to direct dependents
79+
for (const item of record.consumers) {
80+
// Remove this operation from the dependencies, to unblock the scheduler
81+
if (
82+
item.dependencies.delete(record) &&
83+
item.dependencies.size === 0 &&
84+
item.status === OperationStatus.Waiting
85+
) {
86+
item.status = OperationStatus.Ready;
87+
}
88+
}
89+
7790
if (this._completedOperations.size === this._totalOperations) {
7891
this._isDone = true;
7992
}
@@ -88,39 +101,39 @@ export class AsyncOperationQueue
88101

89102
// By iterating in reverse order we do less array shuffling when removing operations
90103
for (let i: number = queue.length - 1; waitingIterators.length > 0 && i >= 0; i--) {
91-
const operation: OperationExecutionRecord = queue[i];
104+
const record: OperationExecutionRecord = queue[i];
92105

93106
if (
94-
operation.status === OperationStatus.Blocked ||
95-
operation.status === OperationStatus.Skipped ||
96-
operation.status === OperationStatus.Success ||
97-
operation.status === OperationStatus.SuccessWithWarning ||
98-
operation.status === OperationStatus.FromCache ||
99-
operation.status === OperationStatus.NoOp ||
100-
operation.status === OperationStatus.Failure
107+
record.status === OperationStatus.Blocked ||
108+
record.status === OperationStatus.Skipped ||
109+
record.status === OperationStatus.Success ||
110+
record.status === OperationStatus.SuccessWithWarning ||
111+
record.status === OperationStatus.FromCache ||
112+
record.status === OperationStatus.NoOp ||
113+
record.status === OperationStatus.Failure
101114
) {
102115
// It shouldn't be on the queue, remove it
103116
queue.splice(i, 1);
104-
} else if (
105-
operation.status === OperationStatus.Queued ||
106-
operation.status === OperationStatus.Executing
107-
) {
117+
} else if (record.status === OperationStatus.Queued || record.status === OperationStatus.Executing) {
108118
// This operation is currently executing
109119
// next one plz :)
120+
} else if (record.status === OperationStatus.Waiting) {
121+
// This operation is not yet ready to be executed
122+
// next one plz :)
110123
continue;
111-
} else if (operation.status === OperationStatus.RemoteExecuting) {
124+
} else if (record.status === OperationStatus.RemoteExecuting) {
112125
// This operation is not ready to execute yet, but it may become ready later
113126
// next one plz :)
114127
continue;
115-
} else if (operation.status !== OperationStatus.Ready) {
128+
} else if (record.status !== OperationStatus.Ready) {
116129
// Sanity check
117-
throw new Error(`Unexpected status "${operation.status}" for queued operation: ${operation.name}`);
118-
} else if (operation.dependencies.size === 0) {
130+
throw new Error(`Unexpected status "${record.status}" for queued operation: ${record.name}`);
131+
} else {
119132
// This task is ready to process, hand it to the iterator.
120133
// Needs to have queue semantics, otherwise tools that iterate it get confused
121-
operation.status = OperationStatus.Queued;
134+
record.status = OperationStatus.Queued;
122135
waitingIterators.shift()!({
123-
value: operation,
136+
value: record,
124137
done: false
125138
});
126139
}

libraries/rush-lib/src/logic/operations/ConsoleTimelinePlugin.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const TIMELINE_WIDTH: number = 109;
7575
* Timeline - symbols representing each operation status
7676
*/
7777
const TIMELINE_CHART_SYMBOLS: Record<OperationStatus, string> = {
78+
[OperationStatus.Waiting]: '?',
7879
[OperationStatus.Ready]: '?',
7980
[OperationStatus.Queued]: '?',
8081
[OperationStatus.Executing]: '?',
@@ -92,6 +93,7 @@ const TIMELINE_CHART_SYMBOLS: Record<OperationStatus, string> = {
9293
* Timeline - colorizer for each operation status
9394
*/
9495
const TIMELINE_CHART_COLORIZER: Record<OperationStatus, (string: string) => string> = {
96+
[OperationStatus.Waiting]: colors.yellow,
9597
[OperationStatus.Ready]: colors.yellow,
9698
[OperationStatus.Queued]: colors.yellow,
9799
[OperationStatus.Executing]: colors.yellow,

libraries/rush-lib/src/logic/operations/OperationExecutionManager.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -394,12 +394,6 @@ export class OperationExecutionManager {
394394
if (record.status !== OperationStatus.RemoteExecuting) {
395395
// If the operation was not remote, then we can notify queue that it is complete
396396
this._executionQueue.complete(record);
397-
398-
// Apply status changes to direct dependents
399-
for (const item of record.consumers) {
400-
// Remove this operation from the dependencies, to unblock the scheduler
401-
item.dependencies.delete(record);
402-
}
403397
}
404398
}
405399
}

libraries/rush-lib/src/logic/operations/OperationExecutionRecord.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@ export class OperationExecutionRecord implements IOperationRunnerContext {
3333
*/
3434
public readonly operation: Operation;
3535

36-
/**
37-
* The current execution status of an operation. Operations start in the 'ready' state,
38-
* but can be 'blocked' if an upstream operation failed. It is 'executing' when
39-
* the operation is executing. Once execution is complete, it is either 'success' or
40-
* 'failure'.
41-
*/
42-
public status: OperationStatus = OperationStatus.Ready;
43-
4436
/**
4537
* The error which occurred while executing this operation, this is stored in case we need
4638
* it later (for example to re-print errors at end of execution).
@@ -101,6 +93,7 @@ export class OperationExecutionRecord implements IOperationRunnerContext {
10193
private readonly _context: IOperationExecutionRecordContext;
10294

10395
private _collatedWriter: CollatedWriter | undefined = undefined;
96+
private _status: OperationStatus;
10497

10598
public constructor(operation: Operation, context: IOperationExecutionRecordContext) {
10699
const { runner, associatedPhase, associatedProject } = operation;
@@ -123,6 +116,7 @@ export class OperationExecutionRecord implements IOperationRunnerContext {
123116
});
124117
}
125118
this._context = context;
119+
this._status = operation.dependencies.size > 0 ? OperationStatus.Waiting : OperationStatus.Ready;
126120
}
127121

128122
public get name(): string {
@@ -159,6 +153,23 @@ export class OperationExecutionRecord implements IOperationRunnerContext {
159153
return this._operationMetadataManager?.stateFile.state?.cobuildRunnerId;
160154
}
161155

156+
/**
157+
* The current execution status of an operation. Operations start in the 'ready' state,
158+
* but can be 'blocked' if an upstream operation failed. It is 'executing' when
159+
* the operation is executing. Once execution is complete, it is either 'success' or
160+
* 'failure'.
161+
*/
162+
public get status(): OperationStatus {
163+
return this._status;
164+
}
165+
public set status(newStatus: OperationStatus) {
166+
if (newStatus === this._status) {
167+
return;
168+
}
169+
this._status = newStatus;
170+
this._context.onOperationStatusChanged?.(this);
171+
}
172+
162173
public async executeAsync({
163174
onStart,
164175
onResult
@@ -169,9 +180,8 @@ export class OperationExecutionRecord implements IOperationRunnerContext {
169180
if (this.status === OperationStatus.RemoteExecuting) {
170181
this.stopwatch.reset();
171182
}
172-
this.status = OperationStatus.Executing;
173183
this.stopwatch.start();
174-
this._context.onOperationStatusChanged?.(this);
184+
this.status = OperationStatus.Executing;
175185

176186
try {
177187
const earlyReturnStatus: OperationStatus | undefined = await onStart(this);
@@ -194,7 +204,6 @@ export class OperationExecutionRecord implements IOperationRunnerContext {
194204
this.stdioSummarizer.close();
195205
this.stopwatch.stop();
196206
}
197-
this._context.onOperationStatusChanged?.(this);
198207
}
199208
}
200209
}

0 commit comments

Comments
 (0)