@hydranium/core 1.0.0-next.140 → 1.0.0-next.148
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
- package/lib/documents/hydranium-text-documents.js +3 -13
- package/lib/documents/hydranium-text-documents.js.map +1 -1
- package/lib/langium/document-builder/document-builder.d.ts +140 -13
- package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
- package/lib/langium/document-builder/document-builder.js +218 -24
- package/lib/langium/document-builder/document-builder.js.map +1 -1
- package/lib/langium/model-service/model-service.d.ts +25 -28
- package/lib/langium/model-service/model-service.d.ts.map +1 -1
- package/lib/langium/model-service/model-service.js +25 -30
- package/lib/langium/model-service/model-service.js.map +1 -1
- package/lib/langium/module.d.ts +5 -6
- package/lib/langium/module.d.ts.map +1 -1
- package/lib/langium/module.js.map +1 -1
- package/lib/langium/workspace/hydranium-workspace-lock.d.ts +13 -0
- package/lib/langium/workspace/hydranium-workspace-lock.d.ts.map +1 -1
- package/lib/langium/workspace/hydranium-workspace-lock.js +18 -0
- package/lib/langium/workspace/hydranium-workspace-lock.js.map +1 -1
- package/lib/lsp/hydranium-document-update-handler.d.ts +24 -21
- package/lib/lsp/hydranium-document-update-handler.d.ts.map +1 -1
- package/lib/lsp/hydranium-document-update-handler.js +27 -28
- package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
- package/lib/testing/make-test-services.d.ts.map +1 -1
- package/lib/testing/make-test-services.js +3 -2
- package/lib/testing/make-test-services.js.map +1 -1
- package/lib/testing/stub-document-builder.d.ts +12 -6
- package/lib/testing/stub-document-builder.d.ts.map +1 -1
- package/lib/testing/stub-document-builder.js +9 -1
- package/lib/testing/stub-document-builder.js.map +1 -1
- package/package.json +5 -5
- package/src/documents/hydranium-text-documents.ts +3 -12
- package/src/langium/document-builder/document-builder.ts +253 -28
- package/src/langium/model-service/model-service.ts +34 -41
- package/src/langium/module.ts +5 -6
- package/src/langium/workspace/hydranium-workspace-lock.ts +21 -0
- package/src/lsp/hydranium-document-update-handler.ts +28 -31
- package/src/testing/make-test-services.ts +3 -2
- package/src/testing/stub-document-builder.ts +16 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"make-test-services.js","sourceRoot":"","sources":["../../src/testing/make-test-services.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAEJ,aAAa,EAGb,UAAU,EAEV,WAAW,EAIb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAqB,MAAM,4BAA4B,CAAC;AACpF,OAAO,EAAE,sBAAsB,EAAwB,MAAM,yBAAyB,CAAC;AAGvF,OAAO,EAGJ,2BAA2B,EAC7B,MAAM,4CAA4C,CAAC;AAIpD,OAAO,EAAE,sBAAsB,EAAwB,MAAM,yCAAyC,CAAC;AACvG,OAAO,EAAE,wBAAwB,EAA0B,MAAM,6CAA6C,CAAC;AAC/G,OAAO,EAAE,sBAAsB,EAAE,MAAM,kDAAkD,CAAC;AAC1F,OAAO,EAAE,0BAA0B,EAA4B,MAAM,wCAAwC,CAAC;AAE9G,OAAO,EAAE,uBAAuB,EAA4B,MAAM,4BAA4B,CAAC;AAC/F,OAAO,EAAE,oBAAoB,EAAyB,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,wBAAwB,EAA6B,MAAM,6BAA6B,CAAC;AAClG,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAA+B,MAAM,gCAAgC,CAAC;AACzG,OAAO,EAAE,sBAAsB,EAA2B,MAAM,2BAA2B,CAAC;AAC5F,OAAO,EAAE,wBAAwB,EAA6B,MAAM,8BAA8B,CAAC;AACnG,OAAO,EAAE,8BAA8B,EAAmC,MAAM,oCAAoC,CAAC;AACrH,OAAO,EAAE,uBAAuB,EAAyD,MAAM,4BAA4B,CAAC;AAC5H,OAAO,EAAE,0BAA0B,EAA+B,MAAM,gCAAgC,CAAC;AAsPzG;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAO7B,UAAgG,EAAE;IAElG,MAAM,SAAS,GAAG,wBAAwB,CAAoB,OAAO,CAAC,aAAa,CAAC,CAAC;IACrF,MAAM,aAAa,GAAG,8BAA8B,EAAE,CAAC;IACvD,MAAM,eAAe,GAAG,uBAAuB,
|
|
1
|
+
{"version":3,"file":"make-test-services.js","sourceRoot":"","sources":["../../src/testing/make-test-services.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
|
-
import { type DocumentBuilder, type DocumentState, type LangiumDocument, type URI } from '@hydranium/langium';
|
|
9
|
+
import { type DocumentBuilder, type DocumentState, type LangiumDocument, type URI, type WorkspaceLock } from '@hydranium/langium';
|
|
10
10
|
import { type CancellationToken } from 'vscode-languageserver';
|
|
11
11
|
import { type HydraniumDocumentBuilder } from '../langium/document-builder/document-builder.js';
|
|
12
12
|
/** Recorded call to a stubbed {@link DocumentBuilder} method. */
|
|
@@ -53,13 +53,14 @@ export interface StubWaitUntilGate {
|
|
|
53
53
|
* Picks the methods the framework reads from {@link DocumentBuilder} on
|
|
54
54
|
* the slot path — the compiler enforces those signatures stay aligned.
|
|
55
55
|
* `update`, `onDocumentPhase`, `onUpdate`, and `waitUntil` carry stubbed
|
|
56
|
-
* implementations
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* implementations, and `scheduleUpdate` calls `update` in one lock write per
|
|
57
|
+
* call (see {@link makeStubDocumentBuilder}); `build`, `onBuildPhase`, and
|
|
58
|
+
* `resetToState` are implemented as loud-failing `notSupported` throwers
|
|
59
|
+
* so production code that goes through the cast at the bind site (i.e. accesses methods the
|
|
59
60
|
* stub claims but doesn't meaningfully implement) fails with a clear
|
|
60
61
|
* message instead of "undefined is not a function".
|
|
61
62
|
*/
|
|
62
|
-
export interface StubDocumentBuilder extends Pick<DocumentBuilder, 'update' | 'onDocumentPhase' | 'onUpdate' | 'build' | 'onBuildPhase' | 'resetToState' | 'updateBuildOptions'>, Pick<HydraniumDocumentBuilder, 'onDocumentPhaseDelivered'> {
|
|
63
|
+
export interface StubDocumentBuilder extends Pick<DocumentBuilder, 'update' | 'onDocumentPhase' | 'onUpdate' | 'build' | 'onBuildPhase' | 'resetToState' | 'updateBuildOptions'>, Pick<HydraniumDocumentBuilder, 'onDocumentPhaseDelivered' | 'scheduleUpdate'> {
|
|
63
64
|
/**
|
|
64
65
|
* Single-overload stub of {@link DocumentBuilder.waitUntil}. Real has
|
|
65
66
|
* two overloads (`(state, cancelToken?): Promise<void>` and
|
|
@@ -103,6 +104,11 @@ export interface StubDocumentBuilder extends Pick<DocumentBuilder, 'update' | 'o
|
|
|
103
104
|
* want to observe rebuilt state should `set` the new root on the document
|
|
104
105
|
* registry and then call `firePhase` to trigger the framework's post-build
|
|
105
106
|
* read path.
|
|
107
|
+
*
|
|
108
|
+
* `workspaceLock` is the lock `scheduleUpdate` writes under, without the real
|
|
109
|
+
* builder's sharing: pass the services tree's own, so a caller's lock ordering
|
|
110
|
+
* and reentrancy behave as against the real builder. Without one,
|
|
111
|
+
* `scheduleUpdate` records the update unlocked.
|
|
106
112
|
*/
|
|
107
|
-
export declare function makeStubDocumentBuilder(): StubDocumentBuilder;
|
|
113
|
+
export declare function makeStubDocumentBuilder(workspaceLock?: WorkspaceLock): StubDocumentBuilder;
|
|
108
114
|
//# sourceMappingURL=stub-document-builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stub-document-builder.d.ts","sourceRoot":"","sources":["../../src/testing/stub-document-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,KAAK,eAAe,EAGpB,KAAK,aAAa,EAElB,KAAK,eAAe,EACpB,KAAK,GAAG,
|
|
1
|
+
{"version":3,"file":"stub-document-builder.d.ts","sourceRoot":"","sources":["../../src/testing/stub-document-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,KAAK,eAAe,EAGpB,KAAK,aAAa,EAElB,KAAK,eAAe,EACpB,KAAK,GAAG,EACR,KAAK,aAAa,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,iBAAiB,EAAc,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,iDAAiD,CAAC;AAEhG,iEAAiE;AACjE,MAAM,WAAW,mBAAmB,CAAC,KAAK,SAAS,OAAO,EAAE;IACzD,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;CACvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAC/B;;;;;;;OAOG;IACH,OAAO,IAAI,IAAI,CAAC;IAChB,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,mBACd,SACG,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,cAAc,GAAG,cAAc,GAAG,oBAAoB,CAAC,EACnI,IAAI,CAAC,wBAAwB,EAAE,0BAA0B,GAAG,gBAAgB,CAAC;IAChF;;;;;;;OAOG;IACH,SAAS,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC;IACrE,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACzE,QAAQ,CAAC,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC,aAAa,EAAE,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IAC9F;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClG,+DAA+D;IAC/D,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACnD;;;;;;OAMG;IACH,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,WAAW,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACtG;;;OAGG;IACH,iBAAiB,IAAI,iBAAiB,CAAC;IACvC,2EAA2E;IAC3E,KAAK,IAAI,IAAI,CAAC;CAChB;AAqBD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,CAAC,EAAE,aAAa,GAAG,mBAAmB,CA+J1F"}
|
|
@@ -31,8 +31,13 @@ function reraise(result) {
|
|
|
31
31
|
* want to observe rebuilt state should `set` the new root on the document
|
|
32
32
|
* registry and then call `firePhase` to trigger the framework's post-build
|
|
33
33
|
* read path.
|
|
34
|
+
*
|
|
35
|
+
* `workspaceLock` is the lock `scheduleUpdate` writes under, without the real
|
|
36
|
+
* builder's sharing: pass the services tree's own, so a caller's lock ordering
|
|
37
|
+
* and reentrancy behave as against the real builder. Without one,
|
|
38
|
+
* `scheduleUpdate` records the update unlocked.
|
|
34
39
|
*/
|
|
35
|
-
export function makeStubDocumentBuilder() {
|
|
40
|
+
export function makeStubDocumentBuilder(workspaceLock) {
|
|
36
41
|
const phaseListeners = new Map();
|
|
37
42
|
const deliveredListeners = new Map();
|
|
38
43
|
const buildPhaseListeners = new Map();
|
|
@@ -56,6 +61,9 @@ export function makeStubDocumentBuilder() {
|
|
|
56
61
|
async update(changed, deleted) {
|
|
57
62
|
updateCalls.push({ args: [changed, deleted] });
|
|
58
63
|
},
|
|
64
|
+
scheduleUpdate(changed, deleted) {
|
|
65
|
+
return workspaceLock ? workspaceLock.write(() => stub.update(changed, deleted)) : stub.update(changed, deleted);
|
|
66
|
+
},
|
|
59
67
|
async waitUntil(state, uri) {
|
|
60
68
|
waitUntilCalls.push({ args: [state, uri] });
|
|
61
69
|
const gate = gates.shift();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stub-document-builder.js","sourceRoot":"","sources":["../../src/testing/stub-document-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;
|
|
1
|
+
{"version":3,"file":"stub-document-builder.js","sourceRoot":"","sources":["../../src/testing/stub-document-builder.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/core",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.148",
|
|
4
4
|
"description": "Foundational runtime of the hydranium framework: AST coordination layer (services, integrity, AST extension, serialization, multi-client documents) plus the LSP textual head at the /lsp subpath. Consumed by protocol-head packages (@hydranium/data-server, @hydranium/glsp-server).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hydranium",
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
"diff": "^5.2.0"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
|
-
"@hydranium/langium": "1.0.0-next.
|
|
108
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
107
|
+
"@hydranium/langium": "1.0.0-next.148",
|
|
108
|
+
"@hydranium/protocol": "1.0.0-next.148",
|
|
109
109
|
"@playwright/test": "^1.40.0",
|
|
110
110
|
"@types/diff": "^5.2.0",
|
|
111
111
|
"rimraf": "^5.0.0",
|
|
@@ -117,8 +117,8 @@
|
|
|
117
117
|
"vscode-languageserver-types": "^3.17.5"
|
|
118
118
|
},
|
|
119
119
|
"peerDependencies": {
|
|
120
|
-
"@hydranium/langium": "1.0.0-next.
|
|
121
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
120
|
+
"@hydranium/langium": "1.0.0-next.148",
|
|
121
|
+
"@hydranium/protocol": "1.0.0-next.148",
|
|
122
122
|
"@playwright/test": "^1.40.0",
|
|
123
123
|
"vscode-jsonrpc": "9.0.1",
|
|
124
124
|
"vscode-languageserver": "~10.0.1",
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
type CanonicalUri,
|
|
51
51
|
type LanguageClientUri,
|
|
52
52
|
asLanguageClientUri,
|
|
53
|
+
cyrb53,
|
|
53
54
|
DisposableCollection,
|
|
54
55
|
type Stopwatch,
|
|
55
56
|
type Tracer
|
|
@@ -318,18 +319,8 @@ function isFileNotFound(err: unknown, target: URI): boolean {
|
|
|
318
319
|
* `node:crypto` (this module must stay runnable in browser hosts).
|
|
319
320
|
*/
|
|
320
321
|
function contentHash(text: string): string {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
for (let index = 0; index < text.length; index++) {
|
|
324
|
-
const code = text.charCodeAt(index);
|
|
325
|
-
h1 = Math.imul(h1 ^ code, 2654435761);
|
|
326
|
-
h2 = Math.imul(h2 ^ code, 1597334677);
|
|
327
|
-
}
|
|
328
|
-
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
|
329
|
-
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
330
|
-
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
|
331
|
-
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
332
|
-
return `${(4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36)}:${text.length}`;
|
|
322
|
+
const { high, low } = cyrb53([text]);
|
|
323
|
+
return `${(4294967296 * (2097151 & high) + low).toString(36)}:${text.length}`;
|
|
333
324
|
}
|
|
334
325
|
|
|
335
326
|
/**
|
|
@@ -33,6 +33,7 @@ import { CST_REHYDRATION_RESET_STATE, isCstShed } from '../residency/cst-residen
|
|
|
33
33
|
import { type ExtendedServiceRegistry } from '../service-registry.js';
|
|
34
34
|
import { type ServerSharedServicesMinimal } from '../shared-services.js';
|
|
35
35
|
import { type DocumentUriPolicy } from '../workspace/document-uri-policy.js';
|
|
36
|
+
import { HydraniumWorkspaceLock } from '../workspace/hydranium-workspace-lock.js';
|
|
36
37
|
import { BuildSession, type BuildSessionContext } from './build-session.js';
|
|
37
38
|
import { type LabeledPhaseListener, labelPhaseListener } from './labeled-phase-listener.js';
|
|
38
39
|
|
|
@@ -125,6 +126,28 @@ export interface DocumentBuilderOptions extends LogNameOptions {
|
|
|
125
126
|
readonly refreshCrossDocumentComputedScopes?: boolean;
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
/**
|
|
130
|
+
* One locked build {@link HydraniumDocumentBuilder.scheduleUpdate} queued,
|
|
131
|
+
* which later requests merge into while it waits and join while it runs.
|
|
132
|
+
*/
|
|
133
|
+
export interface ScheduledUpdate {
|
|
134
|
+
/** URIs to build, by URI string. Merged requests change it while the write is queued; fixed once it runs. */
|
|
135
|
+
readonly changed: Map<string, URI>;
|
|
136
|
+
/** URIs to delete, by URI string, under the same rule as {@link changed}. */
|
|
137
|
+
readonly deleted: Map<string, URI>;
|
|
138
|
+
/** The latest build reason any merged request gave; staged through {@link HydraniumDocumentBuilder.markNextReason} when the write runs. */
|
|
139
|
+
reason?: string;
|
|
140
|
+
/** The lock's {@link HydraniumWorkspaceLock.writeCancellations} right after this write was queued. */
|
|
141
|
+
readonly cancellations: number;
|
|
142
|
+
/**
|
|
143
|
+
* Set when the write starts to run, so its presence marks a running build:
|
|
144
|
+
* the text version of each changed URI the text store holds at that point.
|
|
145
|
+
*/
|
|
146
|
+
takenVersions?: Map<string, number>;
|
|
147
|
+
/** The lock's promise for this write: settles when the build completes or is cancelled. */
|
|
148
|
+
readonly promise: Promise<void>;
|
|
149
|
+
}
|
|
150
|
+
|
|
128
151
|
/**
|
|
129
152
|
* Extends Langium's {@link DefaultDocumentBuilder} with:
|
|
130
153
|
*
|
|
@@ -134,6 +157,8 @@ export interface DocumentBuilderOptions extends LogNameOptions {
|
|
|
134
157
|
* non-validating build from suppressing validation, and a
|
|
135
158
|
* {@link shouldRelink} that never judges a document unaffected on an index
|
|
136
159
|
* that does not describe it.
|
|
160
|
+
* - **Shared locked builds** ({@link scheduleUpdate}): build requests that
|
|
161
|
+
* coincide share one locked build instead of cancelling each other.
|
|
137
162
|
* - **URI handling** (always on): directory-aware flattening and cascade
|
|
138
163
|
* deletes in {@link update}, plus the CST-rehydration and cross-document
|
|
139
164
|
* refresh resets in {@link resetToState}.
|
|
@@ -176,8 +201,10 @@ export class HydraniumDocumentBuilder extends DefaultDocumentBuilder {
|
|
|
176
201
|
protected readonly uriPolicy: DocumentUriPolicy;
|
|
177
202
|
protected readonly clock: Clock;
|
|
178
203
|
protected readonly messageRenderer: MessageRenderer;
|
|
179
|
-
/** The lock
|
|
204
|
+
/** The lock {@link scheduleUpdate} builds under. */
|
|
180
205
|
protected readonly workspaceLock: WorkspaceLock;
|
|
206
|
+
/** The latest write {@link scheduleUpdate} queued, until it ends; see {@link ScheduledUpdate}. */
|
|
207
|
+
protected scheduledUpdate?: ScheduledUpdate;
|
|
181
208
|
/** Narrower handle on the same registry as the inherited `serviceRegistry`, for {@link ExtendedServiceRegistry.registrations}. */
|
|
182
209
|
protected readonly languageRegistry: ExtendedServiceRegistry;
|
|
183
210
|
protected languageFileExtensions: string[] = [];
|
|
@@ -206,6 +233,17 @@ export class HydraniumDocumentBuilder extends DefaultDocumentBuilder {
|
|
|
206
233
|
protected lastCancelledTraceId?: number;
|
|
207
234
|
/** Registered through {@link onDocumentPhaseDelivered}, by phase. */
|
|
208
235
|
protected readonly documentPhaseDeliveredListeners = new MultiMap<DocumentState, (document: LangiumDocument, version: number) => void>();
|
|
236
|
+
/**
|
|
237
|
+
* Called with `false` by {@link buildDocuments} once a build has run every
|
|
238
|
+
* phase, and with `true` by {@link checkWaitsOnceDrained} after a build that
|
|
239
|
+
* threw; see {@link awaitDocumentState}. A listener runs synchronously,
|
|
240
|
+
* inside the build or inside that check's lock read. A throw fails the
|
|
241
|
+
* finished build; in the read it skips the listeners after it and surfaces
|
|
242
|
+
* only as an unhandled rejection.
|
|
243
|
+
*/
|
|
244
|
+
protected readonly buildEndedListeners = new Set<(drained: boolean) => void>();
|
|
245
|
+
/** Set while the lock read {@link checkWaitsOnceDrained} queued has not run. */
|
|
246
|
+
protected drainCheckQueued = false;
|
|
209
247
|
|
|
210
248
|
constructor(services: ServerSharedServicesMinimal, options: DocumentBuilderOptions = {}) {
|
|
211
249
|
super(services);
|
|
@@ -311,10 +349,15 @@ export class HydraniumDocumentBuilder extends DefaultDocumentBuilder {
|
|
|
311
349
|
*
|
|
312
350
|
* Replacing Langium's rejection with a wait makes the wait's liveness this
|
|
313
351
|
* class's responsibility: a listener can only fire if some build is still
|
|
314
|
-
* going to reach `state`.
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
* the
|
|
352
|
+
* going to reach `state`. So the wait re-queues the document whenever
|
|
353
|
+
* {@link isOrphaned} holds: once when it is armed, and again after every
|
|
354
|
+
* build that completes (see {@link buildDocuments}). The second check is
|
|
355
|
+
* the one a wait armed during a build needs, since only the build's end
|
|
356
|
+
* shows whether it left the document behind; Langium's `onBuildPhase` for
|
|
357
|
+
* `Validated` cannot serve, as it stays silent for a build that validates
|
|
358
|
+
* nothing, such as the workspace's initial one. A build that throws is
|
|
359
|
+
* checked once the lock drains instead (see {@link checkWaitsOnceDrained}):
|
|
360
|
+
* whatever cancelled it may build nothing. Re-queuing stops after
|
|
318
361
|
* {@link MAX_STALLED_REQUEUES} builds that fail to advance the document, so a
|
|
319
362
|
* document the builder will never carry to `state` degrades to a pending wait
|
|
320
363
|
* plus a warning rather than an endless build loop.
|
|
@@ -389,15 +432,19 @@ export class HydraniumDocumentBuilder extends DefaultDocumentBuilder {
|
|
|
389
432
|
}, 'awaitDocumentState.skipsValidation')
|
|
390
433
|
)
|
|
391
434
|
: Disposable.create(() => undefined);
|
|
392
|
-
const
|
|
435
|
+
const buildEnded = (drained: boolean): void => {
|
|
393
436
|
if (document.state >= state || (state === DocumentState.Validated && this.skipsValidation(document))) {
|
|
394
437
|
cleanup();
|
|
395
438
|
resolve(uri);
|
|
396
|
-
} else {
|
|
397
|
-
//
|
|
398
|
-
|
|
439
|
+
} else if (drained ? this.activeSession === undefined : this.isOrphaned(document, state)) {
|
|
440
|
+
// No build is left to carry the document to `state`. Either
|
|
441
|
+
// check fails only while an unlocked build still runs; that
|
|
442
|
+
// build carries the document or ends here too.
|
|
443
|
+
requeue(drained ? 'build ended early' : 'build ended short of the target');
|
|
399
444
|
}
|
|
400
|
-
}
|
|
445
|
+
};
|
|
446
|
+
this.buildEndedListeners.add(buildEnded);
|
|
447
|
+
const buildDisposable = Disposable.create(() => this.buildEndedListeners.delete(buildEnded));
|
|
401
448
|
const cancelDisposable = cancelToken.onCancellationRequested(() => {
|
|
402
449
|
cleanup();
|
|
403
450
|
reject(OperationCancelled);
|
|
@@ -409,9 +456,9 @@ export class HydraniumDocumentBuilder extends DefaultDocumentBuilder {
|
|
|
409
456
|
cancelDisposable.dispose();
|
|
410
457
|
};
|
|
411
458
|
// Orphaned BEFORE the wait was armed: the build that would have
|
|
412
|
-
// advanced this document has already finished, so
|
|
413
|
-
//
|
|
414
|
-
// the resulting build resolves this wait.
|
|
459
|
+
// advanced this document has already finished, so none of these
|
|
460
|
+
// listeners fires until some other build runs. Re-queue now — the listeners are
|
|
461
|
+
// registered, so the resulting build resolves this wait.
|
|
415
462
|
if (this.isOrphaned(document, state)) {
|
|
416
463
|
requeue('quiescent builder');
|
|
417
464
|
}
|
|
@@ -452,7 +499,9 @@ export class HydraniumDocumentBuilder extends DefaultDocumentBuilder {
|
|
|
452
499
|
* `currentState` still advances to `Validated` (the validation phase runs
|
|
453
500
|
* over an empty document list). Any first read that wants diagnostics — a
|
|
454
501
|
* one-shot data-head read, a CLI query, `ModelService.validated` — lands
|
|
455
|
-
* here.
|
|
502
|
+
* here. A read that arrives while that build still runs finds `currentState`
|
|
503
|
+
* below `state`, so this holds for it only once the build has ended, which
|
|
504
|
+
* is why {@link awaitDocumentState} asks again then.
|
|
456
505
|
*/
|
|
457
506
|
protected isOrphaned(document: LangiumDocument, state: DocumentState): boolean {
|
|
458
507
|
return document.state < state && this.currentState >= state;
|
|
@@ -465,13 +514,16 @@ export class HydraniumDocumentBuilder extends DefaultDocumentBuilder {
|
|
|
465
514
|
* logged rather than swallowed — it leaves the waiter pending until its own
|
|
466
515
|
* cancellation token fires, which is worth a line in the log.
|
|
467
516
|
*
|
|
468
|
-
* The build is
|
|
469
|
-
* The read waits until no write runs or is queued, so the build neither
|
|
470
|
-
* beside a locked one nor cancels the build a re-queue is fired from.
|
|
471
|
-
* it runs beside a locked build that cannot cancel it: both
|
|
472
|
-
* version and deliver it twice. Taking the write directly,
|
|
473
|
-
* cancels the running build and skips the rest of its
|
|
474
|
-
* The read must not await the write, which queues
|
|
517
|
+
* The build is a {@link scheduleUpdate}, entered from a {@link WorkspaceLock}
|
|
518
|
+
* read. The read waits until no write runs or is queued, so the build neither
|
|
519
|
+
* runs beside a locked one nor cancels the build a re-queue is fired from.
|
|
520
|
+
* Unlocked, it runs beside a locked build that cannot cancel it: both
|
|
521
|
+
* validate the same version and deliver it twice. Taking the write directly,
|
|
522
|
+
* without the read, cancels the running build and skips the rest of its
|
|
523
|
+
* build-phase listeners. The read must not await the write, which queues
|
|
524
|
+
* behind that read. A build scheduled in the gap between the read batch
|
|
525
|
+
* starting and this action running is still queued, so the re-queue merges
|
|
526
|
+
* into it rather than cancelling it.
|
|
475
527
|
*
|
|
476
528
|
* A write that ran while the read waited may have carried the document to
|
|
477
529
|
* `state` already, and a second build would deliver that version again; the
|
|
@@ -484,13 +536,185 @@ export class HydraniumDocumentBuilder extends DefaultDocumentBuilder {
|
|
|
484
536
|
if (document.state >= state) {
|
|
485
537
|
return;
|
|
486
538
|
}
|
|
487
|
-
this.
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
539
|
+
this.scheduleUpdate([document.uri], []).catch((err: unknown) => {
|
|
540
|
+
if (!isOperationCancelled(err)) {
|
|
541
|
+
tracer.error(`Re-queue build failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ============================================================
|
|
548
|
+
// Scheduled updates — one locked build for requests that coincide
|
|
549
|
+
// ============================================================
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Build `changed` and `deleted` under the {@link WorkspaceLock}, as
|
|
553
|
+
* `workspaceLock.write(token => update(changed, deleted, token))` would,
|
|
554
|
+
* except where the build this method queued last already carries the
|
|
555
|
+
* request. The promise settles when the build that carries the request
|
|
556
|
+
* ends, completed or cancelled, as the lock's own promise does.
|
|
557
|
+
*
|
|
558
|
+
* Without it, requests that coincide cancel each other: each lock write
|
|
559
|
+
* cancels the one before it, after Langium's `update` has already reset the
|
|
560
|
+
* documents, scanned the workspace for relinking and called every
|
|
561
|
+
* `onUpdate` listener.
|
|
562
|
+
*
|
|
563
|
+
* - **Queued, not yet running:** the request merges into it, so nothing is
|
|
564
|
+
* cancelled. The later of a change and a deletion of one URI wins, so
|
|
565
|
+
* each URI reaches `update` in one list only: a file deleted and written
|
|
566
|
+
* again before the build starts is a change.
|
|
567
|
+
* - **Running:** the request joins it only when {@link canJoinRunningUpdate}
|
|
568
|
+
* holds, which keeps an edit made while it runs cancelling it.
|
|
569
|
+
* - **Otherwise,** or when anything else has cancelled that write since it
|
|
570
|
+
* was queued, a new write, which cancels the running build as any lock
|
|
571
|
+
* write does.
|
|
572
|
+
*
|
|
573
|
+
* `reason` is staged through {@link markNextReason} when the write runs. The
|
|
574
|
+
* latest reason of a merged request wins, as it does in the update
|
|
575
|
+
* handler's own debounced burst; a joining request's reason is dropped, since
|
|
576
|
+
* the build it joins has already started. A request without one leaves a
|
|
577
|
+
* reason staged by the caller in place.
|
|
578
|
+
*
|
|
579
|
+
* A caller that must run code of its own inside the write action takes the
|
|
580
|
+
* lock itself, and forgoes the sharing. A lock that is not a
|
|
581
|
+
* {@link HydraniumWorkspaceLock} cannot report a cancelled queued write, so
|
|
582
|
+
* every request then takes its own write.
|
|
583
|
+
*/
|
|
584
|
+
scheduleUpdate(changed: URI[], deleted: URI[], reason?: string): Promise<void> {
|
|
585
|
+
const lock = this.workspaceLock;
|
|
586
|
+
if (!(lock instanceof HydraniumWorkspaceLock)) {
|
|
587
|
+
return lock.write(token => {
|
|
588
|
+
if (reason !== undefined) {
|
|
589
|
+
this.markNextReason(reason);
|
|
590
|
+
}
|
|
591
|
+
return this.update(changed, deleted, token);
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
const scheduled = this.scheduledUpdate;
|
|
595
|
+
if (scheduled && scheduled.cancellations === lock.writeCancellations) {
|
|
596
|
+
if (!scheduled.takenVersions) {
|
|
597
|
+
for (const uri of changed) {
|
|
598
|
+
scheduled.changed.set(uri.toString(), uri);
|
|
599
|
+
scheduled.deleted.delete(uri.toString());
|
|
600
|
+
}
|
|
601
|
+
for (const uri of deleted) {
|
|
602
|
+
scheduled.deleted.set(uri.toString(), uri);
|
|
603
|
+
scheduled.changed.delete(uri.toString());
|
|
604
|
+
}
|
|
605
|
+
scheduled.reason = reason ?? scheduled.reason;
|
|
606
|
+
return scheduled.promise;
|
|
607
|
+
}
|
|
608
|
+
if (this.canJoinRunningUpdate(scheduled, changed, deleted)) {
|
|
609
|
+
return scheduled.promise;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
// The action runs on a later tick, by which time `request` is assigned.
|
|
613
|
+
const promise = lock.write(token => this.runScheduledUpdate(request, token));
|
|
614
|
+
const request: ScheduledUpdate = {
|
|
615
|
+
changed: new Map(changed.map(uri => [uri.toString(), uri])),
|
|
616
|
+
deleted: new Map(deleted.map(uri => [uri.toString(), uri])),
|
|
617
|
+
reason,
|
|
618
|
+
cancellations: lock.writeCancellations,
|
|
619
|
+
promise
|
|
620
|
+
};
|
|
621
|
+
this.scheduledUpdate = request;
|
|
622
|
+
return promise;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Whether a request for `changed` and `deleted` is carried by the running
|
|
627
|
+
* `scheduled` build, whose write nothing has cancelled: the request deletes
|
|
628
|
+
* nothing, and each changed URI is one the build took, held by the text
|
|
629
|
+
* store at the version it had when the build started.
|
|
630
|
+
*
|
|
631
|
+
* Each condition keeps a request from joining a build that would miss its
|
|
632
|
+
* change. Text that moved since the start may be newer than what the build
|
|
633
|
+
* parsed; a URI the text store does not hold is read from its file, which
|
|
634
|
+
* may have changed after the build read it; and a deletion may concern a
|
|
635
|
+
* document the build has already built. A joined request is also absent
|
|
636
|
+
* from the running `update`'s `onUpdate` call, which reported the store as
|
|
637
|
+
* it was when the build started; at an unchanged version that is the store
|
|
638
|
+
* the request sees.
|
|
639
|
+
*/
|
|
640
|
+
protected canJoinRunningUpdate(scheduled: ScheduledUpdate, changed: URI[], deleted: URI[]): boolean {
|
|
641
|
+
const taken = scheduled.takenVersions;
|
|
642
|
+
if (!taken || deleted.length > 0) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
return changed.every(uri => taken.has(uri.toString()) && taken.get(uri.toString()) === this.textDocuments?.get(uri)?.version);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* The write action of a {@link scheduleUpdate}: freeze the request, record
|
|
650
|
+
* what it takes, stage its reason and run `update`. Releases
|
|
651
|
+
* {@link scheduledUpdate} inside the lock, whatever throws, so a request
|
|
652
|
+
* arriving after the build ends starts a write of its own rather than
|
|
653
|
+
* joining a build that is over.
|
|
654
|
+
*/
|
|
655
|
+
protected async runScheduledUpdate(request: ScheduledUpdate, token: CancellationToken): Promise<void> {
|
|
656
|
+
try {
|
|
657
|
+
const taken = new Map<string, number>();
|
|
658
|
+
for (const [key, uri] of request.changed) {
|
|
659
|
+
const version = this.textDocuments?.get(uri)?.version;
|
|
660
|
+
if (version !== undefined) {
|
|
661
|
+
taken.set(key, version);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
request.takenVersions = taken;
|
|
665
|
+
if (request.reason !== undefined) {
|
|
666
|
+
this.markNextReason(request.reason);
|
|
667
|
+
}
|
|
668
|
+
await this.update([...request.changed.values()], [...request.deleted.values()], token);
|
|
669
|
+
} finally {
|
|
670
|
+
if (this.scheduledUpdate === request) {
|
|
671
|
+
this.scheduledUpdate = undefined;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Tell the {@link buildEndedListeners} once every phase has run, which is
|
|
678
|
+
* the one point where a build that skipped a waited-on document can be seen
|
|
679
|
+
* to have ended. Langium's `onBuildPhase` for `Validated` is not: it stays
|
|
680
|
+
* silent when no document reached that phase, which is every build that
|
|
681
|
+
* validates nothing, the workspace's initial one among them.
|
|
682
|
+
*
|
|
683
|
+
* A build that throws, whether cancelled or failed, tells none of them
|
|
684
|
+
* here: `currentState` is then below `Validated`, so {@link isOrphaned}
|
|
685
|
+
* could hold for no wait. {@link checkWaitsOnceDrained} tells them instead.
|
|
686
|
+
*/
|
|
687
|
+
protected override async buildDocuments(
|
|
688
|
+
documents: LangiumDocument[],
|
|
689
|
+
options: BuildOptions,
|
|
690
|
+
cancelToken: CancellationToken
|
|
691
|
+
): Promise<void> {
|
|
692
|
+
await super.buildDocuments(documents, options, cancelToken);
|
|
693
|
+
for (const listener of [...this.buildEndedListeners]) {
|
|
694
|
+
listener(false);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* After a build that threw, tell the {@link buildEndedListeners}, with
|
|
700
|
+
* `drained` set, from a {@link WorkspaceLock} read: it runs once no write
|
|
701
|
+
* runs or is queued, so no locked build is left that could still carry a
|
|
702
|
+
* waited-on document. A cancelled build is usually followed by its
|
|
703
|
+
* canceller's, but a write that builds nothing, such as a last-close revert
|
|
704
|
+
* that finds its document reopened, leaves a wait armed during the build
|
|
705
|
+
* with nothing to resolve it, and so does a build that fails. One read
|
|
706
|
+
* serves every build that throws before it runs.
|
|
707
|
+
*/
|
|
708
|
+
protected checkWaitsOnceDrained(): void {
|
|
709
|
+
if (this.drainCheckQueued) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
this.drainCheckQueued = true;
|
|
713
|
+
void this.workspaceLock.read(() => {
|
|
714
|
+
this.drainCheckQueued = false;
|
|
715
|
+
for (const listener of [...this.buildEndedListeners]) {
|
|
716
|
+
listener(true);
|
|
717
|
+
}
|
|
494
718
|
});
|
|
495
719
|
}
|
|
496
720
|
|
|
@@ -860,6 +1084,7 @@ export class HydraniumDocumentBuilder extends DefaultDocumentBuilder {
|
|
|
860
1084
|
if (isOperationCancelled(err)) {
|
|
861
1085
|
session.cancelled = true;
|
|
862
1086
|
}
|
|
1087
|
+
this.checkWaitsOnceDrained();
|
|
863
1088
|
throw err;
|
|
864
1089
|
} finally {
|
|
865
1090
|
this.endSession(session);
|