Skip to content

Commit

Permalink
Add an async lazy class and lazy lifetimes (#1861)
Browse files Browse the repository at this point in the history
* Add an async lazy class and lazy lifetimes

* Add unit tests
  • Loading branch information
bwateratmsft authored Apr 20, 2020
1 parent df38c83 commit d0b342d
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 6 deletions.
2 changes: 2 additions & 0 deletions extension.bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export { DotNetClient } from './src/debugging/coreclr/CommandLineDotNetClient';
export { compareBuildImageOptions, LaunchOptions } from './src/debugging/coreclr/dockerManager';
export { FileSystemProvider } from './src/debugging/coreclr/fsProvider';
export { LineSplitter } from './src/debugging/coreclr/lineSplitter';
export { delay } from './src/utils/delay';
export { Lazy, AsyncLazy } from './src/utils/lazy';
export { OSProvider } from './src/utils/LocalOSProvider';
export { DockerDaemonIsLinuxPrerequisite, DockerfileExistsPrerequisite, DotNetSdkInstalledPrerequisite, LinuxUserInDockerGroupPrerequisite, MacNuGetFallbackFolderSharedPrerequisite } from './src/debugging/coreclr/prereqManager';
export { ext } from './src/extensionVariables';
Expand Down
2 changes: 1 addition & 1 deletion src/tasks/node/NodeTaskHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import * as path from 'path';
import { WorkspaceFolder } from 'vscode';
import Lazy from '../../utils/lazy';
import { Lazy } from '../../utils/lazy';
import { inferCommand, inferPackageName, InspectMode, NodePackage, readPackage } from '../../utils/nodeUtils';
import { resolveVariables, unresolveWorkspaceFolder } from '../../utils/resolveVariables';
import { DockerBuildOptions, DockerBuildTaskDefinitionBase } from '../DockerBuildTaskDefinitionBase';
Expand Down
66 changes: 61 additions & 5 deletions src/utils/lazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,77 @@ export class Lazy<T> {
private _isValueCreated: boolean = false;
private _value: T | undefined;

public constructor(private readonly valueFactory: () => T) {
public constructor(private readonly valueFactory: () => T, private readonly _valueLifetime?: number) {
}

public get isValueCreated(): boolean {
return this._isValueCreated;
}

public get value(): T {
if (!this._isValueCreated) {
this._value = this.valueFactory();
this._isValueCreated = true;
if (this._isValueCreated) {
return this._value;
}

this._value = this.valueFactory();
this._isValueCreated = true;

if (this._valueLifetime) {
const reset = setTimeout(() => {
this._isValueCreated = false;
this._value = undefined;
clearTimeout(reset);
}, this._valueLifetime);
}

return this._value;
}
}

export default Lazy;
export class AsyncLazy<T> {
private _isValueCreated: boolean = false;
private _value: T | undefined;
private _valuePromise: Promise<T> | undefined;

public constructor(private readonly valueFactory: () => Promise<T>, private readonly _valueLifetime?: number) {
}

public get isValueCreated(): boolean {
return this._isValueCreated;
}

public async getValue(): Promise<T> {
if (this._isValueCreated) {
return this._value;
}

const isPrimaryPromise = this._valuePromise === undefined; // The first caller is "primary"

if (isPrimaryPromise) {
this._valuePromise = this.valueFactory();
}

const result = await this._valuePromise;

if (isPrimaryPromise) {
this._value = result;
this._valuePromise = undefined;
this._isValueCreated = true;
}

if (this._valueLifetime && isPrimaryPromise) {
const reset = setTimeout(() => {
// Will only clear out values if there isn't a currently-running Promise
// If there is, this timer will skip, but when that Promise finishes it will go through this code and register a new timer
if (this._valuePromise === undefined) {
this._isValueCreated = false;
this._value = undefined;
}

clearTimeout(reset);
}, this._valueLifetime);
}

return result;
}
}
117 changes: 117 additions & 0 deletions test/utils/lazy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as assert from 'assert';
import { Lazy, AsyncLazy } from "../../extension.bundle";
import { delay } from '../../extension.bundle';

suite('(unit) Lazy tests', () => {
suite('Lazy<T>', () => {
test('Normal', async () => {
let factoryCallCount = 0;
const lazy: Lazy<boolean> = new Lazy(() => {
factoryCallCount++;
return true;
});

lazy.value;
lazy.value;

assert.equal(factoryCallCount, 1, 'Incorrect number of value factory calls.');
});

test('With lifetime', async () => {
let factoryCallCount = 0;
const lazy: Lazy<boolean> = new Lazy(() => {
factoryCallCount++;
return true;
}, 5);

lazy.value;
lazy.value;

assert.equal(factoryCallCount, 1, 'Incorrect number of value factory calls.');

await delay(10);
lazy.value;
lazy.value;

assert.equal(factoryCallCount, 2, 'Incorrect number of value factory calls.');
});
});

suite('AsyncLazy<T>', () => {
test('Normal', async () => {
let factoryCallCount = 0;
const lazy: AsyncLazy<boolean> = new AsyncLazy(async () => {
factoryCallCount++;
await delay(5);
return true;
});

await lazy.getValue();
await lazy.getValue();

assert.equal(factoryCallCount, 1, 'Incorrect number of value factory calls.');
});

test('Simultaneous callers', async () => {
let factoryCallCount = 0;
const lazy: AsyncLazy<boolean> = new AsyncLazy(async () => {
factoryCallCount++;
await delay(5);
return true;
});

const p1 = lazy.getValue();
const p2 = lazy.getValue();
await Promise.all([p1, p2]);

assert.equal(factoryCallCount, 1, 'Incorrect number of value factory calls.');
});

test('With lifetime', async () => {
let factoryCallCount = 0;
const lazy: AsyncLazy<boolean> = new AsyncLazy(async () => {
factoryCallCount++;
await delay(5);
return true;
}, 10);

await lazy.getValue();
await lazy.getValue();

assert.equal(factoryCallCount, 1, 'Incorrect number of value factory calls.');

await delay(15);
await lazy.getValue();
await lazy.getValue();

assert.equal(factoryCallCount, 2, 'Incorrect number of value factory calls.');
});

test('Simultaneous callers with lifetime', async () => {
let factoryCallCount = 0;
const lazy: AsyncLazy<boolean> = new AsyncLazy(async () => {
factoryCallCount++;
await delay(5);
return true;
}, 10);

const p1 = lazy.getValue();
const p2 = lazy.getValue();
await Promise.all([p1, p2]);

assert.equal(factoryCallCount, 1, 'Incorrect number of value factory calls.');

await delay(15);
const p3 = lazy.getValue();
const p4 = lazy.getValue();
await Promise.all([p3, p4]);

assert.equal(factoryCallCount, 2, 'Incorrect number of value factory calls.');
});
});
});

0 comments on commit d0b342d

Please sign in to comment.