Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions extensions/ql-vscode/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,12 @@ export function getQueryName(info: EvaluationInfo) {
* the last invocation of that function.
*/
export class InvocationRateLimiter<T> {
constructor(extensionContext: ExtensionContext, funcIdentifier: string, func: () => Promise<T>) {
constructor(
extensionContext: ExtensionContext,
funcIdentifier: string,
func: () => Promise<T>,
createDate: (dateString?: string) => Date = s => s ? new Date(s) : new Date()) {
this._createDate = createDate;
this._extensionContext = extensionContext;
this._func = func;
this._funcIdentifier = funcIdentifier;
Expand All @@ -150,7 +155,7 @@ export class InvocationRateLimiter<T> {
* Invoke the function if `minSecondsSinceLastInvocation` seconds have elapsed since the last invocation.
*/
public async invokeFunctionIfIntervalElapsed(minSecondsSinceLastInvocation: number): Promise<InvocationRateLimiterResult<T>> {
const updateCheckStartDate = new Date();
const updateCheckStartDate = this._createDate();
const lastInvocationDate = this.getLastInvocationDate();
if (minSecondsSinceLastInvocation && lastInvocationDate && lastInvocationDate <= updateCheckStartDate &&
lastInvocationDate.getTime() + minSecondsSinceLastInvocation * 1000 > updateCheckStartDate.getTime()) {
Expand All @@ -162,15 +167,16 @@ export class InvocationRateLimiter<T> {
}

private getLastInvocationDate(): Date | undefined {
const maybeDate: Date | undefined =
const maybeDateString: string | undefined =
this._extensionContext.globalState.get(InvocationRateLimiter._invocationRateLimiterPrefix + this._funcIdentifier);
return maybeDate ? new Date(maybeDate) : undefined;
return maybeDateString ? this._createDate(maybeDateString) : undefined;
}

private async setLastInvocationDate(date: Date): Promise<void> {
return await this._extensionContext.globalState.update(InvocationRateLimiter._invocationRateLimiterPrefix + this._funcIdentifier, date);
}

private readonly _createDate: (dateString?: string) => Date;
private readonly _extensionContext: ExtensionContext;
private readonly _func: () => Promise<T>;
private readonly _funcIdentifier: string;
Expand Down
32 changes: 27 additions & 5 deletions extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@ import { ExtensionContext, Memento } from "vscode";
import { InvocationRateLimiter } from "../../helpers";

describe("Invocation rate limiter", () => {
// 1 January 2020
let currentUnixTime = 1577836800;

function createDate(dateString?: string): Date {
if (dateString) {
return new Date(dateString);
}
const numMillisecondsPerSecond = 1000;
return new Date(currentUnixTime * numMillisecondsPerSecond);
}

function createInvocationRateLimiter<T>(funcIdentifier: string, func: () => Promise<T>): InvocationRateLimiter<T> {
return new InvocationRateLimiter(new MockExtensionContext(), funcIdentifier, func);
return new InvocationRateLimiter(new MockExtensionContext(), funcIdentifier, func, s => createDate(s));
}

it("initially invokes function", async () => {
Expand All @@ -17,7 +28,7 @@ describe("Invocation rate limiter", () => {
expect(numTimesFuncCalled).to.equal(1);
});

it("doesn't invoke function within time period", async () => {
it("doesn't invoke function again if no time has passed", async () => {
let numTimesFuncCalled = 0;
const invocationRateLimiter = createInvocationRateLimiter("funcid", async () => {
numTimesFuncCalled++;
Expand All @@ -27,7 +38,18 @@ describe("Invocation rate limiter", () => {
expect(numTimesFuncCalled).to.equal(1);
});

it("invoke function again after 0s time period has elapsed", async () => {
it("doesn't invoke function again if requested time since last invocation hasn't passed", async () => {
let numTimesFuncCalled = 0;
const invocationRateLimiter = createInvocationRateLimiter("funcid", async () => {
numTimesFuncCalled++;
});
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(100);
currentUnixTime += 1;
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(2);
expect(numTimesFuncCalled).to.equal(1);
});

it("invokes function again immediately if requested time since last invocation is 0 seconds", async () => {
let numTimesFuncCalled = 0;
const invocationRateLimiter = createInvocationRateLimiter("funcid", async () => {
numTimesFuncCalled++;
Expand All @@ -37,13 +59,13 @@ describe("Invocation rate limiter", () => {
expect(numTimesFuncCalled).to.equal(2);
});

it("invoke function again after 1s time period has elapsed", async () => {
it("invokes function again after requested time since last invocation has elapsed", async () => {
let numTimesFuncCalled = 0;
const invocationRateLimiter = createInvocationRateLimiter("funcid", async () => {
numTimesFuncCalled++;
});
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(1);
await new Promise((resolve, _reject) => setTimeout(() => resolve(), 1000));
currentUnixTime += 1;
await invocationRateLimiter.invokeFunctionIfIntervalElapsed(1);
expect(numTimesFuncCalled).to.equal(2);
});
Expand Down