// 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 // import "modernc.org/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" "modernc.org/fileutil" "modernc.org/internal/buffer" "modernc.org/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< 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) }