nginx-lint-plugin 0.10.1 → 0.10.2
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/README.md +1 -1
- package/package.json +1 -1
- package/wasm/parser/parser.core.wasm +0 -0
- package/wasm/parser/parser.js +1667 -880
package/wasm/parser/parser.js
CHANGED
|
@@ -1,115 +1,150 @@
|
|
|
1
1
|
"use jco";
|
|
2
2
|
|
|
3
|
+
function promiseWithResolvers() {
|
|
4
|
+
if (Promise.withResolvers) {
|
|
5
|
+
return Promise.withResolvers();
|
|
6
|
+
} else {
|
|
7
|
+
let resolve;
|
|
8
|
+
let reject;
|
|
9
|
+
const promise = new Promise((res, rej) => {
|
|
10
|
+
resolve = res;
|
|
11
|
+
reject = rej;
|
|
12
|
+
});
|
|
13
|
+
return { promise, resolve, reject };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
const _debugLog = (...args) => {
|
|
4
18
|
if (!globalThis?.process?.env?.JCO_DEBUG) { return; }
|
|
5
19
|
console.debug(...args);
|
|
6
|
-
}
|
|
20
|
+
};
|
|
7
21
|
const ASYNC_DETERMINISM = 'random';
|
|
22
|
+
const GLOBAL_COMPONENT_MEMORY_MAP = new Map();
|
|
23
|
+
const CURRENT_TASK_META = {};
|
|
8
24
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
function _getGlobalCurrentTaskMeta(componentIdx) {
|
|
26
|
+
const v = CURRENT_TASK_META[componentIdx];
|
|
27
|
+
if (v === undefined) { return v; }
|
|
28
|
+
return { ...v };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function _setGlobalCurrentTaskMeta(args) {
|
|
32
|
+
if (!args) { throw new TypeError('args missing'); }
|
|
33
|
+
if (args.taskID === undefined) { throw new TypeError('missing task ID'); }
|
|
34
|
+
if (args.componentIdx === undefined) { throw new TypeError('missing component idx'); }
|
|
35
|
+
const { taskID, componentIdx } = args;
|
|
36
|
+
return CURRENT_TASK_META[componentIdx] = { taskID, componentIdx };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _withGlobalCurrentTaskMeta(args) {
|
|
40
|
+
_debugLog('[_withGlobalCurrentTaskMeta()] args', args);
|
|
41
|
+
if (!args) { throw new TypeError('args missing'); }
|
|
42
|
+
if (args.taskID === undefined) { throw new TypeError('missing task ID'); }
|
|
43
|
+
if (args.componentIdx === undefined) { throw new TypeError('missing component idx'); }
|
|
44
|
+
if (!args.fn) { throw new TypeError('missing fn'); }
|
|
45
|
+
const { taskID, componentIdx, fn } = args;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
CURRENT_TASK_META[componentIdx] = { taskID, componentIdx };
|
|
49
|
+
return fn();
|
|
50
|
+
} catch (err) {
|
|
51
|
+
_debugLog("error while executing sync callee/callback", {
|
|
52
|
+
...args,
|
|
53
|
+
err,
|
|
54
|
+
});
|
|
55
|
+
throw err;
|
|
56
|
+
} finally {
|
|
57
|
+
CURRENT_TASK_META[componentIdx] = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function _withGlobalCurrentTaskMetaAsync(args) {
|
|
62
|
+
_debugLog('[_withGlobalCurrentTaskMetaAsync()] args', args);
|
|
63
|
+
if (!args) { throw new TypeError('args missing'); }
|
|
64
|
+
if (args.taskID === undefined) { throw new TypeError('missing task ID'); }
|
|
65
|
+
if (args.componentIdx === undefined) { throw new TypeError('missing component idx'); }
|
|
66
|
+
if (!args.fn) { throw new TypeError('missing fn'); }
|
|
67
|
+
const { taskID, componentIdx, fn } = args;
|
|
68
|
+
|
|
69
|
+
// If there is already an async task executing, we must wait for it
|
|
70
|
+
// to complete before we can can run the closure we were given
|
|
71
|
+
//
|
|
72
|
+
let current = CURRENT_TASK_META[componentIdx];
|
|
73
|
+
let cstate;
|
|
74
|
+
if (current && current.taskID !== taskID) {
|
|
75
|
+
cstate = getOrCreateAsyncState(componentIdx);
|
|
76
|
+
while (current && current.taskID !== taskID) {
|
|
77
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
78
|
+
cstate.onNextExclusiveRelease(resolve);
|
|
79
|
+
await promise;
|
|
80
|
+
current = CURRENT_TASK_META[componentIdx];
|
|
20
81
|
}
|
|
21
82
|
|
|
22
|
-
|
|
83
|
+
// Since we've just waited for the component to not be locked, re-lock
|
|
84
|
+
// exclusivity so we can run the fn below (likely a callee/callback)
|
|
85
|
+
cstate.exclusiveLock();
|
|
23
86
|
}
|
|
24
87
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// but contain/will call an async function in the host.
|
|
37
|
-
//
|
|
38
|
-
// One such case is `stream.write`/`stream.read` trampolines which are
|
|
39
|
-
// actually re-exported through a patch up container *before*
|
|
40
|
-
// they call the relevant async host trampoline.
|
|
41
|
-
//
|
|
42
|
-
// So the path of execution from a component export would be:
|
|
43
|
-
//
|
|
44
|
-
// async guest export --> stream.write import (host wired) -> guest export (patch component) -> async host trampoline
|
|
45
|
-
//
|
|
46
|
-
// On top of all this, the trampoline that is eventually called is async,
|
|
47
|
-
// so we must await the patched guest export call.
|
|
48
|
-
//
|
|
49
|
-
if (qualifiedImportFn.includes("[stream-write-") || qualifiedImportFn.includes("[stream-read-")) {
|
|
50
|
-
return async (...args) => {
|
|
51
|
-
const [originalFn, ...params] = args;
|
|
52
|
-
return await originalFn(...params);
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// All other cases can call the registered function directly
|
|
57
|
-
return (...args) => {
|
|
58
|
-
const [originalFn, ...params] = args;
|
|
59
|
-
return originalFn(...params);
|
|
60
|
-
};
|
|
88
|
+
try {
|
|
89
|
+
CURRENT_TASK_META[componentIdx] = { taskID, componentIdx };
|
|
90
|
+
return await fn();
|
|
91
|
+
} catch (err) {
|
|
92
|
+
_debugLog("error while executing async callee/callback", {
|
|
93
|
+
...args,
|
|
94
|
+
err,
|
|
95
|
+
});
|
|
96
|
+
throw err;
|
|
97
|
+
} finally {
|
|
98
|
+
CURRENT_TASK_META[componentIdx] = null;
|
|
61
99
|
}
|
|
62
100
|
}
|
|
63
101
|
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
async function _clearCurrentTask(args) {
|
|
103
|
+
_debugLog('[_clearCurrentTask()] args', args);
|
|
104
|
+
if (!args) { throw new TypeError('args missing'); }
|
|
105
|
+
if (args.taskID === undefined) { throw new TypeError('missing task ID'); }
|
|
106
|
+
if (args.componentIdx === undefined) { throw new TypeError('missing component idx'); }
|
|
107
|
+
const { taskID, componentIdx } = args;
|
|
66
108
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (componentIdx === undefined) { throw new TypeError("missing component idx"); }
|
|
70
|
-
if (iface === undefined) { throw new TypeError("missing iface name"); }
|
|
71
|
-
if (fnName === undefined) { throw new TypeError("missing function name"); }
|
|
72
|
-
return `${componentIdx}-${iface}-${fnName}`;
|
|
73
|
-
}
|
|
109
|
+
const meta = CURRENT_TASK_META[componentIdx];
|
|
110
|
+
if (!meta) { throw new Error(`missing current task meta for component idx [${componentIdx}]`); }
|
|
74
111
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (!fn) { throw new TypeError('missing function'); }
|
|
78
|
-
const key = GlobalAsyncParamLowers.generateKey(args);
|
|
79
|
-
GlobalAsyncParamLowers.map.set(key, fn);
|
|
112
|
+
if (meta.taskID !== taskID) {
|
|
113
|
+
throw new Error(`task ID [${meta.taskID}] != requested ID [${taskID}]`);
|
|
80
114
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const { componentIdx, iface, fnName } = args;
|
|
84
|
-
const key = GlobalAsyncParamLowers.generateKey(args);
|
|
85
|
-
return GlobalAsyncParamLowers.map.get(key);
|
|
115
|
+
if (meta.componentIdx !== componentIdx) {
|
|
116
|
+
throw new Error(`component idx [${meta.componentIdx}] != requested idx [${componentIdx}]`);
|
|
86
117
|
}
|
|
118
|
+
|
|
119
|
+
CURRENT_TASK_META[componentIdx] = null;
|
|
87
120
|
}
|
|
88
121
|
|
|
89
|
-
|
|
90
|
-
|
|
122
|
+
function lookupMemoriesForComponent(args) {
|
|
123
|
+
const { componentIdx } = args ?? {};
|
|
124
|
+
if (args.componentIdx === undefined) { throw new TypeError("missing component idx"); }
|
|
91
125
|
|
|
92
|
-
|
|
126
|
+
const metas = GLOBAL_COMPONENT_MEMORY_MAP.get(componentIdx);
|
|
127
|
+
if (!metas) { return []; }
|
|
93
128
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
let inner = GlobalComponentMemories.map.get(componentIdx);
|
|
97
|
-
if (!inner) {
|
|
98
|
-
inner = [];
|
|
99
|
-
GlobalComponentMemories.map.set(componentIdx, inner);
|
|
100
|
-
}
|
|
101
|
-
inner.push({ memory, idx });
|
|
129
|
+
if (args.memoryIdx === undefined) {
|
|
130
|
+
return Object.values(metas);
|
|
102
131
|
}
|
|
103
132
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
133
|
+
const meta = metas[args.memoryIdx];
|
|
134
|
+
return meta?.memory;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function registerGlobalMemoryForComponent(args) {
|
|
138
|
+
const { componentIdx, memory, memoryIdx } = args ?? {};
|
|
139
|
+
if (componentIdx === undefined) { throw new TypeError('missing component idx'); }
|
|
140
|
+
if (memory === undefined && memoryIdx === undefined) { throw new TypeError('missing both memory & memory idx'); }
|
|
141
|
+
let inner = GLOBAL_COMPONENT_MEMORY_MAP.get(componentIdx);
|
|
142
|
+
if (!inner) {
|
|
143
|
+
inner = {};
|
|
144
|
+
GLOBAL_COMPONENT_MEMORY_MAP.set(componentIdx, inner);
|
|
107
145
|
}
|
|
108
146
|
|
|
109
|
-
|
|
110
|
-
const metas = GlobalComponentMemories.map.get(componentIdx);
|
|
111
|
-
return metas.find(meta => meta.idx === idx)?.memory;
|
|
112
|
-
}
|
|
147
|
+
inner[memoryIdx] = { memory, memoryIdx, componentIdx };
|
|
113
148
|
}
|
|
114
149
|
|
|
115
150
|
class RepTable {
|
|
@@ -120,23 +155,30 @@ class RepTable {
|
|
|
120
155
|
this.target = args?.target;
|
|
121
156
|
}
|
|
122
157
|
|
|
158
|
+
data() { return this.#data; }
|
|
159
|
+
|
|
123
160
|
insert(val) {
|
|
124
161
|
_debugLog('[RepTable#insert()] args', { val, target: this.target });
|
|
125
162
|
const freeIdx = this.#data[0];
|
|
126
163
|
if (freeIdx === 0) {
|
|
127
164
|
this.#data.push(val);
|
|
128
165
|
this.#data.push(null);
|
|
129
|
-
|
|
166
|
+
const rep = (this.#data.length >> 1) - 1;
|
|
167
|
+
_debugLog('[RepTable#insert()] inserted', { val, target: this.target, rep });
|
|
168
|
+
return rep;
|
|
130
169
|
}
|
|
131
170
|
this.#data[0] = this.#data[freeIdx << 1];
|
|
132
171
|
const placementIdx = freeIdx << 1;
|
|
133
172
|
this.#data[placementIdx] = val;
|
|
134
173
|
this.#data[placementIdx + 1] = null;
|
|
174
|
+
_debugLog('[RepTable#insert()] inserted', { val, target: this.target, rep: freeIdx });
|
|
135
175
|
return freeIdx;
|
|
136
176
|
}
|
|
137
177
|
|
|
138
178
|
get(rep) {
|
|
139
179
|
_debugLog('[RepTable#get()] args', { rep, target: this.target });
|
|
180
|
+
if (rep === 0) { throw new Error('invalid resource rep during get, (cannot be 0)'); }
|
|
181
|
+
|
|
140
182
|
const baseIdx = rep << 1;
|
|
141
183
|
const val = this.#data[baseIdx];
|
|
142
184
|
return val;
|
|
@@ -144,17 +186,19 @@ class RepTable {
|
|
|
144
186
|
|
|
145
187
|
contains(rep) {
|
|
146
188
|
_debugLog('[RepTable#contains()] args', { rep, target: this.target });
|
|
189
|
+
if (rep === 0) { throw new Error('invalid resource rep during contains, (cannot be 0)'); }
|
|
190
|
+
|
|
147
191
|
const baseIdx = rep << 1;
|
|
148
192
|
return !!this.#data[baseIdx];
|
|
149
193
|
}
|
|
150
194
|
|
|
151
195
|
remove(rep) {
|
|
152
196
|
_debugLog('[RepTable#remove()] args', { rep, target: this.target });
|
|
197
|
+
if (rep === 0) { throw new Error('invalid resource rep during remove, (cannot be 0)'); }
|
|
153
198
|
if (this.#data.length === 2) { throw new Error('invalid'); }
|
|
154
199
|
|
|
155
200
|
const baseIdx = rep << 1;
|
|
156
201
|
const val = this.#data[baseIdx];
|
|
157
|
-
if (val === 0) { throw new Error('invalid resource rep (cannot be 0)'); }
|
|
158
202
|
|
|
159
203
|
this.#data[baseIdx] = this.#data[0];
|
|
160
204
|
this.#data[0] = rep;
|
|
@@ -178,11 +222,41 @@ const _typeCheckAsyncFn= (f) => {
|
|
|
178
222
|
};
|
|
179
223
|
|
|
180
224
|
const ASYNC_FN_CTOR = (async () => {}).constructor;
|
|
225
|
+
|
|
226
|
+
function clearCurrentTask(componentIdx, taskID) {
|
|
227
|
+
_debugLog('[clearCurrentTask()] args', { componentIdx, taskID });
|
|
228
|
+
|
|
229
|
+
if (componentIdx === undefined || componentIdx === null) {
|
|
230
|
+
throw new Error('missing/invalid component instance index while ending current task');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const tasks = ASYNC_TASKS_BY_COMPONENT_IDX.get(componentIdx);
|
|
234
|
+
if (!tasks || !Array.isArray(tasks)) {
|
|
235
|
+
throw new Error('missing/invalid tasks for component instance while ending task');
|
|
236
|
+
}
|
|
237
|
+
if (tasks.length == 0) {
|
|
238
|
+
throw new Error(`no current tasks for component instance [${componentIdx}] while ending task`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (taskID !== undefined) {
|
|
242
|
+
const last = tasks[tasks.length - 1];
|
|
243
|
+
if (last.id !== taskID) {
|
|
244
|
+
// throw new Error('current task does not match expected task ID');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
ASYNC_CURRENT_TASK_IDS.pop();
|
|
250
|
+
ASYNC_CURRENT_COMPONENT_IDXS.pop();
|
|
251
|
+
|
|
252
|
+
const taskMeta = tasks.pop();
|
|
253
|
+
return taskMeta.task;
|
|
254
|
+
}
|
|
255
|
+
const CURRENT_TASK_MAY_BLOCK = new WebAssembly.Global({ value: 'i32', mutable: true }, 0);
|
|
181
256
|
const ASYNC_CURRENT_TASK_IDS = [];
|
|
182
257
|
const ASYNC_CURRENT_COMPONENT_IDXS = [];
|
|
183
258
|
|
|
184
259
|
function unpackCallbackResult(result) {
|
|
185
|
-
_debugLog('[unpackCallbackResult()] args', { result });
|
|
186
260
|
if (!(_typeCheckValidI32(result))) { throw new Error('invalid callback return value [' + result + '], not a valid i32'); }
|
|
187
261
|
const eventCode = result & 0xF;
|
|
188
262
|
if (eventCode < 0 || eventCode > 3) {
|
|
@@ -194,17 +268,345 @@ function unpackCallbackResult(result) {
|
|
|
194
268
|
return [eventCode, waitableSetRep];
|
|
195
269
|
}
|
|
196
270
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
271
|
+
class AsyncSubtask {
|
|
272
|
+
static _ID = 0n;
|
|
273
|
+
|
|
274
|
+
static State = {
|
|
275
|
+
STARTING: 0,
|
|
276
|
+
STARTED: 1,
|
|
277
|
+
RETURNED: 2,
|
|
278
|
+
CANCELLED_BEFORE_STARTED: 3,
|
|
279
|
+
CANCELLED_BEFORE_RETURNED: 4,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
#id;
|
|
283
|
+
#state = AsyncSubtask.State.STARTING;
|
|
284
|
+
#componentIdx;
|
|
285
|
+
|
|
286
|
+
#parentTask;
|
|
287
|
+
#childTask = null;
|
|
288
|
+
|
|
289
|
+
#dropped = false;
|
|
290
|
+
#cancelRequested = false;
|
|
291
|
+
|
|
292
|
+
#memoryIdx = null;
|
|
293
|
+
#lenders = null;
|
|
294
|
+
|
|
295
|
+
#waitable = null;
|
|
296
|
+
|
|
297
|
+
#callbackFn = null;
|
|
298
|
+
#callbackFnName = null;
|
|
299
|
+
|
|
300
|
+
#postReturnFn = null;
|
|
301
|
+
#onProgressFn = null;
|
|
302
|
+
#pendingEventFn = null;
|
|
303
|
+
|
|
304
|
+
#callMetadata = {};
|
|
305
|
+
|
|
306
|
+
#resolved = false;
|
|
307
|
+
|
|
308
|
+
#onResolveHandlers = [];
|
|
309
|
+
#onStartHandlers = [];
|
|
310
|
+
|
|
311
|
+
#result = null;
|
|
312
|
+
#resultSet = false;
|
|
313
|
+
|
|
314
|
+
fnName;
|
|
315
|
+
target;
|
|
316
|
+
isAsync;
|
|
317
|
+
isManualAsync;
|
|
318
|
+
|
|
319
|
+
constructor(args) {
|
|
320
|
+
if (typeof args.componentIdx !== 'number') {
|
|
321
|
+
throw new Error('invalid componentIdx for subtask creation');
|
|
322
|
+
}
|
|
323
|
+
this.#componentIdx = args.componentIdx;
|
|
324
|
+
|
|
325
|
+
this.#id = ++AsyncSubtask._ID;
|
|
326
|
+
this.fnName = args.fnName;
|
|
327
|
+
|
|
328
|
+
if (!args.parentTask) { throw new Error('missing parent task during subtask creation'); }
|
|
329
|
+
this.#parentTask = args.parentTask;
|
|
330
|
+
|
|
331
|
+
if (args.childTask) { this.#childTask = args.childTask; }
|
|
332
|
+
|
|
333
|
+
if (args.memoryIdx) { this.#memoryIdx = args.memoryIdx; }
|
|
334
|
+
|
|
335
|
+
if (!args.waitable) { throw new Error("missing/invalid waitable"); }
|
|
336
|
+
this.#waitable = args.waitable;
|
|
337
|
+
|
|
338
|
+
if (args.callMetadata) { this.#callMetadata = args.callMetadata; }
|
|
339
|
+
|
|
340
|
+
this.#lenders = [];
|
|
341
|
+
this.target = args.target;
|
|
342
|
+
this.isAsync = args.isAsync;
|
|
343
|
+
this.isManualAsync = args.isManualAsync;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
id() { return this.#id; }
|
|
347
|
+
parentTaskID() { return this.#parentTask?.id(); }
|
|
348
|
+
childTaskID() { return this.#childTask?.id(); }
|
|
349
|
+
state() { return this.#state; }
|
|
350
|
+
|
|
351
|
+
waitable() { return this.#waitable; }
|
|
352
|
+
waitableRep() { return this.#waitable.idx(); }
|
|
353
|
+
|
|
354
|
+
join() { return this.#waitable.join(...arguments); }
|
|
355
|
+
getPendingEvent() { return this.#waitable.getPendingEvent(...arguments); }
|
|
356
|
+
hasPendingEvent() { return this.#waitable.hasPendingEvent(...arguments); }
|
|
357
|
+
setPendingEvent() { return this.#waitable.setPendingEvent(...arguments); }
|
|
358
|
+
|
|
359
|
+
setTarget(tgt) { this.target = tgt; }
|
|
360
|
+
|
|
361
|
+
getResult() {
|
|
362
|
+
if (!this.#resultSet) { throw new Error("subtask result has not been set") }
|
|
363
|
+
return this.#result;
|
|
364
|
+
}
|
|
365
|
+
setResult(v) {
|
|
366
|
+
if (this.#resultSet) { throw new Error("subtask result has already been set"); }
|
|
367
|
+
this.#result = v;
|
|
368
|
+
this.#resultSet = true;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
componentIdx() { return this.#componentIdx; }
|
|
372
|
+
|
|
373
|
+
setChildTask(t) {
|
|
374
|
+
if (!t) { throw new Error('cannot set missing/invalid child task on subtask'); }
|
|
375
|
+
if (this.#childTask) { throw new Error('child task is already set on subtask'); }
|
|
376
|
+
if (this.#parentTask === t) { throw new Error("parent cannot be child"); }
|
|
377
|
+
this.#childTask = t;
|
|
378
|
+
}
|
|
379
|
+
getChildTask(t) { return this.#childTask; }
|
|
380
|
+
|
|
381
|
+
getParentTask() { return this.#parentTask; }
|
|
382
|
+
|
|
383
|
+
setCallbackFn(f, name) {
|
|
384
|
+
if (!f) { return; }
|
|
385
|
+
if (this.#callbackFn) { throw new Error('callback fn can only be set once'); }
|
|
386
|
+
this.#callbackFn = f;
|
|
387
|
+
this.#callbackFnName = name;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
getCallbackFnName() {
|
|
391
|
+
if (!this.#callbackFn) { return undefined; }
|
|
392
|
+
return this.#callbackFn.name;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
setPostReturnFn(f) {
|
|
396
|
+
if (!f) { return; }
|
|
397
|
+
if (this.#postReturnFn) { throw new Error('postReturn fn can only be set once'); }
|
|
398
|
+
this.#postReturnFn = f;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
setOnProgressFn(f) {
|
|
402
|
+
if (this.#onProgressFn) { throw new Error('on progress fn can only be set once'); }
|
|
403
|
+
this.#onProgressFn = f;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
isNotStarted() {
|
|
407
|
+
return this.#state == AsyncSubtask.State.STARTING;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
registerOnStartHandler(f) {
|
|
411
|
+
this.#onStartHandlers.push(f);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
onStart(args) {
|
|
415
|
+
_debugLog('[AsyncSubtask#onStart()] args', {
|
|
416
|
+
componentIdx: this.#componentIdx,
|
|
417
|
+
subtaskID: this.#id,
|
|
418
|
+
parentTaskID: this.parentTaskID(),
|
|
419
|
+
fnName: this.fnName,
|
|
206
420
|
});
|
|
207
|
-
|
|
421
|
+
|
|
422
|
+
if (this.#onProgressFn) { this.#onProgressFn(); }
|
|
423
|
+
|
|
424
|
+
this.#state = AsyncSubtask.State.STARTED;
|
|
425
|
+
|
|
426
|
+
let result;
|
|
427
|
+
|
|
428
|
+
// If we have been provided a helper start function as a result of
|
|
429
|
+
// component fusion performed by wasmtime tooling, then we can call that helper and lifts/lowers will
|
|
430
|
+
// be performed for us.
|
|
431
|
+
//
|
|
432
|
+
// See also documentation on `HostIntrinsic::PrepareCall`
|
|
433
|
+
//
|
|
434
|
+
if (this.#callMetadata.startFn) {
|
|
435
|
+
result = this.#callMetadata.startFn.apply(null, args?.startFnParams ?? []);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
registerOnResolveHandler(f) {
|
|
443
|
+
this.#onResolveHandlers.push(f);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
reject(subtaskErr) {
|
|
447
|
+
this.#childTask?.reject(subtaskErr);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
onResolve(subtaskValue) {
|
|
451
|
+
_debugLog('[AsyncSubtask#onResolve()] args', {
|
|
452
|
+
componentIdx: this.#componentIdx,
|
|
453
|
+
subtaskID: this.#id,
|
|
454
|
+
isAsync: this.isAsync,
|
|
455
|
+
childTaskID: this.childTaskID(),
|
|
456
|
+
parentTaskID: this.parentTaskID(),
|
|
457
|
+
parentTaskFnName: this.#parentTask?.entryFnName(),
|
|
458
|
+
fnName: this.fnName,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
if (this.#resolved) {
|
|
462
|
+
throw new Error('subtask has already been resolved');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (this.#onProgressFn) { this.#onProgressFn(); }
|
|
466
|
+
|
|
467
|
+
if (subtaskValue === null) {
|
|
468
|
+
if (this.#cancelRequested) {
|
|
469
|
+
throw new Error('cancel was not requested, but no value present at return');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (this.#state === AsyncSubtask.State.STARTING) {
|
|
473
|
+
this.#state = AsyncSubtask.State.CANCELLED_BEFORE_STARTED;
|
|
474
|
+
} else {
|
|
475
|
+
if (this.#state !== AsyncSubtask.State.STARTED) {
|
|
476
|
+
throw new Error('resolved subtask must have been started before cancellation');
|
|
477
|
+
}
|
|
478
|
+
this.#state = AsyncSubtask.State.CANCELLED_BEFORE_RETURNED;
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
if (this.#state !== AsyncSubtask.State.STARTED) {
|
|
482
|
+
throw new Error('resolved subtask must have been started before completion');
|
|
483
|
+
}
|
|
484
|
+
this.#state = AsyncSubtask.State.RETURNED;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
this.setResult(subtaskValue);
|
|
488
|
+
|
|
489
|
+
for (const f of this.#onResolveHandlers) {
|
|
490
|
+
try {
|
|
491
|
+
f(subtaskValue);
|
|
492
|
+
} catch (err) {
|
|
493
|
+
console.error("error during subtask resolve handler", err);
|
|
494
|
+
throw err;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const callMetadata = this.getCallMetadata();
|
|
499
|
+
|
|
500
|
+
// TODO(fix): we should be able to easily have the caller's meomry
|
|
501
|
+
// to lower into here, but it's not present in PrepareCall
|
|
502
|
+
const memory = callMetadata.memory ?? this.#parentTask?.getReturnMemory() ?? lookupMemoriesForComponent({ componentIdx: this.#parentTask?.componentIdx() })[0];
|
|
503
|
+
if (callMetadata && !callMetadata.returnFn && this.isAsync && callMetadata.resultPtr && memory) {
|
|
504
|
+
const { resultPtr, realloc } = callMetadata;
|
|
505
|
+
const lowers = callMetadata.lowers; // may have been updated in task.return of the child
|
|
506
|
+
if (lowers && lowers.length > 0) {
|
|
507
|
+
lowers[0]({
|
|
508
|
+
componentIdx: this.#componentIdx,
|
|
509
|
+
memory,
|
|
510
|
+
realloc,
|
|
511
|
+
vals: [subtaskValue],
|
|
512
|
+
storagePtr: resultPtr,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
this.#resolved = true;
|
|
518
|
+
this.#parentTask.removeSubtask(this);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
getStateNumber() { return this.#state; }
|
|
522
|
+
isReturned() { return this.#state === AsyncSubtask.State.RETURNED; }
|
|
523
|
+
|
|
524
|
+
getCallMetadata() { return this.#callMetadata; }
|
|
525
|
+
|
|
526
|
+
isResolved() {
|
|
527
|
+
if (this.#state === AsyncSubtask.State.STARTING
|
|
528
|
+
|| this.#state === AsyncSubtask.State.STARTED) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
if (this.#state === AsyncSubtask.State.RETURNED
|
|
532
|
+
|| this.#state === AsyncSubtask.State.CANCELLED_BEFORE_STARTED
|
|
533
|
+
|| this.#state === AsyncSubtask.State.CANCELLED_BEFORE_RETURNED) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
throw new Error('unrecognized internal Subtask state [' + this.#state + ']');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
addLender(handle) {
|
|
540
|
+
_debugLog('[AsyncSubtask#addLender()] args', { handle });
|
|
541
|
+
if (!Number.isNumber(handle)) { throw new Error('missing/invalid lender handle [' + handle + ']'); }
|
|
542
|
+
|
|
543
|
+
if (this.#lenders.length === 0 || this.isResolved()) {
|
|
544
|
+
throw new Error('subtask has no lendors or has already been resolved');
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
handle.lends++;
|
|
548
|
+
this.#lenders.push(handle);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
deliverResolve() {
|
|
552
|
+
_debugLog('[AsyncSubtask#deliverResolve()] args', {
|
|
553
|
+
lenders: this.#lenders,
|
|
554
|
+
parentTaskID: this.parentTaskID(),
|
|
555
|
+
subtaskID: this.#id,
|
|
556
|
+
childTaskID: this.childTaskID(),
|
|
557
|
+
resolved: this.isResolved(),
|
|
558
|
+
resolveDelivered: this.resolveDelivered(),
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
const cannotDeliverResolve = this.resolveDelivered() || !this.isResolved();
|
|
562
|
+
if (cannotDeliverResolve) {
|
|
563
|
+
throw new Error('subtask cannot deliver resolution twice, and the subtask must be resolved');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
for (const lender of this.#lenders) {
|
|
567
|
+
lender.lends--;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
this.#lenders = null;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
resolveDelivered() {
|
|
574
|
+
_debugLog('[AsyncSubtask#resolveDelivered()] args', { });
|
|
575
|
+
if (this.#lenders === null && !this.isResolved()) {
|
|
576
|
+
throw new Error('invalid subtask state, lenders missing and subtask has not been resolved');
|
|
577
|
+
}
|
|
578
|
+
return this.#lenders === null;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
drop() {
|
|
582
|
+
_debugLog('[AsyncSubtask#drop()] args', {
|
|
583
|
+
componentIdx: this.#componentIdx,
|
|
584
|
+
parentTaskID: this.#parentTask?.id(),
|
|
585
|
+
parentTaskFnName: this.#parentTask?.entryFnName(),
|
|
586
|
+
childTaskID: this.#childTask?.id(),
|
|
587
|
+
childTaskFnName: this.#childTask?.entryFnName(),
|
|
588
|
+
subtaskFnName: this.fnName,
|
|
589
|
+
});
|
|
590
|
+
if (!this.#waitable) { throw new Error('missing/invalid inner waitable'); }
|
|
591
|
+
if (!this.resolveDelivered()) {
|
|
592
|
+
throw new Error('cannot drop subtask before resolve is delivered');
|
|
593
|
+
}
|
|
594
|
+
if (this.#waitable) { this.#waitable.drop() }
|
|
595
|
+
this.#dropped = true;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
#getComponentState() {
|
|
599
|
+
const state = getOrCreateAsyncState(this.#componentIdx);
|
|
600
|
+
if (!state) {
|
|
601
|
+
throw new Error('invalid/missing async state for component [' + componentIdx + ']');
|
|
602
|
+
}
|
|
603
|
+
return state;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
getWaitableHandleIdx() {
|
|
607
|
+
_debugLog('[AsyncSubtask#getWaitableHandleIdx()] args', { });
|
|
608
|
+
if (!this.#waitable) { throw new Error('missing/invalid waitable'); }
|
|
609
|
+
return this.waitableRep();
|
|
208
610
|
}
|
|
209
611
|
}
|
|
210
612
|
|
|
@@ -213,46 +615,40 @@ memoryIdx,
|
|
|
213
615
|
getMemoryFn,
|
|
214
616
|
startFn,
|
|
215
617
|
returnFn,
|
|
216
|
-
|
|
217
|
-
|
|
618
|
+
callerComponentIdx,
|
|
619
|
+
calleeComponentIdx,
|
|
218
620
|
taskReturnTypeIdx,
|
|
219
|
-
|
|
621
|
+
calleeIsAsyncInt,
|
|
220
622
|
stringEncoding,
|
|
221
623
|
resultCountOrAsync,
|
|
222
624
|
) {
|
|
223
625
|
_debugLog('[_prepareCall()]', {
|
|
224
|
-
|
|
225
|
-
|
|
626
|
+
memoryIdx,
|
|
627
|
+
callerComponentIdx,
|
|
628
|
+
calleeComponentIdx,
|
|
226
629
|
taskReturnTypeIdx,
|
|
227
|
-
|
|
630
|
+
calleeIsAsyncInt,
|
|
228
631
|
stringEncoding,
|
|
229
632
|
resultCountOrAsync,
|
|
230
633
|
});
|
|
231
634
|
const argArray = [...arguments];
|
|
232
635
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
// (c) any other value => callee is sync with the given result count
|
|
237
|
-
//
|
|
238
|
-
// Due to JS handling the value as 2s complement, the `resultCountOrAsync` ends up being:
|
|
239
|
-
// (a) -1 as u32 max size
|
|
240
|
-
// (b) -2 as u32 max size - 1
|
|
241
|
-
// (c) x
|
|
242
|
-
//
|
|
243
|
-
// Due to JS mishandling the value as 2s complement, the actual values we get are:
|
|
244
|
-
// see. https://github.com/wasm-bindgen/wasm-bindgen/issues/1388
|
|
636
|
+
// value passed in *may* be as large as u32::MAX which may be mangled into -2
|
|
637
|
+
resultCountOrAsync >>>= 0;
|
|
638
|
+
|
|
245
639
|
let isAsync = false;
|
|
246
640
|
let hasResultPointer = false;
|
|
247
|
-
if (resultCountOrAsync === -1) {
|
|
641
|
+
if (resultCountOrAsync === 2**32 - 1) {
|
|
642
|
+
// prepare async with no result (u32::MAX)
|
|
248
643
|
isAsync = true;
|
|
249
644
|
hasResultPointer = false;
|
|
250
|
-
} else if (resultCountOrAsync === -2) {
|
|
645
|
+
} else if (resultCountOrAsync === 2**32 - 2) {
|
|
646
|
+
// prepare async with result (u32::MAX - 1)
|
|
251
647
|
isAsync = true;
|
|
252
648
|
hasResultPointer = true;
|
|
253
649
|
}
|
|
254
650
|
|
|
255
|
-
const currentCallerTaskMeta = getCurrentTask(
|
|
651
|
+
const currentCallerTaskMeta = getCurrentTask(callerComponentIdx);
|
|
256
652
|
if (!currentCallerTaskMeta) {
|
|
257
653
|
throw new Error('invalid/missing current task for caller during prepare call');
|
|
258
654
|
}
|
|
@@ -262,18 +658,19 @@ resultCountOrAsync,
|
|
|
262
658
|
throw new Error('unexpectedly missing task in meta for caller during prepare call');
|
|
263
659
|
}
|
|
264
660
|
|
|
265
|
-
if (currentCallerTask.componentIdx() !==
|
|
266
|
-
throw new Error(`task component idx [${ currentCallerTask.componentIdx() }] !== [${
|
|
661
|
+
if (currentCallerTask.componentIdx() !== callerComponentIdx) {
|
|
662
|
+
throw new Error(`task component idx [${ currentCallerTask.componentIdx() }] !== [${ callerComponentIdx }] (callee ${ calleeComponentIdx })`);
|
|
267
663
|
}
|
|
268
664
|
|
|
269
665
|
let getCalleeParamsFn;
|
|
270
666
|
let resultPtr = null;
|
|
667
|
+
let directParamsArr;
|
|
271
668
|
if (hasResultPointer) {
|
|
272
|
-
|
|
669
|
+
directParamsArr = argArray.slice(10, argArray.length - 1);
|
|
273
670
|
getCalleeParamsFn = () => directParamsArr;
|
|
274
|
-
resultPtr = argArray[
|
|
671
|
+
resultPtr = argArray[argArray.length - 1];
|
|
275
672
|
} else {
|
|
276
|
-
|
|
673
|
+
directParamsArr = argArray.slice(10);
|
|
277
674
|
getCalleeParamsFn = () => directParamsArr;
|
|
278
675
|
}
|
|
279
676
|
|
|
@@ -292,21 +689,12 @@ resultCountOrAsync,
|
|
|
292
689
|
throw new Error(`unrecognized string encoding enum [${stringEncoding}]`);
|
|
293
690
|
}
|
|
294
691
|
|
|
295
|
-
const [newTask, newTaskID] = createNewCurrentTask({
|
|
296
|
-
componentIdx: calleeInstanceIdx,
|
|
297
|
-
isAsync: isCalleeAsyncInt !== 0,
|
|
298
|
-
getCalleeParamsFn,
|
|
299
|
-
// TODO: find a way to pass the import name through here
|
|
300
|
-
entryFnName: 'task/' + currentCallerTask.id() + '/new-prepare-task',
|
|
301
|
-
stringEncoding,
|
|
302
|
-
});
|
|
303
|
-
|
|
304
692
|
const subtask = currentCallerTask.createSubtask({
|
|
305
|
-
componentIdx:
|
|
693
|
+
componentIdx: callerComponentIdx,
|
|
306
694
|
parentTask: currentCallerTask,
|
|
307
|
-
|
|
695
|
+
isAsync,
|
|
308
696
|
callMetadata: {
|
|
309
|
-
|
|
697
|
+
getMemoryFn,
|
|
310
698
|
memoryIdx,
|
|
311
699
|
resultPtr,
|
|
312
700
|
returnFn,
|
|
@@ -314,26 +702,75 @@ resultCountOrAsync,
|
|
|
314
702
|
}
|
|
315
703
|
});
|
|
316
704
|
|
|
705
|
+
const [newTask, newTaskID] = createNewCurrentTask({
|
|
706
|
+
componentIdx: calleeComponentIdx,
|
|
707
|
+
isAsync,
|
|
708
|
+
getCalleeParamsFn,
|
|
709
|
+
entryFnName: [
|
|
710
|
+
'task',
|
|
711
|
+
subtask.getParentTask().id(),
|
|
712
|
+
'subtask',
|
|
713
|
+
subtask.id(),
|
|
714
|
+
'new-prepared-async-task'
|
|
715
|
+
].join('/'),
|
|
716
|
+
stringEncoding,
|
|
717
|
+
});
|
|
317
718
|
newTask.setParentSubtask(subtask);
|
|
318
|
-
// NOTE: This isn't really a return memory idx for the caller, it's for checking
|
|
319
|
-
// against the task.return (which will be called from the callee)
|
|
320
719
|
newTask.setReturnMemoryIdx(memoryIdx);
|
|
720
|
+
newTask.setReturnMemory(getMemoryFn);
|
|
721
|
+
subtask.setChildTask(newTask);
|
|
722
|
+
|
|
723
|
+
newTask.subtaskMeta = {
|
|
724
|
+
subtask,
|
|
725
|
+
calleeComponentIdx,
|
|
726
|
+
callerComponentIdx,
|
|
727
|
+
getCalleeParamsFn,
|
|
728
|
+
stringEncoding,
|
|
729
|
+
isAsync,
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
_setGlobalCurrentTaskMeta({
|
|
733
|
+
taskID: newTask.id(),
|
|
734
|
+
componentIdx: newTask.componentIdx(),
|
|
735
|
+
});
|
|
321
736
|
}
|
|
322
737
|
|
|
323
738
|
function _asyncStartCall(args, callee, paramCount, resultCount, flags) {
|
|
324
|
-
const
|
|
325
|
-
_debugLog('[_asyncStartCall()] args', args);
|
|
739
|
+
const componentIdx = ASYNC_CURRENT_COMPONENT_IDXS.at(-1);
|
|
326
740
|
|
|
327
|
-
const
|
|
328
|
-
if (!
|
|
741
|
+
const globalTaskMeta = _getGlobalCurrentTaskMeta(componentIdx);
|
|
742
|
+
if (!globalTaskMeta) { throw new Error('missing global current task globalTaskMeta'); }
|
|
743
|
+
const taskID = globalTaskMeta.taskID;
|
|
329
744
|
|
|
330
|
-
|
|
745
|
+
_debugLog('[_asyncStartCall()] args', { args, componentIdx });
|
|
746
|
+
const { getCallbackFn, callbackIdx, getPostReturnFn, postReturnIdx } = args;
|
|
331
747
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const preparedTask =
|
|
336
|
-
if (!preparedTask) { throw new Error('unexpectedly missing
|
|
748
|
+
const preparedTaskMeta = getCurrentTask(componentIdx, taskID);
|
|
749
|
+
if (!preparedTaskMeta) { throw new Error('unexpectedly missing current task'); }
|
|
750
|
+
|
|
751
|
+
const preparedTask = preparedTaskMeta.task;
|
|
752
|
+
if (!preparedTask) { throw new Error('unexpectedly missing current task'); }
|
|
753
|
+
if (!preparedTask.subtaskMeta) { throw new Error('missing subtask meta from prepare'); }
|
|
754
|
+
|
|
755
|
+
const {
|
|
756
|
+
subtask,
|
|
757
|
+
returnMemoryIdx,
|
|
758
|
+
getReturnMemoryFn,
|
|
759
|
+
callerComponentIdx,
|
|
760
|
+
calleeComponentIdx,
|
|
761
|
+
getCalleeParamsFn,
|
|
762
|
+
isAsync,
|
|
763
|
+
stringEncoding,
|
|
764
|
+
} = preparedTask.subtaskMeta;
|
|
765
|
+
if (!subtask) { throw new Error("missing subtask from cstate during async start call"); }
|
|
766
|
+
if (calleeComponentIdx !== preparedTask.componentIdx()) {
|
|
767
|
+
throw new Error(`meta callee idx [${calleeComponentIdx}] != current task idx [${preparedTask.componentIdx()}] during async start call`);
|
|
768
|
+
}
|
|
769
|
+
if (calleeComponentIdx !== componentIdx) {
|
|
770
|
+
throw new Error("mismatched componentIdx for async start call (does not match prepare)");
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const argArray = [...arguments];
|
|
337
774
|
|
|
338
775
|
if (resultCount < 0 || resultCount > 1) { throw new Error('invalid/unsupported result count'); }
|
|
339
776
|
|
|
@@ -342,34 +779,16 @@ function _asyncStartCall(args, callee, paramCount, resultCount, flags) {
|
|
|
342
779
|
preparedTask.setCallbackFn(callbackFn, callbackFnName);
|
|
343
780
|
preparedTask.setPostReturnFn(getPostReturnFn());
|
|
344
781
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
782
|
+
if (resultCount < 0 || resultCount > 1) {
|
|
783
|
+
throw new Error(`unsupported result count [${ resultCount }]`);
|
|
784
|
+
}
|
|
348
785
|
|
|
349
786
|
const params = preparedTask.getCalleeParams();
|
|
350
787
|
if (paramCount !== params.length) {
|
|
351
788
|
throw new Error(`unexpected callee param count [${ params.length }], _asyncStartCall invocation expected [${ paramCount }]`);
|
|
352
789
|
}
|
|
353
790
|
|
|
354
|
-
subtask.setOnProgressFn(() => {
|
|
355
|
-
subtask.setPendingEventFn(() => {
|
|
356
|
-
if (subtask.resolved()) { subtask.deliverResolve(); }
|
|
357
|
-
return {
|
|
358
|
-
code: ASYNC_EVENT_CODE.SUBTASK,
|
|
359
|
-
index: rep,
|
|
360
|
-
result: subtask.getStateNumber(),
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
const subtaskState = subtask.getStateNumber();
|
|
366
|
-
if (subtaskState < 0 || subtaskState > 2**5) {
|
|
367
|
-
throw new Error('invalid subtask state, out of valid range');
|
|
368
|
-
}
|
|
369
|
-
|
|
370
791
|
const callerComponentState = getOrCreateAsyncState(subtask.componentIdx());
|
|
371
|
-
const rep = callerComponentState.subtasks.insert(subtask);
|
|
372
|
-
subtask.setRep(rep);
|
|
373
792
|
|
|
374
793
|
const calleeComponentState = getOrCreateAsyncState(preparedTask.componentIdx());
|
|
375
794
|
const calleeBackpressure = calleeComponentState.hasBackpressure();
|
|
@@ -380,6 +799,7 @@ function _asyncStartCall(args, callee, paramCount, resultCount, flags) {
|
|
|
380
799
|
// lowering manually, as fused modules provider helper functions that can
|
|
381
800
|
subtask.registerOnResolveHandler((res) => {
|
|
382
801
|
_debugLog('[_asyncStartCall()] handling subtask result', { res, subtaskID: subtask.id() });
|
|
802
|
+
|
|
383
803
|
let subtaskCallMeta = subtask.getCallMetadata();
|
|
384
804
|
|
|
385
805
|
// NOTE: in the case of guest -> guest async calls, there may be no memory/realloc present,
|
|
@@ -397,8 +817,13 @@ function _asyncStartCall(args, callee, paramCount, resultCount, flags) {
|
|
|
397
817
|
|
|
398
818
|
// If a helper function was provided we are likely in a fused guest->guest call,
|
|
399
819
|
// and the result will be delivered (lift/lowered) via helper function
|
|
400
|
-
if (subtaskCallMeta.returnFn) {
|
|
401
|
-
_debugLog('[_asyncStartCall()] return function present while
|
|
820
|
+
if (subtaskCallMeta && subtaskCallMeta.returnFn) {
|
|
821
|
+
_debugLog('[_asyncStartCall()] return function present while handling subtask result, returning early (skipping lower)');
|
|
822
|
+
|
|
823
|
+
// TODO: centralize calling of returnFn to *one place* (if possible)
|
|
824
|
+
if (subtaskCallMeta.returnFnCalled) { return; }
|
|
825
|
+
|
|
826
|
+
subtaskCallMeta.returnFn.apply(null, [subtaskCallMeta.resultPtr]);
|
|
402
827
|
return;
|
|
403
828
|
}
|
|
404
829
|
|
|
@@ -409,27 +834,31 @@ function _asyncStartCall(args, callee, paramCount, resultCount, flags) {
|
|
|
409
834
|
}
|
|
410
835
|
|
|
411
836
|
let callerMemory;
|
|
412
|
-
if (callerMemoryIdx) {
|
|
413
|
-
callerMemory =
|
|
837
|
+
if (callerMemoryIdx !== null && callerMemoryIdx !== undefined) {
|
|
838
|
+
callerMemory = lookupMemoriesForComponent({ componentIdx: callerComponentIdx, memoryIdx: callerMemoryIdx });
|
|
414
839
|
} else {
|
|
415
|
-
const callerMemories =
|
|
416
|
-
if (callerMemories.length
|
|
840
|
+
const callerMemories = lookupMemoriesForComponent({ componentIdx: callerComponentIdx });
|
|
841
|
+
if (callerMemories.length !== 1) { throw new Error(`unsupported amount of caller memories`); }
|
|
417
842
|
callerMemory = callerMemories[0];
|
|
418
843
|
}
|
|
419
844
|
|
|
420
845
|
if (!callerMemory) {
|
|
846
|
+
_debugLog('[_asyncStartCall()] missing memory', { subtaskID: subtask.id(), res });
|
|
421
847
|
throw new Error(`missing memory for to guest->guest call result (subtask [${subtask.id()}])`);
|
|
422
848
|
}
|
|
423
849
|
|
|
424
850
|
const lowerFns = calleeTask.getReturnLowerFns();
|
|
425
851
|
if (!lowerFns || lowerFns.length === 0) {
|
|
426
|
-
|
|
852
|
+
_debugLog('[_asyncStartCall()] missing result lower metadata for guest->guest call', { subtaskID: subtask.id() });
|
|
853
|
+
throw new Error(`missing result lower metadata for guest->guest call (subtask [${subtask.id()}])`);
|
|
427
854
|
}
|
|
428
855
|
|
|
429
856
|
if (lowerFns.length !== 1) {
|
|
857
|
+
_debugLog('[_asyncStartCall()] only single result reportetd for guest->guest call', { subtaskID: subtask.id() });
|
|
430
858
|
throw new Error(`only single result supported for guest->guest calls (subtask [${subtask.id()}])`);
|
|
431
859
|
}
|
|
432
860
|
|
|
861
|
+
_debugLog('[_asyncStartCall()] lowering results', { subtaskID: subtask.id() });
|
|
433
862
|
lowerFns[0]({
|
|
434
863
|
realloc: undefined,
|
|
435
864
|
memory: callerMemory,
|
|
@@ -440,115 +869,96 @@ function _asyncStartCall(args, callee, paramCount, resultCount, flags) {
|
|
|
440
869
|
|
|
441
870
|
});
|
|
442
871
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
// if not we need to pass params to the callee instead
|
|
453
|
-
startFnParams.push(...params);
|
|
454
|
-
calleeParams.push(...params);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
preparedTask.registerOnResolveHandler((res) => {
|
|
458
|
-
_debugLog('[_asyncStartCall()] signaling subtask completion due to task completion', {
|
|
459
|
-
childTaskID: preparedTask.id(),
|
|
460
|
-
subtaskID: subtask.id(),
|
|
461
|
-
parentTaskID: subtask.getParentTask().id(),
|
|
872
|
+
subtask.setOnProgressFn(() => {
|
|
873
|
+
subtask.setPendingEvent(() => {
|
|
874
|
+
if (subtask.isResolved()) { subtask.deliverResolve(); }
|
|
875
|
+
const event = {
|
|
876
|
+
code: ASYNC_EVENT_CODE.SUBTASK,
|
|
877
|
+
payload0: subtask.waitableRep(),
|
|
878
|
+
payload1: subtask.getStateNumber(),
|
|
879
|
+
};
|
|
880
|
+
return event;
|
|
462
881
|
});
|
|
463
|
-
subtask.onResolve(res);
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
// TODO(fix): start fns sometimes produce results, how should they be used?
|
|
467
|
-
// the result should theoretically be used for flat lowering, but fused components do
|
|
468
|
-
// this automatically!
|
|
469
|
-
subtask.onStart({ startFnParams });
|
|
470
|
-
|
|
471
|
-
_debugLog("[_asyncStartCall()] initial call", {
|
|
472
|
-
task: preparedTask.id(),
|
|
473
|
-
subtaskID: subtask.id(),
|
|
474
|
-
calleeFnName: callee.name,
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
const callbackResult = callee.apply(null, calleeParams);
|
|
478
|
-
|
|
479
|
-
_debugLog("[_asyncStartCall()] after initial call", {
|
|
480
|
-
task: preparedTask.id(),
|
|
481
|
-
subtaskID: subtask.id(),
|
|
482
|
-
calleeFnName: callee.name,
|
|
483
882
|
});
|
|
484
883
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
calleeComponentIdx: preparedTask.componentIdx(),
|
|
494
|
-
task: preparedTask.id(),
|
|
495
|
-
subtaskID: subtask.id(),
|
|
496
|
-
callerComponentIdx: subtask.componentIdx(),
|
|
884
|
+
// Start the (event) driver loop that will resolve the task
|
|
885
|
+
queueMicrotask(async () => {
|
|
886
|
+
let startRes = subtask.onStart({ startFnParams: params });
|
|
887
|
+
startRes = Array.isArray(startRes) ? startRes : [startRes];
|
|
888
|
+
|
|
889
|
+
await calleeComponentState.suspendTask({
|
|
890
|
+
task: preparedTask,
|
|
891
|
+
readyFn: () => !calleeComponentState.isExclusivelyLocked(),
|
|
497
892
|
});
|
|
498
893
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
return Number(subtask.waitableRep()) << 4 | subtaskState;
|
|
894
|
+
const started = await preparedTask.enter();
|
|
895
|
+
if (!started) {
|
|
896
|
+
_debugLog('[_asyncStartCall()] task failed early', {
|
|
897
|
+
taskID: preparedTask.id(),
|
|
898
|
+
subtaskID: subtask.id(),
|
|
899
|
+
});
|
|
900
|
+
throw new Error("task failed to start");
|
|
901
|
+
return;
|
|
508
902
|
}
|
|
509
903
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
_debugLog("[_asyncStartCall()] instantly resolved after cleared backpressure", {
|
|
520
|
-
calleeComponentIdx: preparedTask.componentIdx(),
|
|
521
|
-
task: preparedTask.id(),
|
|
522
|
-
subtaskID: subtask.id(),
|
|
523
|
-
callerComponentIdx: subtask.componentIdx(),
|
|
904
|
+
let callbackResult;
|
|
905
|
+
try {
|
|
906
|
+
let jspiCallee = WebAssembly.promising(callee);
|
|
907
|
+
callbackResult = await _withGlobalCurrentTaskMetaAsync({
|
|
908
|
+
taskID: preparedTask.id(),
|
|
909
|
+
componentIdx: preparedTask.componentIdx(),
|
|
910
|
+
fn: () => {
|
|
911
|
+
return jspiCallee.apply(null, startRes);
|
|
912
|
+
}
|
|
524
913
|
});
|
|
914
|
+
} catch(err) {
|
|
915
|
+
_debugLog("[_asyncStartCall()] initial subtask callee run failed", err);
|
|
916
|
+
// NOTE: a good place to rejectt the parent task, if rejection API is enabled
|
|
917
|
+
// subtask.reject(err);
|
|
918
|
+
// subtask.getParentTask().reject(err);
|
|
919
|
+
|
|
920
|
+
subtask.getParentTask().setErrored(err);
|
|
921
|
+
|
|
525
922
|
return;
|
|
526
923
|
}
|
|
527
924
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
925
|
+
// If there was no callback function, we're dealing with a sync function
|
|
926
|
+
// that was lifted as async without one, there is only the callee.
|
|
927
|
+
if (!callbackFn) {
|
|
928
|
+
_debugLog("[_asyncStartCall()] no callback, resolving w/ callee result", {
|
|
531
929
|
taskID: preparedTask.id(),
|
|
532
|
-
|
|
930
|
+
componentIdx: preparedTask.componentIdx(),
|
|
931
|
+
preparedTask,
|
|
932
|
+
stateNumber: preparedTask.taskState(),
|
|
933
|
+
isResolved: preparedTask.isResolved(),
|
|
934
|
+
callbackFn,
|
|
533
935
|
});
|
|
534
|
-
|
|
936
|
+
preparedTask.resolve([callbackResult]);
|
|
535
937
|
return;
|
|
536
938
|
}
|
|
537
939
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
940
|
+
let fnName = callbackFn.fnName;
|
|
941
|
+
if (!fnName) {
|
|
942
|
+
fnName = [
|
|
943
|
+
'<task ',
|
|
944
|
+
subtask.parentTaskID(),
|
|
945
|
+
'/subtask ',
|
|
946
|
+
subtask.id(),
|
|
947
|
+
'/task ',
|
|
948
|
+
preparedTask.id(),
|
|
949
|
+
'>',
|
|
950
|
+
].join("");
|
|
951
|
+
}
|
|
549
952
|
|
|
550
953
|
try {
|
|
551
|
-
_debugLog("[_asyncStartCall()] starting driver loop", {
|
|
954
|
+
_debugLog("[_asyncStartCall()] starting driver loop", {
|
|
955
|
+
fnName,
|
|
956
|
+
componentIdx: preparedTask.componentIdx(),
|
|
957
|
+
subtaskID: subtask.id(),
|
|
958
|
+
childTaskID: subtask.childTaskID(),
|
|
959
|
+
parentTaskID: subtask.parentTaskID(),
|
|
960
|
+
});
|
|
961
|
+
|
|
552
962
|
await _driverLoop({
|
|
553
963
|
componentState: calleeComponentState,
|
|
554
964
|
task: preparedTask,
|
|
@@ -564,6 +974,18 @@ function _asyncStartCall(args, callee, paramCount, resultCount, flags) {
|
|
|
564
974
|
|
|
565
975
|
});
|
|
566
976
|
|
|
977
|
+
const subtaskState = subtask.getStateNumber();
|
|
978
|
+
if (subtaskState < 0 || subtaskState > 2**5) {
|
|
979
|
+
throw new Error('invalid subtask state, out of valid range');
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
_debugLog('[_asyncStartCall()] returning subtask rep & state', {
|
|
983
|
+
subtask: {
|
|
984
|
+
rep: subtask.waitableRep(),
|
|
985
|
+
state: subtaskState,
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
|
|
567
989
|
return Number(subtask.waitableRep()) << 4 | subtaskState;
|
|
568
990
|
}
|
|
569
991
|
|
|
@@ -572,612 +994,822 @@ function _syncStartCall(callbackIdx) {
|
|
|
572
994
|
throw new Error('synchronous start call not implemented!');
|
|
573
995
|
}
|
|
574
996
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const TEXT_DECODER_UTF8 = new TextDecoder();
|
|
578
|
-
const TEXT_ENCODER_UTF8 = new TextEncoder();
|
|
579
|
-
|
|
580
|
-
function _utf8AllocateAndEncode(s, realloc, memory) {
|
|
581
|
-
if (typeof s !== 'string') {
|
|
582
|
-
throw new TypeError('expected a string, received [' + typeof s + ']');
|
|
583
|
-
}
|
|
584
|
-
if (s.length === 0) { return { ptr: 1, len: 0 }; }
|
|
585
|
-
let buf = TEXT_ENCODER_UTF8.encode(s);
|
|
586
|
-
let ptr = realloc(0, 0, 1, buf.length);
|
|
587
|
-
new Uint8Array(memory.buffer).set(buf, ptr);
|
|
588
|
-
return { ptr, len: buf.length, codepoints: [...s].length };
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
function createNewCurrentTask(args) {
|
|
593
|
-
_debugLog('[createNewCurrentTask()] args', args);
|
|
594
|
-
const {
|
|
595
|
-
componentIdx,
|
|
596
|
-
isAsync,
|
|
597
|
-
entryFnName,
|
|
598
|
-
parentSubtaskID,
|
|
599
|
-
callbackFnName,
|
|
600
|
-
getCallbackFn,
|
|
601
|
-
getParamsFn,
|
|
602
|
-
stringEncoding,
|
|
603
|
-
errHandling,
|
|
604
|
-
getCalleeParamsFn,
|
|
605
|
-
resultPtr,
|
|
606
|
-
callingWasmExport,
|
|
607
|
-
} = args;
|
|
608
|
-
if (componentIdx === undefined || componentIdx === null) {
|
|
609
|
-
throw new Error('missing/invalid component instance index while starting task');
|
|
610
|
-
}
|
|
611
|
-
const taskMetas = ASYNC_TASKS_BY_COMPONENT_IDX.get(componentIdx);
|
|
612
|
-
const callbackFn = getCallbackFn ? getCallbackFn() : null;
|
|
997
|
+
class Waitable {
|
|
998
|
+
#componentIdx;
|
|
613
999
|
|
|
614
|
-
|
|
615
|
-
componentIdx,
|
|
616
|
-
isAsync,
|
|
617
|
-
entryFnName,
|
|
618
|
-
callbackFn,
|
|
619
|
-
callbackFnName,
|
|
620
|
-
stringEncoding,
|
|
621
|
-
getCalleeParamsFn,
|
|
622
|
-
resultPtr,
|
|
623
|
-
errHandling,
|
|
624
|
-
});
|
|
1000
|
+
#pendingEventFn = null;
|
|
625
1001
|
|
|
626
|
-
|
|
627
|
-
|
|
1002
|
+
#promise;
|
|
1003
|
+
#resolve;
|
|
1004
|
+
#reject;
|
|
628
1005
|
|
|
629
|
-
|
|
630
|
-
ASYNC_CURRENT_COMPONENT_IDXS.push(componentIdx);
|
|
1006
|
+
#waitableSet = null;
|
|
631
1007
|
|
|
632
|
-
|
|
633
|
-
ASYNC_TASKS_BY_COMPONENT_IDX.set(componentIdx, [newTaskMeta]);
|
|
634
|
-
} else {
|
|
635
|
-
taskMetas.push(newTaskMeta);
|
|
636
|
-
}
|
|
1008
|
+
#idx = null; // to component-global waitables
|
|
637
1009
|
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function endCurrentTask(componentIdx, taskID) {
|
|
642
|
-
componentIdx ??= ASYNC_CURRENT_COMPONENT_IDXS.at(-1);
|
|
643
|
-
taskID ??= ASYNC_CURRENT_TASK_IDS.at(-1);
|
|
644
|
-
_debugLog('[endCurrentTask()] args', { componentIdx, taskID });
|
|
1010
|
+
target;
|
|
645
1011
|
|
|
646
|
-
|
|
647
|
-
|
|
1012
|
+
constructor(args) {
|
|
1013
|
+
const { componentIdx, target } = args;
|
|
1014
|
+
this.#componentIdx = componentIdx;
|
|
1015
|
+
this.target = args.target;
|
|
1016
|
+
this.#resetPromise();
|
|
648
1017
|
}
|
|
649
1018
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
throw new Error('missing/invalid tasks for component instance while ending task');
|
|
653
|
-
}
|
|
654
|
-
if (tasks.length == 0) {
|
|
655
|
-
throw new Error('no current task(s) for component instance while ending task');
|
|
656
|
-
}
|
|
1019
|
+
componentIdx() { return this.#componentIdx; }
|
|
1020
|
+
isInSet() { return this.#waitableSet !== null; }
|
|
657
1021
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (
|
|
661
|
-
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
1022
|
+
idx() { return this.#idx; }
|
|
1023
|
+
setIdx(idx) {
|
|
1024
|
+
if (idx === 0) { throw new Error("waitable idx cannot be zero"); }
|
|
1025
|
+
this.#idx = idx;
|
|
664
1026
|
}
|
|
665
1027
|
|
|
666
|
-
|
|
667
|
-
ASYNC_CURRENT_COMPONENT_IDXS.pop();
|
|
668
|
-
|
|
669
|
-
const taskMeta = tasks.pop();
|
|
670
|
-
return taskMeta.task;
|
|
671
|
-
}
|
|
672
|
-
const ASYNC_TASKS_BY_COMPONENT_IDX = new Map();
|
|
673
|
-
|
|
674
|
-
class AsyncTask {
|
|
675
|
-
static _ID = 0n;
|
|
676
|
-
|
|
677
|
-
static State = {
|
|
678
|
-
INITIAL: 'initial',
|
|
679
|
-
CANCELLED: 'cancelled',
|
|
680
|
-
CANCEL_PENDING: 'cancel-pending',
|
|
681
|
-
CANCEL_DELIVERED: 'cancel-delivered',
|
|
682
|
-
RESOLVED: 'resolved',
|
|
683
|
-
}
|
|
1028
|
+
setTarget(tgt) { this.target = tgt; }
|
|
684
1029
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1030
|
+
#resetPromise() {
|
|
1031
|
+
const { promise, resolve, reject } = promiseWithResolvers()
|
|
1032
|
+
this.#promise = promise;
|
|
1033
|
+
this.#resolve = resolve;
|
|
1034
|
+
this.#reject = reject;
|
|
688
1035
|
}
|
|
689
1036
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
#isAsync;
|
|
694
|
-
#entryFnName = null;
|
|
695
|
-
#subtasks = [];
|
|
696
|
-
|
|
697
|
-
#onResolveHandlers = [];
|
|
698
|
-
#completionPromise = null;
|
|
699
|
-
|
|
700
|
-
#memoryIdx = null;
|
|
701
|
-
|
|
702
|
-
#callbackFn = null;
|
|
703
|
-
#callbackFnName = null;
|
|
704
|
-
|
|
705
|
-
#postReturnFn = null;
|
|
706
|
-
|
|
707
|
-
#getCalleeParamsFn = null;
|
|
708
|
-
|
|
709
|
-
#stringEncoding = null;
|
|
710
|
-
|
|
711
|
-
#parentSubtask = null;
|
|
1037
|
+
resolve() { this.#resolve(); }
|
|
1038
|
+
reject(err) { this.#reject(err); }
|
|
1039
|
+
promise() { return this.#promise; }
|
|
712
1040
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1041
|
+
hasPendingEvent() {
|
|
1042
|
+
// _debugLog('[Waitable#hasPendingEvent()]', {
|
|
1043
|
+
// componentIdx: this.#componentIdx,
|
|
1044
|
+
// waitable: this,
|
|
1045
|
+
// waitableSet: this.#waitableSet,
|
|
1046
|
+
// hasPendingEvent: this.#pendingEventFn !== null,
|
|
1047
|
+
// });
|
|
1048
|
+
return this.#pendingEventFn !== null;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
setPendingEvent(fn) {
|
|
1052
|
+
_debugLog('[Waitable#setPendingEvent()] args', {
|
|
1053
|
+
waitable: this,
|
|
1054
|
+
inSet: this.#waitableSet,
|
|
1055
|
+
});
|
|
1056
|
+
this.#pendingEventFn = fn;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
getPendingEvent() {
|
|
1060
|
+
_debugLog('[Waitable#getPendingEvent()] args', {
|
|
1061
|
+
waitable: this,
|
|
1062
|
+
inSet: this.#waitableSet,
|
|
1063
|
+
hasPendingEvent: this.#pendingEventFn !== null,
|
|
1064
|
+
});
|
|
1065
|
+
if (this.#pendingEventFn === null) { return null; }
|
|
1066
|
+
const eventFn = this.#pendingEventFn;
|
|
1067
|
+
this.#pendingEventFn = null;
|
|
1068
|
+
const e = eventFn();
|
|
1069
|
+
this.#resetPromise();
|
|
1070
|
+
return e;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
join(waitableSet) {
|
|
1074
|
+
_debugLog('[Waitable#join()] args', {
|
|
1075
|
+
waitable: this,
|
|
1076
|
+
waitableSet: waitableSet,
|
|
1077
|
+
});
|
|
1078
|
+
if (this.#waitableSet) { this.#waitableSet.removeWaitable(this); }
|
|
1079
|
+
if (!waitableSet) {
|
|
1080
|
+
this.#waitableSet = null;
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
waitableSet.addWaitable(this);
|
|
1084
|
+
this.#waitableSet = waitableSet;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
drop() {
|
|
1088
|
+
_debugLog('[Waitable#drop()] args', {
|
|
1089
|
+
componentIdx: this.#componentIdx,
|
|
1090
|
+
waitable: this,
|
|
1091
|
+
});
|
|
1092
|
+
if (this.hasPendingEvent()) {
|
|
1093
|
+
throw new Error('waitables with pending events cannot be dropped');
|
|
1094
|
+
}
|
|
1095
|
+
this.join(null);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
}
|
|
716
1099
|
|
|
717
|
-
|
|
718
|
-
#backpressureWaiters = 0n;
|
|
1100
|
+
const ERR_CTX_TABLES = {};
|
|
719
1101
|
|
|
720
|
-
|
|
1102
|
+
let dv = new DataView(new ArrayBuffer());
|
|
1103
|
+
const dataView = mem => dv.buffer === mem.buffer ? dv : dv = new DataView(mem.buffer);
|
|
1104
|
+
const TEXT_DECODER_UTF8 = new TextDecoder();
|
|
1105
|
+
const TEXT_ENCODER_UTF8 = new TextEncoder();
|
|
721
1106
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1107
|
+
function _utf8AllocateAndEncode(s, realloc, memory) {
|
|
1108
|
+
if (typeof s !== 'string') {
|
|
1109
|
+
throw new TypeError('expected a string, received [' + typeof s + ']');
|
|
1110
|
+
}
|
|
1111
|
+
if (s.length === 0) { return { ptr: 1, len: 0 }; }
|
|
1112
|
+
let buf = TEXT_ENCODER_UTF8.encode(s);
|
|
1113
|
+
let ptr = realloc(0, 0, 1, buf.length);
|
|
1114
|
+
new Uint8Array(memory.buffer).set(buf, ptr);
|
|
1115
|
+
const res = { ptr, len: buf.length, codepoints: [...s].length };
|
|
1116
|
+
return res;
|
|
1117
|
+
}
|
|
725
1118
|
|
|
726
|
-
returnCalls = 0;
|
|
727
|
-
storage = [0, 0];
|
|
728
|
-
borrowedHandles = {};
|
|
729
1119
|
|
|
730
|
-
|
|
731
|
-
|
|
1120
|
+
function createNewCurrentTask(args) {
|
|
1121
|
+
_debugLog('[createNewCurrentTask()] args', args);
|
|
1122
|
+
const {
|
|
1123
|
+
componentIdx,
|
|
1124
|
+
isAsync,
|
|
1125
|
+
isManualAsync,
|
|
1126
|
+
entryFnName,
|
|
1127
|
+
parentSubtaskID,
|
|
1128
|
+
callbackFnName,
|
|
1129
|
+
getCallbackFn,
|
|
1130
|
+
getParamsFn,
|
|
1131
|
+
stringEncoding,
|
|
1132
|
+
errHandling,
|
|
1133
|
+
getCalleeParamsFn,
|
|
1134
|
+
resultPtr,
|
|
1135
|
+
callingWasmExport,
|
|
1136
|
+
} = args;
|
|
1137
|
+
if (componentIdx === undefined || componentIdx === null) {
|
|
1138
|
+
throw new Error('missing/invalid component instance index while starting task');
|
|
1139
|
+
}
|
|
1140
|
+
let taskMetas = ASYNC_TASKS_BY_COMPONENT_IDX.get(componentIdx);
|
|
1141
|
+
const callbackFn = getCallbackFn ? getCallbackFn() : null;
|
|
1142
|
+
|
|
1143
|
+
const newTask = new AsyncTask({
|
|
1144
|
+
componentIdx,
|
|
1145
|
+
isAsync,
|
|
1146
|
+
isManualAsync,
|
|
1147
|
+
entryFnName,
|
|
1148
|
+
callbackFn,
|
|
1149
|
+
callbackFnName,
|
|
1150
|
+
stringEncoding,
|
|
1151
|
+
getCalleeParamsFn,
|
|
1152
|
+
resultPtr,
|
|
1153
|
+
errHandling,
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
const newTaskID = newTask.id();
|
|
1157
|
+
const newTaskMeta = { id: newTaskID, componentIdx, task: newTask };
|
|
1158
|
+
|
|
1159
|
+
// NOTE: do not track host tasks
|
|
1160
|
+
ASYNC_CURRENT_TASK_IDS.push(newTaskID);
|
|
1161
|
+
ASYNC_CURRENT_COMPONENT_IDXS.push(componentIdx);
|
|
1162
|
+
|
|
1163
|
+
if (!taskMetas) {
|
|
1164
|
+
taskMetas = [newTaskMeta];
|
|
1165
|
+
ASYNC_TASKS_BY_COMPONENT_IDX.set(componentIdx, [newTaskMeta]);
|
|
1166
|
+
} else {
|
|
1167
|
+
taskMetas.push(newTaskMeta);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
return [newTask, newTaskID];
|
|
1171
|
+
}
|
|
1172
|
+
const ASYNC_TASKS_BY_COMPONENT_IDX = new Map();
|
|
732
1173
|
|
|
733
|
-
|
|
734
|
-
|
|
1174
|
+
class AsyncTask {
|
|
1175
|
+
static _ID = 0n;
|
|
735
1176
|
|
|
736
|
-
|
|
737
|
-
|
|
1177
|
+
static State = {
|
|
1178
|
+
INITIAL: 'initial',
|
|
1179
|
+
CANCELLED: 'cancelled',
|
|
1180
|
+
CANCEL_PENDING: 'cancel-pending',
|
|
1181
|
+
CANCEL_DELIVERED: 'cancel-delivered',
|
|
1182
|
+
RESOLVED: 'resolved',
|
|
738
1183
|
}
|
|
739
|
-
this.#componentIdx = opts.componentIdx;
|
|
740
1184
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1185
|
+
static BlockResult = {
|
|
1186
|
+
CANCELLED: 'block.cancelled',
|
|
1187
|
+
NOT_CANCELLED: 'block.not-cancelled',
|
|
1188
|
+
}
|
|
744
1189
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1190
|
+
#id;
|
|
1191
|
+
#componentIdx;
|
|
1192
|
+
#state;
|
|
1193
|
+
#isAsync;
|
|
1194
|
+
#isManualAsync;
|
|
1195
|
+
#entryFnName = null;
|
|
751
1196
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1197
|
+
#onResolveHandlers = [];
|
|
1198
|
+
#completionPromise = null;
|
|
1199
|
+
#rejected = false;
|
|
755
1200
|
|
|
756
|
-
|
|
757
|
-
|
|
1201
|
+
#exitPromise = null;
|
|
1202
|
+
#onExitHandlers = [];
|
|
758
1203
|
|
|
759
|
-
|
|
1204
|
+
#memoryIdx = null;
|
|
1205
|
+
#memory = null;
|
|
760
1206
|
|
|
761
|
-
|
|
1207
|
+
#callbackFn = null;
|
|
1208
|
+
#callbackFnName = null;
|
|
762
1209
|
|
|
763
|
-
|
|
1210
|
+
#postReturnFn = null;
|
|
764
1211
|
|
|
765
|
-
|
|
1212
|
+
#getCalleeParamsFn = null;
|
|
766
1213
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1214
|
+
#stringEncoding = null;
|
|
1215
|
+
|
|
1216
|
+
#parentSubtask = null;
|
|
1217
|
+
|
|
1218
|
+
#needsExclusiveLock = false;
|
|
1219
|
+
|
|
1220
|
+
#errHandling;
|
|
1221
|
+
|
|
1222
|
+
#backpressurePromise;
|
|
1223
|
+
#backpressureWaiters = 0n;
|
|
1224
|
+
|
|
1225
|
+
#returnLowerFns = null;
|
|
1226
|
+
|
|
1227
|
+
#subtasks = [];
|
|
1228
|
+
|
|
1229
|
+
#entered = false;
|
|
1230
|
+
#exited = false;
|
|
1231
|
+
#errored = null;
|
|
1232
|
+
|
|
1233
|
+
cancelled = false;
|
|
1234
|
+
cancelRequested = false;
|
|
1235
|
+
alwaysTaskReturn = false;
|
|
1236
|
+
|
|
1237
|
+
returnCalls = 0;
|
|
1238
|
+
storage = [0, 0];
|
|
1239
|
+
borrowedHandles = {};
|
|
1240
|
+
|
|
1241
|
+
constructor(opts) {
|
|
1242
|
+
this.#id = ++AsyncTask._ID;
|
|
1243
|
+
|
|
1244
|
+
if (opts?.componentIdx === undefined) {
|
|
1245
|
+
throw new TypeError('missing component id during task creation');
|
|
1246
|
+
}
|
|
1247
|
+
this.#componentIdx = opts.componentIdx;
|
|
1248
|
+
|
|
1249
|
+
this.#state = AsyncTask.State.INITIAL;
|
|
1250
|
+
this.#isAsync = opts?.isAsync ?? false;
|
|
1251
|
+
this.#isManualAsync = opts?.isManualAsync ?? false;
|
|
1252
|
+
this.#entryFnName = opts.entryFnName;
|
|
1253
|
+
|
|
1254
|
+
const {
|
|
1255
|
+
promise: completionPromise,
|
|
1256
|
+
resolve: resolveCompletionPromise,
|
|
1257
|
+
reject: rejectCompletionPromise,
|
|
1258
|
+
} = promiseWithResolvers();
|
|
1259
|
+
this.#completionPromise = completionPromise;
|
|
1260
|
+
|
|
1261
|
+
this.#onResolveHandlers.push((results) => {
|
|
1262
|
+
if (this.#errored !== null) {
|
|
1263
|
+
rejectCompletionPromise(this.#errored);
|
|
1264
|
+
return;
|
|
1265
|
+
} else if (this.#rejected) {
|
|
1266
|
+
rejectCompletionPromise(results);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
resolveCompletionPromise(results);
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
const {
|
|
1273
|
+
promise: exitPromise,
|
|
1274
|
+
resolve: resolveExitPromise,
|
|
1275
|
+
reject: rejectExitPromise,
|
|
1276
|
+
} = promiseWithResolvers();
|
|
1277
|
+
this.#exitPromise = exitPromise;
|
|
1278
|
+
|
|
1279
|
+
this.#onExitHandlers.push(() => {
|
|
1280
|
+
resolveExitPromise();
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
if (opts.callbackFn) { this.#callbackFn = opts.callbackFn; }
|
|
1284
|
+
if (opts.callbackFnName) { this.#callbackFnName = opts.callbackFnName; }
|
|
1285
|
+
|
|
1286
|
+
if (opts.getCalleeParamsFn) { this.#getCalleeParamsFn = opts.getCalleeParamsFn; }
|
|
1287
|
+
|
|
1288
|
+
if (opts.stringEncoding) { this.#stringEncoding = opts.stringEncoding; }
|
|
1289
|
+
|
|
1290
|
+
if (opts.parentSubtask) { this.#parentSubtask = opts.parentSubtask; }
|
|
1291
|
+
|
|
1292
|
+
this.#needsExclusiveLock = this.isSync() || !this.hasCallback();
|
|
1293
|
+
|
|
1294
|
+
if (opts.errHandling) { this.#errHandling = opts.errHandling; }
|
|
806
1295
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
this.#
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
this.#
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
runCallbackFn(...args) {
|
|
829
|
-
if (!this.#callbackFn) { throw new Error('on callback function has been set for task'); }
|
|
830
|
-
return this.#callbackFn.apply(null, args);
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
getCalleeParams() {
|
|
834
|
-
if (!this.#getCalleeParamsFn) { throw new Error('missing/invalid getCalleeParamsFn'); }
|
|
835
|
-
return this.#getCalleeParamsFn();
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
mayEnter(task) {
|
|
839
|
-
const cstate = getOrCreateAsyncState(this.#componentIdx);
|
|
840
|
-
if (cstate.hasBackpressure()) {
|
|
841
|
-
_debugLog('[AsyncTask#mayEnter()] disallowed due to backpressure', { taskID: this.#id });
|
|
842
|
-
return false;
|
|
1296
|
+
|
|
1297
|
+
taskState() { return this.#state; }
|
|
1298
|
+
id() { return this.#id; }
|
|
1299
|
+
componentIdx() { return this.#componentIdx; }
|
|
1300
|
+
entryFnName() { return this.#entryFnName; }
|
|
1301
|
+
|
|
1302
|
+
completionPromise() { return this.#completionPromise; }
|
|
1303
|
+
exitPromise() { return this.#exitPromise; }
|
|
1304
|
+
|
|
1305
|
+
isAsync() { return this.#isAsync; }
|
|
1306
|
+
isSync() { return !this.isAsync(); }
|
|
1307
|
+
|
|
1308
|
+
getErrHandling() { return this.#errHandling; }
|
|
1309
|
+
|
|
1310
|
+
hasCallback() { return this.#callbackFn !== null; }
|
|
1311
|
+
|
|
1312
|
+
getReturnMemoryIdx() { return this.#memoryIdx; }
|
|
1313
|
+
setReturnMemoryIdx(idx) {
|
|
1314
|
+
if (idx === null) { return; }
|
|
1315
|
+
this.#memoryIdx = idx;
|
|
843
1316
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1317
|
+
|
|
1318
|
+
getReturnMemory() { return this.#memory; }
|
|
1319
|
+
setReturnMemory(m) {
|
|
1320
|
+
if (m === null) { return; }
|
|
1321
|
+
this.#memory = m;
|
|
847
1322
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1323
|
+
|
|
1324
|
+
setReturnLowerFns(fns) { this.#returnLowerFns = fns; }
|
|
1325
|
+
getReturnLowerFns() { return this.#returnLowerFns; }
|
|
1326
|
+
|
|
1327
|
+
setParentSubtask(subtask) {
|
|
1328
|
+
if (!subtask || !(subtask instanceof AsyncSubtask)) { return }
|
|
1329
|
+
if (this.#parentSubtask) { throw new Error('parent subtask can only be set once'); }
|
|
1330
|
+
this.#parentSubtask = subtask;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
getParentSubtask() { return this.#parentSubtask; }
|
|
1334
|
+
|
|
1335
|
+
// TODO(threads): this is very inefficient, we can pass along a root task,
|
|
1336
|
+
// and ideally do not need this once thread support is in place
|
|
1337
|
+
getRootTask() {
|
|
1338
|
+
let currentSubtask = this.getParentSubtask();
|
|
1339
|
+
let task = this;
|
|
1340
|
+
while (currentSubtask) {
|
|
1341
|
+
task = currentSubtask.getParentTask();
|
|
1342
|
+
currentSubtask = task.getParentSubtask();
|
|
1343
|
+
}
|
|
1344
|
+
return task;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
setPostReturnFn(f) {
|
|
1348
|
+
if (!f) { return; }
|
|
1349
|
+
if (this.#postReturnFn) { throw new Error('postReturn fn can only be set once'); }
|
|
1350
|
+
this.#postReturnFn = f;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
setCallbackFn(f, name) {
|
|
1354
|
+
if (!f) { return; }
|
|
1355
|
+
if (this.#callbackFn) { throw new Error('callback fn can only be set once'); }
|
|
1356
|
+
this.#callbackFn = f;
|
|
1357
|
+
this.#callbackFnName = name;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
getCallbackFnName() {
|
|
1361
|
+
if (!this.#callbackFnName) { return undefined; }
|
|
1362
|
+
return this.#callbackFnName;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
async runCallbackFn(...args) {
|
|
1366
|
+
if (!this.#callbackFn) { throw new Error('on callback function has been set for task'); }
|
|
1367
|
+
return await this.#callbackFn.apply(null, args);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
getCalleeParams() {
|
|
1371
|
+
if (!this.#getCalleeParamsFn) { throw new Error('missing/invalid getCalleeParamsFn'); }
|
|
1372
|
+
return this.#getCalleeParamsFn();
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
mayBlock() { return this.isAsync() || this.isResolvedState() }
|
|
1376
|
+
|
|
1377
|
+
mayEnter(task) {
|
|
1378
|
+
const cstate = getOrCreateAsyncState(this.#componentIdx);
|
|
1379
|
+
if (cstate.hasBackpressure()) {
|
|
1380
|
+
_debugLog('[AsyncTask#mayEnter()] disallowed due to backpressure', { taskID: this.#id });
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
if (!cstate.callingSyncImport()) {
|
|
1384
|
+
_debugLog('[AsyncTask#mayEnter()] disallowed due to sync import call', { taskID: this.#id });
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
const callingSyncExportWithSyncPending = cstate.callingSyncExport && !task.isAsync;
|
|
1388
|
+
if (!callingSyncExportWithSyncPending) {
|
|
1389
|
+
_debugLog('[AsyncTask#mayEnter()] disallowed due to sync export w/ sync pending', { taskID: this.#id });
|
|
1390
|
+
return false;
|
|
1391
|
+
}
|
|
1392
|
+
return true;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
enterSync() {
|
|
1396
|
+
if (this.needsExclusiveLock()) {
|
|
1397
|
+
const cstate = getOrCreateAsyncState(this.#componentIdx);
|
|
1398
|
+
cstate.exclusiveLock();
|
|
1399
|
+
}
|
|
1400
|
+
return true;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
async enter(opts) {
|
|
1404
|
+
_debugLog('[AsyncTask#enter()] args', {
|
|
1405
|
+
taskID: this.#id,
|
|
1406
|
+
componentIdx: this.#componentIdx,
|
|
1407
|
+
subtaskID: this.getParentSubtask()?.id(),
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
if (this.#entered) {
|
|
1411
|
+
throw new Error(`task with ID [${this.#id}] should not be entered twice`);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
const cstate = getOrCreateAsyncState(this.#componentIdx);
|
|
1415
|
+
|
|
1416
|
+
// If a task is either synchronous or host-provided (e.g. a host import, whether sync or async)
|
|
1417
|
+
// then we can avoid component-relevant tracking and immediately enter
|
|
1418
|
+
if (this.isSync() || opts?.isHost) {
|
|
1419
|
+
this.#entered = true;
|
|
1420
|
+
|
|
1421
|
+
// TODO(breaking): remove once manually-spccifying async fns is removed
|
|
1422
|
+
// It is currently possible for an actually sync export to be specified
|
|
1423
|
+
// as async via JSPI
|
|
1424
|
+
if (this.#isManualAsync) {
|
|
1425
|
+
if (this.needsExclusiveLock()) { cstate.exclusiveLock(); }
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
return this.#entered;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
if (cstate.hasBackpressure()) {
|
|
1432
|
+
cstate.addBackpressureWaiter();
|
|
1433
|
+
|
|
1434
|
+
const result = await this.waitUntil({
|
|
1435
|
+
readyFn: () => !cstate.hasBackpressure(),
|
|
1436
|
+
cancellable: true,
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
cstate.removeBackpressureWaiter();
|
|
1440
|
+
|
|
1441
|
+
if (result === AsyncTask.BlockResult.CANCELLED) {
|
|
1442
|
+
this.cancel();
|
|
1443
|
+
return false;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
if (this.needsExclusiveLock()) { cstate.exclusiveLock(); }
|
|
1448
|
+
|
|
1449
|
+
this.#entered = true;
|
|
1450
|
+
return this.#entered;
|
|
852
1451
|
}
|
|
853
|
-
return true;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
async enter() {
|
|
857
|
-
_debugLog('[AsyncTask#enter()] args', { taskID: this.#id });
|
|
858
|
-
const cstate = getOrCreateAsyncState(this.#componentIdx);
|
|
859
1452
|
|
|
860
|
-
|
|
1453
|
+
isRunningState() { return this.#state !== AsyncTask.State.RESOLVED; }
|
|
1454
|
+
isResolvedState() { return this.#state === AsyncTask.State.RESOLVED; }
|
|
1455
|
+
isResolved() { return this.#state === AsyncTask.State.RESOLVED; }
|
|
861
1456
|
|
|
862
|
-
|
|
863
|
-
|
|
1457
|
+
async waitUntil(opts) {
|
|
1458
|
+
const { readyFn, waitableSetRep, cancellable } = opts;
|
|
1459
|
+
_debugLog('[AsyncTask#waitUntil()] args', { taskID: this.#id, waitableSetRep, cancellable });
|
|
864
1460
|
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
cancellable: true,
|
|
868
|
-
});
|
|
1461
|
+
const state = getOrCreateAsyncState(this.#componentIdx);
|
|
1462
|
+
const wset = state.handles.get(waitableSetRep);
|
|
869
1463
|
|
|
870
|
-
|
|
1464
|
+
let event;
|
|
871
1465
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1466
|
+
wset.incrementNumWaiting();
|
|
1467
|
+
|
|
1468
|
+
const keepGoing = await this.suspendUntil({
|
|
1469
|
+
readyFn: () => {
|
|
1470
|
+
const hasPendingEvent = wset.hasPendingEvent();
|
|
1471
|
+
const ready = readyFn();
|
|
1472
|
+
return ready && hasPendingEvent;
|
|
1473
|
+
},
|
|
1474
|
+
cancellable,
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
if (keepGoing) {
|
|
1478
|
+
event = wset.getPendingEvent();
|
|
1479
|
+
} else {
|
|
1480
|
+
event = {
|
|
1481
|
+
code: ASYNC_EVENT_CODE.TASK_CANCELLED,
|
|
1482
|
+
payload0: 0,
|
|
1483
|
+
payload1: 0,
|
|
1484
|
+
};
|
|
875
1485
|
}
|
|
1486
|
+
|
|
1487
|
+
wset.decrementNumWaiting();
|
|
1488
|
+
|
|
1489
|
+
return event;
|
|
876
1490
|
}
|
|
877
1491
|
|
|
878
|
-
|
|
1492
|
+
async yieldUntil(opts) {
|
|
1493
|
+
const { readyFn, cancellable } = opts;
|
|
1494
|
+
_debugLog('[AsyncTask#yieldUntil()] args', { taskID: this.#id, cancellable });
|
|
1495
|
+
|
|
1496
|
+
const keepGoing = await this.suspendUntil({ readyFn, cancellable });
|
|
1497
|
+
if (keepGoing) {
|
|
1498
|
+
return {
|
|
1499
|
+
code: ASYNC_EVENT_CODE.NONE,
|
|
1500
|
+
payload0: 0,
|
|
1501
|
+
payload1: 0,
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
return {
|
|
1506
|
+
code: ASYNC_EVENT_CODE.TASK_CANCELLED,
|
|
1507
|
+
payload0: 0,
|
|
1508
|
+
payload1: 0,
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
879
1511
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1512
|
+
async suspendUntil(opts) {
|
|
1513
|
+
const { cancellable, readyFn } = opts;
|
|
1514
|
+
_debugLog('[AsyncTask#suspendUntil()] args', { cancellable });
|
|
1515
|
+
|
|
1516
|
+
const pendingCancelled = this.deliverPendingCancel({ cancellable });
|
|
1517
|
+
if (pendingCancelled) { return false; }
|
|
1518
|
+
|
|
1519
|
+
const completed = await this.immediateSuspendUntil({ readyFn, cancellable });
|
|
1520
|
+
return completed;
|
|
1521
|
+
}
|
|
890
1522
|
|
|
891
|
-
|
|
892
|
-
|
|
1523
|
+
// TODO(threads): equivalent to thread.suspend_until()
|
|
1524
|
+
async immediateSuspendUntil(opts) {
|
|
1525
|
+
const { cancellable, readyFn } = opts;
|
|
1526
|
+
_debugLog('[AsyncTask#immediateSuspendUntil()] args', { cancellable, readyFn });
|
|
1527
|
+
|
|
1528
|
+
const ready = readyFn();
|
|
1529
|
+
if (ready && ASYNC_DETERMINISM === 'random') {
|
|
1530
|
+
const coinFlip = _coinFlip();
|
|
1531
|
+
if (coinFlip) { return true }
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
const keepGoing = await this.immediateSuspend({ cancellable, readyFn });
|
|
1535
|
+
return keepGoing;
|
|
1536
|
+
}
|
|
893
1537
|
|
|
894
|
-
|
|
1538
|
+
async immediateSuspend(opts) { // NOTE: equivalent to thread.suspend()
|
|
1539
|
+
// TODO(threads): store readyFn on the thread
|
|
1540
|
+
const { cancellable, readyFn } = opts;
|
|
1541
|
+
_debugLog('[AsyncTask#immediateSuspend()] args', { cancellable, readyFn });
|
|
895
1542
|
|
|
896
|
-
|
|
1543
|
+
const pendingCancelled = this.deliverPendingCancel({ cancellable });
|
|
1544
|
+
if (pendingCancelled) { return false; }
|
|
897
1545
|
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
1546
|
+
const cstate = getOrCreateAsyncState(this.#componentIdx);
|
|
1547
|
+
const keepGoing = await cstate.suspendTask({ task: this, readyFn });
|
|
1548
|
+
return keepGoing;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
deliverPendingCancel(opts) {
|
|
1552
|
+
const { cancellable } = opts;
|
|
1553
|
+
_debugLog('[AsyncTask#deliverPendingCancel()] args', { cancellable });
|
|
905
1554
|
|
|
906
|
-
if (
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
event = {
|
|
910
|
-
code: ASYNC_EVENT_CODE.TASK_CANCELLED,
|
|
911
|
-
index: 0,
|
|
912
|
-
result: 0,
|
|
913
|
-
};
|
|
1555
|
+
if (cancellable && this.#state === AsyncTask.State.PENDING_CANCEL) {
|
|
1556
|
+
this.#state = AsyncTask.State.CANCEL_DELIVERED;
|
|
1557
|
+
return true;
|
|
914
1558
|
}
|
|
915
1559
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
isCancelled() { return this.cancelled }
|
|
1564
|
+
|
|
1565
|
+
cancel(args) {
|
|
1566
|
+
_debugLog('[AsyncTask#cancel()] args', { });
|
|
1567
|
+
if (this.taskState() !== AsyncTask.State.CANCEL_DELIVERED) {
|
|
1568
|
+
throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] invalid task state [${this.taskState()}] for cancellation`);
|
|
1569
|
+
}
|
|
1570
|
+
if (this.borrowedHandles.length > 0) { throw new Error('task still has borrow handles'); }
|
|
1571
|
+
this.cancelled = true;
|
|
1572
|
+
this.onResolve(args?.error ?? new Error('task cancelled'));
|
|
1573
|
+
this.#state = AsyncTask.State.RESOLVED;
|
|
919
1574
|
}
|
|
920
1575
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1576
|
+
onResolve(taskValue) {
|
|
1577
|
+
const handlers = this.#onResolveHandlers;
|
|
1578
|
+
this.#onResolveHandlers = [];
|
|
1579
|
+
for (const f of handlers) {
|
|
1580
|
+
try {
|
|
1581
|
+
// TODO(fix): resolve handlers getting called a ton?
|
|
1582
|
+
f(taskValue);
|
|
1583
|
+
} catch (err) {
|
|
1584
|
+
_debugLog("[AsyncTask#onResolve] error during task resolve handler", err);
|
|
1585
|
+
throw err;
|
|
1586
|
+
}
|
|
925
1587
|
}
|
|
926
1588
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1589
|
+
if (this.#parentSubtask) {
|
|
1590
|
+
const meta = this.#parentSubtask.getCallMetadata();
|
|
1591
|
+
// Run the rturn fn if it has not already been called -- this *should* have happened in
|
|
1592
|
+
// `task.return`, but some paths do not go through task.return (e.g. async lower of sync fn
|
|
1593
|
+
// which goes through prepare + async-start-call)
|
|
1594
|
+
if (meta.returnFn && !meta.returnFnCalled) {
|
|
1595
|
+
_debugLog('[AsyncTask#onResolve()] running returnFn', {
|
|
1596
|
+
componentIdx: this.#componentIdx,
|
|
1597
|
+
taskID: this.#id,
|
|
1598
|
+
subtaskID: this.#parentSubtask.id(),
|
|
1599
|
+
});
|
|
1600
|
+
const memory = meta.getMemoryFn();
|
|
1601
|
+
meta.returnFn.apply(null, [taskValue, meta.resultPtr]);
|
|
1602
|
+
meta.returnFnCalled = true;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
937
1605
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1606
|
+
if (this.#postReturnFn) {
|
|
1607
|
+
_debugLog('[AsyncTask#onResolve()] running post return ', {
|
|
1608
|
+
componentIdx: this.#componentIdx,
|
|
1609
|
+
taskID: this.#id,
|
|
1610
|
+
});
|
|
1611
|
+
try {
|
|
1612
|
+
this.#postReturnFn(taskValue);
|
|
1613
|
+
} catch (err) {
|
|
1614
|
+
_debugLog("[AsyncTask#onResolve] error during task resolve handler", err);
|
|
1615
|
+
throw err;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
941
1618
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
return AsyncTask.BlockResult.NOT_CANCELLED;
|
|
945
|
-
} catch (err) {
|
|
946
|
-
// rejection means task cancellation
|
|
947
|
-
return AsyncTask.BlockResult.CANCELLED;
|
|
1619
|
+
if (this.#parentSubtask) {
|
|
1620
|
+
this.#parentSubtask.onResolve(taskValue);
|
|
948
1621
|
}
|
|
949
1622
|
}
|
|
950
1623
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
// - only once per subtask
|
|
960
|
-
// - do not wait on the scheduler
|
|
961
|
-
// - control flow should go to the subtask (only once)
|
|
962
|
-
// - Once subtask blocks/resolves, reqlinquishControl() will tehn resolve request_cancel_end (without scheduler lock release)
|
|
963
|
-
// - control flow goes back to request_cancel
|
|
964
|
-
//
|
|
965
|
-
// Subtask cancellation should work similarly to an async import call -- runs sync up until
|
|
966
|
-
// the subtask blocks or resolves
|
|
967
|
-
//
|
|
968
|
-
throw new Error('AsyncTask#asyncOnBlock() not yet implemented');
|
|
1624
|
+
registerOnResolveHandler(f) {
|
|
1625
|
+
this.#onResolveHandlers.push(f);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
isRejected() { return this.#rejected; }
|
|
1629
|
+
|
|
1630
|
+
setErrored(err) {
|
|
1631
|
+
this.#errored = err;
|
|
969
1632
|
}
|
|
970
1633
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1634
|
+
reject(taskErr) {
|
|
1635
|
+
_debugLog('[AsyncTask#reject()] args', {
|
|
1636
|
+
componentIdx: this.#componentIdx,
|
|
1637
|
+
taskID: this.#id,
|
|
1638
|
+
parentSubtask: this.#parentSubtask,
|
|
1639
|
+
parentSubtaskID: this.#parentSubtask?.id(),
|
|
1640
|
+
entryFnName: this.entryFnName(),
|
|
1641
|
+
callbackFnName: this.#callbackFnName,
|
|
1642
|
+
errMsg: taskErr.message,
|
|
1643
|
+
});
|
|
974
1644
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
index: 0,
|
|
980
|
-
result: 0,
|
|
981
|
-
};
|
|
1645
|
+
if (this.isResolvedState() || this.#rejected) { return; }
|
|
1646
|
+
|
|
1647
|
+
for (const subtask of this.#subtasks) {
|
|
1648
|
+
subtask.reject(taskErr);
|
|
982
1649
|
}
|
|
983
1650
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
async suspendUntil(opts) {
|
|
992
|
-
const { cancellable, readyFn } = opts;
|
|
993
|
-
_debugLog('[AsyncTask#suspendUntil()] args', { cancellable });
|
|
1651
|
+
this.#rejected = true;
|
|
1652
|
+
this.cancelRequested = true;
|
|
1653
|
+
this.#state = AsyncTask.State.PENDING_CANCEL;
|
|
1654
|
+
const cancelled = this.deliverPendingCancel({ cancellable: true });
|
|
1655
|
+
|
|
1656
|
+
// TODO: do cleanup here to reset the machinery so we can run again?
|
|
994
1657
|
|
|
995
|
-
const pendingCancelled = this.deliverPendingCancel({ cancellable });
|
|
996
|
-
if (pendingCancelled) { return false; }
|
|
997
1658
|
|
|
998
|
-
|
|
999
|
-
return completed;
|
|
1659
|
+
this.cancel({ error: taskErr });
|
|
1000
1660
|
}
|
|
1001
1661
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1662
|
+
resolve(results) {
|
|
1663
|
+
_debugLog('[AsyncTask#resolve()] args', {
|
|
1664
|
+
componentIdx: this.#componentIdx,
|
|
1665
|
+
taskID: this.#id,
|
|
1666
|
+
entryFnName: this.entryFnName(),
|
|
1667
|
+
callbackFnName: this.#callbackFnName,
|
|
1668
|
+
});
|
|
1006
1669
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
return true;
|
|
1670
|
+
if (this.#state === AsyncTask.State.RESOLVED) {
|
|
1671
|
+
throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] is already resolved (did you forget to wait for an import?)`);
|
|
1010
1672
|
}
|
|
1011
1673
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1674
|
+
if (this.borrowedHandles.length > 0) {
|
|
1675
|
+
throw new Error('task still has borrow handles');
|
|
1676
|
+
}
|
|
1014
1677
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
return keepGoing;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
deliverPendingCancel(opts) {
|
|
1038
|
-
const { cancellable } = opts;
|
|
1039
|
-
_debugLog('[AsyncTask#deliverPendingCancel()] args', { cancellable });
|
|
1040
|
-
|
|
1041
|
-
if (cancellable && this.#state === AsyncTask.State.PENDING_CANCEL) {
|
|
1042
|
-
this.#state = Task.State.CANCEL_DELIVERED;
|
|
1043
|
-
return true;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
return false;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
isCancelled() { return this.cancelled }
|
|
1050
|
-
|
|
1051
|
-
cancel() {
|
|
1052
|
-
_debugLog('[AsyncTask#cancel()] args', { });
|
|
1053
|
-
if (!this.taskState() !== AsyncTask.State.CANCEL_DELIVERED) {
|
|
1054
|
-
throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] invalid task state for cancellation`);
|
|
1055
|
-
}
|
|
1056
|
-
if (this.borrowedHandles.length > 0) { throw new Error('task still has borrow handles'); }
|
|
1057
|
-
this.cancelled = true;
|
|
1058
|
-
this.onResolve(new Error('cancelled'));
|
|
1059
|
-
this.#state = AsyncTask.State.RESOLVED;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
onResolve(taskValue) {
|
|
1063
|
-
for (const f of this.#onResolveHandlers) {
|
|
1064
|
-
try {
|
|
1065
|
-
f(taskValue);
|
|
1066
|
-
} catch (err) {
|
|
1067
|
-
console.error("error during task resolve handler", err);
|
|
1068
|
-
throw err;
|
|
1678
|
+
this.#state = AsyncTask.State.RESOLVED;
|
|
1679
|
+
|
|
1680
|
+
switch (results.length) {
|
|
1681
|
+
case 0:
|
|
1682
|
+
this.onResolve(undefined);
|
|
1683
|
+
break;
|
|
1684
|
+
case 1:
|
|
1685
|
+
this.onResolve(results[0]);
|
|
1686
|
+
break;
|
|
1687
|
+
default:
|
|
1688
|
+
_debugLog('[AsyncTask#resolve()] unexpected number of results', {
|
|
1689
|
+
componentIdx: this.#componentIdx,
|
|
1690
|
+
results,
|
|
1691
|
+
taskID: this.#id,
|
|
1692
|
+
subtaskID: this.#parentSubtask?.id(),
|
|
1693
|
+
entryFnName: this.#entryFnName,
|
|
1694
|
+
callbackFnName: this.#callbackFnName,
|
|
1695
|
+
});
|
|
1696
|
+
throw new Error('unexpected number of results');
|
|
1069
1697
|
}
|
|
1070
1698
|
}
|
|
1071
1699
|
|
|
1072
|
-
|
|
1073
|
-
_debugLog('[AsyncTask#
|
|
1700
|
+
exit() {
|
|
1701
|
+
_debugLog('[AsyncTask#exit()]', {
|
|
1074
1702
|
componentIdx: this.#componentIdx,
|
|
1075
1703
|
taskID: this.#id,
|
|
1076
1704
|
});
|
|
1077
|
-
|
|
1705
|
+
|
|
1706
|
+
if (this.#exited) { throw new Error("task has already exited"); }
|
|
1707
|
+
|
|
1708
|
+
if (this.#state !== AsyncTask.State.RESOLVED) {
|
|
1709
|
+
// TODO(fix): only fused, manually specified post returns seem to break this invariant,
|
|
1710
|
+
// as the TaskReturn trampoline is not activated it seems.
|
|
1711
|
+
//
|
|
1712
|
+
// see: test/p3/ported/wasmtime/component-async/post-return.js
|
|
1713
|
+
//
|
|
1714
|
+
// We *should* be able to upgrade this to be more strict and throw at some point,
|
|
1715
|
+
// which may involve rewriting the upstream test to surface task return manually somehow.
|
|
1716
|
+
//
|
|
1717
|
+
//throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] exited without resolution`);
|
|
1718
|
+
_debugLog('[AsyncTask#exit()] task exited without resolution', {
|
|
1719
|
+
componentIdx: this.#componentIdx,
|
|
1720
|
+
taskID: this.#id,
|
|
1721
|
+
subtask: this.getParentSubtask(),
|
|
1722
|
+
subtaskID: this.getParentSubtask()?.id(),
|
|
1723
|
+
});
|
|
1724
|
+
this.#state = AsyncTask.State.RESOLVED;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
if (this.borrowedHandles > 0) {
|
|
1728
|
+
throw new Error('task [${this.#id}] exited without clearing borrowed handles');
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
const state = getOrCreateAsyncState(this.#componentIdx);
|
|
1732
|
+
if (!state) { throw new Error('missing async state for component [' + this.#componentIdx + ']'); }
|
|
1733
|
+
|
|
1734
|
+
// Exempt the host from exclusive lock check
|
|
1735
|
+
if (this.#componentIdx !== -1 && this.needsExclusiveLock() && !state.isExclusivelyLocked()) {
|
|
1736
|
+
throw new Error(`task [${this.#id}] exit: component [${this.#componentIdx}] should have been exclusively locked`);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
state.exclusiveRelease();
|
|
1740
|
+
|
|
1741
|
+
for (const f of this.#onExitHandlers) {
|
|
1742
|
+
try {
|
|
1743
|
+
f();
|
|
1744
|
+
} catch (err) {
|
|
1745
|
+
console.error("error during task exit handler", err);
|
|
1746
|
+
throw err;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
this.#exited = true;
|
|
1751
|
+
clearCurrentTask(this.#componentIdx, this.id());
|
|
1078
1752
|
}
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
registerOnResolveHandler(f) {
|
|
1082
|
-
this.#onResolveHandlers.push(f);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
resolve(results) {
|
|
1086
|
-
_debugLog('[AsyncTask#resolve()] args', {
|
|
1087
|
-
results,
|
|
1088
|
-
componentIdx: this.#componentIdx,
|
|
1089
|
-
taskID: this.#id,
|
|
1090
|
-
});
|
|
1091
1753
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1754
|
+
needsExclusiveLock() {
|
|
1755
|
+
return !this.#isAsync || this.hasCallback();
|
|
1094
1756
|
}
|
|
1095
|
-
if (this.borrowedHandles.length > 0) { throw new Error('task still has borrow handles'); }
|
|
1096
|
-
switch (results.length) {
|
|
1097
|
-
case 0:
|
|
1098
|
-
this.onResolve(undefined);
|
|
1099
|
-
break;
|
|
1100
|
-
case 1:
|
|
1101
|
-
this.onResolve(results[0]);
|
|
1102
|
-
break;
|
|
1103
|
-
default:
|
|
1104
|
-
throw new Error('unexpected number of results');
|
|
1105
|
-
}
|
|
1106
|
-
this.#state = AsyncTask.State.RESOLVED;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
exit() {
|
|
1110
|
-
_debugLog('[AsyncTask#exit()] args', { });
|
|
1111
1757
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
//throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] exited without resolution`);
|
|
1123
|
-
_debugLog('[AsyncTask#exit()] task exited without resolution', {
|
|
1758
|
+
createSubtask(args) {
|
|
1759
|
+
_debugLog('[AsyncTask#createSubtask()] args', args);
|
|
1760
|
+
const { componentIdx, childTask, callMetadata, fnName, isAsync, isManualAsync } = args;
|
|
1761
|
+
|
|
1762
|
+
const cstate = getOrCreateAsyncState(this.#componentIdx);
|
|
1763
|
+
if (!cstate) {
|
|
1764
|
+
throw new Error(`invalid/missing async state for component idx [${componentIdx}]`);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
const waitable = new Waitable({
|
|
1124
1768
|
componentIdx: this.#componentIdx,
|
|
1125
|
-
|
|
1126
|
-
subtask: this.getParentSubtask(),
|
|
1127
|
-
subtaskID: this.getParentSubtask()?.id(),
|
|
1769
|
+
target: `subtask (internal ID [${this.#id}])`,
|
|
1128
1770
|
});
|
|
1129
|
-
|
|
1771
|
+
|
|
1772
|
+
const newSubtask = new AsyncSubtask({
|
|
1773
|
+
componentIdx,
|
|
1774
|
+
childTask,
|
|
1775
|
+
parentTask: this,
|
|
1776
|
+
callMetadata,
|
|
1777
|
+
isAsync,
|
|
1778
|
+
isManualAsync,
|
|
1779
|
+
fnName,
|
|
1780
|
+
waitable,
|
|
1781
|
+
});
|
|
1782
|
+
this.#subtasks.push(newSubtask);
|
|
1783
|
+
newSubtask.setTarget(`subtask (internal ID [${newSubtask.id()}], waitable [${waitable.idx()}], component [${componentIdx}])`);
|
|
1784
|
+
waitable.setIdx(cstate.handles.insert(newSubtask));
|
|
1785
|
+
waitable.setTarget(`waitable for subtask (waitable id [${waitable.idx()}], subtask internal ID [${newSubtask.id()}])`);
|
|
1786
|
+
|
|
1787
|
+
return newSubtask;
|
|
1130
1788
|
}
|
|
1131
1789
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1790
|
+
getLatestSubtask() {
|
|
1791
|
+
return this.#subtasks.at(-1);
|
|
1134
1792
|
}
|
|
1135
1793
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
throw new Error('sync task must be run from components known to be in a sync export call');
|
|
1794
|
+
getSubtaskByWaitableRep(rep) {
|
|
1795
|
+
if (rep === undefined) { throw new TypeError('missing rep'); }
|
|
1796
|
+
return this.#subtasks.find(s => s.waitableRep() === rep);
|
|
1140
1797
|
}
|
|
1141
|
-
state.inSyncExportCall = false;
|
|
1142
1798
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1799
|
+
currentSubtask() {
|
|
1800
|
+
_debugLog('[AsyncTask#currentSubtask()]');
|
|
1801
|
+
if (this.#subtasks.length === 0) { return undefined; }
|
|
1802
|
+
return this.#subtasks.at(-1);
|
|
1145
1803
|
}
|
|
1146
1804
|
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
createSubtask(args) {
|
|
1153
|
-
_debugLog('[AsyncTask#createSubtask()] args', args);
|
|
1154
|
-
const { componentIdx, childTask, callMetadata } = args;
|
|
1155
|
-
const newSubtask = new AsyncSubtask({
|
|
1156
|
-
componentIdx,
|
|
1157
|
-
childTask,
|
|
1158
|
-
parentTask: this,
|
|
1159
|
-
callMetadata,
|
|
1160
|
-
});
|
|
1161
|
-
this.#subtasks.push(newSubtask);
|
|
1162
|
-
return newSubtask;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
getLatestSubtask() { return this.#subtasks.at(-1); }
|
|
1166
|
-
|
|
1167
|
-
currentSubtask() {
|
|
1168
|
-
_debugLog('[AsyncTask#currentSubtask()]');
|
|
1169
|
-
if (this.#subtasks.length === 0) { return undefined; }
|
|
1170
|
-
return this.#subtasks.at(-1);
|
|
1805
|
+
removeSubtask(subtask) {
|
|
1806
|
+
if (this.#subtasks.length === 0) { throw new Error('cannot end current subtask: no current subtask'); }
|
|
1807
|
+
this.#subtasks = this.#subtasks.filter(t => t !== subtask);
|
|
1808
|
+
return subtask;
|
|
1809
|
+
}
|
|
1171
1810
|
}
|
|
1172
1811
|
|
|
1173
|
-
|
|
1174
|
-
_debugLog('[AsyncTask#endCurrentSubtask()]');
|
|
1175
|
-
if (this.#subtasks.length === 0) { throw new Error('cannot end current subtask: no current subtask'); }
|
|
1176
|
-
const subtask = this.#subtasks.pop();
|
|
1177
|
-
subtask.drop();
|
|
1178
|
-
return subtask;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1812
|
+
const STREAMS = new RepTable({ target: 'global stream map' });
|
|
1181
1813
|
const ASYNC_STATE = new Map();
|
|
1182
1814
|
|
|
1183
1815
|
function getOrCreateAsyncState(componentIdx, init) {
|
|
@@ -1198,7 +1830,6 @@ class ComponentAsyncState {
|
|
|
1198
1830
|
#parkedTasks = new Map();
|
|
1199
1831
|
#suspendedTasksByTaskID = new Map();
|
|
1200
1832
|
#suspendedTaskIDs = [];
|
|
1201
|
-
#pendingTasks = [];
|
|
1202
1833
|
#errored = null;
|
|
1203
1834
|
|
|
1204
1835
|
#backpressure = 0;
|
|
@@ -1207,24 +1838,23 @@ class ComponentAsyncState {
|
|
|
1207
1838
|
#handlerMap = new Map();
|
|
1208
1839
|
#nextHandlerID = 0n;
|
|
1209
1840
|
|
|
1210
|
-
|
|
1841
|
+
#tickLoop = null;
|
|
1842
|
+
#tickLoopInterval = null;
|
|
1843
|
+
|
|
1844
|
+
#onExclusiveReleaseHandlers = [];
|
|
1211
1845
|
|
|
1212
|
-
|
|
1846
|
+
mayLeave = true;
|
|
1213
1847
|
|
|
1214
|
-
|
|
1215
|
-
waitables;
|
|
1848
|
+
handles;
|
|
1216
1849
|
subtasks;
|
|
1217
1850
|
|
|
1218
1851
|
constructor(args) {
|
|
1219
1852
|
this.#componentIdx = args.componentIdx;
|
|
1220
|
-
this.
|
|
1221
|
-
this.waitables = new RepTable({ target: `component [${this.#componentIdx}] waitables` });
|
|
1853
|
+
this.handles = new RepTable({ target: `component [${this.#componentIdx}] handles (waitable objects)` });
|
|
1222
1854
|
this.subtasks = new RepTable({ target: `component [${this.#componentIdx}] subtasks` });
|
|
1223
|
-
this.#streams = new Map();
|
|
1224
1855
|
};
|
|
1225
1856
|
|
|
1226
1857
|
componentIdx() { return this.#componentIdx; }
|
|
1227
|
-
streams() { return this.#streams; }
|
|
1228
1858
|
|
|
1229
1859
|
errored() { return this.#errored !== null; }
|
|
1230
1860
|
setErrored(err) {
|
|
@@ -1257,15 +1887,34 @@ class ComponentAsyncState {
|
|
|
1257
1887
|
await this.#syncImportWait.promise;
|
|
1258
1888
|
}
|
|
1259
1889
|
|
|
1260
|
-
setBackpressure(v) {
|
|
1261
|
-
|
|
1890
|
+
setBackpressure(v) {
|
|
1891
|
+
this.#backpressure = v;
|
|
1892
|
+
return this.#backpressure
|
|
1893
|
+
}
|
|
1894
|
+
getBackpressure() { return this.#backpressure; }
|
|
1895
|
+
|
|
1262
1896
|
incrementBackpressure() {
|
|
1897
|
+
const current = this.#backpressure;
|
|
1898
|
+
if (current < 0 || current > 2**16) {
|
|
1899
|
+
throw new Error(`invalid current backpressure value [${current}]`);
|
|
1900
|
+
}
|
|
1263
1901
|
const newValue = this.getBackpressure() + 1;
|
|
1264
|
-
if (newValue
|
|
1265
|
-
|
|
1902
|
+
if (newValue >= 2**16) {
|
|
1903
|
+
throw new Error(`invalid new backpressure value [${newValue}], overflow`);
|
|
1904
|
+
}
|
|
1905
|
+
return this.setBackpressure(newValue);
|
|
1266
1906
|
}
|
|
1907
|
+
|
|
1267
1908
|
decrementBackpressure() {
|
|
1268
|
-
|
|
1909
|
+
const current = this.#backpressure;
|
|
1910
|
+
if (current < 0 || current > 2**16) {
|
|
1911
|
+
throw new Error(`invalid current backpressure value [${current}]`);
|
|
1912
|
+
}
|
|
1913
|
+
const newValue = Math.max(0, current - 1);
|
|
1914
|
+
if (newValue < 0) {
|
|
1915
|
+
throw new Error(`invalid new backpressure value [${newValue}], underflow`);
|
|
1916
|
+
}
|
|
1917
|
+
return this.setBackpressure(newValue);
|
|
1269
1918
|
}
|
|
1270
1919
|
hasBackpressure() { return this.#backpressure > 0; }
|
|
1271
1920
|
|
|
@@ -1330,55 +1979,44 @@ class ComponentAsyncState {
|
|
|
1330
1979
|
}
|
|
1331
1980
|
}
|
|
1332
1981
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
const { awaitable, task } = args;
|
|
1337
|
-
|
|
1338
|
-
let taskList = this.#parkedTasks.get(awaitable.id());
|
|
1339
|
-
if (!taskList) {
|
|
1340
|
-
taskList = [];
|
|
1341
|
-
this.#parkedTasks.set(awaitable.id(), taskList);
|
|
1342
|
-
}
|
|
1343
|
-
taskList.push(task);
|
|
1344
|
-
|
|
1345
|
-
this.wakeNextTaskForAwaitable(awaitable);
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
wakeNextTaskForAwaitable(awaitable) {
|
|
1349
|
-
if (!awaitable) { throw new TypeError('missing awaitable when waking next task'); }
|
|
1350
|
-
const awaitableID = awaitable.id();
|
|
1351
|
-
|
|
1352
|
-
const taskList = this.#parkedTasks.get(awaitableID);
|
|
1353
|
-
if (!taskList || taskList.length === 0) {
|
|
1354
|
-
_debugLog('[ComponentAsyncState] no tasks waiting for awaitable', { awaitableID: awaitable.id() });
|
|
1355
|
-
return;
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
let task = taskList.shift(); // todo(perf)
|
|
1359
|
-
if (!task) { throw new Error('no task in parked list despite previous check'); }
|
|
1360
|
-
|
|
1361
|
-
if (!task.awaitableResume) {
|
|
1362
|
-
throw new Error('task ready due to awaitable is missing resume', { taskID: task.id(), awaitableID });
|
|
1363
|
-
}
|
|
1364
|
-
task.awaitableResume();
|
|
1982
|
+
isExclusivelyLocked() { return this.#locked === true; }
|
|
1983
|
+
setLocked(locked) {
|
|
1984
|
+
this.#locked = locked;
|
|
1365
1985
|
}
|
|
1366
1986
|
|
|
1367
|
-
// TODO: we might want to check for pre-locked status here
|
|
1987
|
+
// TODO(fix): we might want to check for pre-locked status here, we should be deterministically
|
|
1988
|
+
// going from locked -> unlocked and vice versa
|
|
1368
1989
|
exclusiveLock() {
|
|
1369
|
-
|
|
1990
|
+
_debugLog('[ComponentAsyncState#exclusiveLock()]', {
|
|
1991
|
+
locked: this.#locked,
|
|
1992
|
+
componentIdx: this.#componentIdx,
|
|
1993
|
+
});
|
|
1994
|
+
this.setLocked(true);
|
|
1370
1995
|
}
|
|
1371
1996
|
|
|
1372
1997
|
exclusiveRelease() {
|
|
1373
|
-
_debugLog('[ComponentAsyncState#exclusiveRelease()]
|
|
1998
|
+
_debugLog('[ComponentAsyncState#exclusiveRelease()] args', {
|
|
1374
1999
|
locked: this.#locked,
|
|
1375
2000
|
componentIdx: this.#componentIdx,
|
|
1376
2001
|
});
|
|
1377
|
-
|
|
1378
|
-
|
|
2002
|
+
this.setLocked(false);
|
|
2003
|
+
|
|
2004
|
+
this.#onExclusiveReleaseHandlers = this.#onExclusiveReleaseHandlers.filter(v => !!v);
|
|
2005
|
+
for (const [idx, f] of this.#onExclusiveReleaseHandlers.entries()) {
|
|
2006
|
+
try {
|
|
2007
|
+
this.#onExclusiveReleaseHandlers[idx] = null;
|
|
2008
|
+
f();
|
|
2009
|
+
} catch (err) {
|
|
2010
|
+
_debugLog("error while executing handler for next exclusive release", err);
|
|
2011
|
+
throw err;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
1379
2014
|
}
|
|
1380
2015
|
|
|
1381
|
-
|
|
2016
|
+
onNextExclusiveRelease(fn) {
|
|
2017
|
+
_debugLog('[ComponentAsyncState#()onNextExclusiveRelease] registering');
|
|
2018
|
+
this.#onExclusiveReleaseHandlers.push(fn);
|
|
2019
|
+
}
|
|
1382
2020
|
|
|
1383
2021
|
#getSuspendedTaskMeta(taskID) {
|
|
1384
2022
|
return this.#suspendedTasksByTaskID.get(taskID);
|
|
@@ -1403,17 +2041,22 @@ class ComponentAsyncState {
|
|
|
1403
2041
|
}
|
|
1404
2042
|
}
|
|
1405
2043
|
|
|
2044
|
+
// TODO(threads): readyFn is normally on the thread
|
|
1406
2045
|
suspendTask(args) {
|
|
1407
|
-
// TODO(threads): readyFn is normally on the thread
|
|
1408
2046
|
const { task, readyFn } = args;
|
|
1409
2047
|
const taskID = task.id();
|
|
1410
|
-
_debugLog('[ComponentAsyncState#suspendTask()]', {
|
|
2048
|
+
_debugLog('[ComponentAsyncState#suspendTask()]', {
|
|
2049
|
+
taskID,
|
|
2050
|
+
componentIdx: this.#componentIdx,
|
|
2051
|
+
taskEntryFnName: task.entryFnName(),
|
|
2052
|
+
subtask: task.getParentSubtask(),
|
|
2053
|
+
});
|
|
1411
2054
|
|
|
1412
2055
|
if (this.#getSuspendedTaskMeta(taskID)) {
|
|
1413
|
-
throw new Error(
|
|
2056
|
+
throw new Error(`task [${taskID}] already suspended`);
|
|
1414
2057
|
}
|
|
1415
2058
|
|
|
1416
|
-
const { promise, resolve } =
|
|
2059
|
+
const { promise, resolve, reject } = promiseWithResolvers();
|
|
1417
2060
|
this.#addSuspendedTaskMeta({
|
|
1418
2061
|
task,
|
|
1419
2062
|
taskID,
|
|
@@ -1425,6 +2068,8 @@ class ComponentAsyncState {
|
|
|
1425
2068
|
},
|
|
1426
2069
|
});
|
|
1427
2070
|
|
|
2071
|
+
this.runTickLoop();
|
|
2072
|
+
|
|
1428
2073
|
return promise;
|
|
1429
2074
|
}
|
|
1430
2075
|
|
|
@@ -1435,8 +2080,22 @@ class ComponentAsyncState {
|
|
|
1435
2080
|
meta.resume();
|
|
1436
2081
|
}
|
|
1437
2082
|
|
|
2083
|
+
async runTickLoop() {
|
|
2084
|
+
if (this.#tickLoop !== null) { return; }
|
|
2085
|
+
this.#tickLoop = 1;
|
|
2086
|
+
setTimeout(async () => {
|
|
2087
|
+
let done = this.tick();
|
|
2088
|
+
while (!done) {
|
|
2089
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
2090
|
+
done = this.tick();
|
|
2091
|
+
}
|
|
2092
|
+
this.#tickLoop = null;
|
|
2093
|
+
}, 10);
|
|
2094
|
+
}
|
|
2095
|
+
|
|
1438
2096
|
tick() {
|
|
1439
|
-
_debugLog('[ComponentAsyncState#tick()]', { suspendedTaskIDs: this.#suspendedTaskIDs });
|
|
2097
|
+
// _debugLog('[ComponentAsyncState#tick()]', { suspendedTaskIDs: this.#suspendedTaskIDs });
|
|
2098
|
+
|
|
1440
2099
|
const resumableTasks = this.#suspendedTaskIDs.filter(t => t !== null);
|
|
1441
2100
|
for (const taskID of resumableTasks) {
|
|
1442
2101
|
const meta = this.#suspendedTasksByTaskID.get(taskID);
|
|
@@ -1444,6 +2103,14 @@ class ComponentAsyncState {
|
|
|
1444
2103
|
throw new Error(`missing/invalid task despite ID [${taskID}] being present`);
|
|
1445
2104
|
}
|
|
1446
2105
|
|
|
2106
|
+
// If the task failed via any means, allow the task to resume because
|
|
2107
|
+
// it's been cancelled -- the callback should immediately exit as well
|
|
2108
|
+
if (meta.task.isRejected()) {
|
|
2109
|
+
_debugLog('[ComponentAsyncState#suspendTask()] detected task rejection, leaving early', { meta });
|
|
2110
|
+
this.resumeTaskByID(taskID);
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
1447
2114
|
const isReady = meta.readyFn();
|
|
1448
2115
|
if (!isReady) { continue; }
|
|
1449
2116
|
|
|
@@ -1453,22 +2120,37 @@ class ComponentAsyncState {
|
|
|
1453
2120
|
return this.#suspendedTaskIDs.filter(t => t !== null).length === 0;
|
|
1454
2121
|
}
|
|
1455
2122
|
|
|
1456
|
-
|
|
1457
|
-
this.#pendingTasks.push(task);
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
addStreamEnd(args) {
|
|
2123
|
+
addStreamEndToTable(args) {
|
|
1461
2124
|
_debugLog('[ComponentAsyncState#addStreamEnd()] args', args);
|
|
1462
2125
|
const { tableIdx, streamEnd } = args;
|
|
2126
|
+
if (typeof streamEnd === 'number') { throw new Error("INSERTING BAD STREAMEND"); }
|
|
1463
2127
|
|
|
1464
|
-
let
|
|
1465
|
-
if (!
|
|
1466
|
-
|
|
1467
|
-
this.#streams.set(tableIdx, tbl);
|
|
2128
|
+
let { table, componentIdx } = STREAM_TABLES[tableIdx];
|
|
2129
|
+
if (componentIdx === undefined || !table) {
|
|
2130
|
+
throw new Error(`invalid global stream table state for table [${tableIdx}]`);
|
|
1468
2131
|
}
|
|
1469
2132
|
|
|
1470
|
-
const
|
|
1471
|
-
|
|
2133
|
+
const handle = table.insert(streamEnd);
|
|
2134
|
+
streamEnd.setHandle(handle);
|
|
2135
|
+
streamEnd.setStreamTableIdx(tableIdx);
|
|
2136
|
+
|
|
2137
|
+
const cstate = getOrCreateAsyncState(componentIdx);
|
|
2138
|
+
const waitableIdx = cstate.handles.insert(streamEnd);
|
|
2139
|
+
streamEnd.setWaitableIdx(waitableIdx);
|
|
2140
|
+
|
|
2141
|
+
_debugLog('[ComponentAsyncState#addStreamEnd()] added stream end', {
|
|
2142
|
+
tableIdx,
|
|
2143
|
+
table,
|
|
2144
|
+
handle,
|
|
2145
|
+
streamEnd,
|
|
2146
|
+
destComponentIdx: componentIdx,
|
|
2147
|
+
});
|
|
2148
|
+
|
|
2149
|
+
return { handle, waitableIdx };
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
createWaitable(args) {
|
|
2153
|
+
return new Waitable({ target: args?.target, });
|
|
1472
2154
|
}
|
|
1473
2155
|
|
|
1474
2156
|
createStream(args) {
|
|
@@ -1477,63 +2159,143 @@ class ComponentAsyncState {
|
|
|
1477
2159
|
if (tableIdx === undefined) { throw new Error("missing table idx while adding stream"); }
|
|
1478
2160
|
if (elemMeta === undefined) { throw new Error("missing element metadata while adding stream"); }
|
|
1479
2161
|
|
|
1480
|
-
|
|
1481
|
-
if (!
|
|
1482
|
-
|
|
1483
|
-
this.#streams.set(tableIdx, tbl);
|
|
2162
|
+
const { table: localStreamTable, componentIdx } = STREAM_TABLES[tableIdx];
|
|
2163
|
+
if (!localStreamTable) {
|
|
2164
|
+
throw new Error(`missing global stream table lookup for table [${tableIdx}] while creating stream`);
|
|
1484
2165
|
}
|
|
2166
|
+
if (componentIdx !== this.#componentIdx) {
|
|
2167
|
+
throw new Error('component idx mismatch while creating stream');
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
const readWaitable = this.createWaitable();
|
|
2171
|
+
const writeWaitable = this.createWaitable();
|
|
1485
2172
|
|
|
1486
2173
|
const stream = new InternalStream({
|
|
1487
2174
|
tableIdx,
|
|
1488
2175
|
componentIdx: this.#componentIdx,
|
|
1489
2176
|
elemMeta,
|
|
2177
|
+
readWaitable,
|
|
2178
|
+
writeWaitable,
|
|
1490
2179
|
});
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
const
|
|
1494
|
-
|
|
2180
|
+
stream.setGlobalStreamMapRep(STREAMS.insert(stream));
|
|
2181
|
+
|
|
2182
|
+
const writeEnd = stream.writeEnd();
|
|
2183
|
+
writeEnd.setWaitableIdx(this.handles.insert(writeEnd));
|
|
2184
|
+
writeEnd.setHandle(localStreamTable.insert(writeEnd));
|
|
2185
|
+
if (writeEnd.streamTableIdx() !== tableIdx) { throw new Error("unexpectedly mismatched stream table"); }
|
|
2186
|
+
|
|
2187
|
+
const writeEndWaitableIdx = writeEnd.waitableIdx();
|
|
2188
|
+
const writeEndHandle = writeEnd.handle();
|
|
2189
|
+
writeWaitable.setTarget(`waitable for stream write end (waitable [${writeEndWaitableIdx}])`);
|
|
2190
|
+
writeEnd.setTarget(`stream write end (waitable [${writeEndWaitableIdx}])`);
|
|
1495
2191
|
|
|
1496
|
-
const
|
|
1497
|
-
|
|
2192
|
+
const readEnd = stream.readEnd();
|
|
2193
|
+
readEnd.setWaitableIdx(this.handles.insert(readEnd));
|
|
2194
|
+
readEnd.setHandle(localStreamTable.insert(readEnd));
|
|
2195
|
+
if (readEnd.streamTableIdx() !== tableIdx) { throw new Error("unexpectedly mismatched stream table"); }
|
|
1498
2196
|
|
|
1499
|
-
|
|
2197
|
+
const readEndWaitableIdx = readEnd.waitableIdx();
|
|
2198
|
+
const readEndHandle = readEnd.handle();
|
|
2199
|
+
readWaitable.setTarget(`waitable for read end (waitable [${readEndWaitableIdx}])`);
|
|
2200
|
+
readEnd.setTarget(`stream read end (waitable [${readEndWaitableIdx}])`);
|
|
2201
|
+
|
|
2202
|
+
return {
|
|
2203
|
+
writeEndWaitableIdx,
|
|
2204
|
+
writeEndHandle,
|
|
2205
|
+
readEndWaitableIdx,
|
|
2206
|
+
readEndHandle,
|
|
2207
|
+
};
|
|
1500
2208
|
}
|
|
1501
2209
|
|
|
1502
2210
|
getStreamEnd(args) {
|
|
1503
2211
|
_debugLog('[ComponentAsyncState#getStreamEnd()] args', args);
|
|
1504
|
-
const { tableIdx,
|
|
1505
|
-
if (tableIdx === undefined) { throw new Error('missing table idx while
|
|
1506
|
-
|
|
2212
|
+
const { tableIdx, streamEndHandle, streamEndWaitableIdx } = args;
|
|
2213
|
+
if (tableIdx === undefined) { throw new Error('missing table idx while getting stream end'); }
|
|
2214
|
+
|
|
2215
|
+
const { table, componentIdx } = STREAM_TABLES[tableIdx];
|
|
2216
|
+
const cstate = getOrCreateAsyncState(componentIdx);
|
|
2217
|
+
|
|
2218
|
+
let streamEnd;
|
|
2219
|
+
if (streamEndWaitableIdx !== undefined) {
|
|
2220
|
+
streamEnd = cstate.handles.get(streamEndWaitableIdx);
|
|
2221
|
+
} else if (streamEndHandle !== undefined) {
|
|
2222
|
+
if (!table) { throw new Error(`missing/invalid table [${tableIdx}] while getting stream end`); }
|
|
2223
|
+
streamEnd = table.get(streamEndHandle);
|
|
2224
|
+
} else {
|
|
2225
|
+
throw new TypeError("must specify either waitable idx or handle to retrieve stream");
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
if (!streamEnd) {
|
|
2229
|
+
throw new Error(`missing stream end (tableIdx [${tableIdx}], handle [${streamEndHandle}], waitableIdx [${streamEndWaitableIdx}])`);
|
|
2230
|
+
}
|
|
2231
|
+
if (tableIdx && streamEnd.streamTableIdx() !== tableIdx) {
|
|
2232
|
+
throw new Error(`stream end table idx [${streamEnd.streamTableIdx()}] does not match [${tableIdx}]`);
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
return streamEnd;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
deleteStreamEnd(args) {
|
|
2239
|
+
_debugLog('[ComponentAsyncState#deleteStreamEnd()] args', args);
|
|
2240
|
+
const { tableIdx, streamEndWaitableIdx } = args;
|
|
2241
|
+
if (tableIdx === undefined) { throw new Error("missing table idx while removing stream end"); }
|
|
2242
|
+
if (streamEndWaitableIdx === undefined) { throw new Error("missing stream idx while removing stream end"); }
|
|
1507
2243
|
|
|
1508
|
-
const
|
|
1509
|
-
|
|
1510
|
-
|
|
2244
|
+
const { table, componentIdx } = STREAM_TABLES[tableIdx];
|
|
2245
|
+
const cstate = getOrCreateAsyncState(componentIdx);
|
|
2246
|
+
|
|
2247
|
+
const streamEnd = cstate.handles.get(streamEndWaitableIdx);
|
|
2248
|
+
if (!streamEnd) {
|
|
2249
|
+
throw new Error(`missing stream end [${streamEndWaitableIdx}] in component handles while deleting stream`);
|
|
2250
|
+
}
|
|
2251
|
+
if (streamEnd.streamTableIdx() !== tableIdx) {
|
|
2252
|
+
throw new Error(`stream end table idx [${streamEnd.streamTableIdx()}] does not match [${tableIdx}]`);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
let removed = cstate.handles.remove(streamEnd.waitableIdx());
|
|
2256
|
+
if (!removed) {
|
|
2257
|
+
throw new Error(`failed to remove stream end [${streamEndWaitableIdx}] waitable obj in component [${componentIdx}]`);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
removed = table.remove(streamEnd.handle());
|
|
2261
|
+
if (!removed) {
|
|
2262
|
+
throw new Error(`failed to remove stream end with handle [${streamEnd.handle()}] from stream table [${tableIdx}] in component [${componentIdx}]`);
|
|
1511
2263
|
}
|
|
1512
2264
|
|
|
1513
|
-
|
|
1514
|
-
return stream;
|
|
2265
|
+
return streamEnd;
|
|
1515
2266
|
}
|
|
1516
2267
|
|
|
1517
|
-
|
|
1518
|
-
_debugLog('[ComponentAsyncState#
|
|
1519
|
-
|
|
2268
|
+
removeStreamEndFromTable(args) {
|
|
2269
|
+
_debugLog('[ComponentAsyncState#removeStreamEndFromTable()] args', args);
|
|
2270
|
+
|
|
2271
|
+
const { tableIdx, streamWaitableIdx } = args;
|
|
1520
2272
|
if (tableIdx === undefined) { throw new Error("missing table idx while removing stream end"); }
|
|
1521
|
-
if (
|
|
2273
|
+
if (streamWaitableIdx === undefined) {
|
|
2274
|
+
throw new Error("missing stream end waitable idx while removing stream end");
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
const { table, componentIdx } = STREAM_TABLES[tableIdx];
|
|
2278
|
+
if (!table) { throw new Error(`missing/invalid table [${tableIdx}] while removing stream end`); }
|
|
2279
|
+
|
|
2280
|
+
const cstate = getOrCreateAsyncState(componentIdx);
|
|
1522
2281
|
|
|
1523
|
-
const
|
|
1524
|
-
if (!
|
|
1525
|
-
throw new Error(`missing stream
|
|
2282
|
+
const streamEnd = cstate.handles.get(streamWaitableIdx);
|
|
2283
|
+
if (!streamEnd) {
|
|
2284
|
+
throw new Error(`missing stream end (handle [${streamWaitableIdx}], table [${tableIdx}])`);
|
|
1526
2285
|
}
|
|
2286
|
+
const handle = streamEnd.handle();
|
|
1527
2287
|
|
|
1528
|
-
|
|
1529
|
-
if (!
|
|
2288
|
+
let removed = cstate.handles.remove(streamWaitableIdx);
|
|
2289
|
+
if (!removed) {
|
|
2290
|
+
throw new Error(`failed to remove streamEnd from handles (waitable idx [${streamWaitableIdx}]), component [${componentIdx}])`);
|
|
2291
|
+
}
|
|
1530
2292
|
|
|
1531
|
-
|
|
2293
|
+
removed = table.remove(handle);
|
|
1532
2294
|
if (!removed) {
|
|
1533
|
-
throw new Error(`
|
|
2295
|
+
throw new Error(`failed to remove streamEnd from table (handle [${handle}]), table [${tableIdx}], component [${componentIdx}])`);
|
|
1534
2296
|
}
|
|
1535
2297
|
|
|
1536
|
-
return
|
|
2298
|
+
return streamEnd;
|
|
1537
2299
|
}
|
|
1538
2300
|
}
|
|
1539
2301
|
|
|
@@ -1565,7 +2327,9 @@ const instantiateCore = WebAssembly.instantiate;
|
|
|
1565
2327
|
let exports0;
|
|
1566
2328
|
let memory0;
|
|
1567
2329
|
let realloc0;
|
|
2330
|
+
let realloc0Async;
|
|
1568
2331
|
let postReturn0;
|
|
2332
|
+
let postReturn0Async;
|
|
1569
2333
|
let exports0ParseConfig;
|
|
1570
2334
|
|
|
1571
2335
|
function parseConfig(arg0, arg1) {
|
|
@@ -1598,6 +2362,7 @@ function parseConfig(arg0, arg1) {
|
|
|
1598
2362
|
const [task, _wasm_call_currentTaskID] = createNewCurrentTask({
|
|
1599
2363
|
componentIdx: 0,
|
|
1600
2364
|
isAsync: false,
|
|
2365
|
+
isManualAsync: false,
|
|
1601
2366
|
entryFnName: 'exports0ParseConfig',
|
|
1602
2367
|
getCallbackFn: () => null,
|
|
1603
2368
|
callbackFnName: 'null',
|
|
@@ -1605,8 +2370,15 @@ function parseConfig(arg0, arg1) {
|
|
|
1605
2370
|
callingWasmExport: true,
|
|
1606
2371
|
});
|
|
1607
2372
|
|
|
1608
|
-
|
|
1609
|
-
|
|
2373
|
+
const started = task.enterSync();
|
|
2374
|
+
task.setReturnMemoryIdx(0);
|
|
2375
|
+
task.setReturnMemory(memory0);
|
|
2376
|
+
let ret = _withGlobalCurrentTaskMeta({
|
|
2377
|
+
taskID: task.id(),
|
|
2378
|
+
componentIdx: task.componentIdx(),
|
|
2379
|
+
fn: () => exports0ParseConfig(ptr0, len0, result2, len2),
|
|
2380
|
+
});
|
|
2381
|
+
|
|
1610
2382
|
let variant60;
|
|
1611
2383
|
switch (dataView(memory0).getUint8(ret + 0, true)) {
|
|
1612
2384
|
case 0: {
|
|
@@ -2124,11 +2896,13 @@ function parseConfig(arg0, arg1) {
|
|
|
2124
2896
|
postReturn: true
|
|
2125
2897
|
});
|
|
2126
2898
|
const retCopy = variant60;
|
|
2899
|
+
task.resolve([retCopy.val]);
|
|
2127
2900
|
|
|
2128
2901
|
let cstate = getOrCreateAsyncState(0);
|
|
2129
2902
|
cstate.mayLeave = false;
|
|
2130
2903
|
postReturn0(ret);
|
|
2131
2904
|
cstate.mayLeave = true;
|
|
2905
|
+
task.exit();
|
|
2132
2906
|
|
|
2133
2907
|
|
|
2134
2908
|
|
|
@@ -2144,9 +2918,22 @@ const $init = (() => {
|
|
|
2144
2918
|
const module0 = fetchCompile(new URL('./parser.core.wasm', import.meta.url));
|
|
2145
2919
|
({ exports: exports0 } = yield instantiateCore(yield module0));
|
|
2146
2920
|
memory0 = exports0.memory;
|
|
2147
|
-
GlobalComponentMemories.save({ idx: 0, componentIdx: 0, memory: memory0 });
|
|
2148
2921
|
realloc0 = exports0.cabi_realloc;
|
|
2922
|
+
|
|
2923
|
+
try {
|
|
2924
|
+
realloc0Async = WebAssembly.promising(exports0.cabi_realloc);
|
|
2925
|
+
} catch(err) {
|
|
2926
|
+
realloc0Async = exports0.cabi_realloc;
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2149
2929
|
postReturn0 = exports0['cabi_post_parse-config'];
|
|
2930
|
+
|
|
2931
|
+
try {
|
|
2932
|
+
postReturn0Async = WebAssembly.promising(exports0['cabi_post_parse-config']);
|
|
2933
|
+
} catch(err) {
|
|
2934
|
+
postReturn0Async = exports0['cabi_post_parse-config'];
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2150
2937
|
exports0ParseConfig = exports0['parse-config'];
|
|
2151
2938
|
})();
|
|
2152
2939
|
let promise, resolve, reject;
|