Files
infocom-systems-design/node_modules/langium/lib/parser/async-parser.js
2025-10-03 22:27:28 +03:00

149 lines
5.1 KiB
JavaScript

/******************************************************************************
* Copyright 2023 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { Deferred, OperationCancelled } from '../utils/promise-utils.js';
import { Emitter } from '../utils/event.js';
/**
* Default implementation of the async parser which simply wraps the sync parser in a promise.
*
* @remarks
* A real implementation would create worker threads or web workers to offload the parsing work.
*/
export class DefaultAsyncParser {
constructor(services) {
this.syncParser = services.parser.LangiumParser;
}
parse(text, _cancelToken) {
return Promise.resolve(this.syncParser.parse(text));
}
}
export class AbstractThreadedAsyncParser {
constructor(services) {
/**
* The thread count determines how many threads are used to parse files in parallel.
* The default value is 8. Decreasing this value increases startup performance, but decreases parallel parsing performance.
*/
this.threadCount = 8;
/**
* The termination delay determines how long the parser waits for a thread to finish after a cancellation request.
* The default value is 200(ms).
*/
this.terminationDelay = 200;
this.workerPool = [];
this.queue = [];
this.hydrator = services.serializer.Hydrator;
}
initializeWorkers() {
while (this.workerPool.length < this.threadCount) {
const worker = this.createWorker();
worker.onReady(() => {
if (this.queue.length > 0) {
const deferred = this.queue.shift();
if (deferred) {
worker.lock();
deferred.resolve(worker);
}
}
});
this.workerPool.push(worker);
}
}
async parse(text, cancelToken) {
const worker = await this.acquireParserWorker(cancelToken);
const deferred = new Deferred();
let timeout;
// If the cancellation token is requested, we wait for a certain time before terminating the worker.
// Since the cancellation token lives longer than the parsing process, we need to dispose the event listener.
// Otherwise, we might accidentally terminate the worker after the parsing process has finished.
const cancellation = cancelToken.onCancellationRequested(() => {
timeout = setTimeout(() => {
this.terminateWorker(worker);
}, this.terminationDelay);
});
worker.parse(text).then(result => {
const hydrated = this.hydrator.hydrate(result);
deferred.resolve(hydrated);
}).catch(err => {
deferred.reject(err);
}).finally(() => {
cancellation.dispose();
clearTimeout(timeout);
});
return deferred.promise;
}
terminateWorker(worker) {
worker.terminate();
const index = this.workerPool.indexOf(worker);
if (index >= 0) {
this.workerPool.splice(index, 1);
}
}
async acquireParserWorker(cancelToken) {
this.initializeWorkers();
for (const worker of this.workerPool) {
if (worker.ready) {
worker.lock();
return worker;
}
}
const deferred = new Deferred();
cancelToken.onCancellationRequested(() => {
const index = this.queue.indexOf(deferred);
if (index >= 0) {
this.queue.splice(index, 1);
}
deferred.reject(OperationCancelled);
});
this.queue.push(deferred);
return deferred.promise;
}
}
export class ParserWorker {
get ready() {
return this._ready;
}
get onReady() {
return this.onReadyEmitter.event;
}
constructor(sendMessage, onMessage, onError, terminate) {
this.onReadyEmitter = new Emitter();
this.deferred = new Deferred();
this._ready = true;
this._parsing = false;
this.sendMessage = sendMessage;
this._terminate = terminate;
onMessage(result => {
const parseResult = result;
this.deferred.resolve(parseResult);
this.unlock();
});
onError(error => {
this.deferred.reject(error);
this.unlock();
});
}
terminate() {
this.deferred.reject(OperationCancelled);
this._terminate();
}
lock() {
this._ready = false;
}
unlock() {
this._parsing = false;
this._ready = true;
this.onReadyEmitter.fire();
}
parse(text) {
if (this._parsing) {
throw new Error('Parser worker is busy');
}
this._parsing = true;
this.deferred = new Deferred();
this.sendMessage(text);
return this.deferred.promise;
}
}
//# sourceMappingURL=async-parser.js.map