@@ -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
6876impl 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}
0 commit comments