diff --git a/packages/common/src/events/EventEmitter.ts b/packages/common/src/events/EventEmitter.ts index 4cb7f4e1..3b061ca0 100644 --- a/packages/common/src/events/EventEmitter.ts +++ b/packages/common/src/events/EventEmitter.ts @@ -1,26 +1,68 @@ import { EventsRecord } from "./EventEmittingComponent"; type ListenersHolder = { - // eslint-disable-next-line putout/putout - [key in keyof Events]?: ((...args: Events[key]) => void)[]; + [key in keyof Events]?: { + id: number; + listener: (...args: Events[key]) => void; + }[]; }; export class EventEmitter { private readonly listeners: ListenersHolder = {}; + private counter = 0; + + // Fields used for offSelf() + private currentListenerId: number | undefined = undefined; + + private currentListenerEventName: keyof Events | undefined = undefined; + public emit(event: keyof Events, ...parameters: Events[typeof event]) { const listeners = this.listeners[event]; - if(listeners !== undefined) { + if (listeners !== undefined) { + this.currentListenerEventName = event; + listeners.forEach((listener) => { - listener(...parameters); + this.currentListenerId = listener.id; + + listener.listener(...parameters); + + this.currentListenerId = undefined; }); + this.currentListenerEventName = undefined; } } public on( event: Key, listener: (...args: Events[Key]) => void - ) { - (this.listeners[event] ??= []).push(listener); + ): number { + // eslint-disable-next-line no-multi-assign + const id = (this.counter += 1); + (this.listeners[event] ??= []).push({ + id, + listener, + }); + return id; + } + + // eslint-disable-next-line no-warning-comments + // TODO Improve to be thread-safe + public offSelf() { + if ( + this.currentListenerEventName !== undefined && + this.currentListenerId !== undefined + ) { + this.off(this.currentListenerEventName, this.currentListenerId); + } + } + + public off(event: Key, id: number) { + const listeners = this.listeners[event]; + if (listeners !== undefined) { + this.listeners[event] = listeners.filter( + (listener) => listener.id !== id + ); + } } } diff --git a/packages/sequencer/src/protocol/production/trigger/AutomaticBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/AutomaticBlockTrigger.ts index 16e87f68..683af37c 100644 --- a/packages/sequencer/src/protocol/production/trigger/AutomaticBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/AutomaticBlockTrigger.ts @@ -28,7 +28,21 @@ export class AutomaticBlockTrigger // eslint-disable-next-line @typescript-eslint/no-misused-promises this.mempool.events.on("transactionAdded", async () => { log.info("Transaction received, creating block..."); - await this.unprovenProducerModule.tryProduceUnprovenBlock(); + const block = await this.unprovenProducerModule.tryProduceUnprovenBlock(); + + // In case the block producer was busy, we need to re-trigger production + // as soon as the previous production was finished + if (block === undefined) { + this.unprovenProducerModule.events.on( + "unprovenBlockProduced", + async () => { + // eslint-disable-next-line max-len + // Make sure this comes before await, because otherwise we have a race condition + this.unprovenProducerModule.events.offSelf(); + await this.unprovenProducerModule.tryProduceUnprovenBlock(); + } + ); + } }); } }