@@ -11,6 +11,8 @@ export class ExecutionController<TMessage extends IMessage> {
1111 private promiseQueue : PromiseQueue ;
1212 private signal : AbortSignal ;
1313 private createExecutor : ( record : TMessage , signal : AbortSignal ) => IExecutor ;
14+ private readonly maxConcurrent : number ;
15+ private slotWaiters = new Set < ( ) => void > ( ) ;
1416
1517 constructor (
1618 executorFactory : ( record : TMessage , signal : AbortSignal ) => IExecutor ,
@@ -20,23 +22,59 @@ export class ExecutionController<TMessage extends IMessage> {
2022 ) {
2123 this . signal = abortSignal ;
2224 this . createExecutor = executorFactory ;
25+ this . maxConcurrent = config . maxConcurrent ;
2326 this . promiseQueue = newQueue ( config . maxConcurrent ) ;
2427 this . logger = logger ;
2528 }
2629
27- async start ( record : TMessage ) {
30+ get availableSlots ( ) : number {
31+ return Math . max ( 0 , this . maxConcurrent - this . promiseQueue . size ( ) ) ;
32+ }
33+
34+ start ( record : TMessage ) {
2835 const executor = this . createExecutor ( record , this . signal ) ;
2936
3037 this . logger . debug ( `Scheduling execution of task ${ executor . msgId } ` ) ;
3138
32- return await this . promiseQueue . add ( async ( ) => {
39+ return this . promiseQueue . add ( async ( ) => {
3340 try {
3441 this . logger . debug ( `Executing task ${ executor . msgId } ...` ) ;
3542 await executor . execute ( ) ;
3643 this . logger . debug ( `Execution successful for ${ executor . msgId } ` ) ;
3744 } catch ( error ) {
3845 this . logger . error ( `Execution failed for ${ executor . msgId } ` , error ) ;
3946 throw error ;
47+ } finally {
48+ this . notifySlotWaiters ( ) ;
49+ }
50+ } ) ;
51+ }
52+
53+ async waitForSlot ( ) : Promise < void > {
54+ if ( this . signal . aborted || this . availableSlots > 0 ) {
55+ return ;
56+ }
57+
58+ await new Promise < void > ( ( resolve ) => {
59+ const onAbort = ( ) => {
60+ cleanup ( ) ;
61+ resolve ( ) ;
62+ } ;
63+ const onSlotFreed = ( ) => {
64+ cleanup ( ) ;
65+ resolve ( ) ;
66+ } ;
67+ const cleanup = ( ) => {
68+ this . slotWaiters . delete ( onSlotFreed ) ;
69+ this . signal . removeEventListener ( 'abort' , onAbort ) ;
70+ } ;
71+
72+ this . slotWaiters . add ( onSlotFreed ) ;
73+ this . signal . addEventListener ( 'abort' , onAbort , { once : true } ) ;
74+
75+ if ( this . signal . aborted || this . availableSlots > 0 ) {
76+ cleanup ( ) ;
77+ resolve ( ) ;
4078 }
4179 } ) ;
4280 }
@@ -50,4 +88,12 @@ export class ExecutionController<TMessage extends IMessage> {
5088 ) ;
5189 await this . promiseQueue . done ( ) ;
5290 }
91+
92+ private notifySlotWaiters ( ) {
93+ const waiters = [ ...this . slotWaiters ] ;
94+ this . slotWaiters . clear ( ) ;
95+ for ( const waiter of waiters ) {
96+ waiter ( ) ;
97+ }
98+ }
5399}
0 commit comments