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); }
public virtual int CopyPage(I4KbBatchWrites destI4KbBatchWrites, int scratchNumber, long p, PagerState pagerState) { var item = GetScratchBufferFile(scratchNumber); ScratchBufferFile bufferFile = item.File; return(bufferFile.CopyPage(destI4KbBatchWrites, p, pagerState)); }
public Page ReadPage(LowLevelTransaction tx, int scratchNumber, long p, PagerState pagerState = null) { var item = GetScratchBufferFile(scratchNumber); ScratchBufferFile bufferFile = item.File; return(bufferFile.ReadPage(tx, p, pagerState)); }
public byte *AcquirePagePointerForNewPage(LowLevelTransaction tx, int scratchNumber, long p, int numberOfPages) { var item = GetScratchBufferFile(scratchNumber); ScratchBufferFile bufferFile = item.File; return(bufferFile.AcquirePagePointerForNewPage(tx, p, numberOfPages)); }
public byte *AcquirePagePointerWithOverflowHandling(LowLevelTransaction tx, int scratchNumber, long p) { var item = GetScratchBufferFile(scratchNumber); ScratchBufferFile bufferFile = item.File; return(bufferFile.AcquirePagePointerWithOverflowHandling(tx, p)); }
public void EnsureMapped(LowLevelTransaction tx, int scratchNumber, long positionInScratchBuffer, int numberOfPages) { var item = GetScratchBufferFile(scratchNumber); ScratchBufferFile bufferFile = item.File; bufferFile.EnsureMapped(tx, positionInScratchBuffer, numberOfPages); }
public byte *AcquirePagePointer(LowLevelTransaction tx, int scratchNumber, long p) { var item = _scratchBuffers[scratchNumber]; ScratchBufferFile bufferFile = item.File; return(bufferFile.AcquirePagePointer(tx, p)); }
public byte *AcquirePagePointer(Transaction tx, int scratchNumber, long p) { ScratchBufferItem item = lastScratchBuffer; if (item.Number != scratchNumber) { item = _scratchBuffers[scratchNumber]; } ScratchBufferFile bufferFile = item.File; return(bufferFile.AcquirePagePointer(tx, p)); }
public Page ReadPage(Transaction tx, int scratchNumber, long p, PagerState pagerState = null) { ScratchBufferItem item = lastScratchBuffer; if (item.Number != scratchNumber) { item = _scratchBuffers[scratchNumber]; } ScratchBufferFile bufferFile = item.File; return(bufferFile.ReadPage(tx, p, pagerState)); }
private ScratchBufferFile NextFile() { _currentScratchNumber++; var scratchPager = _options.CreateScratchPager(StorageEnvironmentOptions.ScratchBufferName(_currentScratchNumber)); scratchPager.AllocateMorePages(null, _options.InitialFileSize.HasValue ? Math.Max(_options.InitialFileSize.Value, _options.InitialLogFileSize) : _options.InitialLogFileSize); var scratchFile = new ScratchBufferFile(scratchPager, _currentScratchNumber); _scratchBuffers.Add(_currentScratchNumber, scratchFile); _oldestTransactionWhenFlushWasForced = -1; return scratchFile; }
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); }
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); }
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); }
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); }
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 = _current.SizeAfterAllocation(size); long oldestActiveTransaction = tx.Environment.OldestTransaction; if (_scratchBuffers.Count > 1) { var scratchesToDelete = new List<int>(); // determine how many bytes of older scratches are still in use foreach (var olderScratch in _scratchBuffers.Values.Except(new []{_current})) { var bytesInUse = olderScratch.ActivelyUsedBytes(oldestActiveTransaction); if (bytesInUse > 0) sizeAfterAllocation += bytesInUse; else scratchesToDelete.Add(olderScratch.Number); } // delete inactive scratches foreach (var scratchNumber in scratchesToDelete) { var scratchBufferFile = _scratchBuffers[scratchNumber]; _scratchBuffers.Remove(scratchNumber); scratchBufferFile.Dispose(); } } if (sizeAfterAllocation > 0.8 * _sizeLimit && oldestActiveTransaction > _oldestTransactionWhenFlushWasForced) { _oldestTransactionWhenFlushWasForced = oldestActiveTransaction; // 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) tx.Environment.ForceLogFlushToDataFile(tx, allowToFlushOverwrittenPages: true); } 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(); if (_current.HasDiscontinuousSpaceFor(tx, size)) { // 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 _current = NextFile(); _current.PagerState.AddRef(); tx.AddPagerState(_current.PagerState); return _current.Allocate(tx, numberOfPages, size); } var debugInfoBuilder = new StringBuilder(); debugInfoBuilder.AppendFormat("Requested number of pages: {0} (NearestPowerOfTwo: {1})\r\n", numberOfPages, size); 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); 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) { 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) { 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); } } string debugInfo = debugInfoBuilder.ToString(); string message = string.Format("Cannot allocate more space for the scratch buffer.\r\n" + "Current size is:\t{0:#,#;;0} KB.\r\n" + "Limit:\t\t\t{1:#,#;;0} KB.\r\n" + "Requested Size:\t{2:#,#;;0} KB.\r\n" + "Already flushed and waited for {3:#,#;;0} ms for read transactions to complete.\r\n" + "Do you have a long running read transaction executing?\r\n" + "Debug info:\r\n{4}", _current.Size / 1024, _sizeLimit / 1024, (_current.Size + (size * AbstractPager.PageSize)) / 1024, 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); return result; }
public ScratchBufferPool(StorageEnvironment env) { _options = env.Options; _sizeLimit = env.Options.MaxScratchBufferSize; _current = NextFile(); }
public ScratchBufferItem(int number, ScratchBufferFile file) { Number = number; File = file; OldestTransactionWhenFlushWasForced = -1; }
public ScratchFileDebugInfo(ScratchBufferFile parent) { _parent = parent; }
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(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 = _current.SizeAfterAllocation(size); long oldestActiveTransaction = tx.Environment.OldestTransaction; if (_scratchBuffers.Count > 1) { var scratchesToDelete = new List <int>(); // determine how many bytes of older scratches are still in use foreach (var olderScratch in _scratchBuffers.Values.Except(new [] { _current })) { var bytesInUse = olderScratch.ActivelyUsedBytes(oldestActiveTransaction); if (bytesInUse > 0) { sizeAfterAllocation += bytesInUse; } else { scratchesToDelete.Add(olderScratch.Number); } } // delete inactive scratches foreach (var scratchNumber in scratchesToDelete) { var scratchBufferFile = _scratchBuffers[scratchNumber]; _scratchBuffers.Remove(scratchNumber); scratchBufferFile.Dispose(); } } if (sizeAfterAllocation > 0.8 * _sizeLimit && oldestActiveTransaction > _oldestTransactionWhenFlushWasForced) { _oldestTransactionWhenFlushWasForced = oldestActiveTransaction; // 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) tx.Environment.ForceLogFlushToDataFile(tx, allowToFlushOverwrittenPages: true); } 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(); if (_current.HasDiscontinuousSpaceFor(tx, size)) { // 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 _current = NextFile(); _current.PagerState.AddRef(); tx.AddPagerState(_current.PagerState); return(_current.Allocate(tx, numberOfPages, size)); } string message = string.Format("Cannot allocate more space for the scratch buffer.\r\n" + "Current size is:\t{0:#,#;;0} kb.\r\n" + "Limit:\t\t\t{1:#,#;;0} kb.\r\n" + "Requested Size:\t{2:#,#;;0} kb.\r\n" + "Already flushed and waited for {3:#,#;;0} ms for read transactions to complete.\r\n" + "Do you have a long running read transaction executing?", _current.Size / 1024, _sizeLimit / 1024, (_current.Size + (size * AbstractPager.PageSize)) / 1024, sp.ElapsedMilliseconds); throw new ScratchBufferSizeLimitException(message); } // we don't have free pages to give out, need to allocate some result = _current.Allocate(tx, numberOfPages, size); return(result); }
public ScratchBufferCacheItem(int number, ScratchBufferFile file) { this.Number = number; this.File = file; }
public ScratchBufferPool(StorageEnvironment env) { _options = env.Options; _sizeLimit = env.Options.MaxScratchBufferSize; _current = NextFile(); }
public ScratchBufferItem(int number, ScratchBufferFile file) { Number = number; File = file; }
public ScratchBufferCacheItem(int number, ScratchBufferFile file) { this.Number = number; this.File = file; }
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); }