github.com/scottcagno/storage@v1.8.0/pkg/mmap/transaction/transaction.go (about)

     1  package transaction
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/scottcagno/storage/pkg/mmap/segment"
     6  	"math"
     7  	"runtime"
     8  )
     9  
    10  var (
    11  	ErrClosed      = fmt.Errorf("transaction: transaction closed")
    12  	ErrOutOfBounds = fmt.Errorf("transaction: out of bounds")
    13  )
    14  
    15  // Tx is a transaction on the raw byte data.
    16  type Tx struct {
    17  	// original specifies the raw byte data associated with this transaction.
    18  	original []byte
    19  	// lowOffset specifies the lowest offset, from start of the original,
    20  	// which is available for this transaction.
    21  	lowOffset int64
    22  	// highOffset specifies the highest offset plus one, from start of the original,
    23  	// which is available for this transaction.
    24  	highOffset int64
    25  	// snapshot specifies the snapshot of the original.
    26  	snapshot []byte
    27  	// segment specifies the lazily initialized data segment on top of the snapshot.
    28  	segment *segment.Segment
    29  }
    30  
    31  // Begin starts and returns a new transaction.
    32  // The given raw byte data starting from the given offset and ends after the given length
    33  // copies to the snapshot which is allocated into the heap.
    34  func Begin(data []byte, offset int64, length uintptr) (*Tx, error) {
    35  	if length == 0 || length > math.MaxInt64 {
    36  		return nil, ErrOutOfBounds
    37  	}
    38  	if offset < 0 || offset >= int64(len(data)) || offset > math.MaxInt64-int64(length) {
    39  		return nil, ErrOutOfBounds
    40  	}
    41  	highOffset := offset + int64(length)
    42  	if highOffset > int64(len(data)) {
    43  		return nil, ErrOutOfBounds
    44  	}
    45  	tx := &Tx{
    46  		original:   data,
    47  		lowOffset:  offset,
    48  		highOffset: highOffset,
    49  		snapshot:   make([]byte, length),
    50  	}
    51  	copy(tx.snapshot, data[tx.lowOffset:tx.highOffset])
    52  	runtime.SetFinalizer(tx, (*Tx).Rollback)
    53  	return tx, nil
    54  }
    55  
    56  // Segment returns the data segment on top of the snapshot.
    57  func (tx *Tx) Segment() *segment.Segment {
    58  	if tx.segment == nil {
    59  		tx.segment = segment.New(tx.lowOffset, tx.snapshot)
    60  	}
    61  	return tx.segment
    62  }
    63  
    64  // offset checks given offset and length to match the available bounds and returns the relative offset
    65  // from start of the segment data or ErrOutOfBounds error at the access violation.
    66  func (tx *Tx) offset(offset int64, length int) (int64, error) {
    67  	if offset < tx.lowOffset {
    68  		return 0, ErrOutOfBounds
    69  	}
    70  	offset -= tx.lowOffset
    71  	if offset > math.MaxInt64-int64(length) || offset+int64(length) > tx.highOffset {
    72  		return 0, ErrOutOfBounds
    73  	}
    74  	return offset, nil
    75  }
    76  
    77  // ReadAt reads len(buf) bytes at given offset from start of the original from the snapshot.
    78  // If the given offset is out of the available bounds or there are not enough bytes to read
    79  // the ErrOutOfBounds error will be returned. Otherwise len(buf) will be returned with no errors.
    80  // ReadAt implements the io.ReaderAt interface.
    81  func (tx *Tx) ReadAt(buf []byte, offset int64) (int, error) {
    82  	if tx.snapshot == nil {
    83  		return 0, ErrClosed
    84  	}
    85  	off, err := tx.offset(offset, len(buf))
    86  	if err != nil {
    87  		return 0, err
    88  	}
    89  	return copy(buf, tx.snapshot[off:]), nil
    90  }
    91  
    92  // WriteAt writes len(buf) bytes at given offset from start of the original into the snapshot.
    93  // If the given offset is out of the available bounds or there are not enough space to write all given bytes
    94  // the ErrOutOfBounds error will be returned. Otherwise len(buf) will be returned with no errors.
    95  // WriteAt implements the io.WriterAt interface.
    96  func (tx *Tx) WriteAt(buf []byte, offset int64) (int, error) {
    97  	if tx.snapshot == nil {
    98  		return 0, ErrClosed
    99  	}
   100  	off, err := tx.offset(offset, len(buf))
   101  	if err != nil {
   102  		return 0, err
   103  	}
   104  	return copy(tx.snapshot[off:], buf), nil
   105  }
   106  
   107  // Commit flushes the snapshot to the original, closes this transaction
   108  // and frees all resources associated with it.
   109  func (tx *Tx) Commit() error {
   110  	if tx.snapshot == nil {
   111  		return ErrClosed
   112  	}
   113  	copy(tx.original[tx.lowOffset:tx.highOffset], tx.snapshot)
   114  	tx.snapshot = nil
   115  	return nil
   116  }
   117  
   118  // Rollback closes this transaction and frees all resources associated with it.
   119  func (tx *Tx) Rollback() error {
   120  	if tx.snapshot == nil {
   121  		return ErrClosed
   122  	}
   123  	tx.snapshot = nil
   124  	return nil
   125  }