jldb/lib/rep/replicator-open.go
2023-10-16 08:50:19 +00:00

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