| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- // Copyright 2014 The lldb Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- // Structural transactions.
- package lldb
- //DONE+ TransactionalMemoryFiler
- // ----
- // Use NewRollbackFiler(myMemFiler, ...)
- /*
- bfBits: 3
- BenchmarkRollbackFiler 20000000 102 ns/op 9.73 MB/s
- bfBits: 4
- BenchmarkRollbackFiler 50000000 55.7 ns/op 17.95 MB/s
- bfBits: 5
- BenchmarkRollbackFiler 100000000 32.2 ns/op 31.06 MB/s
- bfBits: 6
- BenchmarkRollbackFiler 100000000 20.6 ns/op 48.46 MB/s
- bfBits: 7
- BenchmarkRollbackFiler 100000000 15.1 ns/op 66.12 MB/s
- bfBits: 8
- BenchmarkRollbackFiler 100000000 10.5 ns/op 95.66 MB/s
- bfBits: 9
- BenchmarkRollbackFiler 200000000 8.02 ns/op 124.74 MB/s
- bfBits: 10
- BenchmarkRollbackFiler 200000000 9.25 ns/op 108.09 MB/s
- bfBits: 11
- BenchmarkRollbackFiler 100000000 11.7 ns/op 85.47 MB/s
- bfBits: 12
- BenchmarkRollbackFiler 100000000 17.2 ns/op 57.99 MB/s
- bfBits: 13
- BenchmarkRollbackFiler 100000000 32.7 ns/op 30.58 MB/s
- bfBits: 14
- BenchmarkRollbackFiler 50000000 39.6 ns/op 25.27 MB/s
- */
- import (
- "fmt"
- "io"
- "sync"
- "github.com/cznic/fileutil"
- "github.com/cznic/internal/buffer"
- "github.com/cznic/mathutil"
- )
- var (
- _ Filer = &bitFiler{} // Ensure bitFiler is a Filer.
- _ Filer = &RollbackFiler{} // ditto
- )
- const (
- bfBits = 12
- bfSize = 1 << bfBits
- bfMask = bfSize - 1
- )
- type (
- bitPage struct {
- prev, next *bitPage
- pdata *[]byte
- data []byte
- dirty bool
- }
- bitFilerMap map[int64]*bitPage
- bitFiler struct {
- parent Filer
- m bitFilerMap
- size int64
- }
- )
- func newBitFiler(parent Filer) (f *bitFiler, err error) {
- sz, err := parent.Size()
- if err != nil {
- return
- }
- return &bitFiler{parent: parent, m: bitFilerMap{}, size: sz}, nil
- }
- func (f *bitFiler) BeginUpdate() error { panic("internal error") }
- func (f *bitFiler) EndUpdate() error { panic("internal error") }
- func (f *bitFiler) Rollback() error { panic("internal error") }
- func (f *bitFiler) Sync() error { panic("internal error") }
- func (f *bitFiler) Close() (err error) { return }
- func (f *bitFiler) Name() string { return fmt.Sprintf("%p.bitfiler", f) }
- func (f *bitFiler) Size() (int64, error) { return f.size, nil }
- func (f *bitFiler) free() {
- for _, pg := range f.m {
- buffer.Put(pg.pdata)
- }
- }
- func (f *bitFiler) PunchHole(off, size int64) (err error) {
- first := off >> bfBits
- if off&bfMask != 0 {
- first++
- }
- off += size - 1
- last := off >> bfBits
- if off&bfMask != 0 {
- last--
- }
- if limit := f.size >> bfBits; last > limit {
- last = limit
- }
- for pgI := first; pgI <= last; pgI++ {
- pg := &bitPage{}
- pg.pdata = buffer.CGet(bfSize)
- pg.data = *pg.pdata
- pg.dirty = true
- f.m[pgI] = pg
- }
- return
- }
- func (f *bitFiler) ReadAt(b []byte, off int64) (n int, err error) {
- avail := f.size - off
- pgI := off >> bfBits
- pgO := int(off & bfMask)
- rem := len(b)
- if int64(rem) >= avail {
- rem = int(avail)
- err = io.EOF
- }
- for rem != 0 && avail > 0 {
- pg := f.m[pgI]
- if pg == nil {
- pg = &bitPage{}
- pg.pdata = buffer.CGet(bfSize)
- pg.data = *pg.pdata
- if f.parent != nil {
- _, err = f.parent.ReadAt(pg.data, off&^bfMask)
- if err != nil && !fileutil.IsEOF(err) {
- return
- }
- err = nil
- }
- f.m[pgI] = pg
- }
- nc := copy(b[:mathutil.Min(rem, bfSize)], pg.data[pgO:])
- pgI++
- pgO = 0
- rem -= nc
- n += nc
- b = b[nc:]
- off += int64(nc)
- }
- return
- }
- func (f *bitFiler) Truncate(size int64) (err error) {
- switch {
- case size < 0:
- return &ErrINVAL{"Truncate size", size}
- case size == 0:
- f.m = bitFilerMap{}
- f.size = 0
- return
- }
- first := size >> bfBits
- if size&bfMask != 0 {
- first++
- }
- last := f.size >> bfBits
- if f.size&bfMask != 0 {
- last++
- }
- for ; first < last; first++ {
- if bp, ok := f.m[first]; ok {
- buffer.Put(bp.pdata)
- }
- delete(f.m, first)
- }
- f.size = size
- return
- }
- func (f *bitFiler) WriteAt(b []byte, off int64) (n int, err error) {
- off0 := off
- pgI := off >> bfBits
- pgO := int(off & bfMask)
- n = len(b)
- rem := n
- var nc int
- for rem != 0 {
- pg := f.m[pgI]
- if pg == nil {
- pg = &bitPage{}
- pg.pdata = buffer.CGet(bfSize)
- pg.data = *pg.pdata
- if f.parent != nil {
- _, err = f.parent.ReadAt(pg.data, off&^bfMask)
- if err != nil && !fileutil.IsEOF(err) {
- return
- }
- err = nil
- }
- f.m[pgI] = pg
- }
- nc = copy(pg.data[pgO:], b)
- pgI++
- pg.dirty = true
- pgO = 0
- rem -= nc
- b = b[nc:]
- off += int64(nc)
- }
- f.size = mathutil.MaxInt64(f.size, off0+int64(n))
- return
- }
- func (f *bitFiler) link() {
- for pgI, pg := range f.m {
- nx, ok := f.m[pgI+1]
- if !ok || !nx.dirty {
- continue
- }
- nx.prev, pg.next = pg, nx
- }
- }
- func (f *bitFiler) dumpDirty(w io.WriterAt) (nwr int, err error) {
- f.link()
- for pgI, pg := range f.m {
- if !pg.dirty {
- continue
- }
- for pg.prev != nil && pg.prev.dirty {
- pg = pg.prev
- pgI--
- }
- for pg != nil && pg.dirty {
- if _, err := w.WriteAt(pg.data, pgI<<bfBits); err != nil {
- return 0, err
- }
- nwr++
- pg.dirty = false
- pg = pg.next
- pgI++
- }
- }
- return
- }
- // RollbackFiler is a Filer implementing structural transaction handling.
- // Structural transactions should be small and short lived because all non
- // committed data are held in memory until committed or discarded by a
- // Rollback.
- //
- // While using RollbackFiler, every intended update of the wrapped Filler, by
- // WriteAt, Truncate or PunchHole, _must_ be made within a transaction.
- // Attempts to do it outside of a transaction will return ErrPERM. OTOH,
- // invoking ReadAt outside of a transaction is not a problem.
- //
- // No nested transactions: All updates within a transaction are held in memory.
- // On a matching EndUpdate the updates held in memory are actually written to
- // the wrapped Filer.
- //
- // Nested transactions: Correct data will be seen from RollbackFiler when any
- // level of a nested transaction is rollbacked. The actual writing to the
- // wrapped Filer happens only when the outer most transaction nesting level is
- // closed.
- //
- // Invoking Rollback is an alternative to EndUpdate. It discards all changes
- // made at the current transaction level and returns the "state" (possibly not
- // yet persisted) of the Filer to what it was before the corresponding
- // BeginUpdate.
- //
- // During an open transaction, all reads (using ReadAt) are "dirty" reads,
- // seeing the uncommitted changes made to the Filer's data.
- //
- // Lldb databases should be based upon a RollbackFiler.
- //
- // With a wrapped MemFiler one gets transactional memory. With, for example a
- // wrapped disk based SimpleFileFiler it protects against at least some HW
- // errors - if Rollback is properly invoked on such failures and/or if there's
- // some WAL or 2PC or whatever other safe mechanism based recovery procedure
- // used by the client.
- //
- // The "real" writes to the wrapped Filer (or WAL instead) go through the
- // writerAt supplied to NewRollbackFiler.
- //
- // List of functions/methods which are recommended to be wrapped in a
- // BeginUpdate/EndUpdate structural transaction:
- //
- // Allocator.Alloc
- // Allocator.Free
- // Allocator.Realloc
- //
- // CreateBTree
- // RemoveBTree
- // BTree.Clear
- // BTree.Delete
- // BTree.DeleteAny
- // BTree.Clear
- // BTree.Extract
- // BTree.Get (it can mutate the DB)
- // BTree.Put
- // BTree.Set
- //
- // NOTE: RollbackFiler is a generic solution intended to wrap Filers provided
- // by this package which do not implement any of the transactional methods.
- // RollbackFiler thus _does not_ invoke any of the transactional methods of its
- // wrapped Filer.
- //
- // RollbackFiler is safe for concurrent use by multiple goroutines.
- type RollbackFiler struct {
- mu sync.RWMutex
- inCallback bool
- inCallbackMu sync.RWMutex
- bitFiler *bitFiler
- checkpoint func(int64) error
- closed bool
- f Filer
- parent Filer
- tlevel int // transaction nesting level, 0 == not in transaction
- writerAt io.WriterAt
- // afterRollback, if not nil, is called after performing Rollback
- // without errros.
- afterRollback func() error
- }
- // NewRollbackFiler returns a RollbackFiler wrapping f.
- //
- // The checkpoint parameter
- //
- // The checkpoint function is called after closing (by EndUpdate) the upper
- // most level open transaction if all calls of writerAt were successful and the
- // DB (or eg. a WAL) is thus now in a consistent state (virtually, in the ideal
- // world with no write caches, no HW failures, no process crashes, ...).
- //
- // NOTE: In, for example, a 2PC it is necessary to reflect also the sz
- // parameter as the new file size (as in the parameter to Truncate). All
- // changes were successfully written already by writerAt before invoking
- // checkpoint.
- //
- // The writerAt parameter
- //
- // The writerAt interface is used to commit the updates of the wrapped Filer.
- // If any invocation of writerAt fails then a non nil error will be returned
- // from EndUpdate and checkpoint will _not_ ne called. Neither is necessary to
- // call Rollback. The rule of thumb: The [structural] transaction [level] is
- // closed by invoking exactly once one of EndUpdate _or_ Rollback.
- //
- // It is presumed that writerAt uses WAL or 2PC or whatever other safe
- // mechanism to physically commit the updates.
- //
- // Updates performed by invocations of writerAt are byte-precise, but not
- // necessarily maximum possible length precise. IOW, for example an update
- // crossing page boundaries may be performed by more than one writerAt
- // invocation. No offset sorting is performed. This may change if it proves
- // to be a problem. Such change would be considered backward compatible.
- //
- // NOTE: Using RollbackFiler, but failing to ever invoke a matching "closing"
- // EndUpdate after an "opening" BeginUpdate means neither writerAt or
- // checkpoint will ever get called - with all the possible data loss
- // consequences.
- func NewRollbackFiler(f Filer, checkpoint func(sz int64) error, writerAt io.WriterAt) (r *RollbackFiler, err error) {
- if f == nil || checkpoint == nil || writerAt == nil {
- return nil, &ErrINVAL{Src: "lldb.NewRollbackFiler, nil argument"}
- }
- return &RollbackFiler{
- checkpoint: checkpoint,
- f: f,
- writerAt: writerAt,
- }, nil
- }
- // Implements Filer.
- func (r *RollbackFiler) BeginUpdate() (err error) {
- r.mu.Lock()
- defer r.mu.Unlock()
- parent := r.f
- if r.tlevel != 0 {
- parent = r.bitFiler
- }
- r.bitFiler, err = newBitFiler(parent)
- if err != nil {
- return
- }
- r.tlevel++
- return
- }
- // Implements Filer.
- //
- // Close will return an error if not invoked at nesting level 0. However, to
- // allow emergency closing from eg. a signal handler; if Close is invoked
- // within an open transaction(s), it rollbacks any non committed open
- // transactions and performs the Close operation.
- //
- // IOW: Regardless of the transaction nesting level the Close is always
- // performed but any uncommitted transaction data are lost.
- func (r *RollbackFiler) Close() (err error) {
- r.mu.Lock()
- defer r.mu.Unlock()
- if r.closed {
- return &ErrPERM{r.f.Name() + ": Already closed"}
- }
- r.closed = true
- if err = r.f.Close(); err != nil {
- return
- }
- if r.tlevel != 0 {
- err = &ErrPERM{r.f.Name() + ": Close inside an open transaction"}
- }
- if r.bitFiler != nil {
- r.bitFiler.free()
- r.bitFiler = nil
- }
- return
- }
- // Implements Filer.
- func (r *RollbackFiler) EndUpdate() (err error) {
- r.mu.Lock()
- defer r.mu.Unlock()
- if r.tlevel == 0 {
- return &ErrPERM{r.f.Name() + " : EndUpdate outside of a transaction"}
- }
- sz, err := r.size() // Cannot call .Size() -> deadlock
- if err != nil {
- return
- }
- r.tlevel--
- bf := r.bitFiler
- parent := bf.parent
- w := r.writerAt
- if r.tlevel != 0 {
- w = parent
- }
- nwr, err := bf.dumpDirty(w)
- if err != nil {
- return
- }
- switch {
- case r.tlevel == 0:
- defer func() {
- r.bitFiler.free()
- r.bitFiler = nil
- }()
- if nwr == 0 {
- return
- }
- return r.checkpoint(sz)
- default:
- r.bitFiler.free()
- r.bitFiler = parent.(*bitFiler)
- sz, _ := bf.Size() // bitFiler.Size() never returns err != nil
- return parent.Truncate(sz)
- }
- }
- // Implements Filer.
- func (r *RollbackFiler) Name() string {
- r.mu.RLock()
- defer r.mu.RUnlock()
- return r.f.Name()
- }
- // Implements Filer.
- func (r *RollbackFiler) PunchHole(off, size int64) error {
- r.mu.Lock()
- defer r.mu.Unlock()
- if r.tlevel == 0 {
- return &ErrPERM{r.f.Name() + ": PunchHole outside of a transaction"}
- }
- if off < 0 {
- return &ErrINVAL{r.f.Name() + ": PunchHole off", off}
- }
- if size < 0 || off+size > r.bitFiler.size {
- return &ErrINVAL{r.f.Name() + ": PunchHole size", size}
- }
- return r.bitFiler.PunchHole(off, size)
- }
- // Implements Filer.
- func (r *RollbackFiler) ReadAt(b []byte, off int64) (n int, err error) {
- r.inCallbackMu.RLock()
- defer r.inCallbackMu.RUnlock()
- if !r.inCallback {
- r.mu.RLock()
- defer r.mu.RUnlock()
- }
- if r.tlevel == 0 {
- return r.f.ReadAt(b, off)
- }
- return r.bitFiler.ReadAt(b, off)
- }
- // Implements Filer.
- func (r *RollbackFiler) Rollback() (err error) {
- r.mu.Lock()
- defer r.mu.Unlock()
- if r.tlevel == 0 {
- return &ErrPERM{r.f.Name() + ": Rollback outside of a transaction"}
- }
- if r.tlevel > 1 {
- r.bitFiler.free()
- r.bitFiler = r.bitFiler.parent.(*bitFiler)
- }
- r.tlevel--
- if f := r.afterRollback; f != nil {
- r.inCallbackMu.Lock()
- r.inCallback = true
- r.inCallbackMu.Unlock()
- defer func() {
- r.inCallbackMu.Lock()
- r.inCallback = false
- r.inCallbackMu.Unlock()
- }()
- return f()
- }
- return
- }
- func (r *RollbackFiler) size() (sz int64, err error) {
- if r.tlevel == 0 {
- return r.f.Size()
- }
- return r.bitFiler.Size()
- }
- // Implements Filer.
- func (r *RollbackFiler) Size() (sz int64, err error) {
- r.mu.Lock()
- defer r.mu.Unlock()
- return r.size()
- }
- // Implements Filer.
- func (r *RollbackFiler) Sync() error {
- r.mu.Lock()
- defer r.mu.Unlock()
- return r.f.Sync()
- }
- // Implements Filer.
- func (r *RollbackFiler) Truncate(size int64) error {
- r.mu.Lock()
- defer r.mu.Unlock()
- if r.tlevel == 0 {
- return &ErrPERM{r.f.Name() + ": Truncate outside of a transaction"}
- }
- return r.bitFiler.Truncate(size)
- }
- // Implements Filer.
- func (r *RollbackFiler) WriteAt(b []byte, off int64) (n int, err error) {
- r.mu.Lock()
- defer r.mu.Unlock()
- if r.tlevel == 0 {
- return 0, &ErrPERM{r.f.Name() + ": WriteAt outside of a transaction"}
- }
- return r.bitFiler.WriteAt(b, off)
- }
|