/* * PROGRAM: Client/Server Common Code * MODULE: alloc.h * DESCRIPTION: Memory Pool Manager (based on B+ tree) * * 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 Nickolay Samofatov * for the Firebird Open Source RDBMS project. * * STL allocator is based on one by Mike Nordell and John Bellardo * * Copyright (c) 2004 Nickolay Samofatov * and all contributors signed below. * * All Rights Reserved. * * The Original Code was created by James A. Starkey for IBPhoenix. * * Copyright (c) 2004 James A. Starkey * All Rights Reserved. * * Contributor(s): * * Alex Peshkoff * added PermanentStorage and AutoStorage classes. * merged parts of Nickolay and Jim code to be used together * */ #ifndef CLASSES_ALLOC_H #define CLASSES_ALLOC_H #include "firebird.h" #include "fb_types.h" #include "../common/classes/locks.h" #include "../common/classes/auto.h" #include "../common/classes/fb_atomic.h" #include #if defined(MVS) || defined(__VMS) || defined (DARWIN) #include #else #include #endif #include #undef MEM_DEBUG #ifdef DEBUG_GDS_ALLOC #define MEM_DEBUG #endif #ifdef USE_VALGRIND // Size of Valgrind red zone applied before and after memory block allocated for user #define VALGRIND_REDZONE 0 //8 // When memory block is deallocated by user from the pool it must pass queue of this // length before it is actually deallocated and access protection from it removed. #define DELAYED_FREE_COUNT 1024 // When memory extent is deallocated when pool is destroying it must pass through // queue of this length before it is actually returned to system #define DELAYED_EXTENT_COUNT 32 #undef MEM_DEBUG // valgrind works instead #else #define VALGRIND_REDZONE 8 #endif namespace Firebird { // Alignment for all memory blocks. Sizes of memory blocks in headers are measured in this units const size_t ALLOC_ALIGNMENT = FB_ALIGNMENT; static inline size_t MEM_ALIGN(size_t value) { return FB_ALIGN(value, ALLOC_ALIGNMENT); } static const int DEFAULT_ROUNDING = 8; static const int DEFAULT_CUTOFF = 4096; static const int DEFAULT_ALLOCATION = 65536; class MemoryPool; class MemoryStats { public: explicit MemoryStats(MemoryStats* parent = NULL) : mst_parent(parent), mst_usage(0), mst_mapped(0), mst_max_usage(0), mst_max_mapped(0) {} ~MemoryStats() {} size_t getCurrentUsage() const throw () { return mst_usage.value(); } size_t getMaximumUsage() const throw () { return mst_max_usage; } size_t getCurrentMapping() const throw () { return mst_mapped.value(); } size_t getMaximumMapping() const throw () { return mst_max_mapped; } private: // Forbid copying/assignment MemoryStats(const MemoryStats&); MemoryStats& operator=(const MemoryStats&); MemoryStats* mst_parent; // Currently allocated memory (without allocator overhead) // Useful for monitoring engine memory leaks AtomicCounter mst_usage; // Amount of memory mapped (including all overheads) // Useful for monitoring OS memory consumption AtomicCounter mst_mapped; // We don't particularily care about extreme precision of these max values, // this is why we don't synchronize them size_t mst_max_usage; size_t mst_max_mapped; // These methods are thread-safe due to usage of atomic counters only void increment_usage(size_t size) throw () { for (MemoryStats* statistics = this; statistics; statistics = statistics->mst_parent) { const size_t temp = statistics->mst_usage += size; if (temp > statistics->mst_max_usage) statistics->mst_max_usage = temp; } } void decrement_usage(size_t size) throw () { for (MemoryStats* statistics = this; statistics; statistics = statistics->mst_parent) { statistics->mst_usage -= size; } } void increment_mapping(size_t size) throw () { for (MemoryStats* statistics = this; statistics; statistics = statistics->mst_parent) { const size_t temp = statistics->mst_mapped += size; if (temp > statistics->mst_max_mapped) statistics->mst_max_mapped = temp; } } void decrement_mapping(size_t size) throw () { for (MemoryStats* statistics = this; statistics; statistics = statistics->mst_parent) { statistics->mst_mapped -= size; } } friend class MemoryPool; }; typedef SLONG INT32; class MemHeader { public: MemoryPool *pool; SINT64 length; #ifdef DEBUG_GDS_ALLOC INT32 lineNumber; const char *fileName; #endif #if defined(USE_VALGRIND) && (VALGRIND_REDZONE != 0) const char mbk_valgrind_redzone[VALGRIND_REDZONE]; #endif }; class MemBlock : public MemHeader { public: UCHAR body; }; class MemBigObject; class MemBigHeader { public: MemBigObject *next; MemBigObject *prior; }; class MemBigObject : public MemBigHeader { public: MemHeader memHeader; }; class MemFreeBlock : public MemBigObject { public: MemFreeBlock *nextLarger; MemFreeBlock *priorSmaller; MemFreeBlock *nextTwin; MemFreeBlock *priorTwin; }; class MemSmallHunk { public: MemSmallHunk *nextHunk; size_t length; UCHAR *memory; size_t spaceRemaining; }; class MemBigHunk { public: MemBigHunk *nextHunk; size_t length; MemBigHeader blocks; }; class MemoryPool { public: MemoryPool(MemoryStats& stats = *default_stats_group, bool shared = true, int rounding = DEFAULT_ROUNDING, int cutoff = DEFAULT_CUTOFF, int minAllocation = DEFAULT_ALLOCATION); virtual ~MemoryPool(void); static MemoryPool* defaultMemoryManager; private: size_t roundingSize, threshold, minAllocation; //int headerSize; MemBlock **freeObjects; MemBigHunk *bigHunks; MemSmallHunk *smallHunks; MemFreeBlock freeBlocks; MemFreeBlock junk; Mutex mutex; int blocksAllocated; int blocksActive; bool threadShared; // Shared across threads, requires locking bool pool_destroying; // Default statistics group for process static MemoryStats* default_stats_group; // Statistics group for the pool MemoryStats* stats; // Memory used AtomicCounter used_memory, mapped_memory; protected: MemBlock* alloc(size_t size) throw (std::bad_alloc); void releaseBlock(MemBlock *block) throw (); public: void* allocate(size_t size #ifdef DEBUG_GDS_ALLOC , const char* fileName = NULL, int line = 0 #endif ) throw (std::bad_alloc); protected: void corrupt(const char* text) throw (); private: virtual void memoryIsExhausted(void) throw (std::bad_alloc); void remove(MemFreeBlock* block) throw (); void insert(MemFreeBlock* block) throw (); void* allocRaw(size_t length) throw (std::bad_alloc); void debugStop(void); void validateFreeList(void) throw (); void validateBigBlock(MemBigObject* block) throw (); static void release(void* block) throw (); void releaseRaw(void *block, size_t size) throw (); #ifdef USE_VALGRIND // Circular FIFO buffer of read/write protected blocks pending free operation MemBlock* delayedFree[DELAYED_FREE_COUNT]; size_t delayedFreeCount; size_t delayedFreePos; #endif public: static void deletePool(MemoryPool* pool); static void globalFree(void* block) throw (); void* calloc(size_t size #ifdef DEBUG_GDS_ALLOC , const char* fileName, int line #endif ) throw (std::bad_alloc); static void deallocate(void* block) throw (); void validate(void) throw (); // Allocate memory block. In case of problems this method returns NULL void* allocate_nothrow(size_t size #ifdef DEBUG_GDS_ALLOC , const char* file = NULL, int line = 0 #endif ) throw (); #ifdef LIBC_CALLS_NEW static void* globalAlloc(size_t s) throw (std::bad_alloc); #else static void* globalAlloc(size_t s) throw (std::bad_alloc) { return defaultMemoryManager->allocate(s #ifdef DEBUG_GDS_ALLOC , __FILE__, __LINE__ #endif ); } #endif // LIBC_CALLS_NEW // Create memory pool instance static MemoryPool* createPool(MemoryPool* parent = NULL, MemoryStats& stats = *default_stats_group); // Set context pool for current thread of execution static MemoryPool* setContextPool(MemoryPool* newPool); // Get context pool for current thread of execution static MemoryPool* getContextPool(); // Set statistics group for pool. Usage counters will be decremented from // previously set group and added to new void setStatsGroup(MemoryStats& stats) throw (); // Just a helper for AutoPtr. static void clear(MemoryPool* pool) { deletePool(pool); } // Initialize and finalize global memory pool static void init(); static void cleanup(); // Statistics void increment_usage(size_t size) throw () { stats->increment_usage(size); used_memory += size; } void decrement_usage(size_t size) throw () { stats->decrement_usage(size); used_memory -= size; } void increment_mapping(size_t size) throw () { stats->increment_mapping(size); mapped_memory += size; } void decrement_mapping(size_t size) throw () { stats->decrement_mapping(size); mapped_memory -= size; } // Print out pool contents. This is debugging routine void print_contents(FILE*, bool = false, const char* filter_path = 0) throw (); // The same routine, but more easily callable from the debugger void print_contents(const char* filename, bool = false, const char* filter_path = 0) throw (); }; } // namespace Firebird static inline Firebird::MemoryPool* getDefaultMemoryPool() throw () { return Firebird::MemoryPool::defaultMemoryManager; } namespace Firebird { // Class intended to manage execution context pool stack // Declare instance of this class when you need to set new context pool and it // will be restored automatically as soon holder variable gets out of scope class ContextPoolHolder { public: explicit ContextPoolHolder(MemoryPool* newPool) { savedPool = MemoryPool::setContextPool(newPool); } ~ContextPoolHolder() { MemoryPool::setContextPool(savedPool); } private: MemoryPool* savedPool; }; // template enabling common use of old and new pools control code // to be dropped when old-style code goes away template class SubsystemContextPoolHolder : public ContextPoolHolder { public: SubsystemContextPoolHolder ( SubsystemThreadData* subThreadData, SubsystemPool* newPool ) : ContextPoolHolder(newPool), savedThreadData(subThreadData), savedPool(savedThreadData->getDefaultPool()) { savedThreadData->setDefaultPool(newPool); } ~SubsystemContextPoolHolder() { savedThreadData->setDefaultPool(savedPool); } private: SubsystemThreadData* savedThreadData; SubsystemPool* savedPool; }; } // namespace Firebird using Firebird::MemoryPool; // Global versions of operators new and delete inline void* operator new(size_t s) throw (std::bad_alloc) { return MemoryPool::globalAlloc(s); } inline void* operator new[](size_t s) throw (std::bad_alloc) { return MemoryPool::globalAlloc(s); } inline void operator delete(void* mem) throw() { MemoryPool::globalFree(mem); } inline void operator delete[](void* mem) throw() { MemoryPool::globalFree(mem); } #ifdef DEBUG_GDS_ALLOC inline void* operator new(size_t s, Firebird::MemoryPool& pool, const char* file, int line) { return pool.allocate(s, file, line); } inline void* operator new[](size_t s, Firebird::MemoryPool& pool, const char* file, int line) { return pool.allocate(s, file, line); } #define FB_NEW(pool) new(pool, __FILE__, __LINE__) #define FB_NEW_RPT(pool, count) new(pool, count, __FILE__, __LINE__) #else inline void* operator new(size_t s, Firebird::MemoryPool& pool) { return pool.allocate(s); } inline void* operator new[](size_t s, Firebird::MemoryPool& pool) { return pool.allocate(s); } #define FB_NEW(pool) new(pool) #define FB_NEW_RPT(pool, count) new(pool, count) #endif namespace Firebird { // Global storage makes it possible to use new and delete for classes, // based on it, to behave traditionally, i.e. get memory from permanent pool. class GlobalStorage { public: void* operator new(size_t size) { return getDefaultMemoryPool()->allocate(size); } void operator delete(void* mem) { getDefaultMemoryPool()->deallocate(mem); } MemoryPool& getPool() const { return *getDefaultMemoryPool(); } }; // Permanent storage is used as base class for all objects, // performing memory allocation in methods other than // constructors of this objects. Permanent means that pool, // which will be later used for such allocations, must // be explicitly passed in all constructors of such object. class PermanentStorage { private: MemoryPool& pool; protected: explicit PermanentStorage(MemoryPool& p) : pool(p) { } MemoryPool& getPool() const { return pool; } }; // Automatic storage is used as base class for objects, // that may have constructors without explicit MemoryPool // parameter. In this case AutoStorage sends AutoMemoryPool // to PermanentStorage. To ensure this operation to be safe // such trick possible only for local (on stack) variables. class AutoStorage : public PermanentStorage { private: #if defined(DEV_BUILD) void ProbeStack() const; #endif public: static MemoryPool& getAutoMemoryPool(); protected: AutoStorage() : PermanentStorage(getAutoMemoryPool()) { #if defined(DEV_BUILD) ProbeStack(); #endif } explicit AutoStorage(MemoryPool& p) : PermanentStorage(p) { } }; typedef AutoPtr AutoMemoryPool; } // namespace Firebird #endif // CLASSES_ALLOC_H