Files
P42_godDaemons/Maestro/prepareQuorum.js
T
2026-06-20 18:50:26 +00:00

104 lines
3.0 KiB
JavaScript

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)
}
}