Skip to content

Commit

Permalink
Better busy status tracking during validation
Browse files Browse the repository at this point in the history
  • Loading branch information
TwitchBronBron committed Apr 5, 2024
1 parent 76ec222 commit aecdb64
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 3 deletions.
42 changes: 42 additions & 0 deletions src/BusyStatusTracker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,46 @@ describe('BusyStatusTracker', () => {
});
expect(tracker.status).to.eql(BusyStatus.idle);
});

describe('scopedTracking', () => {
const scope1 = {};

it('supports scoped tracking', async () => {
let onStatus = tracker.once('change');
tracker.beginScopedRun(scope1, 'run1');
expect(
await onStatus
).to.eql(BusyStatus.busy);

tracker.beginScopedRun(scope1, 'run2');
expect(latestStatus).to.eql(BusyStatus.busy);

await tracker.endScopedRun(scope1, 'run1');
expect(latestStatus).to.eql(BusyStatus.busy);

onStatus = tracker.once('change');
await tracker.endScopedRun(scope1, 'run2');
expect(
await onStatus
).to.eql(BusyStatus.idle);
});

it('clears runs for scope', async () => {
let onChange = tracker.once('change');
tracker.beginScopedRun(scope1, 'run1');
tracker.beginScopedRun(scope1, 'run1');
tracker.beginScopedRun(scope1, 'run1');

expect(
await onChange
).to.eql(BusyStatus.busy);

onChange = tracker.once('change');

tracker.endAllRunsForScope(scope1);
expect(
await onChange
).to.eql(BusyStatus.idle);
});
});
});
79 changes: 78 additions & 1 deletion src/BusyStatusTracker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EventEmitter } from 'eventemitter3';
import { Deferred } from './deferred';

/**
* Tracks the busy/idle status of various sync or async tasks
Expand All @@ -13,7 +14,73 @@ export class BusyStatusTracker {
startTime?: Date;
}>();

private scopedRuns = new Map<any, any>();
/**
* Collection to track scoped runs. All runs represented here also have a corresponding entry in `activeRuns`
*/
private scopedRuns = new Map<any, Array<{
label: string;
deferred: Deferred;
}>>();

/**
* Begin a busy task. It's expected you will call `endScopeRun` when the task is complete.
* @param scope an object used for reference as to what is doing the work. Can be used to bulk-cancel all runs for a given scope.
* @param label label for the run. This is required for the `endScopedRun` method to know what to end.
*/
public beginScopedRun(scope: any, label: string) {
let runsForScope = this.scopedRuns.get(scope);
if (!runsForScope) {
runsForScope = [];
this.scopedRuns.set(scope, runsForScope);
}

const deferred = new Deferred();

void this.run(() => {
//don't mark the busy run as completed until the deferred is resolved
return deferred.promise;
}, label);

runsForScope.push({
label: label,
deferred: deferred
});
}

/**
* End the earliest run for the given scope and label
* @param scope an object used for reference as to what is doing the work. Can be used to bulk-cancel all runs for a given scope.
* @param label label for the run
*/
public endScopedRun(scope: any, label: string) {
const runsForScope = this.scopedRuns.get(scope);
if (!runsForScope) {
return;
}
const earliestRunIndex = runsForScope.findIndex(x => x.label === label);
if (earliestRunIndex === -1) {
return;
}
const earliestRun = runsForScope[earliestRunIndex];
runsForScope.splice(earliestRunIndex, 1);
earliestRun.deferred.resolve();
return earliestRun.deferred.promise;
}

/**
* End all runs for a given scope. This is typically used when the scope is destroyed, and we want to make sure all runs are cleaned up.
* @param scope an object used for reference as to what is doing the work.
*/
public endAllRunsForScope(scope: any) {
const runsForScope = this.scopedRuns.get(scope);
if (!runsForScope) {
return;
}
for (const run of runsForScope) {
run.deferred.resolve();
}
this.scopedRuns.delete(scope);
}

/**
* Start a new piece of work
Expand Down Expand Up @@ -59,6 +126,16 @@ export class BusyStatusTracker {

private emitter = new EventEmitter<string, BusyStatus>();

public once(eventName: 'change'): Promise<BusyStatus>;
public once<T>(eventName: string): Promise<T> {
return new Promise<T>((resolve) => {
const off = this.on(eventName as any, (data) => {
off();
resolve(data as any);
});
});
}

public on(eventName: 'change', handler: (status: BusyStatus) => void) {
this.emitter.on(eventName, handler);
return () => {
Expand Down
5 changes: 3 additions & 2 deletions src/lsp/ProjectManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ export class ProjectManager {
});

this.on('validate-begin', (event) => {
this.busyStatusTracker.pushScoped(event.program, 'validate');
this.busyStatusTracker.beginScopedRun(event.project, `validate-project-${event.project.projectNumber}`);
});
this.on('validate-end', (event) => {
this.busyStatusTracker.popScoped(event.program, 'validate');
void this.busyStatusTracker.endScopedRun(event.project, `validate-project-${event.project.projectNumber}`);
});
}

Expand Down Expand Up @@ -581,6 +581,7 @@ export class ProjectManager {
this.projects.splice(idx, 1);
}
project?.dispose();
this.busyStatusTracker.endAllRunsForScope(project);
}

/**
Expand Down

0 comments on commit aecdb64

Please sign in to comment.