Skip to content

Commit 50ef1ac

Browse files
fix(jco): async impl in bindgen
1 parent 84e1853 commit 50ef1ac

File tree

16 files changed

+425
-365
lines changed

16 files changed

+425
-365
lines changed

crates/js-component-bindgen/src/function_bindgen.rs

Lines changed: 127 additions & 155 deletions
Large diffs are not rendered by default.

crates/js-component-bindgen/src/intrinsics/component.rs

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ pub enum ComponentIntrinsic {
6363
/// ```
6464
///
6565
LowerImport,
66+
67+
/// Intrinsic used to set all component async states to error.
68+
///
69+
/// Practically, this stops all individual component event loops (`AsyncComponentState#tick()`)
70+
/// and will usually allow the JS event loop which would otherwise be running `tick()` intervals
71+
/// forever.
72+
///
73+
ComponentStateSetAllError,
6674
}
6775

6876
impl ComponentIntrinsic {
@@ -86,6 +94,7 @@ impl ComponentIntrinsic {
8694
Self::BackpressureDec => "backpressureDec",
8795
Self::ComponentAsyncStateClass => "ComponentAsyncState",
8896
Self::LowerImport => "_intrinsic_component_lowerImport",
97+
Self::ComponentStateSetAllError => "_ComponentStateSetAllError",
8998
}
9099
}
91100

@@ -147,33 +156,58 @@ impl ComponentIntrinsic {
147156
output.push_str(&format!(
148157
r#"
149158
class {class_name} {{
159+
#componentIdx;
150160
#callingAsyncImport = false;
151161
#syncImportWait = Promise.withResolvers();
152162
#lock = null;
153-
154-
mayLeave = true;
155-
waitableSets = new {rep_table_class}();
156-
waitables = new {rep_table_class}();
157-
subtasks = new {rep_table_class}();
158-
159163
#parkedTasks = new Map();
160-
161164
#suspendedTasksByTaskID = new Map();
162165
#suspendedTaskIDs = [];
163166
#taskResumerInterval = null;
164-
165167
#pendingTasks = [];
168+
#errored = null;
169+
170+
mayLeave = true;
171+
waitableSets = new {rep_table_class}();
172+
waitables = new {rep_table_class}();
173+
subtasks = new {rep_table_class}();
166174
167175
constructor(args) {{
168-
this.#taskResumerInterval = setInterval(() => {{
169-
try {{
170-
this.tick();
171-
}} catch (err) {{
172-
{debug_log_fn}('[{class_name}#taskResumer()] tick failed', {{ err }});
173-
}}
176+
this.#componentIdx = args.componentIdx;
177+
const self = this;
178+
179+
this.#taskResumerInterval = setTimeout(() => {{
180+
try {{
181+
if (self.errored()) {{
182+
self.stopTaskResumer();
183+
console.error(`(component ${{this.#errored.componentIdx}}) ASYNC ERROR:`, this.#errored);
184+
return;
185+
}}
186+
if (this.tick()) {{ setTimeout(() => {{ this.tick(); }}, 0); }}
187+
}} catch (err) {{
188+
{debug_log_fn}('[{class_name}#taskResumer()] tick failed', {{ err }});
189+
}}
174190
}}, 0);
175191
}};
176192
193+
stopTaskResumer() {{
194+
if (!this.#taskResumerInterval) {{ throw new Error('missing task resumer interval'); }}
195+
clearInterval(this.#taskResumerInterval);
196+
}}
197+
198+
componentIdx() {{ return this.#componentIdx; }}
199+
200+
errored() {{ return this.#errored !== null; }}
201+
setErrored(err) {{
202+
{debug_log_fn}('[{class_name}#setErrored()] component errored', {{ err, componentIdx: this.#componentIdx }});
203+
if (this.#errored) {{ return; }}
204+
if (!err) {{
205+
err = new Error('error elswehere (see other component instance error)')
206+
err.componentIdx = this.#componentIdx;
207+
}}
208+
this.#errored = err;
209+
}}
210+
177211
callingSyncImport(val) {{
178212
if (val === undefined) {{ return this.#callingAsyncImport; }}
179213
if (typeof val !== 'boolean') {{ throw new TypeError('invalid setting for async import'); }}
@@ -328,14 +362,17 @@ impl ComponentIntrinsic {
328362
}}
329363
330364
tick() {{
365+
let resumedTask = false;
331366
for (const taskID of this.#suspendedTaskIDs.filter(t => t !== null)) {{
332367
const meta = this.#suspendedTasksByTaskID.get(taskID);
333368
if (!meta || !meta.readyFn) {{
334369
throw new Error('missing/invalid task despite ID [' + taskID + '] being present');
335370
}}
336371
if (!meta.readyFn()) {{ continue; }}
372+
resumedTask = true;
337373
this.resumeTaskByID(taskID);
338374
}}
375+
return resumedTask;
339376
}}
340377
341378
addPendingTask(task) {{
@@ -352,14 +389,15 @@ impl ComponentIntrinsic {
352389
let async_state_map = Self::GlobalAsyncStateMap.name();
353390
let component_async_state_class = Self::ComponentAsyncStateClass.name();
354391
output.push_str(&format!(
355-
"
392+
r#"
356393
function {get_state_fn}(componentIdx, init) {{
357394
if (!{async_state_map}.has(componentIdx)) {{
358-
{async_state_map}.set(componentIdx, new {component_async_state_class}());
395+
const newState = new {component_async_state_class}({{ componentIdx }});
396+
{async_state_map}.set(componentIdx, newState);
359397
}}
360398
return {async_state_map}.get(componentIdx);
361399
}}
362-
"
400+
"#
363401
));
364402
}
365403

@@ -378,6 +416,22 @@ impl ComponentIntrinsic {
378416
"
379417
));
380418
}
419+
420+
Self::ComponentStateSetAllError => {
421+
let debug_log_fn = Intrinsic::DebugLog.name();
422+
let async_state_map = Self::GlobalAsyncStateMap.name();
423+
let component_state_set_all_error_fn = Self::ComponentStateSetAllError.name();
424+
output.push_str(&format!(
425+
r#"
426+
function {component_state_set_all_error_fn}() {{
427+
{debug_log_fn}('[{component_state_set_all_error_fn}()]');
428+
for (const state of {async_state_map}.values()) {{
429+
state.setErrored();
430+
}}
431+
}}
432+
"#
433+
));
434+
}
381435
}
382436
}
383437
}

crates/js-component-bindgen/src/intrinsics/lift.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ impl LiftIntrinsic {
323323
ctx.params = ctx.params.slice(1);
324324
}} else {{
325325
if (ctx.storageLen < ctx.storagePtr + 2) {{ throw new Error('not enough storage remaining for lift'); }}
326-
val = new DataView(ctx.memory.buffer).getInt16(storagePtr);
326+
val = new DataView(ctx.memory.buffer).getInt16(ctx.storagePtr);
327327
ctx.storagePtr += 2;
328328
ctx.storageLen -= 2;
329329
}}
@@ -538,7 +538,7 @@ impl LiftIntrinsic {
538538
ctx.params = ctx.params.slice(2);
539539
}} else {{
540540
const start = new DataView(ctx.memory.buffer).getUint32(ctx.storagePtr, params[0], true);
541-
const codeUnits = new DataView(memory.buffer).getUint32(storagePtr, params[0] + 4, true);
541+
const codeUnits = new DataView(memory.buffer).getUint32(ctx.storagePtr, params[0] + 4, true);
542542
val = {decoder}.decode(new Uint8Array(ctx.memory.buffer, start, codeUnits));
543543
ctx.storagePtr += codeUnits;
544544
ctx.storageLen -= codeUnits;
@@ -567,8 +567,8 @@ impl LiftIntrinsic {
567567
ctx.params = ctx.params.slice(2);
568568
}} else {{
569569
const data = new DataView(ctx.memory.buffer)
570-
const start = data.getUint32(storagePtr, vals[0], true);
571-
const codeUnits = data.getUint32(storagePtr, vals[0] + 4, true);
570+
const start = data.getUint32(ctx.storagePtr, vals[0], true);
571+
const codeUnits = data.getUint32(ctx.storagePtr, vals[0] + 4, true);
572572
val = {decoder}.decode(new Uint16Array(ctx.memory.buffer, start, codeUnits));
573573
ctx.storagePtr = ctx.storagePtr + 2 * codeUnits,
574574
ctx.storageLen = ctx.storageLen - 2 * codeUnits
@@ -599,7 +599,7 @@ impl LiftIntrinsic {
599599
600600
const res = {{}};
601601
for (const [key, liftFn, alignment32] in keysAndLiftFns) {{
602-
ctx.storagePtr = Math.ceil(storagePtr / alignment32) * alignment32;
602+
ctx.storagePtr = Math.ceil(ctx.storagePtr / alignment32) * alignment32;
603603
let [val, newCtx] = liftFn(ctx);
604604
res[key] = val;
605605
ctx = newCtx;
@@ -844,9 +844,20 @@ impl LiftIntrinsic {
844844
Self::LiftFlatErrorContext => {
845845
let debug_log_fn = Intrinsic::DebugLog.name();
846846
output.push_str(&format!("
847-
function _liftFlatErrorContext(size, memory, vals, storagePtr, storageLen) {{
848-
{debug_log_fn}('[_liftFlatErrorContext()] args', {{ size, memory, vals, storagePtr, storageLen }});
849-
throw new Error('flat lift for error-contexts not yet implemented!');
847+
function _liftFlatErrorContext(ctx) {{
848+
{debug_log_fn}('[_liftFlatErrorContext()] args', ctx);
849+
const {{ useDirectParams, params }} = ctx;
850+
851+
let val;
852+
if (useDirectParams) {{
853+
if (params.length === 0) {{ throw new Error('expected at least one single i32 argument'); }}
854+
val = ctx.params[0];
855+
ctx.params = ctx.params.slice(1);
856+
}} else {{
857+
throw new Error('indirect flat lift for error-contexts not yet implemented!');
858+
}}
859+
860+
return [val, ctx];
850861
}}
851862
"));
852863
}

crates/js-component-bindgen/src/intrinsics/mod.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,13 @@ pub fn render_intrinsics(args: RenderIntrinsicsArgs) -> Source {
199199
));
200200
args.intrinsics
201201
.insert(Intrinsic::PromiseWithResolversPolyfill);
202-
args.intrinsics
203-
.insert(Intrinsic::Host(HostIntrinsic::PrepareCall));
204-
args.intrinsics
205-
.insert(Intrinsic::Host(HostIntrinsic::AsyncStartCall));
206-
args.intrinsics
207-
.insert(Intrinsic::Host(HostIntrinsic::SyncStartCall));
202+
args.intrinsics.extend([
203+
&Intrinsic::Host(HostIntrinsic::PrepareCall),
204+
&Intrinsic::Host(HostIntrinsic::AsyncStartCall),
205+
&Intrinsic::Host(HostIntrinsic::SyncStartCall),
206+
]);
208207

209-
// Handle intrinsic "dependence"
208+
// Handle specific intrinsic inter-dependencies
210209
if args.intrinsics.contains(&Intrinsic::GetErrorPayload)
211210
|| args.intrinsics.contains(&Intrinsic::GetErrorPayloadString)
212211
{
@@ -315,6 +314,7 @@ pub fn render_intrinsics(args: RenderIntrinsicsArgs) -> Source {
315314
args.intrinsics.extend([
316315
&Intrinsic::TypeCheckValidI32,
317316
&Intrinsic::Conversion(ConversionIntrinsic::ToInt32),
317+
&Intrinsic::Component(ComponentIntrinsic::ComponentStateSetAllError),
318318
]);
319319
}
320320

crates/js-component-bindgen/src/intrinsics/p3/async_future.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,9 @@ impl AsyncFutureIntrinsic {
398398
}});
399399
400400
if (!isAsync && !e.hasPendingEvent()) {{
401-
await task.blockOn({{ promise: e.waitForPendingEvent(), isAsync: false }});
401+
// TODO: replace with what block on used to be? wait for?
402+
// await task.blockOn({{ promise: e.waitForPendingEvent(), isAsync: false }});
403+
throw new Error('not implemented');
402404
}}
403405
404406
if (futureEnd.hasPendingEvent()) {{
@@ -458,7 +460,9 @@ impl AsyncFutureIntrinsic {
458460
// TODO: cancel the shared thing (waitable?)
459461
if (!futureEnd.hasPendingEvent()) {{
460462
if (!isAsync) {{
461-
await task.blockOn({{ promise: futureEnd.waitable, isAsync: false }});
463+
// TODO: repalce with what task.blockOn used to do
464+
// await task.blockOn({{ promise: futureEnd.waitable, isAsync: false }});
465+
throw new Error('not implemented');
462466
}} else {{
463467
return {async_blocked_const};
464468
}}

0 commit comments

Comments
 (0)