gristlabs_grist-core/app/server/lib/WorkCoordinator.ts
Dmitry S 51ff72c15e (core) Faster builds all around.
Summary:
Building:
- Builds no longer wait for tsc for either client, server, or test targets. All use esbuild which is very fast.
- Build still runs tsc, but only to report errors. This may be turned off with `SKIP_TSC=1` env var.
- Grist-core continues to build using tsc.
- Esbuild requires ES6 module semantics. Typescript's esModuleInterop is turned
  on, so that tsc accepts and enforces correct usage.
- Client-side code is watched and bundled by webpack as before (using esbuild-loader)

Code changes:
- Imports must now follow ES6 semantics: `import * as X from ...` produces a
  module object; to import functions or class instances, use `import X from ...`.
- Everything is now built with isolatedModules flag. Some exports were updated for it.

Packages:
- Upgraded browserify dependency, and related packages (used for the distribution-building step).
- Building the distribution now uses esbuild's minification. babel-minify is no longer used.

Test Plan: Should have no behavior changes, existing tests should pass, and docker image should build too.

Reviewers: georgegevoian

Reviewed By: georgegevoian

Subscribers: alexmojaki

Differential Revision: https://phab.getgrist.com/D3506
2022-07-04 10:42:40 -04:00

67 lines
2.3 KiB
TypeScript

import log from 'app/server/lib/log';
/**
* WorkCoordinator is a helper to do work serially. It takes a doWork() callback which may either
* do some work and return a Promise, or report no work to be done by returning null. After work
* completes, doWork() will be called again; when idle, ping() should be called to retry doWork().
*/
export class WorkCoordinator {
private _doWorkCB: () => Promise<void>|null;
private _tryNextStepCB: () => void;
private _isStepRunning: boolean = false;
private _isStepScheduled: boolean = false;
/**
* The doWork() callback will be called on ping() and whenever previous doWork() promise
* succeeds. If doWork() had nothing to do, it should return null, and will not be called again
* until the next ping().
*
* Note that doWork() should never fail. If it does, exceptions and rejections will be caught
* and logged, and WorkCoordinator will not be called again until the next ping().
*/
constructor(doWork: () => Promise<void>|null) {
this._doWorkCB = doWork;
this._tryNextStepCB = () => this._tryNextStep(); // bound version of _tryNextStep.
}
/**
* Attempt doWork() again. If doWork() is currently running, it will be attempted again on
* completion even if the current run fails.
*/
public ping(): void {
if (!this._isStepScheduled) {
this._isStepScheduled = true;
this._maybeSchedule();
}
}
private async _tryNextStep(): Promise<void> {
this._isStepScheduled = false;
if (!this._isStepRunning) {
this._isStepRunning = true;
try {
const work = this._doWorkCB();
if (work) {
await work;
// Only schedule the next step if some work was done. If _doWorkCB() did nothing, or
// failed, _doWorkCB() will only be called when an external ping() triggers it.
this._isStepScheduled = true;
}
} catch (err) {
// doWork() should NOT fail. If it does, we log the error here, and stop scheduling work
// as if there is no more work to be done.
log.error("WorkCoordinator: error in doWork()", err);
} finally {
this._isStepRunning = false;
this._maybeSchedule();
}
}
}
private _maybeSchedule() {
if (this._isStepScheduled && !this._isStepRunning) {
setImmediate(this._tryNextStepCB);
}
}
}