package rep import ( "git.crumpington.com/public/jldb/lib/atomicheader" "git.crumpington.com/public/jldb/lib/errs" "git.crumpington.com/public/jldb/lib/flock" "git.crumpington.com/public/jldb/lib/wal" "os" "time" ) func (rep *Replicator) loadConfigDefaults() { conf := rep.conf if conf.NetTimeout <= 0 { conf.NetTimeout = time.Minute } if conf.WALSegMinCount <= 0 { conf.WALSegMinCount = 1024 } if conf.WALSegMaxAgeSec <= 0 { conf.WALSegMaxAgeSec = 3600 } if conf.WALSegGCAgeSec <= 0 { conf.WALSegGCAgeSec = 7 * 86400 } rep.conf = conf rep.pskBytes = make([]byte, 256) copy(rep.pskBytes, []byte(conf.ReplicationPSK)) } func (rep *Replicator) initDirectories() error { if err := os.MkdirAll(walRootDir(rep.conf.RootDir), 0700); err != nil { return errs.IO.WithErr(err) } return nil } func (rep *Replicator) acquireLock() error { lockFile, err := flock.TryLock(lockFilePath(rep.conf.RootDir)) if err != nil { return errs.IO.WithMsg("locked: %s", lockFilePath(rep.conf.RootDir)) } if lockFile == nil { return errs.Locked } rep.lockFile = lockFile return nil } func (rep *Replicator) loadLocalState() error { f, err := os.OpenFile(stateFilePath(rep.conf.RootDir), os.O_RDWR|os.O_CREATE, 0600) if err != nil { return errs.IO.WithErr(err) } info, err := f.Stat() if err != nil { f.Close() return errs.IO.WithErr(err) } if info.Size() < atomicheader.ReservedBytes { if err := atomicheader.Init(f); err != nil { f.Close() return errs.IO.WithErr(err) } } rep.stateHandler, err = atomicheader.Open(f) if err != nil { f.Close() return err } rep.stateFile = f var state localState err = rep.stateHandler.Read(func(page []byte) error { state.readFrom(page) return nil }) if err == nil { rep.state.Store(&state) return nil } // Write a clean state. state = localState{} rep.state.Store(&state) return rep.stateHandler.Write(func(page []byte) error { state.writeTo(page) return nil }) } func (rep *Replicator) walConfig() wal.Config { return wal.Config{ SegMinCount: rep.conf.WALSegMinCount, SegMaxAgeSec: rep.conf.WALSegMaxAgeSec, } } func (rep *Replicator) openWAL() (err error) { rep.wal, err = wal.Open(walRootDir(rep.conf.RootDir), rep.walConfig()) if err != nil { rep.wal, err = wal.Create(walRootDir(rep.conf.RootDir), 1, rep.walConfig()) if err != nil { return err } } return nil } func (rep *Replicator) recvStateIfNecessary() error { if rep.conf.Primary { return nil } sInfo := rep.Info() pInfo, err := rep.client.GetInfo() if err != nil { return err } if pInfo.WALFirstSeqNum <= sInfo.WALLastSeqNum { return nil } // Make a new WAL. rep.wal.Close() if err = rep.client.RecvState(rep.recvState); err != nil { return err } state := rep.getState() rep.wal, err = wal.Create(walRootDir(rep.conf.RootDir), state.SeqNum+1, rep.walConfig()) return err } // Replays un-acked entries in the WAL. Acks after all records are replayed. func (rep *Replicator) replay() error { state := rep.getState() it, err := rep.wal.Iterator(state.SeqNum + 1) if err != nil { return err } defer it.Close() for it.Next(0) { rec := it.Record() if err := rep.app.Replay(rec); err != nil { return err } state.SeqNum = rec.SeqNum state.TimestampMS = rec.TimestampMS } if it.Error() != nil { return it.Error() } return rep.ack(state.SeqNum, state.TimestampMS) } func (rep *Replicator) startWALGC() { rep.done.Add(1) go rep.runWALGC() } func (rep *Replicator) startWALFollower() { rep.done.Add(1) go rep.runWALFollower() } func (rep *Replicator) startWALRecvr() { rep.done.Add(1) go rep.runWALRecvr() }