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 }