export class PrepareQuorum { constructor({ ackChannel, timeoutMs, matchesChan, debug = false }) { this.ackChannel = ackChannel this.timeoutMs = timeoutMs this.matchesChan = matchesChan this.debug = debug this.expected = new Set() this.ready = new Map() this.simulationId = null this.active = false this.resolve = null this.timer = null } begin(expectedParticipantIds, simulationId) { this.cancel() this.expected = new Set(expectedParticipantIds) this.ready.clear() this.simulationId = simulationId this.active = true if(this.debug) { console.log( `[Maestro] Prepare quorum armed: ${this.expected.size} participant(s) ` + `(agents + primordial daemons), timeout ${this.timeoutMs}ms` ) } return(new Promise(resolve => { this.resolve = resolve this.timer = setTimeout(() => { const missing = [...this.expected].filter(id => !this.ready.has(id)) this.#finish({ ok: false, err: `Timeout waiting for readyToStart (${this.timeoutMs}ms); ` + `missing: ${missing.join(', ') || 'unknown'}`, }) }, this.timeoutMs) })) } handleMessage(msg, chan) { if(!this.active) return(false) if(typeof(this.matchesChan) !== 'function') return(false) if(!this.matchesChan(chan, this.ackChannel)) return(false) if(msg?.eventType !== 'readyToStart') return(false) const payload = msg.payload ?? {} if(payload.simulationId !== this.simulationId) return(false) const sender = msg.sender if(!sender || !this.expected.has(sender)) { if(this.debug) { console.warn(`[Maestro] Ignoring readyToStart from unexpected participant: ${sender}`) } return(true) } if(!payload.success) { this.#finish({ ok: false, err: payload.err ?? `Participant ${sender} failed prepare`, }) return(true) } this.ready.set(sender, payload) if(this.debug) { console.log( `[Maestro] readyToStart from ${sender} ` + `(${this.ready.size}/${this.expected.size})` ) } for(const participantId of this.expected) { if(!this.ready.has(participantId)) return(true) } this.#finish({ ok: true }) return(true) } cancel() { if(this.timer) { clearTimeout(this.timer) this.timer = null } this.active = false this.resolve = null this.expected.clear() this.ready.clear() this.simulationId = null } #finish(result) { const resolve = this.resolve this.cancel() if(typeof(resolve) === 'function') resolve(result) } }