Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/npm_and_yarn/word-wrap-1.2.4
Browse files Browse the repository at this point in the history
  • Loading branch information
TwitchBronBron authored Jul 22, 2023
2 parents 4d8cca1 + 80cfe5c commit e611b71
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 47 deletions.
152 changes: 152 additions & 0 deletions src/BusyStatusTracker.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { expect } from 'chai';
import { Deferred } from './deferred';
import { BusyStatus, BusyStatusTracker } from './BusyStatusTracker';

describe('BusyStatusTracker', () => {
let tracker: BusyStatusTracker;

let latestStatus: BusyStatus;

beforeEach(() => {
latestStatus = BusyStatus.idle;
tracker = new BusyStatusTracker();
tracker.on('change', (value) => {
latestStatus = value;
});
});

afterEach(() => {
tracker?.destroy();
});

it('tracks a single run', () => {
expect(latestStatus).to.eql(BusyStatus.idle);
tracker.run(() => {
expect(tracker.status).to.eql(BusyStatus.busy);
});
expect(latestStatus).to.eql(BusyStatus.idle);
});

it('tracks a single async flow', async () => {
const deferred = new Deferred();
const finishedPromise = tracker.run(() => {
return deferred.promise;
});
expect(latestStatus).to.eql(BusyStatus.busy);

deferred.resolve();
await finishedPromise;

expect(latestStatus).to.eql(BusyStatus.idle);
});

it('independently tracks multiple runs for same program', () => {
tracker.run(() => {
expect(latestStatus).to.eql(BusyStatus.busy);
});
tracker.run(() => {
expect(latestStatus).to.eql(BusyStatus.busy);
});
expect(latestStatus).to.eql(BusyStatus.idle);
});

it('tracks as `busy` one of the runs is still pending', async () => {
const deferred = new Deferred();
tracker.run(() => {
expect(latestStatus).to.eql(BusyStatus.busy);
});
const finishedPromise = tracker.run(() => {
expect(latestStatus).to.eql(BusyStatus.busy);
return deferred.promise;
});
expect(latestStatus).to.eql(BusyStatus.busy);

deferred.resolve();
await finishedPromise;

expect(latestStatus).to.eql(BusyStatus.idle);
});

it('handles error during synchronous flow', () => {
try {
tracker.run(() => {
throw new Error('Crash');
});
} catch { }
expect(latestStatus).to.eql(BusyStatus.idle);
});

it('handles error during async flow', async () => {
try {
await tracker.run(() => {
return Promise.reject(new Error('Crash'));
});
} catch { }
expect(latestStatus).to.eql(BusyStatus.idle);
});

it('only finalizes on the first call to finalize', () => {
try {
tracker.run((finalize) => {
expect(latestStatus).to.eql(BusyStatus.busy);
finalize();
expect(latestStatus).to.eql(BusyStatus.idle);
finalize();
expect(latestStatus).to.eql(BusyStatus.idle);
});
} catch { }
expect(latestStatus).to.eql(BusyStatus.idle);
});

it('supports multiple simultaneous projects', async () => {
//run the projects out of order
const deferred2 = new Deferred();
const run1Promise = tracker.run(() => {
expect(latestStatus).to.eql(BusyStatus.busy);
return deferred2.promise;
});

const deferred1 = new Deferred();
const run2Promise = tracker.run(() => {
expect(latestStatus).to.eql(BusyStatus.busy);
return deferred1.promise;
});

expect(latestStatus).to.eql(BusyStatus.busy);


deferred1.resolve();
await run2Promise;
expect(latestStatus).to.eql(BusyStatus.busy);

deferred2.resolve();
await run1Promise;
expect(latestStatus).to.eql(BusyStatus.idle);
});

it('supports unsubscribing from events', () => {
const changes = []; //contains every busy/idle status change
const disconnect = tracker.on('change', (status) => changes.push(status));

expect(changes.length).to.eql(0);

tracker.run(() => { });
expect(changes.length).to.eql(2);

tracker.run(() => { });
expect(changes.length).to.eql(4);

disconnect();

tracker.run(() => { });
expect(changes.length).to.eql(4);
});

it('getStatus returns proper value', () => {
expect(tracker.status).to.eql(BusyStatus.idle);
tracker.run(() => {
expect(tracker.status).to.eql(BusyStatus.busy);
});
expect(tracker.status).to.eql(BusyStatus.idle);
});
});
89 changes: 89 additions & 0 deletions src/BusyStatusTracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { EventEmitter } from 'eventemitter3';

/**
* Tracks the busy/idle status of various sync or async tasks
* Reports the overall status to the client
*/
export class BusyStatusTracker {
/**
* @readonly
*/
public activeRuns = new Set<{
label?: string;
startTime?: Date;
}>();

/**
* Start a new piece of work
*/
public run<T, R = T | Promise<T>>(callback: (finalize?: FinalizeBuildStatusRun) => R, label?: string): R {
const run = {
label: label,
startTime: new Date()
};
this.activeRuns.add(run);

if (this.activeRuns.size === 1) {
this.emit('change', BusyStatus.busy);
}

let isFinalized = false;
const finalizeRun = () => {
if (isFinalized === false) {
isFinalized = true;
this.activeRuns.delete(run);
if (this.activeRuns.size <= 0) {
this.emit('change', BusyStatus.idle);
}
}
};

let result: R | PromiseLike<R>;
//call the callback function
try {
result = callback(finalizeRun);
//if the result is a promise, don't finalize until it completes
if (typeof (result as any)?.then === 'function') {
return Promise.resolve(result).finally(finalizeRun).then(() => result) as any;
} else {
finalizeRun();
return result;
}
} catch (e) {
finalizeRun();
throw e;
}
}

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

public on(eventName: 'change', handler: (status: BusyStatus) => void) {
this.emitter.on(eventName, handler);
return () => {
this.emitter.off(eventName, handler);
};
}

private emit(eventName: 'change', value: BusyStatus) {
this.emitter.emit(eventName, value);
}

public destroy() {
this.emitter.removeAllListeners();
}

/**
* The current status of the busy tracker.
* @readonly
*/
public get status() {
return this.activeRuns.size === 0 ? BusyStatus.idle : BusyStatus.busy;
}
}

export type FinalizeBuildStatusRun = (status?: BusyStatus) => void;

export enum BusyStatus {
busy = 'busy',
idle = 'idle'
}
2 changes: 2 additions & 0 deletions src/LanguageServer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { isBrsFile, isLiteralString } from './astUtils/reflection';
import { createVisitor, WalkMode } from './astUtils/visitors';
import { tempDir, rootDir } from './testHelpers.spec';
import { URI } from 'vscode-uri';
import { BusyStatusTracker } from './BusyStatusTracker';

const sinon = createSandbox();

Expand Down Expand Up @@ -76,6 +77,7 @@ describe('LanguageServer', () => {
beforeEach(() => {
sinon.restore();
server = new LanguageServer();
server['busyStatusTracker'] = new BusyStatusTracker();
workspaceFolders = [workspacePath];

vfs = {};
Expand Down
Loading

0 comments on commit e611b71

Please sign in to comment.