123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- /*
- * EliasDB
- *
- * Copyright 2016 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
- /*
- Package storage contains the low-level API for data storage. Data is stored
- in slots. The interface defines methods to store, retrieve, update and delete
- a given object to and from the disk. There are 3 main implementations:
- DiskStorageManager
- A disk storage manager handles the data storage on disk. It controls the actual
- PhysicalSlotManager and LogicalSlotManager objects. It holds references to all
- involved files and ensures exclusive access to them through a generated lock
- file. The lockfile is checked and attempting to open another instance of the
- DiskStorageManager on the same files will result in an error. The DiskStorageManager
- is also responsible for marshalling given abstract objects into a binary form which
- can be written to physical slots.
- CachedDiskStorageManager
- The CachedDiskStorageManager is a cache wrapper for the DiskStorageManager. Its
- purpose is to intercept calls and to maintain a cache of stored objects. The cache
- is limited in size by the number of total objects it references. Once the cache
- is full it will forget the objects which have been requested the least.
- MemoryStorageManager
- A storage manager which keeps all its data in memory and provides several
- error simulation facilities.
- */
- package storage
- import "sync"
- /*
- CachedDiskStorageManager data structure
- */
- type CachedDiskStorageManager struct {
- diskstoragemanager *DiskStorageManager // Wrapped instance of DiskStorageManager
- mutex *sync.Mutex // Mutex to protect list and map operations
- cache map[uint64]*cacheEntry // Map of stored cacheEntry objects
- maxObjects int // Max number of objects which should be held in the cache
- firstentry *cacheEntry // Pointer to first entry in cacheEntry linked list
- lastentry *cacheEntry // Pointer to last entry in cacheEntry linked list
- }
- /*
- cacheEntry data structure
- */
- type cacheEntry struct {
- location uint64 // Slot (logical) of the entry
- object interface{} // Object of the entry
- prev *cacheEntry // Pointer to previous entry in cacheEntry linked list
- next *cacheEntry // Pointer to next entry in cacheEntry linked list
- }
- /*
- Pool for cache entries
- */
- var entryPool = &sync.Pool{New: func() interface{} { return &cacheEntry{} }}
- /*
- NewCachedDiskStorageManager creates a new cache wrapper for a DiskStorageManger.
- */
- func NewCachedDiskStorageManager(diskstoragemanager *DiskStorageManager, maxObjects int) *CachedDiskStorageManager {
- return &CachedDiskStorageManager{diskstoragemanager, &sync.Mutex{}, make(map[uint64]*cacheEntry),
- maxObjects, nil, nil}
- }
- /*
- Name returns the name of the StorageManager instance.
- */
- func (cdsm *CachedDiskStorageManager) Name() string {
- return cdsm.diskstoragemanager.Name()
- }
- /*
- Root returns a root value.
- */
- func (cdsm *CachedDiskStorageManager) Root(root int) uint64 {
- return cdsm.diskstoragemanager.Root(root)
- }
- /*
- SetRoot writes a root value.
- */
- func (cdsm *CachedDiskStorageManager) SetRoot(root int, val uint64) {
- cdsm.diskstoragemanager.SetRoot(root, val)
- }
- /*
- Insert inserts an object and return its storage location.
- */
- func (cdsm *CachedDiskStorageManager) Insert(o interface{}) (uint64, error) {
- // Cannot cache inserts since the calling code needs a location
- loc, err := cdsm.diskstoragemanager.Insert(o)
- if loc != 0 && err == nil {
- cdsm.mutex.Lock()
- defer cdsm.mutex.Unlock()
- cdsm.addToCache(loc, o)
- }
- return loc, err
- }
- /*
- Update updates a storage location.
- */
- func (cdsm *CachedDiskStorageManager) Update(loc uint64, o interface{}) error {
- // Store the update in the cache
- cdsm.mutex.Lock()
- if entry, ok := cdsm.cache[loc]; !ok {
- cdsm.addToCache(loc, o)
- } else {
- entry.object = o
- cdsm.llTouchEntry(entry)
- }
- cdsm.mutex.Unlock()
- return cdsm.diskstoragemanager.Update(loc, o)
- }
- /*
- Free frees a storage location.
- */
- func (cdsm *CachedDiskStorageManager) Free(loc uint64) error {
- if ret := cdsm.diskstoragemanager.Free(loc); ret != nil {
- return ret
- }
- cdsm.mutex.Lock()
- defer cdsm.mutex.Unlock()
- // Remove location entry from the cache
- if entry, ok := cdsm.cache[loc]; ok {
- delete(cdsm.cache, entry.location)
- cdsm.llRemoveEntry(entry)
- }
- return nil
- }
- /*
- Fetch fetches an object from a given storage location and writes it to
- a given data container.
- */
- func (cdsm *CachedDiskStorageManager) Fetch(loc uint64, o interface{}) error {
- err := cdsm.diskstoragemanager.Fetch(loc, o)
- if err != nil {
- return err
- }
- cdsm.mutex.Lock()
- defer cdsm.mutex.Unlock()
- // Put the retrieved value into the cache
- if entry, ok := cdsm.cache[loc]; !ok {
- cdsm.addToCache(loc, o)
- } else {
- cdsm.llTouchEntry(entry)
- }
- return nil
- }
- /*
- FetchCached fetches an object from a cache and returns its reference.
- Returns a storage.ErrNotInCache error if the entry is not in the cache.
- */
- func (cdsm *CachedDiskStorageManager) FetchCached(loc uint64) (interface{}, error) {
- cdsm.mutex.Lock()
- defer cdsm.mutex.Unlock()
- if entry, ok := cdsm.cache[loc]; ok {
- return entry.object, nil
- }
- return nil, ErrNotInCache
- }
- /*
- Rollback cancels all pending changes which have not yet been written to disk.
- */
- func (cdsm *CachedDiskStorageManager) Rollback() error {
- if cdsm.diskstoragemanager.transDisabled {
- return nil
- }
- err := cdsm.diskstoragemanager.Rollback()
- cdsm.mutex.Lock()
- defer cdsm.mutex.Unlock()
- // Cache is emptied in any case
- cdsm.cache = make(map[uint64]*cacheEntry)
- cdsm.firstentry = nil
- cdsm.lastentry = nil
- return err
- }
- /*
- Close the StorageManager and write all pending changes to disk.
- */
- func (cdsm *CachedDiskStorageManager) Close() error {
- return cdsm.diskstoragemanager.Close()
- }
- /*
- Flush writes all pending changes to disk.
- */
- func (cdsm *CachedDiskStorageManager) Flush() error {
- return cdsm.diskstoragemanager.Flush()
- }
- /*
- addToCache adds an entry to the cache.
- */
- func (cdsm *CachedDiskStorageManager) addToCache(loc uint64, o interface{}) {
- var entry *cacheEntry
- // Get an entry from the pool or recycle an entry from the cacheEntry
- // linked list if the list is full
- if len(cdsm.cache) >= cdsm.maxObjects {
- entry = cdsm.removeOldestFromCache()
- } else {
- entry = entryPool.Get().(*cacheEntry)
- }
- // Fill the entry
- entry.location = loc
- entry.object = o
- // Insert entry into the cacheEntry linked list (this will set the entries
- // prev and next pointer)
- cdsm.llAppendEntry(entry)
- // Insert into the map of stored cacheEntry objects
- cdsm.cache[loc] = entry
- }
- /*
- removeOldestFromCache removes the oldest entry from the cache and return it.
- */
- func (cdsm *CachedDiskStorageManager) removeOldestFromCache() *cacheEntry {
- entry := cdsm.firstentry
- // If no entries were stored yet just return an entry from the pool
- if entry == nil {
- return entryPool.Get().(*cacheEntry)
- }
- // Remove entry from the cacheEntry linked list (this will set the entries
- // prev and next pointer)
- cdsm.llRemoveEntry(entry)
- // Remove entry from the map of stored cacheEntry objects
- delete(cdsm.cache, entry.location)
- return entry
- }
- /*
- llTouchEntry puts an entry to the last position of the cacheEntry linked list.
- Calling llTouchEntry on all requested items ensures that the oldest used
- entry is at the beginning of the list.
- */
- func (cdsm *CachedDiskStorageManager) llTouchEntry(entry *cacheEntry) {
- if cdsm.lastentry == entry {
- return
- }
- cdsm.llRemoveEntry(entry)
- cdsm.llAppendEntry(entry)
- }
- /*
- llAppendEntry appends a cacheEntry to the end of the cacheEntry linked list.
- */
- func (cdsm *CachedDiskStorageManager) llAppendEntry(entry *cacheEntry) {
- if cdsm.firstentry == nil {
- cdsm.firstentry = entry
- cdsm.lastentry = entry
- entry.prev = nil
- } else {
- cdsm.lastentry.next = entry
- entry.prev = cdsm.lastentry
- cdsm.lastentry = entry
- }
- entry.next = nil
- }
- /*
- llRemoveEntry removes a cacheEntry from the cacheEntry linked list.
- */
- func (cdsm *CachedDiskStorageManager) llRemoveEntry(entry *cacheEntry) {
- if entry == cdsm.firstentry {
- cdsm.firstentry = entry.next
- }
- if cdsm.lastentry == entry {
- cdsm.lastentry = entry.prev
- }
- if entry.prev != nil {
- entry.prev.next = entry.next
- entry.prev = nil
- }
- if entry.next != nil {
- entry.next.prev = entry.prev
- entry.next = nil
- }
- }
|