//////////////////////////////////////////////////////////////////////////////// /// @brief basic memory management /// /// @file /// /// DISCLAIMER /// /// Copyright 2014 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Dr. Frank Celler /// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany /// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "Basics/Common.h" #ifdef TRI_ENABLE_FAILURE_TESTS #include #include #endif // ----------------------------------------------------------------------------- // --SECTION-- private defines // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief threshold for producing malloc warnings /// /// this is only active if zone debug is enabled. Any malloc operations that /// try to alloc more memory than the threshold will be logged so we can check /// why so much memory is needed //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_MAINTAINER_MODE #define MALLOC_WARNING_THRESHOLD (4 * 1024 * 1024) #endif //////////////////////////////////////////////////////////////////////////////// /// @brief macros for producing stderr output /// /// these will include the location of the problematic if we are in zone debug /// mode, and will not include it if in non debug mode //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_MAINTAINER_MODE #define ZONE_DEBUG_LOCATION " in %s:%d" #define ZONE_DEBUG_PARAMS ,file, line #else #define ZONE_DEBUG_LOCATION #define ZONE_DEBUG_PARAMS #endif //////////////////////////////////////////////////////////////////////////////// /// @brief wrapper for malloc calls //////////////////////////////////////////////////////////////////////////////// #define BuiltInMalloc(n) malloc(n) #define BuiltInRealloc(ptr,n) realloc(ptr, n) #ifdef TRI_ENABLE_FAILURE_TESTS #define MALLOC_WRAPPER(zone, n) FailMalloc(zone, n) #define REALLOC_WRAPPER(zone, ptr, n) FailRealloc(zone, ptr, n) #else #define MALLOC_WRAPPER(zone, n) BuiltInMalloc(n) #define REALLOC_WRAPPER(zone, ptr, n) BuiltInRealloc(ptr, n) #endif // ----------------------------------------------------------------------------- // --SECTION-- private variables // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief core memory zone, allocation will never fail //////////////////////////////////////////////////////////////////////////////// static TRI_memory_zone_t TriCoreMemZone; //////////////////////////////////////////////////////////////////////////////// /// @brief unknown memory zone //////////////////////////////////////////////////////////////////////////////// static TRI_memory_zone_t TriUnknownMemZone; //////////////////////////////////////////////////////////////////////////////// /// @brief memory reserve for core memory zone //////////////////////////////////////////////////////////////////////////////// static void* CoreReserve; //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the core was initialized //////////////////////////////////////////////////////////////////////////////// static int CoreInitialized = 0; //////////////////////////////////////////////////////////////////////////////// /// @brief configuration parameters for memory error tests //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_FAILURE_TESTS static size_t FailMinSize = 0; static double FailProbability = 0.0; static double FailStartStamp = 0.0; #endif // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief checks the size of the memory that is requested /// prints a warning if size is above a threshold //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_MAINTAINER_MODE static void CheckSize (uint64_t n, char const* file, int line) { // warn in the case of big malloc operations if (n >= MALLOC_WARNING_THRESHOLD) { fprintf(stderr, "big malloc action: %llu bytes" ZONE_DEBUG_LOCATION "\n", (unsigned long long) n ZONE_DEBUG_PARAMS); } } #endif //////////////////////////////////////////////////////////////////////////////// /// @brief timestamp for failing malloc //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_FAILURE_TESTS static double CurrentTimeStamp (void) { struct timeval tv; gettimeofday(&tv, 0); return (tv.tv_sec) + (tv.tv_usec / 1000000.0); } #endif //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not a malloc operation should intentionally fail //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_FAILURE_TESTS static bool ShouldFail (size_t n) { if (FailMinSize > 0 && FailMinSize > n) { return false; } if (FailProbability == 0.0) { return false; } if (FailStartStamp > 0.0 && CurrentTimeStamp() < FailStartStamp) { return false; } if (FailProbability < 1.0 && FailProbability * RAND_MAX < rand()) { return false; } return true; } #endif //////////////////////////////////////////////////////////////////////////////// /// @brief intentionally failing malloc - used for failure tests //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_FAILURE_TESTS static char* FailMalloc (TRI_memory_zone_t* zone, size_t n) { // we can fail, so let's check whether we should fail intentionally... if (zone->_failable && ShouldFail(n)) { // intentionally return NULL errno = ENOMEM; return nullptr; } return static_cast(BuiltInMalloc(n)); } #endif //////////////////////////////////////////////////////////////////////////////// /// @brief intentionally failing realloc - used for failure tests //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_FAILURE_TESTS static char* FailRealloc (TRI_memory_zone_t* zone, void* old, size_t n) { // we can fail, so let's check whether we should fail intentionally... if (zone->_failable && ShouldFail(n)) { // intentionally return NULL errno = ENOMEM; return nullptr; } return static_cast(BuiltInRealloc(old, n)); } #endif //////////////////////////////////////////////////////////////////////////////// /// @brief initialize failing malloc //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_FAILURE_TESTS static void InitFailMalloc (void) { char* value; // get failure probability value = getenv("ARANGO_FAILMALLOC_PROBABILITY"); if (value != nullptr) { double v = strtod(value, nullptr); if (v >= 0.0 && v <= 1.0) { FailProbability = v; } } // get startup delay value = getenv("ARANGO_FAILMALLOC_DELAY"); if (value != nullptr) { double v = strtod(value, nullptr); if (v > 0.0) { FailStartStamp = CurrentTimeStamp() + v; } } // get minimum size for failures value = getenv("ARANGO_FAILMALLOC_MINSIZE"); if (value != nullptr) { unsigned long long v = strtoull(value, nullptr, 10); if (v > 0) { FailMinSize = (size_t) v; } } } #endif // ----------------------------------------------------------------------------- // --SECTION-- public variables // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief core memory zone, allocation will never fail //////////////////////////////////////////////////////////////////////////////// TRI_memory_zone_t* TRI_CORE_MEM_ZONE = &TriCoreMemZone; //////////////////////////////////////////////////////////////////////////////// /// @brief unknown memory zone //////////////////////////////////////////////////////////////////////////////// #ifndef TRI_ENABLE_MAINTAINER_MODE TRI_memory_zone_t* TRI_UNKNOWN_MEM_ZONE = &TriUnknownMemZone; #endif // ----------------------------------------------------------------------------- // --SECTION-- public functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief returns the unknown memory zone //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_MAINTAINER_MODE TRI_memory_zone_t* TRI_UnknownMemZoneZ (char const* file, int line) { return &TriUnknownMemZone; } #endif //////////////////////////////////////////////////////////////////////////////// /// @brief system memory allocation //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_MAINTAINER_MODE void* TRI_SystemAllocateZ (uint64_t n, bool set, char const* file, int line) { #else void* TRI_SystemAllocate (uint64_t n, bool set) { #endif char* m; #ifdef TRI_ENABLE_MAINTAINER_MODE CheckSize(n, file, line); #endif m = static_cast(BuiltInMalloc((size_t) n)); if (m != nullptr) { if (set) { memset(m, 0, (size_t) n); } } return m; } //////////////////////////////////////////////////////////////////////////////// /// @brief basic memory management for allocate //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_MAINTAINER_MODE void* TRI_AllocateZ (TRI_memory_zone_t* zone, uint64_t n, bool set, char const* file, int line) { #else void* TRI_Allocate (TRI_memory_zone_t* zone, uint64_t n, bool set) { #endif char* m; #ifdef TRI_ENABLE_MAINTAINER_MODE CheckSize(n, file, line); m = static_cast(MALLOC_WRAPPER(zone, (size_t) n + sizeof(uintptr_t))); #else m = static_cast(MALLOC_WRAPPER(zone, (size_t) n)); #endif if (m == nullptr) { if (zone->_failable) { TRI_set_errno(TRI_ERROR_OUT_OF_MEMORY); return nullptr; } if (CoreReserve == nullptr) { fprintf(stderr, "FATAL: failed to allocate %llu bytes for memory zone %d" ZONE_DEBUG_LOCATION ", giving up!\n", (unsigned long long) n, (int) zone->_zid ZONE_DEBUG_PARAMS); TRI_EXIT_FUNCTION(EXIT_FAILURE, nullptr); } free(CoreReserve); CoreReserve = nullptr; fprintf(stderr, "failed to allocate %llu bytes for memory zone %d" ZONE_DEBUG_LOCATION ", retrying!\n", (unsigned long long) n, (int) zone->_zid ZONE_DEBUG_PARAMS); #ifdef TRI_ENABLE_MAINTAINER_MODE return TRI_AllocateZ(zone, n, set, file, line); #else return TRI_Allocate(zone, n, set); #endif } #ifdef TRI_ENABLE_MAINTAINER_MODE else if (set) { memset(m, 0, (size_t) n + sizeof(uintptr_t)); } else { // prefill with 0xA5 (magic value, same as Valgrind will use) memset(m, 0xA5, (size_t) n + sizeof(uintptr_t)); } #else else if (set) { memset(m, 0, (size_t) n); } #endif #ifdef TRI_ENABLE_MAINTAINER_MODE * (uintptr_t*) m = zone->_zid; // zone->_zid is a uint32_t but we'll advance sizeof(uintptr_t) bytes for good alignment everywhere m += sizeof(uintptr_t); #endif return m; } //////////////////////////////////////////////////////////////////////////////// /// @brief basic memory management for reallocate //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_MAINTAINER_MODE void* TRI_ReallocateZ (TRI_memory_zone_t* zone, void* m, uint64_t n, char const* file, int line) { #else void* TRI_Reallocate (TRI_memory_zone_t* zone, void* m, uint64_t n) { #endif char* p; if (m == nullptr) { #ifdef TRI_ENABLE_MAINTAINER_MODE return TRI_AllocateZ(zone, n, false, file, line); #else return TRI_Allocate(zone, n, false); #endif } p = (char*) m; #ifdef TRI_ENABLE_MAINTAINER_MODE p -= sizeof(uintptr_t); CheckSize(n, file, line); if (* (uintptr_t*) p != zone->_zid) { fprintf(stderr, "memory zone mismatch in TRI_Reallocate" ZONE_DEBUG_LOCATION ", old zone %d, new zone %d" ZONE_DEBUG_PARAMS, (int) * (uintptr_t*) p, (int) zone->_zid); TRI_ASSERT(false); } p = static_cast(REALLOC_WRAPPER(zone, p, (size_t) n + sizeof(uintptr_t))); #else p = static_cast(REALLOC_WRAPPER(zone, p, (size_t) n)); #endif if (p == nullptr) { if (zone->_failable) { TRI_set_errno(TRI_ERROR_OUT_OF_MEMORY); return nullptr; } if (CoreReserve == nullptr) { fprintf(stderr, "FATAL: failed to re-allocate %llu bytes for memory zone %d" ZONE_DEBUG_LOCATION ", giving up!\n", (unsigned long long) n, zone->_zid ZONE_DEBUG_PARAMS); TRI_EXIT_FUNCTION(EXIT_FAILURE, nullptr); } free(CoreReserve); CoreReserve = nullptr; fprintf(stderr, "failed to re-allocate %llu bytes for memory zone %d" ZONE_DEBUG_LOCATION ", retrying!\n", (unsigned long long) n, (int) zone->_zid ZONE_DEBUG_PARAMS); #ifdef TRI_ENABLE_MAINTAINER_MODE return TRI_ReallocateZ(zone, m, n, file, line); #else return TRI_Reallocate(zone, m, n); #endif } #ifdef TRI_ENABLE_MAINTAINER_MODE // zone->_zid is a uint32_t but we'll advance sizeof(uintptr_t) bytes for good alignment everywhere p += sizeof(uintptr_t); #endif return p; } //////////////////////////////////////////////////////////////////////////////// /// @brief basic memory management for deallocate //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_MAINTAINER_MODE void TRI_FreeZ (TRI_memory_zone_t* zone, void* m, char const* file, int line) { #else void TRI_Free (TRI_memory_zone_t* zone, void* m) { #endif #ifdef TRI_ENABLE_MAINTAINER_MODE char* p; p = (char*) m; if (p == nullptr) { fprintf(stderr, "freeing nil ptr " ZONE_DEBUG_LOCATION ZONE_DEBUG_PARAMS); // crash intentionally TRI_ASSERT(false); } // zone->_zid is a uint32_t but we'll decrease by sizeof(uintptr_t) bytes for good alignment everywhere p -= sizeof(uintptr_t); if (* (uintptr_t*) p != zone->_zid) { fprintf(stderr, "memory zone mismatch in TRI_Free" ZONE_DEBUG_LOCATION ", old zone %d, new %d\n" ZONE_DEBUG_PARAMS, (int) * (uintptr_t*) p, (int) zone->_zid); TRI_ASSERT(false); } free(p); #else free(m); #endif } //////////////////////////////////////////////////////////////////////////////// /// @brief free memory allocated by some low-level functions /// /// this can be used to free memory that was not allocated by TRI_Allocate, but /// by malloc et al //////////////////////////////////////////////////////////////////////////////// #ifdef TRI_ENABLE_MAINTAINER_MODE void TRI_SystemFreeZ (void* p, char const* file, int line) { #else void TRI_SystemFree (void* p) { #endif #ifdef TRI_ENABLE_MAINTAINER_MODE if (p == nullptr) { fprintf(stderr, "freeing nil ptr in %s:%d\n", file, line); } #endif free(p); } //////////////////////////////////////////////////////////////////////////////// /// @brief wrapper for realloc /// /// this wrapper is used together with libev, as the builtin libev allocator /// causes problems with Valgrind: /// - http://lists.schmorp.de/pipermail/libev/2012q2/001917.html /// - http://lists.gnu.org/archive/html/bug-gnulib/2011-03/msg00243.html //////////////////////////////////////////////////////////////////////////////// void* TRI_WrappedReallocate (void* ptr, long size) { if (ptr == nullptr && size == 0) { return nullptr; } return BuiltInRealloc(ptr, (size_t) size); } //////////////////////////////////////////////////////////////////////////////// /// @brief initialize memory subsystem //////////////////////////////////////////////////////////////////////////////// void TRI_InitializeMemory () { if (CoreInitialized == 0) { static size_t const ReserveSize = 1024 * 1024 * 10; TriCoreMemZone._zid = 0; TriCoreMemZone._failed = false; TriCoreMemZone._failable = false; TriUnknownMemZone._zid = 1; TriUnknownMemZone._failed = false; TriUnknownMemZone._failable = true; #ifdef TRI_ENABLE_FAILURE_TESTS InitFailMalloc(); #endif CoreReserve = BuiltInMalloc(ReserveSize); if (CoreReserve == nullptr) { fprintf(stderr, "FATAL: cannot allocate initial core reserve of size %llu, giving up!\n", (unsigned long long) ReserveSize); } else { CoreInitialized = 1; } } } //////////////////////////////////////////////////////////////////////////////// /// @brief shutdown memory subsystem //////////////////////////////////////////////////////////////////////////////// void TRI_ShutdownMemory () { if (CoreInitialized == 1) { free(CoreReserve); CoreInitialized = 0; } } // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE // ----------------------------------------------------------------------------- // Local Variables: // mode: outline-minor // outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}" // End: