184 lines
3.5 KiB
Go
184 lines
3.5 KiB
Go
package rep
|
|
|
|
import (
|
|
"os"
|
|
"time"
|
|
|
|
"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"
|
|
)
|
|
|
|
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 = pskToBytes(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()
|
|
}
|