@@ -19,6 +19,10 @@ export const enum DispatcherState {
1919 Processing ,
2020 // The dispatcher is stopped, tasks queued will not be immediatelly processed.
2121 Stopped ,
22+ // The dispatcher is shutdown, attempting to queue tasks while in this state is a no-op.
23+ //
24+ // This state is irreversible.
25+ Shutdown ,
2226}
2327
2428// The possible commands to be processed by the dispatcher.
@@ -31,6 +35,8 @@ const enum Commands {
3135 Stop ,
3236 // The dispatcher should stop executing the queued tasks and clear the queue.
3337 Clear ,
38+ // The dispatcher will clear the queue and go into the Shutdown state.
39+ Shutdown ,
3440 // Exactly like a normal Task, but spawned for tests.
3541 TestTask ,
3642}
@@ -65,7 +71,7 @@ class Dispatcher {
6571 // This is `undefined` in case there is no ongoing execution of tasks.
6672 private currentJob ?: Promise < void > ;
6773
68- constructor ( readonly maxPreInitQueueSize = 100 ) {
74+ constructor ( readonly maxPreInitQueueSize = 100 , readonly logTag = LOG_TAG ) {
6975 this . queue = [ ] ;
7076 this . state = DispatcherState . Uninitialized ;
7177 }
@@ -88,7 +94,7 @@ class Dispatcher {
8894 try {
8995 await task ( ) ;
9096 } catch ( e ) {
91- log ( LOG_TAG , [ "Error executing task:" , e ] , LoggingLevel . Error ) ;
97+ log ( this . logTag , [ "Error executing task:" , e ] , LoggingLevel . Error ) ;
9298 }
9399 }
94100
@@ -102,6 +108,7 @@ class Dispatcher {
102108 case ( Commands . Stop ) :
103109 this . state = DispatcherState . Stopped ;
104110 return ;
111+ case ( Commands . Shutdown ) :
105112 case ( Commands . Clear ) :
106113 // Unblock test resolvers before clearing the queue.
107114 this . queue . forEach ( c => {
@@ -111,7 +118,12 @@ class Dispatcher {
111118 } ) ;
112119
113120 this . queue = [ ] ;
114- this . state = DispatcherState . Stopped ;
121+ if ( nextCommand . command === Commands . Clear ) {
122+ this . state = DispatcherState . Stopped ;
123+ } else {
124+ this . state = DispatcherState . Shutdown ;
125+ }
126+
115127 return ;
116128 case ( Commands . TestTask ) :
117129 await this . executeTask ( nextCommand . task ) ;
@@ -149,7 +161,7 @@ class Dispatcher {
149161 } )
150162 . catch ( error => {
151163 log (
152- LOG_TAG ,
164+ this . logTag ,
153165 [
154166 "IMPOSSIBLE: Something went wrong while the dispatcher was executing the tasks queue." ,
155167 error
@@ -175,10 +187,19 @@ class Dispatcher {
175187 * @returns Wheter or not the task was queued.
176188 */
177189 private launchInternal ( command : Command , priorityTask = false ) : boolean {
190+ if ( this . state === DispatcherState . Shutdown ) {
191+ log (
192+ this . logTag ,
193+ "Attempted to enqueue a new task but the dispatcher is shutdown. Ignoring." ,
194+ LoggingLevel . Warn
195+ ) ;
196+ return false ;
197+ }
198+
178199 if ( ! priorityTask && this . state === DispatcherState . Uninitialized ) {
179200 if ( this . queue . length >= this . maxPreInitQueueSize ) {
180201 log (
181- LOG_TAG ,
202+ this . logTag ,
182203 "Unable to enqueue task, pre init queue is full." ,
183204 LoggingLevel . Warn
184205 ) ;
@@ -226,7 +247,7 @@ class Dispatcher {
226247 flushInit ( task ?: Task ) : void {
227248 if ( this . state !== DispatcherState . Uninitialized ) {
228249 log (
229- LOG_TAG ,
250+ this . logTag ,
230251 "Attempted to initialize the Dispatcher, but it is already initialized. Ignoring." ,
231252 LoggingLevel . Warn
232253 ) ;
@@ -286,6 +307,27 @@ class Dispatcher {
286307 }
287308 }
288309
310+ /**
311+ * Shutsdown the dispatcher.
312+ *
313+ * 1. Executes all tasks launched prior to this one.
314+ * 2. Clears the queue of any tasks launched after this one.
315+ * 3. Puts the dispatcher in the `Shutdown` state.
316+ *
317+ * # Note
318+ *
319+ * - This is a command like any other, if the dispatcher is uninitialized
320+ * it will get executed when the dispatcher is initialized.
321+ * - If the dispatcher is stopped, it is resumed and all pending tasks are executed.
322+ *
323+ * @returns A promise which resolves once shutdown is complete.
324+ */
325+ shutdown ( ) : Promise < void > {
326+ this . launchInternal ( { command : Commands . Shutdown } ) ;
327+ this . resume ( ) ;
328+ return this . currentJob || Promise . resolve ( ) ;
329+ }
330+
289331 /**
290332 * Test-Only API**
291333 *
@@ -330,6 +372,11 @@ class Dispatcher {
330372 *
331373 * This is important in order not to hang forever in case the dispatcher is stopped.
332374 *
375+ * # Errors
376+ *
377+ * This function will reject in case the task is not launched.
378+ * Make sure the dispatcher is initialized or is not shutdown in these cases.
379+ *
333380 * @param task The task to launch.
334381 * @returns A promise which only resolves once the task is done being executed
335382 * or is guaranteed to not be executed ever i.e. if the queue gets cleared.
0 commit comments