// run is the CSP-style main loop of the keyserver. All code critical for safe // persistence should be directly in run. All functions called from run should // either interpret data and modify their mutable arguments OR interact with the // network and disk, but not both. func (ks *Keyserver) run() { defer close(ks.stopped) var step proto.KeyserverStep wb := ks.db.NewBatch() for { select { case <-ks.stop: return case stepEntry := <-ks.log.WaitCommitted(): if stepEntry.ConfChange != nil { ks.log.ApplyConfChange(stepEntry.ConfChange) } stepBytes := stepEntry.Data if stepBytes == nil { continue // allow logs to skip slots for indexing purposes } if err := step.Unmarshal(stepBytes); err != nil { log.Panicf("invalid step pb in replicated log: %s", err) } // TODO: (for throughput) allow multiple steps per log entry // (pipelining). Maybe this would be better implemented at the log level? deferredIO := ks.step(&step, &ks.rs, wb) ks.rs.NextIndexLog++ wb.Put(tableReplicaState, proto.MustMarshal(&ks.rs)) if err := ks.db.Write(wb); err != nil { log.Panicf("sync step to db: %s", err) } wb.Reset() step.Reset() if deferredIO != nil { deferredIO() } case ks.leaderHint = <-ks.log.LeaderHintSet(): ks.updateEpochProposer() case <-ks.minEpochIntervalTimer.C: ks.minEpochIntervalPassed = true ks.updateEpochProposer() case <-ks.maxEpochIntervalTimer.C: ks.maxEpochIntervalPassed = true ks.updateEpochProposer() } } }