/* * The contents of this file are subject to the Initial * Developer's Public License Version 1.0 (the "License"); * you may not use this file except in compliance with the * License. You may obtain a copy of the License at * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. * * Software distributed under the License is distributed AS IS, * WITHOUT WARRANTY OF ANY KIND, either express or implied. * See the License for the specific language governing rights * and limitations under the License. * * The Original Code was created by Dmitry Yemanov * for the Firebird Open Source RDBMS project. * * Copyright (c) 2006 Dmitry Yemanov * and all contributors signed below. * * All Rights Reserved. * Contributor(s): ______________________________________. */ #include "firebird.h" #include "iberror.h" #include "../common/classes/TempFile.h" #include "../common/config/config.h" #include "../common/config/dir_list.h" #include "../common/gdsassert.h" #include "../common/isc_proto.h" #include "../common/os/path_utils.h" #include "../jrd/jrd.h" #include "../jrd/TempSpace.h" using namespace Firebird; using namespace Jrd; // Static definitions/initializations GlobalPtr TempSpace::initMutex; TempDirectoryList* TempSpace::tempDirs = NULL; FB_SIZE_T TempSpace::minBlockSize = 0; namespace { const size_t MIN_TEMP_BLOCK_SIZE = 64 * 1024; class TempCacheLimitGuard { public: explicit TempCacheLimitGuard(FB_SIZE_T size) : m_dbb(GET_DBB()), m_size(size), m_guard(m_dbb->dbb_temp_cache_mutex, FB_FUNCTION) { m_allowed = (m_dbb->dbb_temp_cache_size + size <= m_dbb->dbb_config->getTempCacheLimit()); } bool isAllowed() const { return m_allowed; } void increment() { fb_assert(m_allowed); m_dbb->dbb_temp_cache_size += m_size; } static void decrement(FB_SIZE_T size) { Database* const dbb = GET_DBB(); MutexLockGuard guard(dbb->dbb_temp_cache_mutex, FB_FUNCTION); dbb->dbb_temp_cache_size -= size; } private: Database* const m_dbb; FB_SIZE_T m_size; MutexLockGuard m_guard; bool m_allowed; }; } // // In-memory block class // FB_SIZE_T TempSpace::MemoryBlock::read(offset_t offset, void* buffer, FB_SIZE_T length) { if (offset + length > size) { length = size - offset; } memcpy(buffer, ptr + offset, length); return length; } FB_SIZE_T TempSpace::MemoryBlock::write(offset_t offset, const void* buffer, FB_SIZE_T length) { if (offset + length > size) { length = size - offset; } memcpy(ptr + offset, buffer, length); return length; } // // On-disk block class // FB_SIZE_T TempSpace::FileBlock::read(offset_t offset, void* buffer, FB_SIZE_T length) { if (offset + length > size) { length = size - offset; } offset += seek; return file->read(offset, buffer, length); } FB_SIZE_T TempSpace::FileBlock::write(offset_t offset, const void* buffer, FB_SIZE_T length) { if (offset + length > size) { length = size - offset; } offset += seek; return file->write(offset, buffer, length); } // // TempSpace::TempSpace // // Constructor // TempSpace::TempSpace(MemoryPool& p, const PathName& prefix, bool dynamic) : pool(p), filePrefix(p, prefix), logicalSize(0), physicalSize(0), localCacheUsage(0), head(NULL), tail(NULL), tempFiles(p), initialBuffer(p), initiallyDynamic(dynamic), freeSegments(p) { if (!tempDirs) { MutexLockGuard guard(initMutex, FB_FUNCTION); if (!tempDirs) { MemoryPool& def_pool = *getDefaultMemoryPool(); tempDirs = FB_NEW_POOL(def_pool) TempDirectoryList(def_pool); minBlockSize = Config::getTempBlockSize(); if (minBlockSize < MIN_TEMP_BLOCK_SIZE) minBlockSize = MIN_TEMP_BLOCK_SIZE; else minBlockSize = FB_ALIGN(minBlockSize, MIN_TEMP_BLOCK_SIZE); } } } // // TempSpace::~TempSpace // // Destructor // TempSpace::~TempSpace() { while (head) { Block* temp = head->next; delete head; head = temp; } TempCacheLimitGuard::decrement(localCacheUsage); while (tempFiles.getCount()) delete tempFiles.pop(); } // // TempSpace::read // // Reads bytes from the temporary space // FB_SIZE_T TempSpace::read(offset_t offset, void* buffer, FB_SIZE_T length) { fb_assert(offset + length <= logicalSize); if (length) { // search for the first needed block Block* block = findBlock(offset); UCHAR* p = static_cast(buffer); FB_SIZE_T l = length; // read data from the block chain for (Block* itr = block; itr && l; itr = itr->next, offset = 0) { const FB_SIZE_T n = itr->read(offset, p, l); p += n; l -= n; } fb_assert(!l); } return length; } // // TempSpace::write // // Writes bytes to the temporary space // FB_SIZE_T TempSpace::write(offset_t offset, const void* buffer, FB_SIZE_T length) { fb_assert(offset <= logicalSize); if (offset + length > logicalSize) { // not enough space, allocate one more block extend(offset + length - logicalSize); } if (length) { // search for the first needed block Block* const block = findBlock(offset); const UCHAR* p = static_cast(buffer); FB_SIZE_T l = length; // write data to as many blocks as necessary for (Block* itr = block; itr && l; itr = itr->next, offset = 0) { const FB_SIZE_T n = itr->write(offset, p, l); p += n; l -= n; } fb_assert(!l); } return length; } // // TempSpace::extend // // Increases size of the temporary space // void TempSpace::extend(FB_SIZE_T size) { logicalSize += size; if (logicalSize > physicalSize) { const FB_SIZE_T initialSize = initialBuffer.getCount(); // If the dynamic mode is specified, then we allocate new blocks // by growing the same initial memory block in the specified chunks. // Once the limit (64KB) is reached, we switch to the generic algorithm // (1MB blocks), copy the existing data there and free the initial buffer. // // This mode should not be used if the caller never works with small blocks. // Also, it MUST NOT be used if the caller deals with inMemory() or allocateBatch() // routines and caches the pointers to use them later. These pointers may become // invalid after resizing the initial block or after switching to large blocks. if (initiallyDynamic && logicalSize < MIN_TEMP_BLOCK_SIZE) { // allocate or extend the initial dynamic block, it will grow up to 64KB if (!initialSize) { fb_assert(!head && !tail); head = tail = FB_NEW_POOL(pool) InitialBlock(initialBuffer.getBuffer(size), size); } else { fb_assert(head == tail); size += initialSize; initialBuffer.resize(size); new(head) InitialBlock(initialBuffer.begin(), size); } physicalSize = size; return; } if (initialSize) { fb_assert(head == tail); delete head; head = tail = NULL; size = static_cast(FB_ALIGN(logicalSize, minBlockSize)); physicalSize = size; } else { size = static_cast(FB_ALIGN(logicalSize - physicalSize, minBlockSize)); physicalSize += size; } Block* block = NULL; { // scope TempCacheLimitGuard guard(size); if (guard.isAllowed()) { try { // allocate block in virtual memory block = FB_NEW_POOL(pool) MemoryBlock(FB_NEW_POOL(pool) UCHAR[size], tail, size); localCacheUsage += size; guard.increment(); } catch (const BadAlloc&) { // not enough memory } } } // NS 2014-07-31: FIXME: missing exception handling. // error thrown in block of code below will leave TempSpace in inconsistent state: // logical/physical size already increased while allocation has in fact failed. if (!block) { // allocate block in the temp file TempFile* const file = setupFile(size); fb_assert(file); if (tail && tail->sameFile(file)) { fb_assert(!initialSize); tail->size += size; return; } block = FB_NEW_POOL(pool) FileBlock(file, tail, size); } // preserve the initial contents, if any if (initialSize) { block->write(0, initialBuffer.begin(), initialSize); initialBuffer.free(); } // append new block to the chain if (!head) { head = block; } tail = block; } } // // TempSpace::findBlock // // Locates the space block corresponding to the given global offset // TempSpace::Block* TempSpace::findBlock(offset_t& offset) const { fb_assert(offset <= logicalSize); Block* block = NULL; if (offset < physicalSize / 2) { // walk forward block = head; while (block && offset >= block->size) { offset -= block->size; block = block->next; } fb_assert(block); } else { // walk backward block = tail; while (block && physicalSize - offset > block->size) { offset += block->size; block = block->prev; } fb_assert(block); offset -= physicalSize - block->size; } fb_assert(offset <= block->size); return block; } // // TempSpace::setupFile // // Allocates the required space in some temporary file // TempFile* TempSpace::setupFile(FB_SIZE_T size) { StaticStatusVector status_vector; for (FB_SIZE_T i = 0; i < tempDirs->getCount(); i++) { TempFile* file = NULL; PathName directory = (*tempDirs)[i]; PathUtils::ensureSeparator(directory); for (FB_SIZE_T j = 0; j < tempFiles.getCount(); j++) { PathName dirname, filename; PathUtils::splitLastComponent(dirname, filename, tempFiles[j]->getName()); PathUtils::ensureSeparator(dirname); if (!directory.compare(dirname)) { file = tempFiles[j]; break; } } try { if (!file) { file = FB_NEW_POOL(pool) TempFile(pool, filePrefix, directory); tempFiles.add(file); } file->extend(size); } catch (const system_error& ex) { ex.stuffException(status_vector); continue; } return file; } // no room in all directories Arg::Gds status(isc_out_of_temp_space); status.append(Arg::StatusVector(status_vector.begin())); iscLogStatus(NULL, status.value()); status.raise(); return NULL; // compiler silencer } // // TempSpace::allocateSpace // // Allocate available space in free segments. Extend file if necessary // offset_t TempSpace::allocateSpace(FB_SIZE_T size) { // Find the best available space. This is defined as the smallest free space // that is big enough. This preserves large blocks. Segment* best = NULL; // Search through the available space in the not used segments list for (bool found = freeSegments.getFirst(); found; found = freeSegments.getNext()) { Segment* const space = &freeSegments.current(); // If this is smaller than our previous best, use it if (space->size >= size && (!best || (space->size < best->size))) { best = space; } } // If we didn't find any space, allocate it at the end of the file if (!best) { extend(size); return getSize() - size; } // Set up the return parameters const offset_t position = best->position; best->size -= size; best->position += size; // If the hunk was an exact fit, remove the segment from the list if (!best->size) { if (!freeSegments.locate(best->position)) fb_assert(false); freeSegments.fastRemove(); } return position; } // // TempSpace::releaseSpace // // Return previously allocated segment back into not used segments list and // join it with adjacent segments if found // void TempSpace::releaseSpace(offset_t position, FB_SIZE_T size) { fb_assert(size > 0); fb_assert(position < getSize()); // Block starts in file const offset_t end = position + size; fb_assert(end <= getSize()); // Block ends in file if (freeSegments.locate(locEqual, end)) { // The next segment is found to be adjacent Segment* const next_seg = &freeSegments.current(); next_seg->position -= size; next_seg->size += size; if (freeSegments.getPrev()) { // Check the prior segment for being adjacent Segment* const prior_seg = &freeSegments.current(); if (position == prior_seg->position + prior_seg->size) { next_seg->position -= prior_seg->size; next_seg->size += prior_seg->size; freeSegments.fastRemove(); } } return; } if (freeSegments.locate(locLess, position)) { // Check the prior segment for being adjacent Segment* const prior_seg = &freeSegments.current(); if (position == prior_seg->position + prior_seg->size) { prior_seg->size += size; return; } } freeSegments.add(Segment(position, size)); } // // TempSpace::inMemory // // Return contiguous chunk of memory if present at given location // UCHAR* TempSpace::inMemory(offset_t begin, size_t size) const { const Block* block = findBlock(begin); return block ? block->inMemory(begin, size) : NULL; } // // TempSpace::findMemory // // Return contiguous chunk of memory and adjust starting offset // of search range if found // UCHAR* TempSpace::findMemory(offset_t& begin, offset_t end, size_t size) const { offset_t local_offset = begin; const offset_t save_begin = begin; const Block* block = findBlock(local_offset); while (block && (begin + size <= end)) { UCHAR* const mem = block->inMemory(local_offset, size); if (mem) { return mem; } begin += block->size - local_offset; local_offset = 0; block = block->next; } begin = save_begin; return NULL; } // // TempSpace::validate // // Validate internal lists for consistency and return back to caller // amount of available free space // bool TempSpace::validate(offset_t& free) const { free = 0; FreeSegmentTree::ConstAccessor accessor(&freeSegments); for (bool found = accessor.getFirst(); found; found = accessor.getNext()) { const offset_t size = accessor.current().size; fb_assert(size != 0); free += size; } offset_t disk = 0; for (FB_SIZE_T i = 0; i < tempFiles.getCount(); i++) disk += tempFiles[i]->getSize(); return ((initialBuffer.getCount() + localCacheUsage + disk) == physicalSize); } // // TempSpace::allocateBatch // // Allocate up to 'count' contiguous chunks of memory available in free // segments if any. Adjust size of chunks between minSize and maxSize // accordingly to available free space (assuming all of the free space // is in memory blocks). Algorithm is very simple and can be improved in future // ULONG TempSpace::allocateBatch(ULONG count, FB_SIZE_T minSize, FB_SIZE_T maxSize, Segments& segments) { // adjust passed chunk size to amount of free memory we have and number // of runs still not allocated. offset_t freeMem = 0; for (bool found = freeSegments.getFirst(); found; found = freeSegments.getNext()) freeMem += freeSegments.current().size; freeMem = MIN(freeMem / count, maxSize); freeMem = MAX(freeMem, minSize); freeMem = MIN(freeMem, minBlockSize); freeMem &= ~(FB_ALIGNMENT - 1); bool is_positioned = freeSegments.getFirst(); while (segments.getCount() < count && is_positioned) { Segment* freeSpace = &freeSegments.current(); offset_t freeSeek = freeSpace->position; const offset_t freeEnd = freeSpace->position + freeSpace->size; UCHAR* const mem = findMemory(freeSeek, freeEnd, freeMem); if (mem) { fb_assert(freeSeek + freeMem <= freeEnd); #ifdef DEV_BUILD offset_t seek1 = freeSeek; UCHAR* const p = findMemory(seek1, freeEnd, freeMem); fb_assert(p == mem); fb_assert(seek1 == freeSeek); #endif if (freeSeek != freeSpace->position) { const offset_t skip_size = freeSeek - freeSpace->position; const Segment skip_space(freeSpace->position, skip_size); freeSpace->position += skip_size; freeSpace->size -= skip_size; fb_assert(freeSpace->size != 0); if (!freeSegments.add(skip_space)) fb_assert(false); if (!freeSegments.locate(skip_space.position + skip_size)) fb_assert(false); freeSpace = &freeSegments.current(); } SegmentInMemory seg; seg.memory = mem; seg.position = freeSeek; seg.size = freeMem; segments.add(seg); freeSpace->position += freeMem; freeSpace->size -= freeMem; if (!freeSpace->size) { is_positioned = freeSegments.fastRemove(); } } else { is_positioned = freeSegments.getNext(); } } return segments.getCount(); }