github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/stateio/writer.go (about)

     1  // Copyright 2019 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package stateio
     6  
     7  import (
     8  	"encoding/binary"
     9  	"io"
    10  	"os"
    11  
    12  	"github.com/Schaudge/grailbase/logio"
    13  )
    14  
    15  type syncer interface {
    16  	Sync() error
    17  }
    18  
    19  // Writer writes snapshots and update entries to an underlying log stream.
    20  type Writer struct {
    21  	syncer syncer
    22  	log    *logio.Writer
    23  	epoch  uint64
    24  }
    25  
    26  // NewWriter initializes and returns a new state log writer which
    27  // writes to the stream w, which is positioned at the provided
    28  // offset. The provided epoch must be the current epoch of the log
    29  // file. If the provided io.Writer is also a syncer:
    30  //
    31  //	type Syncer interface {
    32  //		Sync() error
    33  //	}
    34  //
    35  // Then Sync() is called (and errors returned) after each log entry
    36  // has been written.
    37  func NewWriter(w io.Writer, off int64, epoch uint64) *Writer {
    38  	wr := &Writer{log: logio.NewWriter(w, off), epoch: epoch}
    39  	if s, ok := w.(syncer); ok {
    40  		wr.syncer = s
    41  	}
    42  	return wr
    43  }
    44  
    45  // NewFileWriter initializes a state log writer from the provided
    46  // os file.  The file's contents is committed to stable storage
    47  // after each log write.
    48  func NewFileWriter(file *os.File) (*Writer, error) {
    49  	off, err := file.Seek(0, io.SeekEnd)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	_, epoch, _, err := Restore(file, off)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	off, err = file.Seek(0, io.SeekEnd)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	return NewWriter(file, off, epoch), nil
    62  }
    63  
    64  // Snapshot writes a new snapshot to the state log.
    65  // Subsequent updates are based on this snapshot.
    66  func (w *Writer) Snapshot(snap []byte) error {
    67  	off := w.log.Tell()
    68  	entry := make([]byte, len(snap)+9)
    69  	entry[0] = entrySnap
    70  	binary.LittleEndian.PutUint64(entry[1:], w.epoch)
    71  	copy(entry[9:], snap)
    72  	if err := w.log.Append(entry); err != nil {
    73  		return err
    74  	}
    75  	w.epoch = uint64(off)
    76  	return w.sync()
    77  }
    78  
    79  // Update writes a new state update to the log. The update
    80  // refers to the last snapshot written.
    81  func (w *Writer) Update(update []byte) error {
    82  	entry := make([]byte, 9+len(update))
    83  	entry[0] = entryUpdate
    84  	binary.LittleEndian.PutUint64(entry[1:], uint64(w.epoch))
    85  	copy(entry[9:], update)
    86  	if err := w.log.Append(entry); err != nil {
    87  		return err
    88  	}
    89  	return w.sync()
    90  }
    91  
    92  func (w *Writer) sync() error {
    93  	if w.syncer == nil {
    94  		return nil
    95  	}
    96  	return w.syncer.Sync()
    97  }