fox-wamp
Version:
Web Application Message Router/Server WAMP/MQTT
216 lines (184 loc) • 6.9 kB
text/typescript
import MSG from '../messages'
import { BaseRealm } from '../realm'
import { HyperClient } from '../hyper/client'
import { keyDate, ProduceId, keyComplexId } from './makeid'
import { ComplexId } from './makeid'
import { Event, BODY_PICK_CHALLENGER, BODY_GENERATE_DRAFT, BODY_ELECT_SEGMENT, BODY_ADVANCE_SEGMENT_RESOLVED } from './hyper.h'
type AdvanceStage = {
advanceOwner: string
advanceSegment: string
draftOwner: string
draftId: ComplexId
vouters: Set<string>
}
interface Headers {
advanceOwner?: string
advanceSegment?: string
draftOwner?: string
draftId?: ComplexId
step?: any
maxId?: ComplexId
}
export class StageOneTask {
private realm: BaseRealm
private syncQuorum: number
private advanceMap: Map<string, AdvanceStage> = new Map()
private makeId: ProduceId
private recentValue: string = ''
private advanceIdHeap: Map<string, Set<string>> = new Map()
private doneHeap: Map<string, Set<string>> = new Map()
private draftHeap: Map<string, string[]> = new Map() // draftOwner -> set of draftId
private api: HyperClient
constructor(sysRealm: BaseRealm, syncQuorum: number) {
this.realm = sysRealm
this.syncQuorum = syncQuorum
this.makeId = new ProduceId((date: any) => keyDate(date))
// build api before new session handler to not to be caught
this.api = sysRealm.buildApi()
sysRealm.on(MSG.SESSION_JOIN, (session: any) => {})
sysRealm.on(MSG.SESSION_LEAVE, (session: any) => {})
this.api.subscribe(Event.GENERATE_DRAFT, this.event_generate_draft.bind(this))
this.api.subscribe(Event.PICK_CHALLENGER, this.event_pick_challenger.bind(this))
}
listenEntry(client: HyperClient) {
client.pipe(this.api, Event.GENERATE_DRAFT, {exclude_me: false})
}
listenStageOne(client: HyperClient) {
client.pipe(this.api, Event.PICK_CHALLENGER, {exclude_me: false})
}
// generate new segment id for each advanceId
// if advanceId is duplicated new segment is not generated
// input headers: advanceOwner, advanceSegment
event_generate_draft(body: BODY_GENERATE_DRAFT) {
const advanceId = body.advanceOwner + ':' + body.advanceSegment
if (!this.advanceMap.has(advanceId)) {
const draftId: ComplexId = this.makeId.generateIdRec()
const draftOwner: string = this.realm.getRouter().getId()
const vouters = new Set<string>()
vouters.add(draftOwner)
const stage: AdvanceStage = {
advanceOwner: body.advanceOwner, advanceSegment: body.advanceSegment, draftOwner, draftId, vouters
}
this.advanceMap.set(advanceId, stage)
const draftSegment: BODY_PICK_CHALLENGER = {
advanceOwner: body.advanceOwner, advanceSegment: body.advanceSegment, draftOwner, draftId
}
this.api.publish(Event.PICK_CHALLENGER, draftSegment, {exclude_me: false})
}
}
// when another generator made draft shift my generator
event_pick_challenger(body: BODY_PICK_CHALLENGER) {
this.makeId.reconcilePos(body.draftId.dt, body.draftId.id)
const draftOwner = body.draftOwner
const advanceOwner = body.advanceOwner
const advanceSegment = body.advanceSegment
const advanceId = advanceOwner + ':' + advanceSegment
const draftId = keyComplexId(body.draftId)
if (!this.draftHeap.has(draftOwner)) {
this.draftHeap.set(draftOwner, [])
}
const draftStack = this.draftHeap.get(draftOwner)!
draftStack.push(draftId)
if (this.doneHeap.has(advanceId)) {
const vouterSet = this.doneHeap.get(advanceId)!
vouterSet.add(draftOwner)
return
}
if (!this.advanceIdHeap.has(advanceId)) {
this.advanceIdHeap.set(advanceId, new Set())
}
const vouterSet = this.advanceIdHeap.get(advanceId)!
vouterSet.add(draftOwner)
if (vouterSet.size >= this.syncQuorum) {
this.advanceIdHeap.delete(advanceId)
this.doneHeap.set(advanceId, vouterSet)
const challengerBody: BODY_ELECT_SEGMENT = {
advanceOwner,
advanceSegment,
challenger: this.extractDraft(vouterSet)
}
this.api.publish(Event.ELECT_SEGMENT, challengerBody, {exclude_me: false})
}
}
reconcilePos(segment: string, offset: number): boolean {
return this.makeId.reconcilePos(segment, offset)
}
startActualizePrefixTimer() {
this.makeId.actualizePrefix()
setInterval(() => {
this.makeId.actualizePrefix()
}, 7000)
}
getRecentValue() {
return this.recentValue
}
setRecentValue(newRecentValue: string) {
if (this.recentValue > newRecentValue) {
throw Error('failed to set recentValue: "' + this.recentValue + '">"' + newRecentValue + '"')
}
this.recentValue = newRecentValue
}
// extract minimal from draftHeap and save it to recentValue
extractDraft(vouters: Set<string>): string {
let minValue: string | undefined
for (const curVouter of vouters.values()) {
const stack: string[] = this.draftHeap.get(curVouter)!
const cur = stack[0]
minValue = (minValue && minValue < cur) ? minValue : cur
}
if (!minValue) {
throw Error('No draft found in draftHeap for vouters: ' + Array.from(vouters).join(', '))
}
for ( const curHeap of this.draftHeap.values()) {
// remove all less or equal values
while (curHeap.length > 0 && curHeap[0] <= minValue) {
curHeap.shift()
}
}
this.setRecentValue(minValue)
return minValue
}
}
export class StageTwoTask {
private realm: BaseRealm
private syncQuorum: number
private api: HyperClient
private readyQuorum: Map<string, Set<string>> = new Map() // advanceSegment -> set of readyId
private recentValue: string = ''
constructor(sysRealm: BaseRealm, syncQuorum: number) {
this.realm = sysRealm
this.syncQuorum = syncQuorum
this.api = sysRealm.buildApi()
this.api.subscribe(Event.ELECT_SEGMENT, (body: BODY_ELECT_SEGMENT, opts: any) => {
console.log('=> Event.ELECT_SEGMENT', body)
this.event_elect_segment(body)
})
}
listenStageOne(client: HyperClient) {
client.pipe(this.api, Event.ELECT_SEGMENT, {exclude_me: false})
}
event_elect_segment(body: BODY_ELECT_SEGMENT) {
const advanceSegment = body.advanceSegment
const challenger = body.challenger
if (!this.readyQuorum.has(advanceSegment)) {
this.readyQuorum.set(advanceSegment, new Set())
}
const readySet = this.readyQuorum.get(advanceSegment)!
readySet.add(challenger)
if (readySet.size >= this.syncQuorum) {
this.readyQuorum.delete(advanceSegment)
let maxValue = '';
readySet.forEach((id) => {
if (maxValue === '' || maxValue < id) {
maxValue = id
}
})
const msg: BODY_ADVANCE_SEGMENT_RESOLVED = {
advanceSegment,
advanceOwner: body.advanceOwner,
segment: maxValue
}
this.api.publish(Event.ADVANCE_SEGMENT_RESOLVED, msg, {})
}
}
}