From 45c654c6ec6876470cf86ec8de2782bc213ddcab Mon Sep 17 00:00:00 2001 From: Enrico Giordani Date: Mon, 12 Oct 2015 21:32:53 +0200 Subject: [PATCH] [Cleanup] Comments and code formatting/cleanup/consistency. Moved important comments regarding the QFork architecture at the top of the file. Formatted comments and code. Changed SIZE_T to size_t for consitency. --- src/Win32_Interop/Win32_QFork.cpp | 457 +++++++++++++++--------------- 1 file changed, 228 insertions(+), 229 deletions(-) diff --git a/src/Win32_Interop/Win32_QFork.cpp b/src/Win32_Interop/Win32_QFork.cpp index a43d7e11..05569458 100644 --- a/src/Win32_Interop/Win32_QFork.cpp +++ b/src/Win32_Interop/Win32_QFork.cpp @@ -20,6 +20,88 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* +Redis is an in memory DB. We need to share the redis database with a quasi-forked process so that we can do the RDB and AOF saves +without halting the main redis process, or crashing due to code that was never designed to be thread safe. Essentially we need to +replicate the COW behavior of fork() on Windows, but we don't actually need a complete fork() implementation. A complete fork() +implementation would require subsystem level support to make happen. The following is required to make this quasi-fork scheme work: + +DLMalloc (http://g.oswego.edu/dl/html/malloc.html): +- replaces malloc/realloc/free, either by manual patching of the zmalloc code in Redis or by patching the CRT routines at link time +- partitions space into segments that it allocates from (currently configured as 64MB chunks) +- we map/unmap these chunks as requested into a memory map (unmapping allows the system to decide how to reduce the physical memory +pressure on system) + +DLMallocMemoryMap: +- An uncomitted memory map whose size is the total physical memory on the system less some memory for the rest of the system so that +we avoid excessive swapping. +- This is reserved high in VM space so that it can be mapped at a specific address in the child qforked process (ASLR must be +disabled for these processes) +- This must be mapped in exactly the same virtual memory space in both forker and forkee. + +QForkControlMemoryMap: +- contains a map of the allocated segments in the DLMallocMemoryMap +- contains handles for inter-process synchronization +- contains pointers to some of the global data in the parent process if mapped into DLMallocMemoryMap, and a copy of any other +required global data + +QFork process: +- a copy of the parent process with a command line specifying QFork behavior +- when a COW operation is requested via an event signal +- opens the DLMAllocMemoryMap with PAGE_WRITECOPY +- reserve space for DLMAllocMemoryMap at the memory location specified in ControlMemoryMap +- locks the DLMalloc segments as specified in QForkControlMemoryMap +- maps global data from the QForkControlMEmoryMap into this process +- executes the requested operation +- unmaps all the mm views (discarding any writes) +- signals the parent when the operation is complete + +How the parent invokes the QFork process: +- protects mapped memory segments with VirtualProtect using PAGE_WRITECOPY (both the allocated portions of DLMAllocMemoryMap and +the QForkControlMemoryMap) +- QForked process is signaled to process command +- Parent waits (asynchronously) until QForked process signals that operation is complete, then as an atomic operation: +- signals and waits for the forked process to terminate +- resotres protection status on mapped blocks +- determines which pages have been modified and copies these to a buffer +- unmaps the view of the heap (discarding COW changes form the view) +- remaps the view +- copies the changes back into the view +*/ + +/* +Not specifying the maxmemory or maxheap flags will result in the default behavior of: new key generation not +bounded by heap usage, and the heap size equal to the size of physical memory. + +Redis will respect the maxmemory flag by preventing new key creation when the number of bytes allocated in the heap +exceeds the level specified by the maxmemory flag. This does not account for heap fragmentation or memory usage by +the heap allocator. To allow for this extra space maxheapBytes is implicitly set to (1.5 * maxmemory [rounded up +to the nearest cAllocationGranularity boundary]). The maxheap flag may be specified along with the maxmemory flag to +increase the heap further than this. + +If the maxmemory flag is not specified, but the maxheap flag is specified, the heap is sized according to this flag +(rounded up to the nearest cAllocationGranularity boundary). The heap may be configured larger than physical memory with +this flag. If maxmemory is sufficiently large enough, the heap will also be made larger than physical memory. This +has implications for the system swap file size requirement and disk usage as discussed below. Specifying a heap larger +than physical memory allows Redis to continue operating into virtual memory up to the limit of the heap size specified. + +Since the heap is entirely contained in the memory mapped file we are creating to share with the forked process, the +size of the memory mapped file will be equal to the size of the heap. There must be sufficient disk space for this file. +For instance, launching Redis on a server machine with 512GB of RAM and no flags specified for either maxmemory or +maxheap will result in the allocation of a 512GB memory mapped file. Redis will fail to launch if there is not enough +space available on the disk where redis is being launched from for this file. + +During forking the system swap file will be used for managing virtual memory sharing and the copy on write pages for both +forker and forkee. There must be sufficient swap space availability for this. The maximum size of this swap space commit +is roughly equal to (physical memory + (2 * size of the memory allocated in the redis heap)). For instance, if the heap is nearly +maxed out on an 8GB machine and the heap has been configured to be twice the size of physical memory, the swap file comittment +will be (physical + (2 * (2 * physical)) or (5 * physical). By default Windows will dynamically allocate a swap file that will +expand up to about (3.5 * physical). In this case the forked process will fail with ERROR_COMMITMENT_LIMIT (1455/0x5AF) error. +The fix for this is to ensure the system swap space is sufficiently large enough to handle this. The reason that the default +heap size is equal to physical memory is so that Redis will work on a freshly configured OS without requireing reconfiguring +either Redis or the machine (max comittment of (3 * physical)). +*/ + #include "win32_types.h" #include "Win32_FDAPI.h" #include "Win32_Common.h" @@ -57,58 +139,21 @@ using namespace std; -#define IFFAILTHROW(a,m) if(!(a)) { throw std::system_error(GetLastError(), system_category(), m); } - -#define MAX_GLOBAL_DATA 10000 -struct QForkBeginInfo { - BYTE globalData[MAX_GLOBAL_DATA]; - size_t globalDataSize; - unsigned __int32 dictHashSeed; - char filename[MAX_PATH]; - int *fds; - int numfds; - uint64_t *clientids; - HANDLE pipe_write_handle; - HANDLE aof_pipe_write_ack_handle; - HANDLE aof_pipe_read_ack_handle; - HANDLE aof_pipe_read_data_handle; - LPVOID protocolInfo; -}; - -#ifndef PAGE_REVERT_TO_FILE_MAP -#define PAGE_REVERT_TO_FILE_MAP 0x80000000 // From Win8.1 SDK -#endif - -extern "C" int checkForSentinelMode(int argc, char **argv); -extern "C" void InitTimeFunctions(); - -extern "C" -{ - void*(*g_malloc)(size_t) = nullptr; - void*(*g_calloc)(size_t, size_t) = nullptr; - void*(*g_realloc)(void*, size_t) = nullptr; - void(*g_free)(void*) = nullptr; - size_t(*g_msize)(void*) = nullptr; - - // forward def from util.h. - PORT_LONGLONG memtoll(const char *p, int *err); -} - //#define DEBUG_WITH_PROCMON #ifdef DEBUG_WITH_PROCMON #define FILE_DEVICE_PROCMON_LOG 0x00009535 #define IOCTL_EXTERNAL_LOG_DEBUGOUT (ULONG) CTL_CODE( FILE_DEVICE_PROCMON_LOG, 0x81, METHOD_BUFFERED, FILE_WRITE_ACCESS ) HANDLE hProcMonDevice = INVALID_HANDLE_VALUE; -BOOL WriteToProcmon (wstring message) +BOOL WriteToProcmon(wstring message) { if (hProcMonDevice != INVALID_HANDLE_VALUE) { DWORD nb = 0; return DeviceIoControl( - hProcMonDevice, + hProcMonDevice, IOCTL_EXTERNAL_LOG_DEBUGOUT, - (LPVOID)(message.c_str()), - (DWORD)(message.length() * sizeof(wchar_t)), + (LPVOID) (message.c_str()), + (DWORD) (message.length() * sizeof(wchar_t)), NULL, 0, &nb, @@ -119,77 +164,65 @@ BOOL WriteToProcmon (wstring message) } #endif -/* -Redis is an in memory DB. We need to share the redis database with a quasi-forked process so that we can do the RDB and AOF saves -without halting the main redis process, or crashing due to code that was never designed to be thread safe. Essentially we need to -replicate the COW behavior of fork() on Windows, but we don't actually need a complete fork() implementation. A complete fork() -implementation would require subsystem level support to make happen. The following is required to make this quasi-fork scheme work: - -DLMalloc (http://g.oswego.edu/dl/html/malloc.html): - - replaces malloc/realloc/free, either by manual patching of the zmalloc code in Redis or by patching the CRT routines at link time - - partitions space into segments that it allocates from (currently configured as 64MB chunks) - - we map/unmap these chunks as requested into a memory map (unmapping allows the system to decide how to reduce the physical memory - pressure on system) +#ifndef LODWORD + #define LODWORD(_qw) ((DWORD)(_qw)) +#endif -DLMallocMemoryMap: - - An uncomitted memory map whose size is the total physical memory on the system less some memory for the rest of the system so that - we avoid excessive swapping. - - This is reserved high in VM space so that it can be mapped at a specific address in the child qforked process (ASLR must be - disabled for these processes) - - This must be mapped in exactly the same virtual memory space in both forker and forkee. +#ifndef HIDWORD + #define HIDWORD(_qw) ((DWORD)(((_qw) >> (sizeof(DWORD)*8)) & DWORD(~0))) +#endif -QForkControlMemoryMap: - - contains a map of the allocated segments in the DLMallocMemoryMap - - contains handles for inter-process synchronization - - contains pointers to some of the global data in the parent process if mapped into DLMallocMemoryMap, and a copy of any other - required global data -QFork process: - - a copy of the parent process with a command line specifying QFork behavior - - when a COW operation is requested via an event signal - - opens the DLMAllocMemoryMap with PAGE_WRITECOPY - - reserve space for DLMAllocMemoryMap at the memory location specified in ControlMemoryMap - - locks the DLMalloc segments as specified in QForkControlMemoryMap - - maps global data from the QForkControlMEmoryMap into this process - - executes the requested operation - - unmaps all the mm views (discarding any writes) - - signals the parent when the operation is complete +#define IFFAILTHROW(a,m) if(!(a)) { throw system_error(GetLastError(), system_category(), m); } -How the parent invokes the QFork process: - - protects mapped memory segments with VirtualProtect using PAGE_WRITECOPY (both the allocated portions of DLMAllocMemoryMap and - the QForkControlMemoryMap) - - QForked process is signaled to process command - - Parent waits (asynchronously) until QForked process signals that operation is complete, then as an atomic operation: - - signals and waits for the forked process to terminate - - resotres protection status on mapped blocks - - determines which pages have been modified and copies these to a buffer - - unmaps the view of the heap (discarding COW changes form the view) - - remaps the view - - copies the changes back into the view -*/ +#define MAX_GLOBAL_DATA 10000 +struct QForkBeginInfo { + BYTE globalData[MAX_GLOBAL_DATA]; + size_t globalDataSize; + unsigned __int32 dictHashSeed; + char filename[MAX_PATH]; + int *fds; + int numfds; + uint64_t *clientids; + HANDLE pipe_write_handle; + HANDLE aof_pipe_write_ack_handle; + HANDLE aof_pipe_read_ack_handle; + HANDLE aof_pipe_read_data_handle; + LPVOID protocolInfo; +}; -#ifndef LODWORD - #define LODWORD(_qw) ((DWORD)(_qw)) -#endif -#ifndef HIDWORD - #define HIDWORD(_qw) ((DWORD)(((_qw) >> (sizeof(DWORD)*8)) & DWORD(~0))) +#ifndef PAGE_REVERT_TO_FILE_MAP +#define PAGE_REVERT_TO_FILE_MAP 0x80000000 // From Win8.1 SDK #endif -const SIZE_T cAllocationGranularity = 1 << 18; // 256KB per heap block (matches large block allocation threshold of dlmalloc) -const int cMaxBlocks = 1 << 24; // 256KB * 16M heap blocks = 4TB. 4TB is the largest memory config Windows supports at present. -const char* cMapFileBaseName = "RedisQFork"; -const int cDeadForkWait = 30000; +extern "C" +{ + int checkForSentinelMode(int argc, char **argv); + void InitTimeFunctions(); + PORT_LONGLONG memtoll(const char *p, int *err); // Forward def from util.h + + void*(*g_malloc)(size_t) = nullptr; + void*(*g_calloc)(size_t, size_t) = nullptr; + void*(*g_realloc)(void*, size_t) = nullptr; + void(*g_free)(void*) = nullptr; + size_t(*g_msize)(void*) = nullptr; +} + +const size_t cAllocationGranularity = 1 << 18; // 256KB per heap block (matches large block allocation threshold of dlmalloc) +const int cMaxBlocks = 1 << 24; // 256KB * 16M heap blocks = 4TB. 4TB is the largest memory config Windows supports at present. +const char* cMapFileBaseName = "RedisQFork"; +const int cDeadForkWait = 30000; #ifndef _WIN64 -size_t cDefaultmaxHeap32Bit = 1 << 29; // 512MB +size_t cDefaultmaxHeap32Bit = 1 << 29; // 512MB #endif -enum class BlockState : std::uint8_t {bsINVALID = 0, bsUNMAPPED = 1, bsMAPPED = 2}; +enum class BlockState : uint8_t {bsINVALID = 0, bsUNMAPPED = 1, bsMAPPED = 2}; struct QForkControl { HANDLE heapMemoryMapFile; HANDLE heapMemoryMap; - int availableBlocksInHeap; // number of blocks in blockMap (dynamically determined at run time) + int availableBlocksInHeap; // Number of blocks in blockMap (dynamically determined at run time) BlockState heapBlockMap[cMaxBlocks]; LPVOID heapStart; @@ -197,7 +230,7 @@ struct QForkControl { HANDLE operationComplete; HANDLE operationFailed; - // global data pointers to be passed to the forked process + // Global data pointers to be passed to the forked process QForkBeginInfo globalData; BYTE DLMallocGlobalState[1000]; size_t DLMallocGlobalStateSize; @@ -214,7 +247,7 @@ bool ReportSpecialSystemErrors(int error) { { case ERROR_COMMITMENT_LIMIT: { - ::redisLog( + redisLog( REDIS_WARNING, "\n" "The Windows version of Redis allocates a memory mapped heap for sharing with\n" @@ -237,7 +270,7 @@ bool ReportSpecialSystemErrors(int error) { case ERROR_DISK_FULL: { - ::redisLog( + redisLog( REDIS_WARNING, "\n" "The Windows version of Redis allocates a large memory mapped file for sharing\n" @@ -292,7 +325,7 @@ BOOL QForkChildInit(HANDLE QForkControlMemoryMapHandle, DWORD ParentProcessID) { string("Could not map view of QForkControl in child. Is system swap file large enough?")); g_pQForkControl = sfvParentQForkControl; - // duplicate handles and stuff into control structure (parent protected by PAGE_WRITECOPY) + // Duplicate handles and stuff into control structure (parent protected by PAGE_WRITECOPY) dupHeapFileHandle.Assign(shParent, sfvParentQForkControl->heapMemoryMapFile); g_pQForkControl->heapMemoryMapFile = dupHeapFileHandle; @@ -302,8 +335,8 @@ BOOL QForkChildInit(HANDLE QForkControlMemoryMapHandle, DWORD ParentProcessID) { dupOperationFailed.Assign(shParent, sfvParentQForkControl->operationFailed); g_pQForkControl->operationFailed = dupOperationFailed; - // create section handle on MM file - SIZE_T mmSize = g_pQForkControl->availableBlocksInHeap * cAllocationGranularity; + // Create section handle on MM file + size_t mmSize = g_pQForkControl->availableBlocksInHeap * cAllocationGranularity; SmartFileMapHandle sfmhMapFile( g_pQForkControl->heapMemoryMapFile, PAGE_WRITECOPY, @@ -325,15 +358,15 @@ BOOL QForkChildInit(HANDLE QForkControlMemoryMapHandle, DWORD ParentProcessID) { g_pQForkControl->heapStart, string("Could not map heap in forked process. Is system paging file large enough?")); - // setup DLMalloc global data + // Setup DLMalloc global data if (SetDLMallocGlobalState(g_pQForkControl->DLMallocGlobalStateSize, g_pQForkControl->DLMallocGlobalState) != 0) { - throw std::runtime_error("DLMalloc global state copy failed."); + throw runtime_error("DLMalloc global state copy failed."); } - // copy redis globals into fork process + // Copy redis globals into fork process SetupGlobals(g_pQForkControl->globalData.globalData, g_pQForkControl->globalData.globalDataSize, g_pQForkControl->globalData.dictHashSeed); - // execute requested operation + // Execute requested operation if (g_pQForkControl->typeOfOperation == OperationType::otRDB) { g_ChildExitCode = do_rdbSave(g_pQForkControl->globalData.filename); } else if (g_pQForkControl->typeOfOperation == OperationType::otAOF) { @@ -365,19 +398,19 @@ BOOL QForkChildInit(HANDLE QForkControlMemoryMapHandle, DWORD ParentProcessID) { throw runtime_error("unexpected operation type"); } - // let parent know we are done + // Let parent know we are done SetEvent(g_pQForkControl->operationComplete); g_pQForkControl = NULL; return TRUE; } - catch(std::system_error syserr) { + catch(system_error syserr) { if (ReportSpecialSystemErrors(syserr.code().value()) == false) { - ::redisLog(REDIS_WARNING, "QForkChildInit: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what()); + redisLog(REDIS_WARNING, "QForkChildInit: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what()); } } - catch(std::runtime_error runerr) { - ::redisLog(REDIS_WARNING, "QForkChildInit: runtime error caught. message=%s\n", runerr.what()); + catch(runtime_error runerr) { + redisLog(REDIS_WARNING, "QForkChildInit: runtime error caught. message=%s\n", runerr.what()); } if (g_pQForkControl != NULL) { @@ -393,16 +426,16 @@ string GetLocalAppDataFolder() { char localAppDataPath[_MAX_PATH]; HRESULT hr; if (S_OK != (hr = SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, localAppDataPath))) { - throw std::system_error(hr, system_category(), "SHGetFolderPathA failed"); + throw system_error(hr, system_category(), "SHGetFolderPathA failed"); } char redisAppDataPath[_MAX_PATH]; if (NULL == PathCombineA(redisAppDataPath, localAppDataPath, "Redis")) { - throw std::system_error(hr, system_category(), "PathCombineA failed"); + throw system_error(hr, system_category(), "PathCombineA failed"); } if (PathIsDirectoryA(redisAppDataPath) == FALSE) { if (CreateDirectoryA(redisAppDataPath, NULL) == FALSE) { - throw std::system_error(hr, system_category(), "CreateDirectoryA failed"); + throw system_error(hr, system_category(), "CreateDirectoryA failed"); } } @@ -415,16 +448,16 @@ string GetWorkingDirectory() { string workingDir; if (g_argMap.find(cHeapDir) != g_argMap.end()) { workingDir = g_argMap[cHeapDir][0][0]; - std::replace(workingDir.begin(), workingDir.end(), '/', '\\'); + replace(workingDir.begin(), workingDir.end(), '/', '\\'); if (PathIsRelativeA(workingDir.c_str())) { char cwd[MAX_PATH]; if (0 == ::GetCurrentDirectoryA(MAX_PATH, cwd)) { - throw std::system_error(GetLastError(), system_category(), "GetCurrentDirectoryA failed"); + throw system_error(GetLastError(), system_category(), "GetCurrentDirectoryA failed"); } char fullPath[_MAX_PATH]; if (NULL == PathCombineA(fullPath, cwd, workingDir.c_str())) { - throw std::system_error(GetLastError(), system_category(), "PathCombineA failed"); + throw system_error(GetLastError(), system_category(), "PathCombineA failed"); } workingDir = fullPath; } @@ -444,7 +477,7 @@ string GetWorkingDirectory() { BOOL QForkParentInit(__int64 maxheapBytes) { try { - // allocate file map for qfork control so it can be passed to the forked process + // Allocate file map for qfork control so it can be passed to the forked process g_hQForkControlFileMap = CreateFileMappingW( INVALID_HANDLE_VALUE, NULL, @@ -460,13 +493,13 @@ BOOL QForkParentInit(__int64 maxheapBytes) { 0); IFFAILTHROW(g_pQForkControl, "QForkMasterInit: MapViewOfFile failed"); - // ensure the number of blocks is a multiple of cAllocationGranularity - SIZE_T allocationBlocks = (SIZE_T)maxheapBytes / cAllocationGranularity; + // Ensure the number of blocks is a multiple of cAllocationGranularity + size_t allocationBlocks = (size_t)maxheapBytes / cAllocationGranularity; allocationBlocks += ((maxheapBytes % cAllocationGranularity) != 0); g_pQForkControl->availableBlocksInHeap = (int)allocationBlocks; if (g_pQForkControl->availableBlocksInHeap <= 0) { - throw std::runtime_error( + throw runtime_error( "Invalid number of heap blocks."); } @@ -512,13 +545,13 @@ BOOL QForkParentInit(__int64 maxheapBytes) { FILE_ATTRIBUTE_NORMAL| FILE_FLAG_DELETE_ON_CLOSE, NULL ); if (g_pQForkControl->heapMemoryMapFile == INVALID_HANDLE_VALUE) { - throw std::system_error( + throw system_error( GetLastError(), system_category(), "CreateFileW failed."); } - SIZE_T mmSize = g_pQForkControl->availableBlocksInHeap * cAllocationGranularity; + size_t mmSize = g_pQForkControl->availableBlocksInHeap * cAllocationGranularity; g_pQForkControl->heapMemoryMap = CreateFileMappingW( g_pQForkControl->heapMemoryMapFile, @@ -534,7 +567,7 @@ BOOL QForkParentInit(__int64 maxheapBytes) { IFFAILTHROW(g_pQForkControl->heapMemoryMap, "CreateFileMapping failed."); // Find a place in the virtual memory space where we can reserve space for our allocations that is likely - // to be available in the forked process. (If this ever fails in the forked process, we will have to launch + // to be available in the forked process. (If this ever fails in the forked process, we will have to launch // the forked process and negotiate for a shared memory address here.) LPVOID pHigh = VirtualAllocEx( GetCurrentProcess(), @@ -570,16 +603,16 @@ BOOL QForkParentInit(__int64 maxheapBytes) { return TRUE; } - catch(std::system_error syserr) { + catch(system_error syserr) { if (ReportSpecialSystemErrors(syserr.code().value()) == false) { - ::redisLog(REDIS_WARNING, "QForkParentInit: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what()); + redisLog(REDIS_WARNING, "QForkParentInit: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what()); } } - catch(std::runtime_error runerr) { - ::redisLog(REDIS_WARNING, "QForkParentInit: runtime error caught. message=%s\n", runerr.what()); + catch(runtime_error runerr) { + redisLog(REDIS_WARNING, "QForkParentInit: runtime error caught. message=%s\n", runerr.what()); } catch(...) { - ::redisLog(REDIS_WARNING, "QForkParentInit: other exception caught.\n"); + redisLog(REDIS_WARNING, "QForkParentInit: other exception caught.\n"); } return FALSE; } @@ -589,7 +622,7 @@ LONG CALLBACK VectoredHeapMapper(PEXCEPTION_POINTERS info) { info->ExceptionRecord->NumberParameters == 2) { intptr_t failingMemoryAddress = info->ExceptionRecord->ExceptionInformation[1]; intptr_t heapStart = (intptr_t)g_pQForkControl->heapStart; - intptr_t heapEnd = heapStart + ((SIZE_T) g_pQForkControl->availableBlocksInHeap * cAllocationGranularity); + intptr_t heapEnd = heapStart + ((size_t) g_pQForkControl->availableBlocksInHeap * cAllocationGranularity); if (failingMemoryAddress >= heapStart && failingMemoryAddress < heapEnd) { intptr_t startOfMapping = failingMemoryAddress - failingMemoryAddress % g_systemAllocationGranularity; @@ -614,22 +647,22 @@ LONG CALLBACK VectoredHeapMapper(PEXCEPTION_POINTERS info) { else { DWORD err = GetLastError(); - ::redisLog(REDIS_WARNING, "\n\n=== REDIS BUG REPORT START: Cut & paste starting from here ==="); - ::redisLog(REDIS_WARNING, "--- FATAL ERROR MAPPING VIEW OF MAP FILE"); - ::redisLog(REDIS_WARNING, "\t MapViewOfFileEx failed with error 0x%08X.", err); - ::redisLog(REDIS_WARNING, "\t startOfMapping 0x%p", startOfMapping); - ::redisLog(REDIS_WARNING, "\t heapStart 0x%p", heapStart); - ::redisLog(REDIS_WARNING, "\t heapEnd 0x%p", heapEnd); - ::redisLog(REDIS_WARNING, "\t failing access location 0x%p", failingMemoryAddress); - ::redisLog(REDIS_WARNING, "\t offset into mmf to start mapping 0x%p", mmfOffset); - ::redisLog(REDIS_WARNING, "\t start of new mapping 0x%p", startOfMapping); - ::redisLog(REDIS_WARNING, "\t bytes to map 0x%p\n", bytesToMap); + redisLog(REDIS_WARNING, "\n\n=== REDIS BUG REPORT START: Cut & paste starting from here ==="); + redisLog(REDIS_WARNING, "--- FATAL ERROR MAPPING VIEW OF MAP FILE"); + redisLog(REDIS_WARNING, "\t MapViewOfFileEx failed with error 0x%08X.", err); + redisLog(REDIS_WARNING, "\t startOfMapping 0x%p", startOfMapping); + redisLog(REDIS_WARNING, "\t heapStart 0x%p", heapStart); + redisLog(REDIS_WARNING, "\t heapEnd 0x%p", heapEnd); + redisLog(REDIS_WARNING, "\t failing access location 0x%p", failingMemoryAddress); + redisLog(REDIS_WARNING, "\t offset into mmf to start mapping 0x%p", mmfOffset); + redisLog(REDIS_WARNING, "\t start of new mapping 0x%p", startOfMapping); + redisLog(REDIS_WARNING, "\t bytes to map 0x%p\n", bytesToMap); if (err == 0x000005AF) { - ::redisLog(REDIS_WARNING, "The system paging file is too small for this operation to complete."); - ::redisLog(REDIS_WARNING, "See https://github.com/MSOpenTech/redis/wiki/Memory-Configuration"); - ::redisLog(REDIS_WARNING, "for more information on configuring the system paging file for Redis."); + redisLog(REDIS_WARNING, "The system paging file is too small for this operation to complete."); + redisLog(REDIS_WARNING, "See https://github.com/MSOpenTech/redis/wiki/Memory-Configuration"); + redisLog(REDIS_WARNING, "for more information on configuring the system paging file for Redis."); } - ::redisLog(REDIS_WARNING, "\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n"); + redisLog(REDIS_WARNING, "\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n"); // Call exit to avoid executing the Unhandled Exceptiont Handler since we don't need a call stack exit(1); } @@ -679,38 +712,6 @@ StartupStatus QForkStartup() { } Globals::pageSize = perfinfo.PageSize; - /* - Not specifying the maxmemory or maxheap flags will result in the default behavior of: new key generation not - bounded by heap usage, and the heap size equal to the size of physical memory. - - Redis will respect the maxmemory flag by preventing new key creation when the number of bytes allocated in the heap - exceeds the level specified by the maxmemory flag. This does not account for heap fragmentation or memory usage by - the heap allocator. To allow for this extra space maxheapBytes is implicitly set to (1.5 * maxmemory [rounded up - to the nearest cAllocationGranularity boundary]). The maxheap flag may be specified along with the maxmemory flag to - increase the heap further than this. - - If the maxmemory flag is not specified, but the maxheap flag is specified, the heap is sized according to this flag - (rounded up to the nearest cAllocationGranularity boundary). The heap may be configured larger than physical memory with - this flag. If maxmemory is sufficiently large enough, the heap will also be made larger than physical memory. This - has implications for the system swap file size requirement and disk usage as discussed below. Specifying a heap larger - than physical memory allows Redis to continue operating into virtual memory up to the limit of the heap size specified. - - Since the heap is entirely contained in the memory mapped file we are creating to share with the forked process, the - size of the memory mapped file will be equal to the size of the heap. There must be sufficient disk space for this file. - For instance, launching Redis on a server machine with 512GB of RAM and no flags specified for either maxmemory or - maxheap will result in the allocation of a 512GB memory mapped file. Redis will fail to launch if there is not enough - space available on the disk where redis is being launched from for this file. - - During forking the system swap file will be used for managing virtual memory sharing and the copy on write pages for both - forker and forkee. There must be sufficient swap space availability for this. The maximum size of this swap space commit - is roughly equal to (physical memory + (2 * size of the memory allocated in the redis heap)). For instance, if the heap is nearly - maxed out on an 8GB machine and the heap has been configured to be twice the size of physical memory, the swap file comittment - will be (physical + (2 * (2 * physical)) or (5 * physical). By default Windows will dynamically allocate a swap file that will - expand up to about (3.5 * physical). In this case the forked process will fail with ERROR_COMMITMENT_LIMIT (1455/0x5AF) error. - The fix for this is to ensure the system swap space is sufficiently large enough to handle this. The reason that the default - heap size is equal to physical memory is so that Redis will work on a freshly configured OS without requireing reconfiguring - either Redis or the machine (max comittment of (3 * physical)). - */ int64_t maxMemoryPlusHalf = (3 * maxmemoryBytes) / 2; if (maxmemoryBytes != -1) { if (maxheapBytes < maxMemoryPlusHalf) { @@ -778,10 +779,10 @@ BOOL QForkShutdown() { } void CopyForkOperationData(OperationType type, LPVOID globalData, int sizeOfGlobalData, uint32_t dictHashSeed) { - // copy operation data + // Copy operation data g_pQForkControl->typeOfOperation = type; if (sizeOfGlobalData > MAX_GLOBAL_DATA) { - throw std::runtime_error("Global state too large."); + throw runtime_error("Global state too large."); } memcpy(&(g_pQForkControl->globalData.globalData), globalData, sizeOfGlobalData); g_pQForkControl->globalData.globalDataSize = sizeOfGlobalData; @@ -789,16 +790,16 @@ void CopyForkOperationData(OperationType type, LPVOID globalData, int sizeOfGlob GetDLMallocGlobalState(&g_pQForkControl->DLMallocGlobalStateSize, NULL); if (g_pQForkControl->DLMallocGlobalStateSize > sizeof(g_pQForkControl->DLMallocGlobalState)) { - throw std::runtime_error("DLMalloc global state too large."); + throw runtime_error("DLMalloc global state too large."); } if(GetDLMallocGlobalState(&g_pQForkControl->DLMallocGlobalStateSize, g_pQForkControl->DLMallocGlobalState) != 0) { - throw std::runtime_error("DLMalloc global state copy failed."); + throw runtime_error("DLMalloc global state copy failed."); } - // protect both the heap and the fork control map from propagating local changes + // Protect both the heap and the fork control map from propagating local changes DWORD oldProtect = 0; if (VirtualProtect(g_pQForkControl, sizeof(QForkControl), PAGE_WRITECOPY, &oldProtect) == FALSE) { - throw std::system_error( + throw system_error( GetLastError(), system_category(), "CopyForkOperationData: VirtualProtect failed for the fork control map"); @@ -819,7 +820,7 @@ void CopyForkOperationData(OperationType type, LPVOID globalData, int sizeOfGlob } else { ++retries; if (retries > 10) { - throw std::system_error( + throw system_error( GetLastError(), system_category(), "CopyForkOperationData: VirtualProtect failed for the heap"); @@ -831,7 +832,7 @@ void CopyForkOperationData(OperationType type, LPVOID globalData, int sizeOfGlob } void CreateChildProcess(PROCESS_INFORMATION *pi, DWORD dwCreationFlags = 0) { - // ensure events are in the correst state + // Ensure events are in the correst state IFFAILTHROW(ResetEvent(g_pQForkControl->operationComplete), "CreateChildProcess: ResetEvent() failed."); IFFAILTHROW(ResetEvent(g_pQForkControl->operationFailed), "CreateChildProcess: ResetEvent() failed."); @@ -873,14 +874,14 @@ pid_t BeginForkOperation(OperationType type, LPVOID globalData, int sizeOfGlobal return pi.dwProcessId; } - catch(std::system_error syserr) { - ::redisLog(REDIS_WARNING, "BeginForkOperation: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what()); + catch(system_error syserr) { + redisLog(REDIS_WARNING, "BeginForkOperation: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what()); } - catch(std::runtime_error runerr) { - ::redisLog(REDIS_WARNING, "BeginForkOperation: runtime error caught. message=%s\n", runerr.what()); + catch(runtime_error runerr) { + redisLog(REDIS_WARNING, "BeginForkOperation: runtime error caught. message=%s\n", runerr.what()); } catch(...) { - ::redisLog(REDIS_WARNING, "BeginForkOperation: other exception caught.\n"); + redisLog(REDIS_WARNING, "BeginForkOperation: other exception caught.\n"); } if (pi.hProcess != INVALID_HANDLE_VALUE) { TerminateProcess(pi.hProcess, 1); @@ -988,13 +989,13 @@ BOOL AbortForkOperation() { return EndForkOperation(NULL); } - catch(std::system_error syserr) { - ::redisLog(REDIS_WARNING, "AbortForkOperation(): 0x%08x - %s\n", syserr.code().value(), syserr.what()); + catch(system_error syserr) { + redisLog(REDIS_WARNING, "AbortForkOperation(): 0x%08x - %s\n", syserr.code().value(), syserr.what()); // If we can not properly restore fork state, then another fork operation is not possible. exit(1); } - catch( ... ) { - ::redisLog(REDIS_WARNING, "Some other exception caught in EndForkOperation().\n"); + catch(...) { + redisLog(REDIS_WARNING, "Some other exception caught in EndForkOperation().\n"); exit(1); } return FALSE; @@ -1037,7 +1038,7 @@ void RejoinCOWPages(HANDLE mmHandle, byte* mmStart, size_t mmSize) { // restores all page protections on the view and culls the COW pages. DWORD oldProtect; if (FALSE == VirtualProtect(mmStart, mmSize, PAGE_READWRITE | PAGE_REVERT_TO_FILE_MAP, &oldProtect)) { - throw std::system_error(GetLastError(), std::system_category(), "RejoinCOWPages: COW cull failed"); + throw system_error(GetLastError(), system_category(), "RejoinCOWPages: COW cull failed"); } } else #endif @@ -1057,7 +1058,7 @@ void RejoinCOWPages(HANDLE mmHandle, byte* mmStart, size_t mmSize) { 0, mmStart); if (remapped == NULL) { - throw std::system_error( + throw system_error( GetLastError(), system_category(), "RejoinCOWPages: MapViewOfFileEx failed."); @@ -1067,14 +1068,13 @@ void RejoinCOWPages(HANDLE mmHandle, byte* mmStart, size_t mmSize) { BOOL EndForkOperation(int * pExitCode) { try { - if( g_hForkedProcess != 0 ) - { + if (g_hForkedProcess != 0) { if (WaitForSingleObject(g_hForkedProcess, cDeadForkWait) == WAIT_TIMEOUT) { IFFAILTHROW(TerminateProcess(g_hForkedProcess, 1), "EndForkOperation: Killing forked process failed."); } if (pExitCode != NULL) { - GetExitCodeProcess(g_hForkedProcess, (DWORD*)pExitCode); + GetExitCodeProcess(g_hForkedProcess, (DWORD*) pExitCode); } CloseHandle(g_hForkedProcess); @@ -1084,34 +1084,34 @@ BOOL EndForkOperation(int * pExitCode) { IFFAILTHROW(ResetEvent(g_pQForkControl->operationComplete), "EndForkOperation: ResetEvent() failed."); IFFAILTHROW(ResetEvent(g_pQForkControl->operationFailed), "EndForkOperation: ResetEvent() failed."); - // move local changes back into memory mapped views for next fork operation + // Move local changes back into memory mapped views for next fork operation RejoinCOWPages( g_pQForkControl->heapMemoryMap, - (byte*)g_pQForkControl->heapStart, + (byte*) g_pQForkControl->heapStart, g_pQForkControl->availableBlocksInHeap * cAllocationGranularity); RejoinCOWPages( g_hQForkControlFileMap, - (byte*)g_pQForkControl, + (byte*) g_pQForkControl, sizeof(QForkControl)); return TRUE; } - catch(std::system_error syserr) { - ::redisLog(REDIS_WARNING, "EndForkOperation: 0x%08x - %s\n", syserr.code().value(), syserr.what()); + catch (system_error syserr) { + redisLog(REDIS_WARNING, "EndForkOperation: 0x%08x - %s\n", syserr.code().value(), syserr.what()); // If we can not properly restore fork state, then another fork operation is not possible. exit(1); } - catch( ... ) { - ::redisLog(REDIS_WARNING, "Some other exception caught in EndForkOperation().\n"); + catch (...) { + redisLog(REDIS_WARNING, "Some other exception caught in EndForkOperation().\n"); exit(1); } return FALSE; } LPVOID AllocHeapBlock(size_t size, BOOL allocateHigh) { - LPVOID retPtr = (LPVOID)NULL; + LPVOID retPtr = (LPVOID) NULL; if (size % cAllocationGranularity != 0) { errno = EINVAL; return retPtr; @@ -1126,13 +1126,13 @@ LPVOID AllocHeapBlock(size_t size, BOOL allocateHigh) { size_t mapped = 0; int startIndex = allocateHigh ? g_pQForkControl->availableBlocksInHeap - 1 : 0; int endIndex = allocateHigh ? - contiguousBlocksToAllocate - 2 : - g_pQForkControl->availableBlocksInHeap - contiguousBlocksToAllocate + 1; + contiguousBlocksToAllocate - 2 : + g_pQForkControl->availableBlocksInHeap - contiguousBlocksToAllocate + 1; int direction = allocateHigh ? -1 : 1; int blockIndex = 0; int contiguousBlocksFound = 0; - for(blockIndex = startIndex; - blockIndex != endIndex; + for (blockIndex = startIndex; + blockIndex != endIndex; blockIndex += direction) { for (int n = 0; n < contiguousBlocksToAllocate; n++) { assert((blockIndex + n * direction >= 0) && @@ -1140,8 +1140,7 @@ LPVOID AllocHeapBlock(size_t size, BOOL allocateHigh) { if (g_pQForkControl->heapBlockMap[blockIndex + n * direction] == BlockState::bsUNMAPPED) { contiguousBlocksFound++; - } - else { + } else { contiguousBlocksFound = 0; break; } @@ -1153,16 +1152,15 @@ LPVOID AllocHeapBlock(size_t size, BOOL allocateHigh) { if (contiguousBlocksFound == contiguousBlocksToAllocate) { int allocationStart = blockIndex + (allocateHigh ? 1 - contiguousBlocksToAllocate : 0); - LPVOID blockStart = - reinterpret_cast(g_pQForkControl->heapStart) + + LPVOID blockStart = + reinterpret_cast(g_pQForkControl->heapStart) + (cAllocationGranularity * allocationStart); - for(int n = 0; n < contiguousBlocksToAllocate; n++ ) { - g_pQForkControl->heapBlockMap[allocationStart+n] = BlockState::bsMAPPED; + for (int n = 0; n < contiguousBlocksToAllocate; n++) { + g_pQForkControl->heapBlockMap[allocationStart + n] = BlockState::bsMAPPED; mapped += cAllocationGranularity; } retPtr = blockStart; - } - else { + } else { errno = ENOMEM; } @@ -1191,7 +1189,7 @@ BOOL FreeHeapBlock(LPVOID block, size_t size) { if (err != ERROR_NOT_LOCKED) { return FALSE; } - }; + } for (int n = 0; n < contiguousBlocksToFree; n++ ) { g_pQForkControl->heapBlockMap[blockIndex + n] = BlockState::bsUNMAPPED; } @@ -1252,6 +1250,7 @@ extern "C" } try { + #ifdef DEBUG_WITH_PROCMON hProcMonDevice = CreateFile( @@ -1264,7 +1263,7 @@ extern "C" NULL); #endif - // service commands do not launch an instance of redis directly + // Service commands do not launch an instance of redis directly if (HandleServiceCommands(argc, argv) == TRUE) { return 0; } @@ -1295,28 +1294,28 @@ extern "C" QForkShutdown(); return retval; } else if (status == ssCHILD_EXIT) { - // child is done - clean up and exit + // Child is done - clean up and exit QForkShutdown(); return g_ChildExitCode; } else if (status == ssFAILED) { - // parent or child failed initialization + // Parent or child failed initialization return 1; } else { - // unexpected status return + // Unexpected status return return 2; } } else { return redis_main(argc, argv); } } - catch (std::system_error syserr) { - ::redisLog(REDIS_WARNING, "main: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what()); + catch (system_error syserr) { + redisLog(REDIS_WARNING, "main: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what()); } - catch (std::runtime_error runerr) { - ::redisLog(REDIS_WARNING, "main: runtime error caught. message=%s\n", runerr.what()); + catch (runtime_error runerr) { + redisLog(REDIS_WARNING, "main: runtime error caught. message=%s\n", runerr.what()); } catch (...) { - ::redisLog(REDIS_WARNING, "main: other exception caught.\n"); + redisLog(REDIS_WARNING, "main: other exception caught.\n"); } } } -- GitLab