rename WAL#Flush to WAL#FlushAndSync (#3345)

* rename WAL#Flush to WAL#FlushAndSync

- rename auto#Flush to auto#FlushAndSync
- cleanup WAL interface to not leak implementation details!
  * remove Group()
  * add WALReader interface and return it in SearchForEndHeight()
- add interface assertions

Refs #3337

* replace WALReader with io.ReadCloser
This commit is contained in:
Anton Kaliaev 2019-02-25 09:11:07 +04:00 committed by GitHub
parent 6797d85851
commit ec9bff5234
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 76 additions and 62 deletions

View File

@ -19,7 +19,6 @@ import (
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
cfg "github.com/tendermint/tendermint/config" cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
auto "github.com/tendermint/tendermint/libs/autofile"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/privval"
@ -201,6 +200,8 @@ type crashingWAL struct {
lastPanickedForMsgIndex int // last message for which we panicked lastPanickedForMsgIndex int // last message for which we panicked
} }
var _ WAL = &crashingWAL{}
// WALWriteError indicates a WAL crash. // WALWriteError indicates a WAL crash.
type WALWriteError struct { type WALWriteError struct {
msg string msg string
@ -248,15 +249,15 @@ func (w *crashingWAL) WriteSync(m WALMessage) {
w.Write(m) w.Write(m)
} }
func (w *crashingWAL) Group() *auto.Group { return w.next.Group() } func (w *crashingWAL) FlushAndSync() error { return w.next.FlushAndSync() }
func (w *crashingWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
func (w *crashingWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) {
return w.next.SearchForEndHeight(height, options) return w.next.SearchForEndHeight(height, options)
} }
func (w *crashingWAL) Start() error { return w.next.Start() } func (w *crashingWAL) Start() error { return w.next.Start() }
func (w *crashingWAL) Stop() error { return w.next.Stop() } func (w *crashingWAL) Stop() error { return w.next.Stop() }
func (w *crashingWAL) Wait() { w.next.Wait() } func (w *crashingWAL) Wait() { w.next.Wait() }
func (w *crashingWAL) Flush() error { return w.Group().Flush() }
//------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------
// Handshake Tests // Handshake Tests

View File

@ -910,7 +910,7 @@ func (cs *ConsensusState) defaultDecideProposal(height int64, round int) {
} }
// Flush the WAL. Otherwise, we may not recompute the same proposal to sign, and the privValidator will refuse to sign anything. // Flush the WAL. Otherwise, we may not recompute the same proposal to sign, and the privValidator will refuse to sign anything.
cs.wal.Flush() cs.wal.FlushAndSync()
// Make proposal // Make proposal
propBlockId := types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()} propBlockId := types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()}
@ -1678,7 +1678,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool,
func (cs *ConsensusState) signVote(type_ types.SignedMsgType, hash []byte, header types.PartSetHeader) (*types.Vote, error) { func (cs *ConsensusState) signVote(type_ types.SignedMsgType, hash []byte, header types.PartSetHeader) (*types.Vote, error) {
// Flush the WAL. Otherwise, we may not recompute the same vote to sign, and the privValidator will refuse to sign anything. // Flush the WAL. Otherwise, we may not recompute the same vote to sign, and the privValidator will refuse to sign anything.
cs.wal.Flush() cs.wal.FlushAndSync()
addr := cs.privValidator.GetPubKey().Address() addr := cs.privValidator.GetPubKey().Address()
valIndex, _ := cs.Validators.GetByAddress(addr) valIndex, _ := cs.Validators.GetByAddress(addr)

View File

@ -57,10 +57,11 @@ func RegisterWALMessages(cdc *amino.Codec) {
type WAL interface { type WAL interface {
Write(WALMessage) Write(WALMessage)
WriteSync(WALMessage) WriteSync(WALMessage)
Group() *auto.Group FlushAndSync() error
SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error)
Flush() error
SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error)
// service methods
Start() error Start() error
Stop() error Stop() error
Wait() Wait()
@ -82,6 +83,8 @@ type baseWAL struct {
flushInterval time.Duration flushInterval time.Duration
} }
var _ WAL = &baseWAL{}
// NewWAL returns a new write-ahead logger based on `baseWAL`, which implements // NewWAL returns a new write-ahead logger based on `baseWAL`, which implements
// WAL. It's flushed and synced to disk every 2s and once when stopped. // WAL. It's flushed and synced to disk every 2s and once when stopped.
func NewWAL(walFile string, groupOptions ...func(*auto.Group)) (*baseWAL, error) { func NewWAL(walFile string, groupOptions ...func(*auto.Group)) (*baseWAL, error) {
@ -125,16 +128,19 @@ func (wal *baseWAL) OnStart() error {
wal.WriteSync(EndHeightMessage{0}) wal.WriteSync(EndHeightMessage{0})
} }
err = wal.group.Start() err = wal.group.Start()
if err != nil {
return err
}
wal.flushTicker = time.NewTicker(wal.flushInterval) wal.flushTicker = time.NewTicker(wal.flushInterval)
go wal.processFlushTicks() go wal.processFlushTicks()
return err return nil
} }
func (wal *baseWAL) processFlushTicks() { func (wal *baseWAL) processFlushTicks() {
for { for {
select { select {
case <-wal.flushTicker.C: case <-wal.flushTicker.C:
if err := wal.Flush(); err != nil { if err := wal.FlushAndSync(); err != nil {
wal.Logger.Error("Periodic WAL flush failed", "err", err) wal.Logger.Error("Periodic WAL flush failed", "err", err)
} }
case <-wal.Quit(): case <-wal.Quit():
@ -143,9 +149,10 @@ func (wal *baseWAL) processFlushTicks() {
} }
} }
// Flush will attempt to flush and fsync the underlying group's data to disk. // FlushAndSync flushes and fsync's the underlying group's data to disk.
func (wal *baseWAL) Flush() error { // See auto#FlushAndSync
return wal.group.Flush() func (wal *baseWAL) FlushAndSync() error {
return wal.group.FlushAndSync()
} }
// Stop the underlying autofile group. // Stop the underlying autofile group.
@ -153,7 +160,7 @@ func (wal *baseWAL) Flush() error {
// before cleaning up files. // before cleaning up files.
func (wal *baseWAL) OnStop() { func (wal *baseWAL) OnStop() {
wal.flushTicker.Stop() wal.flushTicker.Stop()
wal.Flush() wal.FlushAndSync()
wal.group.Stop() wal.group.Stop()
wal.group.Close() wal.group.Close()
} }
@ -187,7 +194,7 @@ func (wal *baseWAL) WriteSync(msg WALMessage) {
} }
wal.Write(msg) wal.Write(msg)
if err := wal.Flush(); err != nil { if err := wal.FlushAndSync(); err != nil {
panic(fmt.Sprintf("Error flushing consensus wal buf to file. Error: %v \n", err)) panic(fmt.Sprintf("Error flushing consensus wal buf to file. Error: %v \n", err))
} }
} }
@ -203,8 +210,11 @@ type WALSearchOptions struct {
// Group reader will be nil if found equals false. // Group reader will be nil if found equals false.
// //
// CONTRACT: caller must close group reader. // CONTRACT: caller must close group reader.
func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) { func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) {
var msg *TimedWALMessage var (
msg *TimedWALMessage
gr *auto.GroupReader
)
lastHeightFound := int64(-1) lastHeightFound := int64(-1)
// NOTE: starting from the last file in the group because we're usually // NOTE: starting from the last file in the group because we're usually
@ -371,13 +381,14 @@ func (dec *WALDecoder) Decode() (*TimedWALMessage, error) {
type nilWAL struct{} type nilWAL struct{}
var _ WAL = nilWAL{}
func (nilWAL) Write(m WALMessage) {} func (nilWAL) Write(m WALMessage) {}
func (nilWAL) WriteSync(m WALMessage) {} func (nilWAL) WriteSync(m WALMessage) {}
func (nilWAL) Group() *auto.Group { return nil } func (nilWAL) FlushAndSync() error { return nil }
func (nilWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) { func (nilWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) {
return nil, false, nil return nil, false, nil
} }
func (nilWAL) Start() error { return nil } func (nilWAL) Start() error { return nil }
func (nilWAL) Stop() error { return nil } func (nilWAL) Stop() error { return nil }
func (nilWAL) Wait() {} func (nilWAL) Wait() {}
func (nilWAL) Flush() error { return nil }

View File

@ -13,7 +13,6 @@ import (
"github.com/tendermint/tendermint/abci/example/kvstore" "github.com/tendermint/tendermint/abci/example/kvstore"
bc "github.com/tendermint/tendermint/blockchain" bc "github.com/tendermint/tendermint/blockchain"
cfg "github.com/tendermint/tendermint/config" cfg "github.com/tendermint/tendermint/config"
auto "github.com/tendermint/tendermint/libs/autofile"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
@ -192,14 +191,12 @@ func (w *byteBufferWAL) WriteSync(m WALMessage) {
w.Write(m) w.Write(m)
} }
func (w *byteBufferWAL) Group() *auto.Group { func (w *byteBufferWAL) FlushAndSync() error { return nil }
panic("not implemented")
} func (w *byteBufferWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) {
func (w *byteBufferWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
return nil, false, nil return nil, false, nil
} }
func (w *byteBufferWAL) Start() error { return nil } func (w *byteBufferWAL) Start() error { return nil }
func (w *byteBufferWAL) Stop() error { return nil } func (w *byteBufferWAL) Stop() error { return nil }
func (w *byteBufferWAL) Wait() {} func (w *byteBufferWAL) Wait() {}
func (w *byteBufferWAL) Flush() error { return nil }

View File

@ -32,8 +32,10 @@ func TestWALTruncate(t *testing.T) {
walFile := filepath.Join(walDir, "wal") walFile := filepath.Join(walDir, "wal")
//this magic number 4K can truncate the content when RotateFile. defaultHeadSizeLimit(10M) is hard to simulate. // this magic number 4K can truncate the content when RotateFile.
//this magic number 1 * time.Millisecond make RotateFile check frequently. defaultGroupCheckDuration(5s) is hard to simulate. // defaultHeadSizeLimit(10M) is hard to simulate.
// this magic number 1 * time.Millisecond make RotateFile check frequently.
// defaultGroupCheckDuration(5s) is hard to simulate.
wal, err := NewWAL(walFile, wal, err := NewWAL(walFile,
autofile.GroupHeadSizeLimit(4096), autofile.GroupHeadSizeLimit(4096),
autofile.GroupCheckDuration(1*time.Millisecond), autofile.GroupCheckDuration(1*time.Millisecond),
@ -49,14 +51,15 @@ func TestWALTruncate(t *testing.T) {
wal.Wait() wal.Wait()
}() }()
//60 block's size nearly 70K, greater than group's headBuf size(4096 * 10), when headBuf is full, truncate content will Flush to the file. // 60 block's size nearly 70K, greater than group's headBuf size(4096 * 10),
//at this time, RotateFile is called, truncate content exist in each file. // when headBuf is full, truncate content will Flush to the file. at this
// time, RotateFile is called, truncate content exist in each file.
err = WALGenerateNBlocks(t, wal.Group(), 60) err = WALGenerateNBlocks(t, wal.Group(), 60)
require.NoError(t, err) require.NoError(t, err)
time.Sleep(1 * time.Millisecond) //wait groupCheckDuration, make sure RotateFile run time.Sleep(1 * time.Millisecond) //wait groupCheckDuration, make sure RotateFile run
wal.Group().Flush() wal.FlushAndSync()
h := int64(50) h := int64(50)
gr, found, err := wal.SearchForEndHeight(h, &WALSearchOptions{}) gr, found, err := wal.SearchForEndHeight(h, &WALSearchOptions{})

View File

@ -70,7 +70,7 @@ func main() {
for { for {
n, err := os.Stdin.Read(buf) n, err := os.Stdin.Read(buf)
group.Write(buf[:n]) group.Write(buf[:n])
group.Flush() group.FlushAndSync()
if err != nil { if err != nil {
group.Stop() group.Stop()
if err == io.EOF { if err == io.EOF {

View File

@ -131,21 +131,23 @@ func GroupTotalSizeLimit(limit int64) func(*Group) {
} }
} }
// OnStart implements Service by starting the goroutine that checks file and // OnStart implements cmn.Service by starting the goroutine that checks file
// group limits. // and group limits.
func (g *Group) OnStart() error { func (g *Group) OnStart() error {
g.ticker = time.NewTicker(g.groupCheckDuration) g.ticker = time.NewTicker(g.groupCheckDuration)
go g.processTicks() go g.processTicks()
return nil return nil
} }
// OnStop implements Service by stopping the goroutine described above. // OnStop implements cmn.Service by stopping the goroutine described above.
// NOTE: g.Head must be closed separately using Close. // NOTE: g.Head must be closed separately using Close.
func (g *Group) OnStop() { func (g *Group) OnStop() {
g.ticker.Stop() g.ticker.Stop()
g.Flush() // flush any uncommitted data g.FlushAndSync()
} }
// Wait blocks until all internal goroutines are finished. Supposed to be
// called after Stop.
func (g *Group) Wait() { func (g *Group) Wait() {
// wait for processTicks routine to finish // wait for processTicks routine to finish
<-g.doneProcessTicks <-g.doneProcessTicks
@ -153,7 +155,7 @@ func (g *Group) Wait() {
// Close closes the head file. The group must be stopped by this moment. // Close closes the head file. The group must be stopped by this moment.
func (g *Group) Close() { func (g *Group) Close() {
g.Flush() // flush any uncommitted data g.FlushAndSync()
g.mtx.Lock() g.mtx.Lock()
_ = g.Head.closeFile() _ = g.Head.closeFile()
@ -216,9 +218,9 @@ func (g *Group) Buffered() int {
return g.headBuf.Buffered() return g.headBuf.Buffered()
} }
// Flush writes any buffered data to the underlying file and commits the // FlushAndSync writes any buffered data to the underlying file and commits the
// current content of the file to stable storage. // current content of the file to stable storage (fsync).
func (g *Group) Flush() error { func (g *Group) FlushAndSync() error {
g.mtx.Lock() g.mtx.Lock()
defer g.mtx.Unlock() defer g.mtx.Unlock()
err := g.headBuf.Flush() err := g.headBuf.Flush()

View File

@ -55,7 +55,7 @@ func TestCheckHeadSizeLimit(t *testing.T) {
err := g.WriteLine(cmn.RandStr(999)) err := g.WriteLine(cmn.RandStr(999))
require.NoError(t, err, "Error appending to head") require.NoError(t, err, "Error appending to head")
} }
g.Flush() g.FlushAndSync()
assertGroupInfo(t, g.ReadGroupInfo(), 0, 0, 999000, 999000) assertGroupInfo(t, g.ReadGroupInfo(), 0, 0, 999000, 999000)
// Even calling checkHeadSizeLimit manually won't rotate it. // Even calling checkHeadSizeLimit manually won't rotate it.
@ -65,7 +65,7 @@ func TestCheckHeadSizeLimit(t *testing.T) {
// Write 1000 more bytes. // Write 1000 more bytes.
err := g.WriteLine(cmn.RandStr(999)) err := g.WriteLine(cmn.RandStr(999))
require.NoError(t, err, "Error appending to head") require.NoError(t, err, "Error appending to head")
g.Flush() g.FlushAndSync()
// Calling checkHeadSizeLimit this time rolls it. // Calling checkHeadSizeLimit this time rolls it.
g.checkHeadSizeLimit() g.checkHeadSizeLimit()
@ -74,7 +74,7 @@ func TestCheckHeadSizeLimit(t *testing.T) {
// Write 1000 more bytes. // Write 1000 more bytes.
err = g.WriteLine(cmn.RandStr(999)) err = g.WriteLine(cmn.RandStr(999))
require.NoError(t, err, "Error appending to head") require.NoError(t, err, "Error appending to head")
g.Flush() g.FlushAndSync()
// Calling checkHeadSizeLimit does nothing. // Calling checkHeadSizeLimit does nothing.
g.checkHeadSizeLimit() g.checkHeadSizeLimit()
@ -85,7 +85,7 @@ func TestCheckHeadSizeLimit(t *testing.T) {
err = g.WriteLine(cmn.RandStr(999)) err = g.WriteLine(cmn.RandStr(999))
require.NoError(t, err, "Error appending to head") require.NoError(t, err, "Error appending to head")
} }
g.Flush() g.FlushAndSync()
assertGroupInfo(t, g.ReadGroupInfo(), 0, 1, 2000000, 1000000) assertGroupInfo(t, g.ReadGroupInfo(), 0, 1, 2000000, 1000000)
// Calling checkHeadSizeLimit rolls it again. // Calling checkHeadSizeLimit rolls it again.
@ -95,7 +95,7 @@ func TestCheckHeadSizeLimit(t *testing.T) {
// Write 1000 more bytes. // Write 1000 more bytes.
_, err = g.Head.Write([]byte(cmn.RandStr(999) + "\n")) _, err = g.Head.Write([]byte(cmn.RandStr(999) + "\n"))
require.NoError(t, err, "Error appending to head") require.NoError(t, err, "Error appending to head")
g.Flush() g.FlushAndSync()
assertGroupInfo(t, g.ReadGroupInfo(), 0, 2, 2001000, 1000) assertGroupInfo(t, g.ReadGroupInfo(), 0, 2, 2001000, 1000)
// Calling checkHeadSizeLimit does nothing. // Calling checkHeadSizeLimit does nothing.
@ -212,12 +212,12 @@ func TestRotateFile(t *testing.T) {
g.WriteLine("Line 1") g.WriteLine("Line 1")
g.WriteLine("Line 2") g.WriteLine("Line 2")
g.WriteLine("Line 3") g.WriteLine("Line 3")
g.Flush() g.FlushAndSync()
g.RotateFile() g.RotateFile()
g.WriteLine("Line 4") g.WriteLine("Line 4")
g.WriteLine("Line 5") g.WriteLine("Line 5")
g.WriteLine("Line 6") g.WriteLine("Line 6")
g.Flush() g.FlushAndSync()
// Read g.Head.Path+"000" // Read g.Head.Path+"000"
body1, err := ioutil.ReadFile(g.Head.Path + ".000") body1, err := ioutil.ReadFile(g.Head.Path + ".000")
@ -244,13 +244,13 @@ func TestFindLast1(t *testing.T) {
g.WriteLine("Line 2") g.WriteLine("Line 2")
g.WriteLine("# a") g.WriteLine("# a")
g.WriteLine("Line 3") g.WriteLine("Line 3")
g.Flush() g.FlushAndSync()
g.RotateFile() g.RotateFile()
g.WriteLine("Line 4") g.WriteLine("Line 4")
g.WriteLine("Line 5") g.WriteLine("Line 5")
g.WriteLine("Line 6") g.WriteLine("Line 6")
g.WriteLine("# b") g.WriteLine("# b")
g.Flush() g.FlushAndSync()
match, found, err := g.FindLast("#") match, found, err := g.FindLast("#")
assert.NoError(t, err) assert.NoError(t, err)
@ -267,14 +267,14 @@ func TestFindLast2(t *testing.T) {
g.WriteLine("Line 1") g.WriteLine("Line 1")
g.WriteLine("Line 2") g.WriteLine("Line 2")
g.WriteLine("Line 3") g.WriteLine("Line 3")
g.Flush() g.FlushAndSync()
g.RotateFile() g.RotateFile()
g.WriteLine("# a") g.WriteLine("# a")
g.WriteLine("Line 4") g.WriteLine("Line 4")
g.WriteLine("Line 5") g.WriteLine("Line 5")
g.WriteLine("# b") g.WriteLine("# b")
g.WriteLine("Line 6") g.WriteLine("Line 6")
g.Flush() g.FlushAndSync()
match, found, err := g.FindLast("#") match, found, err := g.FindLast("#")
assert.NoError(t, err) assert.NoError(t, err)
@ -293,12 +293,12 @@ func TestFindLast3(t *testing.T) {
g.WriteLine("Line 2") g.WriteLine("Line 2")
g.WriteLine("# b") g.WriteLine("# b")
g.WriteLine("Line 3") g.WriteLine("Line 3")
g.Flush() g.FlushAndSync()
g.RotateFile() g.RotateFile()
g.WriteLine("Line 4") g.WriteLine("Line 4")
g.WriteLine("Line 5") g.WriteLine("Line 5")
g.WriteLine("Line 6") g.WriteLine("Line 6")
g.Flush() g.FlushAndSync()
match, found, err := g.FindLast("#") match, found, err := g.FindLast("#")
assert.NoError(t, err) assert.NoError(t, err)
@ -315,12 +315,12 @@ func TestFindLast4(t *testing.T) {
g.WriteLine("Line 1") g.WriteLine("Line 1")
g.WriteLine("Line 2") g.WriteLine("Line 2")
g.WriteLine("Line 3") g.WriteLine("Line 3")
g.Flush() g.FlushAndSync()
g.RotateFile() g.RotateFile()
g.WriteLine("Line 4") g.WriteLine("Line 4")
g.WriteLine("Line 5") g.WriteLine("Line 5")
g.WriteLine("Line 6") g.WriteLine("Line 6")
g.Flush() g.FlushAndSync()
match, found, err := g.FindLast("#") match, found, err := g.FindLast("#")
assert.NoError(t, err) assert.NoError(t, err)
@ -336,7 +336,7 @@ func TestWrite(t *testing.T) {
written := []byte("Medusa") written := []byte("Medusa")
g.Write(written) g.Write(written)
g.Flush() g.FlushAndSync()
read := make([]byte, len(written)) read := make([]byte, len(written))
gr, err := g.NewReader(0) gr, err := g.NewReader(0)
@ -357,11 +357,11 @@ func TestGroupReaderRead(t *testing.T) {
professor := []byte("Professor Monster") professor := []byte("Professor Monster")
g.Write(professor) g.Write(professor)
g.Flush() g.FlushAndSync()
g.RotateFile() g.RotateFile()
frankenstein := []byte("Frankenstein's Monster") frankenstein := []byte("Frankenstein's Monster")
g.Write(frankenstein) g.Write(frankenstein)
g.Flush() g.FlushAndSync()
totalWrittenLength := len(professor) + len(frankenstein) totalWrittenLength := len(professor) + len(frankenstein)
read := make([]byte, totalWrittenLength) read := make([]byte, totalWrittenLength)
@ -386,12 +386,12 @@ func TestGroupReaderRead2(t *testing.T) {
professor := []byte("Professor Monster") professor := []byte("Professor Monster")
g.Write(professor) g.Write(professor)
g.Flush() g.FlushAndSync()
g.RotateFile() g.RotateFile()
frankenstein := []byte("Frankenstein's Monster") frankenstein := []byte("Frankenstein's Monster")
frankensteinPart := []byte("Frankenstein") frankensteinPart := []byte("Frankenstein")
g.Write(frankensteinPart) // note writing only a part g.Write(frankensteinPart) // note writing only a part
g.Flush() g.FlushAndSync()
totalLength := len(professor) + len(frankenstein) totalLength := len(professor) + len(frankenstein)
read := make([]byte, totalLength) read := make([]byte, totalLength)
@ -427,7 +427,7 @@ func TestMaxIndex(t *testing.T) {
assert.Zero(t, g.MaxIndex(), "MaxIndex should be zero at the beginning") assert.Zero(t, g.MaxIndex(), "MaxIndex should be zero at the beginning")
g.WriteLine("Line 1") g.WriteLine("Line 1")
g.Flush() g.FlushAndSync()
g.RotateFile() g.RotateFile()
assert.Equal(t, 1, g.MaxIndex(), "MaxIndex should point to the last file") assert.Equal(t, 1, g.MaxIndex(), "MaxIndex should point to the last file")