示例#1
0
        public PageFromScratchBuffer Allocate(Transaction tx, int numberOfPages)
        {
            if (tx == null)
            {
                throw new ArgumentNullException("tx");
            }
            var size = Utils.NearestPowerOfTwo(numberOfPages);

            PageFromScratchBuffer result;

            if (_current.TryGettingFromAllocatedBuffer(tx, numberOfPages, size, out result))
            {
                return(result);
            }

            long sizeAfterAllocation;
            long oldestActiveTransaction = tx.Environment.OldestTransaction;

            if (_scratchBuffers.Count == 1)
            {
                sizeAfterAllocation = _current.SizeAfterAllocation(size);
            }
            else
            {
                sizeAfterAllocation = size * AbstractPager.PageSize;

                var scratchesToDelete = new List <int>();

                // determine how many bytes of older scratches are still in use
                foreach (var scratch in _scratchBuffers.Values)
                {
                    var bytesInUse = scratch.ActivelyUsedBytes(oldestActiveTransaction);

                    if (bytesInUse > 0)
                    {
                        sizeAfterAllocation += bytesInUse;
                    }
                    else
                    {
                        if (scratch != _current)
                        {
                            scratchesToDelete.Add(scratch.Number);
                        }
                    }
                }

                // delete inactive scratches
                foreach (var scratchNumber in scratchesToDelete)
                {
                    var scratchBufferFile = _scratchBuffers[scratchNumber];
                    _scratchBuffers.Remove(scratchNumber);
                    scratchBufferFile.Dispose();
                }
            }

            if (sizeAfterAllocation >= (_sizeLimit * 3) / 4 && oldestActiveTransaction > _oldestTransactionWhenFlushWasForced)
            {
                // we may get recursive flushing, so we want to avoid it
                if (tx.Environment.Journal.Applicator.IsCurrentThreadInFlushOperation == false)
                {
                    // We are starting to force a flush to free scratch pages. We are doing it at this point (80% of the max scratch size)
                    // to make sure that next transactions will be able to allocate pages that we are going to free in the current transaction.
                    // Important notice: all pages freed by this run will get ValidAfterTransactionId == tx.Id (so only next ones can use it)

                    bool flushLockTaken = false;
                    using (tx.Environment.Journal.Applicator.TryTakeFlushingLock(ref flushLockTaken))
                    {
                        if (flushLockTaken) // if we are already flushing, we don't need to force a flush
                        {
                            try
                            {
                                tx.Environment.ForceLogFlushToDataFile(tx, allowToFlushOverwrittenPages: true);
                                _oldestTransactionWhenFlushWasForced = oldestActiveTransaction;
                            }
                            catch (TimeoutException)
                            {
                                // we'll try next time
                            }
                            catch (InvalidJournalFlushRequest)
                            {
                                // journals flushing already in progress
                            }
                        }
                    }
                }
            }

            if (sizeAfterAllocation > _sizeLimit)
            {
                var sp = Stopwatch.StartNew();

                // Our problem is that we don't have any available free pages, probably because
                // there are read transactions that are holding things open. We are going to see if
                // there are any free pages that _might_ be freed for us if we wait for a bit. The idea
                // is that we let the read transactions time to complete and do their work, at which point
                // we can continue running. It is possible that a long running read transaction
                // would in fact generate enough work for us to timeout, but hopefully we can avoid that.

                while (sp.ElapsedMilliseconds < tx.Environment.Options.ScratchBufferOverflowTimeout)
                {
                    if (_current.TryGettingFromAllocatedBuffer(tx, numberOfPages, size, out result))
                    {
                        return(result);
                    }
                    Thread.Sleep(32);
                }

                sp.Stop();

                bool createNextFile = false;

                if (_current.HasDiscontinuousSpaceFor(tx, size, _scratchBuffers.Count))
                {
                    // there is enough space for the requested allocation but the problem is its fragmentation
                    // so we will create a new scratch file and will allow to allocate new continuous range from there

                    createNextFile = true;
                }
                else if (_scratchBuffers.Count == 1 && _current.Size < _sizeLimit &&
                         (_current.ActivelyUsedBytes(oldestActiveTransaction) + size * AbstractPager.PageSize) < _sizeLimit)
                {
                    // there is only one scratch file that hasn't reach the size limit yet and
                    // the number of bytes being in active use allows to allocate the requested size
                    // let it create a new file

                    createNextFile = true;
                }

                if (createNextFile)
                {
                    _current = NextFile();

                    tx.EnsurePagerStateReference(_current.PagerState);

                    return(_current.Allocate(tx, numberOfPages, size));
                }

                var debugInfoBuilder = new StringBuilder();

                debugInfoBuilder.AppendFormat("Current transaction id: {0}\r\n", tx.Id);
                debugInfoBuilder.AppendFormat("Requested number of pages: {0} (adjusted size: {1} == {2:#,#;;0} KB)\r\n", numberOfPages, size, size * AbstractPager.PageSize / 1024);
                debugInfoBuilder.AppendFormat("Oldest active transaction: {0} (snapshot: {1})\r\n", tx.Environment.OldestTransaction, oldestActiveTransaction);
                debugInfoBuilder.AppendFormat("Oldest active transaction when flush was forced: {0}\r\n", _oldestTransactionWhenFlushWasForced);
                debugInfoBuilder.AppendFormat("Next write transaction id: {0}\r\n", tx.Environment.NextWriteTransactionId + 1);

                debugInfoBuilder.AppendLine("Active transactions:");
                foreach (var activeTransaction in tx.Environment.ActiveTransactions)
                {
                    debugInfoBuilder.AppendFormat("\tId: {0} - {1}\r\n", activeTransaction.Id, activeTransaction.Flags);
                }

                debugInfoBuilder.AppendLine("Scratch files usage:");
                foreach (var scratchBufferFile in _scratchBuffers.OrderBy(x => x.Key))
                {
                    debugInfoBuilder.AppendFormat("\t{0} - size: {1:#,#;;0} KB, in active use: {2:#,#;;0} KB\r\n", StorageEnvironmentOptions.ScratchBufferName(scratchBufferFile.Value.Number), scratchBufferFile.Value.Size / 1024, scratchBufferFile.Value.ActivelyUsedBytes(oldestActiveTransaction) / 1024);
                }

                debugInfoBuilder.AppendLine("Most available free pages:");
                foreach (var scratchBufferFile in _scratchBuffers.OrderBy(x => x.Key))
                {
                    debugInfoBuilder.AppendFormat("\t{0}\r\n", StorageEnvironmentOptions.ScratchBufferName(scratchBufferFile.Value.Number));

                    foreach (var freePage in scratchBufferFile.Value.GetMostAvailableFreePagesBySize())
                    {
                        debugInfoBuilder.AppendFormat("\t\tSize:{0}, ValidAfterTransactionId: {1}\r\n", freePage.Key, freePage.Value);
                    }
                }

                debugInfoBuilder.AppendFormat("Compression buffer size: {0:#,#;;0} KB\r\n", tx.Environment.Journal.CompressionBufferSize / 1024);

                string debugInfo = debugInfoBuilder.ToString();

                string message = string.Format("Cannot allocate more space for the scratch buffer.\r\n" +
                                               "Current file size is:\t{0:#,#;;0} KB.\r\n" +
                                               "Requested size for current file:\t{1:#,#;;0} KB.\r\n" +
                                               "Requested total size for all files:\t{2:#,#;;0} KB.\r\n" +
                                               "Limit:\t\t\t{3:#,#;;0} KB.\r\n" +
                                               "Already flushed and waited for {4:#,#;;0} ms for read transactions to complete.\r\n" +
                                               "Do you have a long running read transaction executing?\r\n" +
                                               "Debug info:\r\n{5}",
                                               _current.Size / 1024L,
                                               _current.SizeAfterAllocation(size) / 1024L,
                                               sizeAfterAllocation / 1024L,
                                               _sizeLimit / 1024L,
                                               sp.ElapsedMilliseconds,
                                               debugInfo
                                               );

                throw new ScratchBufferSizeLimitException(message);
            }

            // we don't have free pages to give out, need to allocate some
            result = _current.Allocate(tx, numberOfPages, size);
            _options.OnScratchBufferSizeChanged(sizeAfterAllocation);

            return(result);
        }
        public PageFromScratchBuffer Allocate(LowLevelTransaction tx, int numberOfPages)
        {
            if (tx == null)
            {
                throw new ArgumentNullException(nameof(tx));
            }
            var size = Bits.NextPowerOf2(numberOfPages);

            PageFromScratchBuffer result;
            var current = _current;

            if (current.File.TryGettingFromAllocatedBuffer(tx, numberOfPages, size, out result))
            {
                return(result);
            }

            long sizeAfterAllocation;
            long oldestActiveTransaction = tx.Environment.OldestTransaction;

            if (_scratchBuffers.Count == 1)
            {
                sizeAfterAllocation = current.File.SizeAfterAllocation(size);
            }
            else
            {
                sizeAfterAllocation = size * tx.Environment.Options.PageSize;

                var scratchesToDelete = new List <int>();

                sizeAfterAllocation += current.File.ActivelyUsedBytes(oldestActiveTransaction);

                // determine how many bytes of older scratches are still in use
                foreach (var scratch in _scratchBuffers.Values)
                {
                    var bytesInUse = scratch.File.ActivelyUsedBytes(oldestActiveTransaction);

                    if (bytesInUse <= 0)
                    {
                        if (scratch != current)
                        {
                            scratchesToDelete.Add(scratch.Number);
                        }
                    }
                }

                // delete inactive scratches
                foreach (var scratchNumber in scratchesToDelete)
                {
                    ScratchBufferItem scratchBufferToRemove;
                    if (_scratchBuffers.TryRemove(scratchNumber, out scratchBufferToRemove))
                    {
                        scratchBufferToRemove.File.Dispose();
                    }
                }
            }

            if (sizeAfterAllocation >= (_sizeLimit * 3) / 4 && oldestActiveTransaction > current.OldestTransactionWhenFlushWasForced)
            {
                // we may get recursive flushing, so we want to avoid it
                if (tx.Environment.Journal.Applicator.IsCurrentThreadInFlushOperation == false)
                {
                    // We are starting to force a flush to free scratch pages. We are doing it at this point (80% of the max scratch size)
                    // to make sure that next transactions will be able to allocate pages that we are going to free in the current transaction.
                    // Important notice: all pages freed by this run will get ValidAfterTransactionId == tx.Id (so only next ones can use it)

                    bool flushLockTaken = false;
                    using (tx.Environment.Journal.Applicator.TryTakeFlushingLock(ref flushLockTaken))
                    {
                        if (flushLockTaken) // if we are already flushing, we don't need to force a flush
                        {
                            try
                            {
                                tx.Environment.ForceLogFlushToDataFile(tx, allowToFlushOverwrittenPages: true);
                                current.OldestTransactionWhenFlushWasForced = oldestActiveTransaction;
                            }
                            catch (TimeoutException)
                            {
                                // we'll try next time
                            }
                            catch (InvalidJournalFlushRequestException)
                            {
                                // journals flushing already in progress
                            }
                        }
                    }
                }
            }

            if (sizeAfterAllocation > _sizeLimit)
            {
                var sp = Stopwatch.StartNew();

                // Our problem is that we don't have any available free pages, probably because
                // there are read transactions that are holding things open. We are going to see if
                // there are any free pages that _might_ be freed for us if we wait for a bit. The idea
                // is that we let the read transactions time to complete and do their work, at which point
                // we can continue running. It is possible that a long running read transaction
                // would in fact generate enough work for us to timeout, but hopefully we can avoid that.

                while (tx.IsLazyTransaction == false && // lazy transaction is holding a read tx that will stop this, nothing to do here
                       tx.Environment.Options.ManualFlushing == false &&
                       sp.ElapsedMilliseconds < tx.Environment.Options.ScratchBufferOverflowTimeout)
                {
                    if (current.File.TryGettingFromAllocatedBuffer(tx, numberOfPages, size, out result))
                    {
                        return(result);
                    }
                    Thread.Sleep(32);
                }

                sp.Stop();

                bool createNextFile = false;

                if (tx.IsLazyTransaction)
                {
                    // in lazy transaction when reaching full scratch buffer - we might still have continuous space, but because
                    // of high insertion rate - we reach scratch buffer full, so we will create new scratch anyhow

                    createNextFile = true;
                }
                else if (current.File.HasDiscontinuousSpaceFor(tx, size, _scratchBuffers.Count))
                {
                    // there is enough space for the requested allocation but the problem is its fragmentation
                    // so we will create a new scratch file and will allow to allocate new continuous range from there

                    createNextFile = true;
                }
                else if (_scratchBuffers.Count == 1 && current.File.Size < _sizeLimit &&
                         (current.File.ActivelyUsedBytes(oldestActiveTransaction) + size * tx.Environment.Options.PageSize) < _sizeLimit)
                {
                    // there is only one scratch file that hasn't reach the size limit yet and
                    // the number of bytes being in active use allows to allocate the requested size
                    // let it create a new file

                    createNextFile = true;
                }

                if (createNextFile)
                {
                    // We need to ensure that _current stays constant through the codepath until return.
                    current = NextFile();

                    try
                    {
                        current.File.PagerState.AddRef();
                        tx.EnsurePagerStateReference(current.File.PagerState);

                        return(current.File.Allocate(tx, numberOfPages, size));
                    }
                    finally
                    {
                        // That's why we update only after exiting.
                        _current = current;
                    }
                }

                ThrowScratchBufferTooBig(tx, numberOfPages, size, oldestActiveTransaction, sizeAfterAllocation, sp, current);
            }

            // we don't have free pages to give out, need to allocate some
            result = current.File.Allocate(tx, numberOfPages, size);
            _options.OnScratchBufferSizeChanged(sizeAfterAllocation);

            return(result);
        }