Skip to content

Commit 503df02

Browse files
fix: fix exiting busy loop
1 parent c14548c commit 503df02

File tree

8 files changed

+184
-89
lines changed

8 files changed

+184
-89
lines changed

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

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2224,17 +2224,21 @@ impl Bindgen for FunctionBindgen<'_> {
22242224
const task = taskMeta.task;
22252225
if (!task) {{ throw new Error('missing/invalid task in current task metadata'); }}
22262226
2227-
new Promise((resolve, reject) => {{
2228-
{async_driver_loop_fn}({{
2229-
componentInstanceIdx: {component_instance_idx},
2230-
componentState,
2231-
task,
2232-
fnName: '{name}',
2233-
isAsync: {is_async_js},
2234-
callbackResult: ret,
2235-
resolve,
2236-
reject
2237-
}});
2227+
new Promise(async (resolve, reject) => {{
2228+
try {{
2229+
await {async_driver_loop_fn}({{
2230+
componentInstanceIdx: {component_instance_idx},
2231+
componentState,
2232+
task,
2233+
fnName: '{name}',
2234+
isAsync: {is_async_js},
2235+
callbackResult: ret,
2236+
resolve,
2237+
reject
2238+
}});
2239+
}} catch (err) {{
2240+
console.log("[AsyncTaskReturn] DRIVER LOOP CALL FAILED?", {{ err }});
2241+
}}
22382242
}});
22392243
22402244
let taskRes = await task.completionPromise();

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ pub fn render_intrinsics(args: RenderIntrinsicsArgs) -> Source {
314314
args.intrinsics.extend([
315315
&Intrinsic::TypeCheckValidI32,
316316
&Intrinsic::Conversion(ConversionIntrinsic::ToInt32),
317+
&Intrinsic::Component(ComponentIntrinsic::ComponentStateSetAllError),
317318
]);
318319
}
319320

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

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ impl AsyncTaskIntrinsic {
355355
let task_return_fn = Self::TaskReturn.name();
356356
let current_task_get_fn = Self::GetCurrentTask.name();
357357

358-
output.push_str(&format!("
358+
output.push_str(&format!(r#"
359359
function {task_return_fn}(ctx) {{
360360
const {{ componentIdx, useDirectParams, getMemoryFn, memoryIdx, callbackFnIdx, liftFns }} = ctx;
361361
const params = [...arguments].slice(1);
@@ -404,7 +404,7 @@ impl AsyncTaskIntrinsic {
404404
405405
task.resolve(results);
406406
}}
407-
"));
407+
"#));
408408
}
409409

410410
Self::SubtaskDrop => {
@@ -519,8 +519,8 @@ impl AsyncTaskIntrinsic {
519519
throw new Error('missing/invalid component instance index while starting task');
520520
}}
521521
const tasks = {global_task_map}.get(componentIdx);
522-
523522
const callbackFn = getCallbackFn ? getCallbackFn() : null;
523+
524524
const newTask = new {task_class}({{
525525
componentIdx,
526526
isAsync,
@@ -531,6 +531,8 @@ impl AsyncTaskIntrinsic {
531531
getCalleeParamsFn,
532532
errHandling,
533533
}});
534+
newTask.enter();
535+
534536
const newTaskID = newTask.id();
535537
const newTaskMeta = {{ id: newTaskID, componentIdx, task: newTask }};
536538
@@ -797,37 +799,11 @@ impl AsyncTaskIntrinsic {
797799
async enter() {{
798800
{debug_log_fn}('[{task_class}#enter()] args', {{ taskID: this.#id }});
799801
800-
// TODO: assert scheduler locked
801-
// TODO: trap if on the stack
802-
803802
const cstate = {get_or_create_async_state_fn}(this.#componentIdx);
804803
805-
let mayNotEnter = !this.mayEnter(this);
806-
const componentHasPendingTasks = cstate.pendingTasks > 0;
807-
if (mayNotEnter || componentHasPendingTasks) {{
808-
throw new Error('in enter()'); // TODO: remove
809-
cstate.pendingTasks.set(this.#id, new {awaitable_class}(new Promise()));
810-
811-
const blockResult = await this.onBlock(awaitable);
812-
if (blockResult) {{
813-
// TODO: find this pending task in the component
814-
const pendingTask = cstate.pendingTasks.get(this.#id);
815-
if (!pendingTask) {{
816-
throw new Error('pending task [' + this.#id + '] not found for component instance');
817-
}}
818-
cstate.pendingTasks.remove(this.#id);
819-
this.#onResolve(new Error('failed enter'));
820-
return false;
821-
}}
822-
823-
mayNotEnter = !this.mayEnter(this);
824-
if (!mayNotEnter || !cstate.startPendingTask) {{
825-
throw new Error('invalid component entrance/pending task resolution');
826-
}}
827-
cstate.startPendingTask = false;
828-
}}
804+
// TODO: implement backpressure
829805
830-
if (!this.isAsync) {{ cstate.callingSyncExport = true; }}
806+
if (this.needsExclusiveLock()) {{ cstate.exclusiveLock(); }}
831807
832808
return true;
833809
}}
@@ -1058,7 +1034,7 @@ impl AsyncTaskIntrinsic {
10581034
state.exclusiveRelease();
10591035
}}
10601036
1061-
needsExclusiveLock(v) {{ return this.#needsExclusiveLock; }}
1037+
needsExclusiveLock() {{ return this.#needsExclusiveLock; }}
10621038
10631039
createSubtask(args) {{
10641040
{debug_log_fn}('[{task_class}#createSubtask()] args', args);
@@ -1331,6 +1307,8 @@ impl AsyncTaskIntrinsic {
13311307
let i32_typecheck = Intrinsic::TypeCheckValidI32.name();
13321308
let to_int32_fn = Intrinsic::Conversion(ConversionIntrinsic::ToInt32).name();
13331309
let unpack_callback_result_fn = Self::UnpackCallbackResult.name();
1310+
let error_all_component_states_fn =
1311+
Intrinsic::Component(ComponentIntrinsic::ComponentStateSetAllError).name();
13341312

13351313
output.push_str(&format!(r#"
13361314
async function {driver_loop_fn}(args) {{
@@ -1350,13 +1328,32 @@ impl AsyncTaskIntrinsic {
13501328
try {{
13511329
callbackResult = await callbackResult;
13521330
}} catch (err) {{
1331+
err.componentIdx = task.componentIdx();
1332+
1333+
componentState.setErrored(err);
1334+
{error_all_component_states_fn}();
1335+
13531336
reject(err);
1337+
task.resolve([]);
13541338
return;
13551339
}}
13561340
1341+
// TODO(fix): callbackResult should not ever be undefined, *unless*
1342+
// we are calling it on a function that was not async to begin with?...
1343+
//
1344+
// In practice, the callback of `[async]run` returns undefined.
1345+
//
13571346
if (callbackResult === undefined) {{
1358-
resolve(null);
1359-
return;
1347+
{debug_log_fn}('[{driver_loop_fn}()] early exit due to undefined callback result', {{
1348+
taskID: task.id(),
1349+
subtaskID: task.currentSubtask()?.id(),
1350+
parentTaskID: task.currentSubtask()?.parentTaskID(),
1351+
fnName,
1352+
callbackResult
1353+
}});
1354+
resolve(null);
1355+
task.resolve([]);
1356+
return;
13601357
}}
13611358
13621359
let callbackCode;

0 commit comments

Comments
 (0)