示例#1
0
        private ScratchBufferItem NextFile(long minSize, long?requestedSize)
        {
            var current  = _recycleArea.Last;
            var oldestTx = _env.PossibleOldestReadTransaction(null);

            while (current != null)
            {
                var recycled = current.Value;

                if (recycled.File.Size >= Math.Max(minSize, requestedSize ?? 0) &&
                    // even though this is in the recyle bin, there might still be some transactions looking at it
                    // so we have to make sure that this is really unused before actually reusing it
                    recycled.File.HasActivelyUsedBytes(oldestTx) == false)
                {
                    recycled.File.Reset();
                    recycled.RecycledAt = default(DateTime);
                    _recycleArea.Remove(current);
                    AddScratchBufferFile(recycled);

                    _scratchSpaceMonitor.Increase(recycled.File.NumberOfAllocatedPages * Constants.Storage.PageSize);

                    return(recycled);
                }

                current = current.Previous;
            }

            _currentScratchNumber++;
            AbstractPager scratchPager;

            if (requestedSize != null)
            {
                try
                {
                    scratchPager =
                        _options.CreateScratchPager(StorageEnvironmentOptions.ScratchBufferName(_currentScratchNumber),
                                                    requestedSize.Value);
                }
                catch (Exception)
                {
                    // this can fail because of disk space issue, let us just ignore it
                    // we'll allocate the minimum amount in a bit anyway
                    return(NextFile(minSize, null));
                }
            }
            else
            {
                scratchPager = _options.CreateScratchPager(StorageEnvironmentOptions.ScratchBufferName(_currentScratchNumber),
                                                           Math.Max(_options.InitialLogFileSize, minSize));
            }

            var scratchFile = new ScratchBufferFile(scratchPager, _currentScratchNumber);
            var item        = new ScratchBufferItem(scratchFile.Number, scratchFile);

            AddScratchBufferFile(item);

            _scratchSpaceMonitor.Increase(item.File.NumberOfAllocatedPages * Constants.Storage.PageSize);

            return(item);
        }
示例#2
0
        public void WhenInitialFileSizeIsSetTheFileSizeForDataFileAndScratchFileShouldBeSetAccordinglyAndItWillBeRoundedToTheNearestGranularity()
        {
            var options = StorageEnvironmentOptions.ForPath(DataDir);

            options.InitialFileSize = GetExpectedInitialSize() * 2 + 1;

            using (new StorageEnvironment(options))
            {
                var dataFile    = Path.Combine(DataDir, Constants.DatabaseFilename);
                var scratchFile = Path.Combine(DataDir, StorageEnvironmentOptions.ScratchBufferName(0));

                if (StorageEnvironmentOptions.RunningOnPosix)
                {
                    // on Linux, we use 4K as the allocation granularity
                    Assert.Equal(0, new FileInfo(dataFile).Length % 4096);
                    Assert.Equal(0, new FileInfo(scratchFile).Length % 4096);
                }
                else
                {
                    // on Windows, we use 64K as the allocation granularity
                    Assert.Equal(0, new FileInfo(dataFile).Length % GetExpectedInitialSize());
                    Assert.Equal(0, new FileInfo(scratchFile).Length % GetExpectedInitialSize());
                }
            }
        }
示例#3
0
        public ScratchBufferPoolInfo InfoForDebug(long oldestActiveTransaction)
        {
            var currentFile           = _current.File;
            var scratchBufferPoolInfo = new ScratchBufferPoolInfo
            {
                OldestActiveTransaction     = oldestActiveTransaction,
                NumberOfScratchFiles        = _scratchBuffers.Count,
                CurrentFileNumber           = currentFile.Number,
                CurrentFileSizeInMB         = currentFile.Size / 1024L / 1024L,
                PerScratchFileSizeLimitInMB = _options.MaxScratchBufferSize / 1024L / 1024L,
                CurrentUtcTime = DateTime.UtcNow
            };

            foreach (var scratchBufferItem in _scratchBuffers.Values.OrderBy(x => x.Number))
            {
                var current          = _current;
                var scratchFileUsage = new ScratchFileUsage
                {
                    Name                = StorageEnvironmentOptions.ScratchBufferName(scratchBufferItem.File.Number),
                    SizeInKB            = scratchBufferItem.File.Size / 1024,
                    NumberOfAllocations = scratchBufferItem.File.NumberOfAllocations,
                    AllocatedPagesCount = scratchBufferItem.File.AllocatedPagesCount,
                    CanBeDeleted        = scratchBufferItem != current && scratchBufferItem.File.HasActivelyUsedBytes(oldestActiveTransaction) == false,
                    TxIdAfterWhichLatestFreePagesBecomeAvailable = scratchBufferItem.File.TxIdAfterWhichLatestFreePagesBecomeAvailable,
                    IsInRecycleArea = _recycleArea.Contains(scratchBufferItem),
                    NumberOfResets  = scratchBufferItem.File.DebugInfo.NumberOfResets,
                    LastResetTime   = scratchBufferItem.File.DebugInfo.LastResetTime,
                    LastFreeTime    = scratchBufferItem.File.DebugInfo.LastFreeTime
                };

                foreach (var freePage in scratchBufferItem.File.DebugInfo.GetMostAvailableFreePagesBySize())
                {
                    scratchFileUsage.MostAvailableFreePages.Add(new MostAvailableFreePagesBySize
                    {
                        Size = freePage.Key,
                        ValidAfterTransactionId = freePage.Value
                    });
                }

                foreach (var allocatedPage in scratchBufferItem.File.DebugInfo.GetFirst10AllocatedPages())
                {
                    scratchFileUsage.First10AllocatedPages.Add(new AllocatedPageInScratchBuffer()
                    {
                        NumberOfPages           = allocatedPage.NumberOfPages,
                        PositionInScratchBuffer = allocatedPage.PositionInScratchBuffer,
                        ScratchFileNumber       = allocatedPage.ScratchFileNumber,
                        ScratchPageNumber       = allocatedPage.ScratchPageNumber,
                        Size = allocatedPage.Size
                    });
                }

                scratchBufferPoolInfo.ScratchFilesUsage.Add(scratchFileUsage);
            }

            return(scratchBufferPoolInfo);
        }
示例#4
0
        public void DefaultScratchLocation()
        {
            var options = (StorageEnvironmentOptions.DirectoryStorageEnvironmentOptions)StorageEnvironmentOptions.ForPath(DataDir);

            using (var env = new StorageEnvironment(options))
            {
                var scratchFile = Path.Combine(DataDir, StorageEnvironmentOptions.ScratchBufferName(0));
                Assert.True(File.Exists(scratchFile));
            }
        }
示例#5
0
        public void TempPathForVoronShouldWork2()
        {
            using (var storage = NewTransactionalStorage(requestedStorage: "voron", dataDir: path, tempDir: temp, runInMemory: false))
            {
                var scratchFile     = Path.Combine(path, StorageEnvironmentOptions.ScratchBufferName(0));
                var scratchFileTemp = Path.Combine(temp, StorageEnvironmentOptions.ScratchBufferName(0));

                Assert.False(File.Exists(scratchFile));
                Assert.True(File.Exists(scratchFileTemp));
            }
        }
示例#6
0
        public void ScratchLocationWithTemporaryPathSpecified()
        {
            var options = (StorageEnvironmentOptions.DirectoryStorageEnvironmentOptions)StorageEnvironmentOptions.ForPath(DataDir, DataDir + "Temp");

            using (var env = new StorageEnvironment(options))
            {
                var scratchFile     = Path.Combine(DataDir, StorageEnvironmentOptions.ScratchBufferName(0));
                var scratchFileTemp = Path.Combine(DataDir + "Temp", StorageEnvironmentOptions.ScratchBufferName(0));

                Assert.False(File.Exists(scratchFile));
                Assert.True(File.Exists(scratchFileTemp));
            }
        }
        private ScratchBufferItem NextFile()
        {
            _currentScratchNumber++;
            var scratchPager = _options.CreateScratchPager(StorageEnvironmentOptions.ScratchBufferName(_currentScratchNumber));

            scratchPager.EnsureContinuous(0, (int)(Math.Max(_options.InitialFileSize ?? 0, _options.InitialLogFileSize) / _options.PageSize));

            var scratchFile = new ScratchBufferFile(scratchPager, _currentScratchNumber);
            var item        = new ScratchBufferItem(scratchFile.Number, scratchFile);

            _scratchBuffers.TryAdd(item.Number, item);

            return(item);
        }
示例#8
0
        private ScratchBufferFile NextFile()
        {
            _currentScratchNumber++;
            var scratchPager = _options.CreateScratchPager(StorageEnvironmentOptions.ScratchBufferName(_currentScratchNumber));

            scratchPager.AllocateMorePages(null, Math.Max(_options.InitialFileSize ?? 0, _options.InitialLogFileSize));

            var scratchFile = new ScratchBufferFile(scratchPager, _currentScratchNumber);

            _scratchBuffers.Add(_currentScratchNumber, scratchFile);

            _oldestTransactionWhenFlushWasForced = -1;

            return(scratchFile);
        }
示例#9
0
        public void WhenInitialFileSizeIsSetTheFileSizeForDataFileAndScratchFileShouldBeSetAccordingly()
        {
            var options = StorageEnvironmentOptions.ForPath(DataDir);

            options.InitialFileSize = GetExpectedInitialSize() * 2;

            using (new StorageEnvironment(options))
            {
                var dataFile    = Path.Combine(DataDir, Constants.DatabaseFilename);
                var scratchFile = Path.Combine(DataDir, StorageEnvironmentOptions.ScratchBufferName(0));

                Assert.Equal(0, new FileInfo(dataFile).Length % GetExpectedInitialSize());
                Assert.Equal(0, new FileInfo(scratchFile).Length % GetExpectedInitialSize());
            }
        }
示例#10
0
        public void WhenInitialFileSizeIsSetTheFileSizeForDataFileAndScratchFileShouldBeSetAccordinglyAndItWillBeRoundedToTheNearestGranularity()
        {
            var options = StorageEnvironmentOptions.ForPath(path);

            options.InitialFileSize = GetExpectedInitialSize() * 2 + 1;

            using (new StorageEnvironment(options))
            {
                var dataFile    = Path.Combine(path, Constants.DatabaseFilename);
                var scratchFile = Path.Combine(path, StorageEnvironmentOptions.ScratchBufferName(0));

                Assert.Equal(GetExpectedInitialSize() * 3, new FileInfo(dataFile).Length);
                Assert.Equal(GetExpectedInitialSize() * 3, new FileInfo(scratchFile).Length);
            }
        }
示例#11
0
        private ScratchBufferItem NextFile()
        {
            _currentScratchNumber++;

            var scratchPager = _options.CreateScratchPager(StorageEnvironmentOptions.ScratchBufferName(_currentScratchNumber));

            scratchPager.AllocateMorePages(null, Math.Max(_options.InitialFileSize ?? 0, _options.InitialLogFileSize));

            var scratchFile = new ScratchBufferFile(scratchPager, _currentScratchNumber);
            var item        = new ScratchBufferItem(scratchFile.Number, scratchFile);

            _scratchBuffers.TryAdd(item.Number, item);

            return(item);
        }
示例#12
0
        public void WhenInitialFileSizeIsNotSetTheFileSizeForDataFileAndScratchFileShouldBeSetToSystemAllocationGranularity()
        {
            var options = StorageEnvironmentOptions.ForPath(DataDir);

            options.InitialFileSize = null;

            using (new StorageEnvironment(options))
            {
                var dataFile    = Path.Combine(DataDir, Constants.DatabaseFilename);
                var scratchFile = Path.Combine(DataDir, StorageEnvironmentOptions.ScratchBufferName(0));

                Assert.Equal(GetExpectedInitialSize(), new FileInfo(dataFile).Length);
                Assert.Equal(GetExpectedInitialSize(), new FileInfo(scratchFile).Length);
            }
        }
示例#13
0
        private ScratchBufferItem NextFile(long minSize, long?requestedSize, LowLevelTransaction tx)
        {
            if (_recycleArea.Count > 0)
            {
                var recycled = _recycleArea.Last.Value.Item2;
                _recycleArea.RemoveLast();

                if (recycled.File.Size <= Math.Max(minSize, requestedSize ?? 0))
                {
                    recycled.File.Reset(tx);
                    _scratchBuffers.TryAdd(recycled.Number, recycled);
                    return(recycled);
                }
            }

            _currentScratchNumber++;
            AbstractPager scratchPager;

            if (requestedSize != null)
            {
                try
                {
                    scratchPager =
                        _options.CreateScratchPager(StorageEnvironmentOptions.ScratchBufferName(_currentScratchNumber),
                                                    requestedSize.Value);
                }
                catch (Exception)
                {
                    // this can fail because of disk space issue, let us just ignore it
                    // we'll allocate the minimum amount in a bit anway
                    return(NextFile(minSize, null, tx));
                }
            }
            else
            {
                scratchPager = _options.CreateScratchPager(StorageEnvironmentOptions.ScratchBufferName(_currentScratchNumber),
                                                           Math.Max(_options.InitialLogFileSize, minSize));
            }

            var scratchFile = new ScratchBufferFile(scratchPager, _currentScratchNumber);
            var item        = new ScratchBufferItem(scratchFile.Number, scratchFile);

            _scratchBuffers.TryAdd(item.Number, item);

            return(item);
        }
示例#14
0
        public void WhenInitialFileSizeIsSetTheFileSizeForDataFileAndScratchFileShouldBeSetAccordinglyAndItWillBeRoundedToTheNearestGranularity()
        {
            Win32NativeMethods.SYSTEM_INFO systemInfo;
            Win32NativeMethods.GetSystemInfo(out systemInfo);

            var options = StorageEnvironmentOptions.ForPath(path);

            options.InitialFileSize = systemInfo.allocationGranularity * 2 + 1;

            using (new StorageEnvironment(options))
            {
                var dataFile    = Path.Combine(path, Constants.DatabaseFilename);
                var scratchFile = Path.Combine(path, StorageEnvironmentOptions.ScratchBufferName(0));

                Assert.Equal(systemInfo.allocationGranularity * 3, new FileInfo(dataFile).Length);
                Assert.Equal(systemInfo.allocationGranularity * 3, new FileInfo(scratchFile).Length);
            }
        }
示例#15
0
        public ScratchBufferPoolInfo InfoForDebug(long oldestActiveTransaction)
        {
            var currentFile           = _current.File;
            var scratchBufferPoolInfo = new ScratchBufferPoolInfo
            {
                OldestActiveTransaction     = oldestActiveTransaction,
                NumberOfScratchFiles        = _scratchBuffers.Count,
                CurrentFileNumber           = currentFile.Number,
                CurrentFileSizeInMB         = currentFile.Size / 1024L / 1024L,
                PerScratchFileSizeLimitInMB = _options.MaxScratchBufferSize / 1024L / 1024L
            };

            foreach (var scratchBufferItem in _scratchBuffers.Values.OrderBy(x => x.Number))
            {
                var current          = _current;
                var scratchFileUsage = new ScratchFileUsage
                {
                    Name                = StorageEnvironmentOptions.ScratchBufferName(scratchBufferItem.File.Number),
                    SizeInKB            = scratchBufferItem.File.Size / 1024,
                    NumberOfAllocations = scratchBufferItem.File.NumberOfAllocations,
                    AllocatedPagesCount = scratchBufferItem.File.AllocatedPagesCount,
                    CanBeDeleted        = scratchBufferItem != current && scratchBufferItem.File.HasActivelyUsedBytes(oldestActiveTransaction) == false,
                    TxIdAfterWhichLatestFreePagesBecomeAvailable = scratchBufferItem.File.TxIdAfterWhichLatestFreePagesBecomeAvailable
                };

                foreach (var freePage in scratchBufferItem.File.GetMostAvailableFreePagesBySize())
                {
                    scratchFileUsage.MostAvailableFreePages.Add(new MostAvailableFreePagesBySize
                    {
                        Size = freePage.Key,
                        ValidAfterTransactionId = freePage.Value
                    });
                }

                scratchBufferPoolInfo.ScratchFilesUsage.Add(scratchFileUsage);
            }

            return(scratchBufferPoolInfo);
        }
        private void ThrowScratchBufferTooBig(LowLevelTransaction tx, int numberOfPages, long size, long oldestActiveTransaction,
                                              long sizeAfterAllocation, Stopwatch sp, ScratchBufferItem current)
        {
            var debugInfoBuilder = new StringBuilder();
            var totalPages       = tx.GetTransactionPages().Count;

            debugInfoBuilder.AppendFormat("Current transaction id: {0}\r\n", tx.Id);
            if ((totalPages + numberOfPages) * tx.Environment.Options.PageSize >= _sizeLimit / 2)
            {
                debugInfoBuilder.Append("- - - - - - - - - - - - -\r\n");
                debugInfoBuilder.AppendFormat(
                    "This transaction is VERY big, and requires {0:##,###;;0} kb out of {1:##,###;;0} kb allows!\r\n",
                    ((totalPages + numberOfPages) * tx.Environment.Options.PageSize) / 1024,
                    _sizeLimit / 1024
                    );
                debugInfoBuilder.Append("- - - - - - - - - - - - -\r\n");
            }

            debugInfoBuilder.AppendFormat("Requested number of pages: {0} (adjusted size: {1} == {2:#,#;;0} KB)\r\n", numberOfPages,
                                          size, size * tx.Environment.Options.PageSize / 1024);
            debugInfoBuilder.AppendFormat("Total number of pages in tx: {0} (adjusted size: {1} == {2:#,#;;0} KB)\r\n", totalPages,
                                          totalPages, totalPages * tx.Environment.Options.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",
                                          current.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.File.Size / 1024,
                                              scratchBufferFile.Value.File.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.File.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.File.Size / 1024L,
                                           current.File.SizeAfterAllocation(size) / 1024L,
                                           sizeAfterAllocation / 1024L,
                                           _sizeLimit / 1024L,
                                           sp.ElapsedMilliseconds,
                                           debugInfo
                                           );

            throw new ScratchBufferSizeLimitException(message);
        }
示例#17
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);
        }