Skip to content

Commit

Permalink
Improvements for cancelable operations (#1701)
Browse files Browse the repository at this point in the history
- Use performance.now() for time measurements
- Use the startCancelableOperation function to reset the lastTick variable
  • Loading branch information
spoenemann authored Oct 8, 2024
1 parent 58e28b7 commit 2e39701
Show file tree
Hide file tree
Showing 5 changed files with 23 additions and 23 deletions.
4 changes: 2 additions & 2 deletions packages/langium/src/utils/promise-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ let globalInterruptionPeriod = 10;
* Reset the global interruption period and create a cancellation token source.
*/
export function startCancelableOperation(): AbstractCancellationTokenSource {
lastTick = Date.now();
lastTick = performance.now();
return new CancellationTokenSource();
}

Expand Down Expand Up @@ -74,7 +74,7 @@ export async function interruptAndCheck(token: CancellationToken): Promise<void>
// Early exit in case cancellation was disabled by the caller
return;
}
const current = Date.now();
const current = performance.now();
if (current - lastTick >= globalInterruptionPeriod) {
lastTick = current;
await delayNextTick();
Expand Down
8 changes: 4 additions & 4 deletions packages/langium/src/workspace/workspace-lock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import { CancellationToken, CancellationTokenSource } from '../utils/cancellation.js';
import { Deferred, isOperationCancelled, type MaybePromise } from '../utils/promise-utils.js';
import { type AbstractCancellationTokenSource, CancellationToken, CancellationTokenSource } from '../utils/cancellation.js';
import { Deferred, isOperationCancelled, startCancelableOperation, type MaybePromise } from '../utils/promise-utils.js';

/**
* Utility service to execute mutually exclusive actions.
Expand Down Expand Up @@ -47,14 +47,14 @@ interface LockEntry {

export class DefaultWorkspaceLock implements WorkspaceLock {

private previousTokenSource = new CancellationTokenSource();
private previousTokenSource: AbstractCancellationTokenSource = new CancellationTokenSource();
private writeQueue: LockEntry[] = [];
private readQueue: LockEntry[] = [];
private done = true;

write(action: (token: CancellationToken) => MaybePromise<void>): Promise<void> {
this.cancelWrite();
const tokenSource = new CancellationTokenSource();
const tokenSource = startCancelableOperation();
this.previousTokenSource = tokenSource;
return this.enqueue(this.writeQueue, action, tokenSource.token);
}
Expand Down
12 changes: 6 additions & 6 deletions packages/langium/test/parser/worker-thread-async-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { WorkerThreadAsyncParser } from 'langium/node';
import { createLangiumGrammarServices } from 'langium/grammar';
import type { Grammar, LangiumCoreServices, ParseResult } from 'langium';
import type { LangiumServices } from 'langium/lsp';
import { EmptyFileSystem, GrammarUtils, CstUtils, GrammarAST, isOperationCancelled } from 'langium';
import { CancellationToken, CancellationTokenSource } from 'vscode-languageserver';
import { EmptyFileSystem, GrammarUtils, CstUtils, GrammarAST, isOperationCancelled, startCancelableOperation } from 'langium';
import { CancellationToken } from 'vscode-languageserver';
import { fail } from 'node:assert';
import { fileURLToPath } from 'node:url';

Expand Down Expand Up @@ -46,16 +46,16 @@ describe('WorkerThreadAsyncParser', () => {
// This file should take a few seconds to parse
const file = createLargeFile(100000);
const asyncParser = services.parser.AsyncParser;
const cancellationTokenSource = new CancellationTokenSource();
const cancellationTokenSource = startCancelableOperation();
setTimeout(() => cancellationTokenSource.cancel(), 50);
const start = Date.now();
const start = performance.now();
try {
await asyncParser.parse<Grammar>(file, cancellationTokenSource.token);
fail('Parsing should have been cancelled');
} catch (err) {
expect(isOperationCancelled(err)).toBe(true);
}
const end = Date.now();
const end = performance.now();
// The whole parsing process should have been successfully cancelled within a second
expect(end - start).toBeLessThan(1000);
});
Expand All @@ -65,7 +65,7 @@ describe('WorkerThreadAsyncParser', () => {
// This file should take a few seconds to parse
const file = createLargeFile(100000);
const asyncParser = services.parser.AsyncParser;
const cancellationTokenSource = new CancellationTokenSource();
const cancellationTokenSource = startCancelableOperation();
setTimeout(() => cancellationTokenSource.cancel(), 50);
try {
await asyncParser.parse<Grammar>(file, cancellationTokenSource.token);
Expand Down
16 changes: 8 additions & 8 deletions packages/langium/test/workspace/document-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
******************************************************************************/

import type { AstNode, DocumentBuilder, FileSystemProvider, LangiumDocument, LangiumDocumentFactory, LangiumDocuments, Module, Reference, ValidationChecks } from 'langium';
import { AstUtils, DocumentState, TextDocument, URI, isOperationCancelled } from 'langium';
import { AstUtils, DocumentState, TextDocument, URI, isOperationCancelled, startCancelableOperation } from 'langium';
import { createServicesForGrammar } from 'langium/grammar';
import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest';
import { CancellationToken, CancellationTokenSource } from 'vscode-languageserver';
import { CancellationToken } from 'vscode-languageserver';
import { fail } from 'assert';
import type { LangiumServices, LangiumSharedServices, TextDocuments } from 'langium/lsp';

Expand Down Expand Up @@ -147,7 +147,7 @@ describe('DefaultDocumentBuilder', () => {
documents.addDocument(document2);

const builder = workspace.DocumentBuilder;
const tokenSource1 = new CancellationTokenSource();
const tokenSource1 = startCancelableOperation();
builder.onBuildPhase(DocumentState.IndexedContent, () => {
tokenSource1.cancel();
});
Expand Down Expand Up @@ -316,7 +316,7 @@ describe('DefaultDocumentBuilder', () => {
documents.addDocument(document);

const actual: string[] = [];
const cancelTokenSource = new CancellationTokenSource();
const cancelTokenSource = startCancelableOperation();
function wait(state: DocumentState): void {
builder.onBuildPhase(state, async () => {
actual.push('B' + state);
Expand Down Expand Up @@ -351,7 +351,7 @@ describe('DefaultDocumentBuilder', () => {
const document = documentFactory.fromString('', URI.parse('file:///test1.txt'));
documents.addDocument(document);

const cancelTokenSource = new CancellationTokenSource();
const cancelTokenSource = startCancelableOperation();
builder.waitUntil(DocumentState.IndexedReferences, cancelTokenSource.token).then(() => {
fail('The test should fail here because the cancellation should reject the promise');
}).catch(err => {
Expand Down Expand Up @@ -413,7 +413,7 @@ describe('DefaultDocumentBuilder', () => {
documents.addDocument(document2);

const builder = services.shared.workspace.DocumentBuilder;
const tokenSource = new CancellationTokenSource();
const tokenSource = startCancelableOperation();

const buildPhases = new Set<DocumentState>();

Expand Down Expand Up @@ -493,7 +493,7 @@ describe('DefaultDocumentBuilder', () => {
`, URI.parse('file:///test1.txt'));
documents.addDocument(document);

const tokenSource = new CancellationTokenSource();
const tokenSource = startCancelableOperation();
const builder = workspace.DocumentBuilder;
builder.onBuildPhase(DocumentState.ComputedScopes, () => {
tokenSource.cancel();
Expand Down Expand Up @@ -636,7 +636,7 @@ describe('DefaultDocumentBuilder', () => {

expect(sortedDocs.slice(0, openDocumentCount).every(isDocumentOpen)).toBe(true);
expect(sortedDocs.slice(openDocumentCount).every(doc => !isDocumentOpen(doc))).toBe(true);
expect(endTime - startTime).toBeLessThan(1000); // Adjust this threshold as needed
expect(endTime - startTime).toBeLessThan(1500); // Adjust this threshold as needed
});

test('Sorting an empty list of documents', async () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/langium/test/workspace/workspace-lock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ describe('WorkspaceLock', () => {

test('Write action results can be awaited', async () => {
const mutex = new DefaultWorkspaceLock();
const now = Date.now();
const now = performance.now();
const magicalNumber = await mutex.read(() => new Promise(resolve => setTimeout(() => resolve(42), 10)));
// Confirm that at least 10ms have elapsed
expect(Date.now() - now).toBeGreaterThanOrEqual(10);
// Confirm that at least 5ms have elapsed
expect(performance.now() - now).toBeGreaterThanOrEqual(5);
// Confirm the returned value
expect(magicalNumber).toBe(42);
});
Expand Down

0 comments on commit 2e39701

Please sign in to comment.