Example #1
0
        /// <summary>
        /// <see cref="CachedPageStorage"/> constructor.
        /// </summary>
        /// <param name="baseStorage">The base <see cref="IPageStorage"/>.</param>
        /// <param name="cacheWriteMode">The <see cref="CacheWriteMode"/> that determines when write operations are
        /// sent to the <paramref name="baseStorage"/>.</param>
        /// <param name="cachedPageCapacity">The maximum number of pages to store in cache at once.</param>
        /// <param name="leaveBaseStorageOpen">Should the <paramref name="baseStorage"/> remain open (non-disposed)
        /// after this <see cref="CachedPageStorage"/> is disposed? If false, then when <see cref="Dispose"/>
        /// is called, the <paramref name="baseStorage"/> will also be disposed.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="baseStorage"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown if the <paramref name="baseStorage"/>'s <see cref="IPageStorage.PageSize"/>
        /// is greater than <see cref="int.MaxValue"/>, or if <paramref name="cacheWriteMode"/> is not equal to
        /// <see cref="CacheWriteMode.ReadOnly"/> when the <paramref name="baseStorage"/> is read-only.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="cachedPageCapacity"/> is less
        /// than zero.</exception>
        /// <remarks>
        /// The application should expect that <paramref name="cachedPageCapacity"/> full pages will be loaded into
        /// memory at any given moment. The amount of memory required depends on the <see cref="IPageStorage.PageSize"/>
        /// of the <paramref name="baseStorage"/>. If the page size is very high, and so is <paramref name="cachedPageCapacity"/>,
        /// then there may not be enough memory for the cache. If allocation fails due to an <see cref="OutOfMemoryException"/>,
        /// then some pages may be evicted from cache, or cache may be bypassed entirely (by writing to and reading from the
        /// base <see cref="PageStorage"/> directly, if necessary).
        /// </remarks>
        public CachedPageStorage(IPageStorage baseStorage, CacheWriteMode cacheWriteMode, int cachedPageCapacity, bool leaveBaseStorageOpen)
        {
            if (baseStorage == null)
            {
                throw new ArgumentNullException(nameof(baseStorage));
            }
            if (baseStorage.PageSize > Int32.MaxValue)
            {
                throw new ArgumentException("Cannot use " + nameof(CachedPageStorage) + " for " + nameof(IPageStorage) + "s with page sizes greater than " + nameof(Int32) + "." + nameof(Int32.MaxValue) + ".", nameof(baseStorage));
            }
            if (baseStorage.IsReadOnly && cacheWriteMode != CacheWriteMode.ReadOnly)
            {
                throw new ArgumentException("When the " + nameof(IPageStorage) + " is read-only, the " + nameof(CacheWriteMode) + " must be " + nameof(CacheWriteMode.ReadOnly) + ".", nameof(cacheWriteMode));
            }
            if (cachedPageCapacity < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(cachedPageCapacity), "The page cache capacity cannot be less than zero.");
            }

            this.PageStorage        = baseStorage;
            this.CachedPageCapacity = cachedPageCapacity;
            this.cachedPages        = new Dictionary <long, CachedPage>(cachedPageCapacity);
            this.Mode = cacheWriteMode;
            this.WillLeaveBasePageStorageOpen = leaveBaseStorageOpen;
        }
 private void ResetInner(int currentSize, int currentOffset)
 {
     CountOfBackedDataSet = _countFetcher();
     Size         = Math.Min(currentSize, CountOfBackedDataSet);
     Offset       = Math.Max(0, Math.Min(CountOfBackedDataSet - Size, currentOffset));
     _pageStorage = _pageStoreFactory(CountOfBackedDataSet).AssignTo(_serialPageStore);
 }
Example #3
0
        /// <summary>
        /// Attempts to create an empty <see cref="StorageDictionary{TKey, TValue}"/> using an <see cref="IPageStorage"/>.
        /// </summary>
        /// <param name="pageStorage">The <see cref="IPageStorage"/> on which the <see cref="StorageDictionary{TKey, TValue}"/>
        /// will store its data.</param>
        /// <param name="keySerializer"><see cref="ISerializer{T}"/> that serializes and deserializes the keys.</param>
        /// <param name="valueSerializer"><see cref="ISerializer{T}"/> that serializes and deserializes the values.</param>
        /// <param name="pageIndex">Assigned to the index of the metadata page that the application must remember so that it
        /// may load the <see cref="StorageDictionary{TKey, TValue}"/> in the future. See <see cref="PageIndex"/>.</param>
        /// <returns>True if creation was successful, false if allocation failed.</returns>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="pageStorage"/>, <paramref name="keySerializer"/>,
        /// or <paramref name="valueSerializer"/> is null.</exception>
        /// <exception cref="InvalidOperationException">Thrown if <paramref name="pageStorage"/> is read-only.</exception>
        /// <exception cref="ArgumentException">Thrown if the <paramref name="pageStorage"/>'s <see cref="IPageStorage.PageSize"/>
        /// is less than the very minimum required size (see <see cref="GetVeryMinRequiredPageSize(long, long)"/>).</exception>
        /// <remarks>
        /// <para>
        /// This method will only initialize the data stored on the <paramref name="pageStorage"/>. To obtain the
        /// <see cref="StorageDictionary{TKey, TValue}"/> instance, use the <see cref="Load(IPageStorage, ISerializer{TKey}, ISerializer{TValue}, long, bool, int, long)"/>
        /// method (and remember to call <see cref="StorageDictionary{TKey, TValue}.Dispose()"/> when finished
        /// using it).
        /// </para>
        /// <para>
        /// Note that this method will inflate the <paramref name="pageStorage"/> if it is at full capacity and not fixed-capacity.
        /// If inflation is required but the capacity is fixed, or if inflation fails, false will be returned.
        /// </para>
        /// </remarks>
        /// <seealso cref="Load(IPageStorage, ISerializer{TKey}, ISerializer{TValue}, long, bool, int, long)"/>
        public static bool TryCreate(IPageStorage pageStorage, ISerializer <TKey> keySerializer, ISerializer <TValue> valueSerializer, out long pageIndex)
        {
            if (pageStorage == null)
            {
                throw new ArgumentNullException(nameof(pageStorage));
            }
            if (keySerializer == null)
            {
                throw new ArgumentNullException(nameof(keySerializer));
            }
            if (valueSerializer == null)
            {
                throw new ArgumentNullException(nameof(valueSerializer));
            }
            if (pageStorage.IsReadOnly)
            {
                throw new InvalidOperationException("Cannot create a " + nameof(StorageDictionary <TKey, TValue>) + " on a read-only " + nameof(IPageStorage) + ".");
            }
            if (pageStorage.PageSize < GetVeryMinRequiredPageSize(keySerializer.DataSize, valueSerializer.DataSize))
            {
                throw new ArgumentException("The page size of the specified " + nameof(IPageStorage) + " is too small.", nameof(pageStorage));
            }

            if (pageStorage.AllocatedPageCount == pageStorage.PageCapacity)
            {
                if (!pageStorage.IsCapacityFixed)
                {
                    long got = pageStorage.TryInflate(1, null, new CancellationToken(false));
                    if (got < 0)
                    {
                        //Cannot allocate, capacity remains full
                        pageIndex = -1;
                        return(false);
                    }
                }
                else
                {
                    //Capacity is full and we cannot inflate it
                    pageIndex = -1;
                    return(false);
                }
            }

            if (pageStorage.TryAllocatePage(out pageIndex))
            {
                using (StorageDictionary <TKey, TValue> dict = new StorageDictionary <TKey, TValue>(pageStorage, keySerializer, valueSerializer, pageIndex, false, 0, 256 /*not used for creation, but must be >0*/))
                {
                    dict.BTree.InitializeEmpty();
                    dict.CachedPageStorage.Flush();
                }
                return(true);
            }
            else
            {
                pageIndex = -1;
                return(false);
            }
        }
Example #4
0
 private StorageDictionary(IPageStorage pageStorage, ISerializer <TKey> keySerializer, ISerializer <TValue> valueSerializer, long pageIndex, bool isReadOnly, int cachePageCount, long maxMoveCount)
 {
     this.CachedPageStorage = new CachedPageStorage(pageStorage, isReadOnly ? CachedPageStorage.CacheWriteMode.ReadOnly : CachedPageStorage.CacheWriteMode.WriteBack, cachePageCount, true);
     this.KeySerializer     = keySerializer ?? throw new ArgumentNullException(nameof(keySerializer));
     this.ValueSerializer   = valueSerializer ?? throw new ArgumentNullException(nameof(valueSerializer));
     this.PageIndex         = pageIndex;
     this.IsReadOnly        = isReadOnly;
     this.MaxMovePairCount  = maxMoveCount;
     this.BTree             = new StorageDictionaryBTree <TKey, TValue>(this);
 }
Example #5
0
 public MockBTree(IPageStorage pageStorage, ISerializer <long> keySerializer, ISerializer <string> valueSerializer, long maxMovePairCount = 32) : base(pageStorage, keySerializer, valueSerializer, maxMovePairCount)
 {
     if (pageStorage.EntryPageIndex == null)
     {
         if (pageStorage.TryAllocatePage(out long index))
         {
             pageStorage.EntryPageIndex = index;
             this.Count         = 0;
             this.RootPageIndex = null;
         }
         else
         {
             throw new ArgumentException("Failed to allocate the entry page index on the " + nameof(IPageStorage) + ".", nameof(pageStorage));
         }
     }
 }
Example #6
0
        /// <summary>
        /// <see cref="BTree{TKey, TValue}"/> constructor.
        /// </summary>
        /// <param name="pageStorage">The <see cref="IPageStorage"/> on which this <see cref="BTree{TKey, TValue}"/> is
        /// stored or will be stored.</param>
        /// <param name="keySerializer">The <see cref="ISerializer{T}"/> that will be used to serialize and
        /// deserialize the keys.</param>
        /// <param name="valueSerializer">The <see cref="ISerializer{T}"/> that will be used to serialize and
        /// deserialize the values.</param>
        /// <param name="maxMovePairCount">The maximum size of the internal buffer that may be used to move key-value
        /// pairs during certain operations such as insertions, measured in the number of key-value pairs. Must be
        /// at least one.</param>
        /// <remarks>
        /// <para>
        /// Note that the <paramref name="pageStorage"/>'s pages must be sufficiently large to store a <see cref="BTreeNode{TKey, TValue}"/>
        /// with several key-value pairs. The very minimum key-value pair capacity is <see cref="BTreeNode{TKey, TValue}.VeryMinKeyValuePairCapacity"/>,
        /// but most applications should use a significantly larger capacity. To determine the key-value pair capacity for a given
        /// page size, see <see cref="BTreeNode{TKey, TValue}.GetRequiredPageSize(long, long, long)"/>. The 'key size' and
        /// 'value size' are defined by the <see cref="ISerializer{T}.DataSize"/> of the <paramref name="keySerializer"/>
        /// and <paramref name="valueSerializer"/>.
        /// </para>
        /// <para>
        /// During some operations, such as insertions, some <see cref="BTreeNode{TKey, TValue}"/>s may have to move around some
        /// of their key-value pairs. In most cases, it will be more efficient if several key-value pairs are moved at once
        /// (rather than one at a time). The <paramref name="maxMovePairCount"/> argument defines the maximum number of
        /// key-value pairs that will be moved at once. A larger number should result in improved speed, but at the cost
        /// of increased runtime memory. The size, in bytes, of the internal buffer is approximately the size of the
        /// binary-serialized keys and values (see <see cref="ISerializer{T}.DataSize"/> for <paramref name="keySerializer"/>
        /// and <paramref name="valueSerializer"/>) plus eight bytes of overhead, all multiplied by the <paramref name="maxMovePairCount"/>
        /// argument.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="pageStorage"/>, <paramref name="keySerializer"/>,
        /// or <paramref name="valueSerializer"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="maxMovePairCount"/> is less than one.</exception>
        /// <exception cref="ArgumentException">Thrown if the <paramref name="pageStorage"/>'s pages are too small to contain
        /// a <see cref="BTreeNode{TKey, TValue}"/> with <see cref="BTreeNode{TKey, TValue}.VeryMinKeyValuePairCapacity"/>
        /// key-value pairs. See <see cref="BTreeNode{TKey, TValue}.GetKeyValuePairCapacityForPageSize(long, long, long)"/>
        /// and <see cref="BTreeNode{TKey, TValue}.GetRequiredPageSize(long, long, long)"/>.</exception>
        public BTree(IPageStorage pageStorage, ISerializer <TKey> keySerializer, ISerializer <TValue> valueSerializer, long maxMovePairCount = 32)
        {
            this.PageStorage              = pageStorage ?? throw new ArgumentNullException(nameof(pageStorage));
            this.KeySerializer            = keySerializer ?? throw new ArgumentNullException(nameof(keySerializer));
            this.ValueSerializer          = valueSerializer ?? throw new ArgumentNullException(nameof(valueSerializer));
            this.MaxMoveKeyValuePairCount = maxMovePairCount;

            long veryMinRequiredPageSize = BTreeNode <TKey, TValue> .GetRequiredPageSize(keySerializer.DataSize, valueSerializer.DataSize, BTreeNode <TKey, TValue> .VeryMinKeyValuePairCapacity);

            if (pageStorage.PageSize < veryMinRequiredPageSize)
            {
                throw new ArgumentException("The provided " + nameof(IPageStorage) + " has a page size that is insufficient to store a " + nameof(BTreeNode <TKey, TValue>) + " using the specified key and value serializers.");
            }
            if (maxMovePairCount < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(maxMovePairCount));
            }
        }
Example #7
0
        /// <summary>
        /// Loads a <see cref="StorageDictionary{TKey, TValue}"/> from an <see cref="IPageStorage"/>.
        /// </summary>
        /// <param name="pageStorage">The <see cref="IPageStorage"/> from which to load. This
        /// <see cref="StorageDictionary{TKey, TValue}"/> will <em>not</em> dispose it (because it
        /// is possible that other APIs may also be using the <see cref="IPageStorage"/>).</param>
        /// <param name="keySerializer"><see cref="ISerializer{T}"/> that serializes and deserializes the keys.</param>
        /// <param name="valueSerializer"><see cref="ISerializer{T}"/> that serializes and deserializes the values.</param>
        /// <param name="pageIndex">The index of the page on the <paramref name="pageStorage"/> that contains the
        /// metadata for the <see cref="StorageDictionary{TKey, TValue}"/>. This was determined during creation via
        /// the <see cref="TryCreate(IPageStorage, ISerializer{TKey}, ISerializer{TValue}, out long)"/>
        /// method.</param>
        /// <param name="isReadOnly">Should the <see cref="StorageDictionary{TKey, TValue}"/> be loaded as read-only? Must be true
        /// if the <paramref name="pageStorage"/> is read-only.</param>
        /// <param name="cachePageCount">The maximum number of pages to store in cache. May be zero, in which case caching
        /// will not be used. If <paramref name="pageStorage"/> already provides caching, then this should be zero.</param>
        /// <param name="maxMoveCount">The maximum number of key-value pairs to move at once during certain operations.
        /// Larger values may improve operation speed, but cost more memory. The memory cost is equivalent to the
        /// <see cref="ISerializer{T}.DataSize"/> of the <paramref name="keySerializer"/> and the <paramref name="valueSerializer"/>,
        /// plus 8-bytes of header data, all multiplied by this argument.</param>
        /// <returns>The loaded <see cref="StorageDictionary{TKey, TValue}"/>.</returns>
        /// <exception cref="ArgumentNullException">Thrown if any argument is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="cachePageCount"/> is less than zero or
        /// <paramref name="maxMoveCount"/> is less than one.</exception>
        /// <exception cref="ArgumentException">Thrown if <paramref name="pageStorage"/> is read-only but
        /// <paramref name="isReadOnly"/> is false, or if the page on <paramref name="pageStorage"/> at
        /// <paramref name="pageIndex"/> is not allocated or does not exist, or if the size of the pages on
        /// the <paramref name="pageStorage"/> is less than the very minimum valid page size.</exception>
        public static StorageDictionary <TKey, TValue> Load(IPageStorage pageStorage, ISerializer <TKey> keySerializer, ISerializer <TValue> valueSerializer, long pageIndex, bool isReadOnly, int cachePageCount = 8, long maxMoveCount = 256)
        {
            if (pageStorage == null)
            {
                throw new ArgumentNullException(nameof(pageStorage));
            }
            if (keySerializer == null)
            {
                throw new ArgumentNullException(nameof(keySerializer));
            }
            if (valueSerializer == null)
            {
                throw new ArgumentNullException(nameof(valueSerializer));
            }
            if (cachePageCount < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(cachePageCount));
            }
            if (maxMoveCount < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(maxMoveCount), nameof(maxMoveCount) + " cannot be less than one.");
            }
            if (pageStorage.IsReadOnly && !isReadOnly)
            {
                throw new ArgumentException(nameof(isReadOnly), "If the " + nameof(IPageStorage) + " is read-only, the " + nameof(isReadOnly) + " argument must be true.");
            }
            if (!pageStorage.IsPageOnStorage(pageIndex))
            {
                throw new ArgumentException("The specified index does not refer to a page that exists on the " + nameof(IPageStorage) + ".", nameof(pageIndex));
            }
            if (!pageStorage.IsPageAllocated(pageIndex))
            {
                throw new ArgumentException("The specified index does not refer to a page that is allocated on the " + nameof(IPageStorage) + ".", nameof(pageIndex));
            }
            if (pageStorage.PageSize < GetVeryMinRequiredPageSize(keySerializer.DataSize, valueSerializer.DataSize))
            {
                throw new ArgumentException("The page size of the specified " + nameof(IPageStorage) + " is too small.", nameof(pageStorage));
            }

            return(new StorageDictionary <TKey, TValue>(pageStorage, keySerializer, valueSerializer, pageIndex, isReadOnly, cachePageCount, maxMoveCount));
        }