// Alloc allocates storage space for b and returns the handle of the new block // with content set to b or an error, if any. The returned handle is valid only // while the block is used - until the block is deallocated. No two valid // handles share the same value within the same Filer, but any value of a // handle not referring to any used block may become valid any time as a result // of Alloc. // // Invoking Alloc on an empty Allocator is guaranteed to return handle with // value 1. The intended use of content of handle 1 is a root "directory" of // other data held by an Allocator. // // Passing handles not obtained initially from Alloc or not anymore valid to // any other Allocator methods can result in an irreparably corrupted database. func (a *Allocator) Alloc(b []byte) (handle int64, err error) { buf := bufs.GCache.Get(zappy.MaxEncodedLen(len(b))) defer bufs.GCache.Put(buf) buf, _, cc, err := a.makeUsedBlock(buf, b) if err != nil { return } if handle, err = a.alloc(buf, cc); err == nil { a.cadd(b, handle) } return }
func TestAllocatorMakeUsedBlock(t *testing.T) { f := NewMemFiler() a, err := NewAllocator(f, &Options{}) if err != nil { t.Fatal(err) } dst := bufs.GCache.Get(zappy.MaxEncodedLen(maxRq + 1)) defer bufs.GCache.Put(dst) if _, _, _, err := a.makeUsedBlock(dst, make([]byte, maxRq)); err != nil { t.Fatal(err) } if _, _, _, err := a.makeUsedBlock(dst, make([]byte, maxRq+1)); err == nil { t.Fatal("unexpected success") } }
func (a *Allocator) realloc(handle int64, b []byte) (err error) { var dlen, needAtoms0 int b8 := bufs.GCache.Get(8) defer bufs.GCache.Put(b8) dst := bufs.GCache.Get(zappy.MaxEncodedLen(len(b))) defer bufs.GCache.Put(dst) b, needAtoms0, cc, err := a.makeUsedBlock(dst, b) if err != nil { return } needAtoms := int64(needAtoms0) off := h2off(handle) if err = a.read(b8[:], off); err != nil { return } switch tag := b8[0]; tag { default: dlen = int(b8[0]) case tagUsedLong: dlen = m2n(int(b8[1])<<8 | int(b8[2])) case tagUsedRelocated: if err = a.free(b2h(b8[1:]), handle, false); err != nil { return err } dlen = 0 case tagFreeShort, tagFreeLong: return &ErrINVAL{"Allocator.Realloc: invalid handle", handle} } atoms := int64(n2atoms(dlen)) retry: switch { case needAtoms < atoms: // in place shrink if err = a.writeUsedBlock(handle, cc, b); err != nil { return } fh, fa := handle+needAtoms, atoms-needAtoms sz, err := a.f.Size() if err != nil { return err } if h2off(fh)+16*fa == sz { return a.f.Truncate(h2off(fh)) } return a.free2(fh, fa) case needAtoms == atoms: // in place replace return a.writeUsedBlock(handle, cc, b) } // case needAtoms > atoms: // in place extend or relocate var sz int64 if sz, err = a.f.Size(); err != nil { return } off = h2off(handle) switch { case off+atoms*16 == sz: // relocating tail block - shortcut return a.writeUsedBlock(handle, cc, b) default: if off+atoms*16 < sz { // handle is not a tail block, check right neighbour rh := handle + atoms rtag, ratoms, p, n, e := a.nfo(rh) if e != nil { return e } if rtag == tagFreeShort || rtag == tagFreeLong { // Right neighbour is a free block if needAtoms <= atoms+ratoms { // can expand in place if err = a.unlink(rh, ratoms, p, n); err != nil { return } atoms += ratoms goto retry } } } } if atoms > 1 { if err = a.realloc(handle, nil); err != nil { return } } var newH int64 if newH, err = a.alloc(b, cc); err != nil { return err } rb := bufs.GCache.Cget(16) defer bufs.GCache.Put(rb) rb[0] = tagUsedRelocated h2b(rb[1:], newH) if err = a.writeAt(rb[:], h2off(handle)); err != nil { return } return a.writeUsedBlock(newH, cc, b) }