diff --git a/bin/release/redis-2.8.12.zip b/bin/release/redis-2.8.12.zip index f9d8a7d1..be7938f2 100644 Binary files a/bin/release/redis-2.8.12.zip and b/bin/release/redis-2.8.12.zip differ diff --git a/msvs/setups/PullBinaries.ps1 b/msvs/setups/PullBinaries.ps1 index 6dd21fad..40e99639 100644 --- a/msvs/setups/PullBinaries.ps1 +++ b/msvs/setups/PullBinaries.ps1 @@ -2,7 +2,7 @@ $CurDir = split-path -parent $MyInvocation.MyCommand.Definition -$SourceZip = [System.IO.Path]::Combine($CurDir, "..\..\bin\Release\redis-2.8.9.zip" ) +$SourceZip = [System.IO.Path]::Combine($CurDir, "..\..\bin\Release\redis-2.8.12.zip" ) $Destination = [System.IO.Path]::Combine($CurDir, "signed_binaries" ) [System.IO.Directory]::CreateDirectory($Destination) | Out-Null diff --git a/msvs/setups/chocolatey/Redis.nuspec b/msvs/setups/chocolatey/Redis.nuspec index 8015ac50..5da0ec87 100644 --- a/msvs/setups/chocolatey/Redis.nuspec +++ b/msvs/setups/chocolatey/Redis.nuspec @@ -3,7 +3,7 @@ redis-64 redis-64 - 2.8.9 + 2.8.12 Jonathan Pickett Microsoft Open Technologies, Inc. Redis is a very popular open-source, networked, in-memory, key-value data store known for high performance, flexibility, a rich set of data structures, and a simple straightforward API. @@ -14,7 +14,7 @@ https://github.com/MSOpenTech/redis/blob/2.8/license.txt false http://redis.io/images/redis.png - Includes changes from redis 2.8.9. Please see the release notes for the UNIX 2.8 branch to understand how this impacts Redis functionality. This release adds support for running redis as a Windows Service. Please see RedisService.docx file for documentation. + Includes the changes from Redis 2.8.9 -> 2.8.12. Please see the release notes for the UNIX 2.8 branch to understand how this impacts Redis functionality. diff --git a/msvs/setups/documentation/redis.windows.conf b/msvs/setups/documentation/redis.windows.conf index 9a348c71..632c0351 100644 --- a/msvs/setups/documentation/redis.windows.conf +++ b/msvs/setups/documentation/redis.windows.conf @@ -137,7 +137,8 @@ dbfilename dump.rdb # The DB will be written inside this directory, with the filename specified # above using the 'dbfilename' configuration directive. # -# The Append Only File will also be created inside this directory. +# The Append Only File and the QFork memory mapped file will also be created +# inside this directory. # # Note that you must specify a directory here, not a file name. dir ./ diff --git a/msvs/setups/nuget/Redis.nuspec b/msvs/setups/nuget/Redis.nuspec index 8015ac50..5da0ec87 100644 --- a/msvs/setups/nuget/Redis.nuspec +++ b/msvs/setups/nuget/Redis.nuspec @@ -3,7 +3,7 @@ redis-64 redis-64 - 2.8.9 + 2.8.12 Jonathan Pickett Microsoft Open Technologies, Inc. Redis is a very popular open-source, networked, in-memory, key-value data store known for high performance, flexibility, a rich set of data structures, and a simple straightforward API. @@ -14,7 +14,7 @@ https://github.com/MSOpenTech/redis/blob/2.8/license.txt false http://redis.io/images/redis.png - Includes changes from redis 2.8.9. Please see the release notes for the UNIX 2.8 branch to understand how this impacts Redis functionality. This release adds support for running redis as a Windows Service. Please see RedisService.docx file for documentation. + Includes the changes from Redis 2.8.9 -> 2.8.12. Please see the release notes for the UNIX 2.8 branch to understand how this impacts Redis functionality. diff --git a/src/Win32_Interop/Win32_CommandLine.cpp b/src/Win32_Interop/Win32_CommandLine.cpp index 1470bd56..ce2d3787 100644 --- a/src/Win32_Interop/Win32_CommandLine.cpp +++ b/src/Win32_Interop/Win32_CommandLine.cpp @@ -32,6 +32,8 @@ // definition. #undef solves the warning messages. #undef close +#include + #include #include #include @@ -40,22 +42,26 @@ #include using namespace std; +#pragma comment (lib, "Shlwapi.lib") ArgumentMap g_argMap; +vector g_pathsAccessed; string stripQuotes(string s) { - if (s.at(0) == '\'' && s.at(s.length() - 1) == '\'') { - if (s.length() > 2) { - return s.substr(1, s.length() - 2); - } else { - return string(""); + if (s.length() >= 2) { + if (s.at(0) == '\'' && s.at(s.length() - 1) == '\'') { + if (s.length() > 2) { + return s.substr(1, s.length() - 2); + } else { + return string(""); + } } - } - if (s.at(0) == '\"' && s.at(s.length() - 1) == '\"') { - if (s.length() > 2) { - return s.substr(1, s.length() - 2); - } else { - return string(""); + if (s.at(0) == '\"' && s.at(s.length() - 1) == '\"') { + if (s.length() > 2) { + return s.substr(1, s.length() - 2); + } else { + return string(""); + } } } return s; @@ -376,7 +382,7 @@ static RedisParamterMapper g_redisArgMap = { "rdbcompression", &fp1 }, // rdbcompression [yes/no] { "rdbchecksum", &fp1 }, // rdbchecksum [yes/no] { "dbfilename", &fp1 }, // dbfilename [filename] - { "dir", &fp1 }, // dir [path] + { cDir, &fp1 }, // dir [path] { "slaveof", &fp2 }, // slaveof [masterip] [master port] { "masterauth", &fp1 }, // masterauth [master-password] { "slave-serve-stale-data", &fp1 }, // slave-serve-stale-data [yes/no] @@ -440,7 +446,58 @@ std::vector split(const std::string &s, char delim) { return elems; } -void ParseConfFile(string confFile, ArgumentMap& argMap) { +vector Tokenize(string line) { + vector tokens; + stringstream token; + + // no need to parse empty lines, or comment lines (which may have unbalanced quotes) + if ((line.length() == 0) || + ((line.length() != 0) && (*line.begin()) == '#')) { + return tokens; + } + + for (string::const_iterator sit = line.begin(); sit != line.end(); sit++) { + char c = *(sit); + if (isspace(c) && token.str().length() > 0) { + tokens.push_back(token.str()); + token.str(""); + } else if (c == '\'' || c == '\"') { + char endQuote = c; + string::const_iterator endQuoteIt = sit; + while (++endQuoteIt != line.end()) { + if (*endQuoteIt == endQuote) break; + } + if (endQuoteIt != line.end()) { + while (++sit != endQuoteIt) { + token << (*sit); + } + + // The code above strips quotes. In certain cases (save "") the quotes should be preserved around empty strings + if (token.str().length() == 0) + token << endQuote << endQuote; + + // correct paths for windows nomenclature + string path = token.str(); + replace(path.begin(), path.end(), '/', '\\'); + tokens.push_back(path); + + token.str(""); + } else { + // stuff the imbalanced quote character and continue + token << (*sit); + } + } else { + token << c; + } + } + if (token.str().length() > 0) { + tokens.push_back(token.str()); + } + + return tokens; +} + +void ParseConfFile(string confFile, string cwd, ArgumentMap& argMap) { ifstream config; string line; string value; @@ -448,25 +505,38 @@ void ParseConfFile(string confFile, ArgumentMap& argMap) { #ifdef _DEBUG cout << "processing " << confFile << endl; #endif + char fullConfFilePath[MAX_PATH]; + if (PathIsRelativeA(confFile.c_str())) { + if (NULL == PathCombineA(fullConfFilePath, cwd.c_str(), confFile.c_str())) { + throw std::system_error(GetLastError(), system_category(), "PathCombineA failed"); + } + } else { + strcpy(fullConfFilePath, confFile.c_str()); + } - config.open(confFile); + config.open(fullConfFilePath); if (config.fail()) { stringstream ss; - char buffer[MAX_PATH]; - ::GetCurrentDirectoryA(MAX_PATH, buffer); - ss << "Failed to open the .conf file: " << confFile << " CWD=" << buffer; + ss << "Failed to open the .conf file: " << confFile << " CWD=" << cwd.c_str(); throw runtime_error(ss.str()); + } else { + char confFileDir[MAX_PATH]; + strcpy(confFileDir, fullConfFilePath); + if (FALSE == PathRemoveFileSpecA(confFileDir)) { + throw std::system_error(GetLastError(), system_category(), "PathRemoveFileSpecA failed"); + } + g_pathsAccessed.push_back(confFileDir); } while (!config.eof()) { getline(config, line); - vector tokens = split(line, ' '); + vector tokens = Tokenize(line); if (tokens.size() > 0) { string parameter = tokens.at(0); if (parameter.at(0) == '#') { continue; } else if (parameter.compare(cInclude) == 0) { - ParseConfFile(tokens.at(1), argMap); + ParseConfFile(tokens.at(1), cwd, argMap); } else if (g_redisArgMap.find(parameter) == g_redisArgMap.end()) { stringstream err; err << "unknown conf file parameter : " + parameter; @@ -530,7 +600,27 @@ void ParseCommandLineArguments(int argc, char** argv) { } } - if (confFile) ParseConfFile(confFilePath, g_argMap); + char cwd[MAX_PATH]; + if (0 == ::GetCurrentDirectoryA(MAX_PATH, cwd)) { + throw std::system_error(GetLastError(), system_category(), "ParseCommandLineArguments: GetCurrentDirectoryA failed"); + } + + if (confFile) ParseConfFile(confFilePath, cwd, g_argMap); + + // grab directory where RDB/AOF/DAT files will be created so that service install can add access allowed ACE to path + string fileCreationDirectory = ".\\"; + if (g_argMap.find(cDir) != g_argMap.end()) { + fileCreationDirectory = g_argMap[cDir][0][0]; + replace(fileCreationDirectory.begin(), fileCreationDirectory.end(), '/', '\\'); + } + if (PathIsRelativeA(fileCreationDirectory.c_str())) { + char fullPath[MAX_PATH]; + if (NULL == PathCombineA(fullPath, cwd, fileCreationDirectory.c_str())) { + throw std::system_error(GetLastError(), system_category(), "PathCombineA failed"); + } + fileCreationDirectory = fullPath; + } + g_pathsAccessed.push_back(fileCreationDirectory); #ifdef _DEBUG cout << "arguments seen:" << endl; @@ -552,4 +642,9 @@ void ParseCommandLineArguments(int argc, char** argv) { } } #endif -} \ No newline at end of file +} + +vector GetAccessPaths() { + return g_pathsAccessed; +} + diff --git a/src/Win32_Interop/Win32_CommandLine.h b/src/Win32_Interop/Win32_CommandLine.h index 11ff3742..548c5eb3 100644 --- a/src/Win32_Interop/Win32_CommandLine.h +++ b/src/Win32_Interop/Win32_CommandLine.h @@ -35,8 +35,9 @@ using namespace std; typedef map>> ArgumentMap; extern ArgumentMap g_argMap; -void ParseConfFile(string confFile, ArgumentMap& argMap); +void ParseConfFile(string confFile, string cwd, ArgumentMap& argMap); void ParseCommandLineArguments(int argc, char** argv); +vector GetAccessPaths(); const string cQFork = "qfork"; const string cServiceRun = "service-run"; @@ -49,6 +50,7 @@ const string cSyslogEnabled = "syslog-enabled"; const string cSyslogIdent= "syslog-ident"; const string cLogfile = "logfile"; const string cInclude = "include"; +const string cDir = "dir"; const string cMaxHeap = "maxheap"; const string cMaxMemory = "maxmemory"; diff --git a/src/Win32_Interop/Win32_FDAPI.cpp b/src/Win32_Interop/Win32_FDAPI.cpp index 77a1e9aa..38bc1d85 100644 --- a/src/Win32_Interop/Win32_FDAPI.cpp +++ b/src/Win32_Interop/Win32_FDAPI.cpp @@ -1033,7 +1033,7 @@ BOOL ParseStorageAddress(const char *ip, int port, SOCKADDR_STORAGE* pSotrageAdd hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; /* Setting AI_PASSIVE will give you a wildcard address if addr is NULL */ - hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE; + hints.ai_flags = AI_NUMERICSERV | AI_PASSIVE; if ((status = getaddrinfo(ip, port_buffer, &hints, &res) != 0)) { fprintf(stderr, "getaddrinfo: %S\n", gai_strerror(status)); diff --git a/src/Win32_Interop/Win32_QFork.cpp b/src/Win32_Interop/Win32_QFork.cpp index 1b1ab0e6..91ecb978 100644 --- a/src/Win32_Interop/Win32_QFork.cpp +++ b/src/Win32_Interop/Win32_QFork.cpp @@ -42,6 +42,7 @@ #include #include #include +#include using namespace std; const long long cSentinelHeapSize = 30 * 1024 * 1024; @@ -136,7 +137,7 @@ How the parent invokes the QFork process: 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 wchar_t* cMapFileBaseName = L"RedisQFork"; +const char* cMapFileBaseName = "RedisQFork"; const int cDeadForkWait = 30000; size_t pageSize = 0; @@ -206,8 +207,10 @@ bool ReportSpecialSystemErrors(int error) { "\n" "The Windows version of Redis allocates a large memory mapped file for sharing\n" "the heap with the forked process used in persistence operations. This file\n" - "will be created in the current working directory. Windows is reporting that\n" - "there is insufficient disk space available for this file (Windows error 0x70).\n" + "will be created in the current working directory or the directory specified by\n" + "the 'dir' directive in the .conf file. Windows is reporting that there is \n" + "insufficient disk space available for this file (Windows error 0x70).\n" + "\n" "You may fix this problem by either reducing the size of the Redis heap with\n" "the --maxheap flag, or by starting redis from a working directory with\n" "sufficient space available for the Redis heap. \n" @@ -329,6 +332,18 @@ BOOL QForkSlaveInit(HANDLE QForkConrolMemoryMapHandle, DWORD ParentProcessID) { return FALSE; } +string GetWorkingDirectory() { + string workingDir = ".\\"; + if (g_argMap.find(cDir) != g_argMap.end()) { + workingDir = g_argMap[cDir][0][0]; + } + std::replace(workingDir.begin(), workingDir.end(), '/', '\\'); + if (workingDir.at(workingDir.length() - 1) != '\\') { + workingDir = workingDir.append("\\"); + } + return workingDir; +} + BOOL QForkMasterInit( __int64 maxheapBytes ) { try { // allocate file map for qfork control so it can be passed to the forked process @@ -379,34 +394,38 @@ BOOL QForkMasterInit( __int64 maxheapBytes ) { // FILE_FLAG_DELETE_ON_CLOSE will not clean up files in the case of a BSOD or power failure. // Clean up anything we can to prevent excessive disk usage. - wchar_t heapMemoryMapWildCard[MAX_PATH]; - WIN32_FIND_DATA fd; - swprintf_s( + char heapMemoryMapWildCard[MAX_PATH]; + WIN32_FIND_DATAA fd; + sprintf_s( heapMemoryMapWildCard, MAX_PATH, - L"%s_*.dat", + "%s%s_*.dat", + GetWorkingDirectory().c_str(), cMapFileBaseName); - HANDLE hFind = FindFirstFile(heapMemoryMapWildCard, &fd); + HANDLE hFind = FindFirstFileA(heapMemoryMapWildCard, &fd); while (hFind != INVALID_HANDLE_VALUE) { // Failure likely means the file is in use by another redis instance. - DeleteFile(fd.cFileName); + DeleteFileA(fd.cFileName); - if (FALSE == FindNextFile(hFind, &fd)) { + if (FALSE == FindNextFileA(hFind, &fd)) { FindClose(hFind); hFind = INVALID_HANDLE_VALUE; } } - wchar_t heapMemoryMapPath[MAX_PATH]; - swprintf_s( - heapMemoryMapPath, - MAX_PATH, - L"%s_%d.dat", + string workingDir = GetWorkingDirectory(); + + char heapMemoryMapPath[MAX_PATH]; + sprintf_s( + heapMemoryMapPath, + MAX_PATH, + "%s%s_%d.dat", + workingDir.c_str(), cMapFileBaseName, GetCurrentProcessId()); g_pQForkControl->heapMemoryMapFile = - CreateFileW( + CreateFileA( heapMemoryMapPath, GENERIC_READ | GENERIC_WRITE, 0, diff --git a/src/Win32_Interop/Win32_service.cpp b/src/Win32_Interop/Win32_service.cpp index 11766398..c0631bc6 100644 --- a/src/Win32_Interop/Win32_service.cpp +++ b/src/Win32_Interop/Win32_service.cpp @@ -90,18 +90,39 @@ const char* cServiceInstallPipeName = "\\\\.\\pipe\\redis-service-install"; extern "C" int main(int argc, char** argv); -void WriteServiceInstallMessage(string message) { - HANDLE pipe = CreateFileA(cServiceInstallPipeName, GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); - if (pipe != INVALID_HANDLE_VALUE) { - DWORD bytesWritten = 0; - WriteFile(pipe, message.c_str(), (DWORD)message.length(), &bytesWritten, NULL); - CloseHandle(pipe); - } else { - ::redisLog(REDIS_WARNING, message.c_str()); +typedef class ServicePipeWriter { +public: + static ServicePipeWriter& getInstance() { + static ServicePipeWriter instance; + return instance; } -} + +private: + HANDLE pipe = INVALID_HANDLE_VALUE; + ServicePipeWriter() { + pipe = CreateFileA(cServiceInstallPipeName, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + } + ServicePipeWriter(ServicePipeWriter const&); + void operator=(ServicePipeWriter const&); + ~ServicePipeWriter() { + if (pipe != INVALID_HANDLE_VALUE) { + CloseHandle(pipe); + pipe = INVALID_HANDLE_VALUE; + } + } + +public: + void Write(string message) { + if (pipe != INVALID_HANDLE_VALUE) { + DWORD bytesWritten = 0; + WriteFile(pipe, message.c_str(), (DWORD)message.length(), &bytesWritten, NULL); + } else { + ::redisLog(REDIS_WARNING, message.c_str()); + } + } +} ServicePipeWriter; BOOL RelaunchAsElevatedProcess(int argc, char** argv) { // create pipe for launched process to communicate back on @@ -118,7 +139,12 @@ BOOL RelaunchAsElevatedProcess(int argc, char** argv) { } else { paramString << " "; } - paramString << argv[n]; + string arg = argv[n]; + if (arg.find(' ') != string::npos) { + paramString << "\"" << arg << "\""; + } else { + paramString << arg; + } } CHAR params[32768]; memset(params, 0, 32768); @@ -261,6 +287,7 @@ VOID ServiceInstall(int argc, char ** argv) { if (GetModuleFileNameA(NULL, szPath, MAX_PATH) == 0) { throw std::system_error(GetLastError(), system_category(), "ServiceInstall: GetModuleFileNameA failed"); } + stringstream args; for (int a = 0; a < argc; a++) { if (a == 0) { @@ -271,7 +298,12 @@ VOID ServiceInstall(int argc, char ** argv) { // replace --service-install argument with --service-run args << "--" << cServiceRun; } else { - args << argv[a]; + string arg = argv[a]; + if (arg.find(' ') != arg.npos) { + args << "\"" << argv[a] << "\""; + } else { + args << argv[a]; + } } } } @@ -280,6 +312,7 @@ VOID ServiceInstall(int argc, char ** argv) { if (shSCManager.Invalid()) { throw std::system_error(GetLastError(), system_category(), "OpenSCManager failed"); } + shService = CreateServiceA( shSCManager, g_serviceName, @@ -304,12 +337,16 @@ VOID ServiceInstall(int argc, char ** argv) { RedisEventLog().InstallEventLogSource(szPath); - // make sure NT AUTHORITY\\NetworkService" has rights to the directory the service is installed in (for RDB write) - string folder = szPath; - folder = folder.substr(0, folder.rfind('\\')); - SetAccessACLOnFolder(userName, folder); - - WriteServiceInstallMessage("Redis successfully installed as a service."); + // make sure NT AUTHORITY\\NetworkService" has rights to every directory where a files may be accessed (CONF,AOF,RDB,DAT) + stringstream aceMessage; + aceMessage << "Granting read/write access to 'NT AUTHORITY\\NetworkService' on: "; + for (auto folder : GetAccessPaths()) { + SetAccessACLOnFolder(userName, folder); + aceMessage << "\"" << folder.c_str() << "\" "; + } + ServicePipeWriter::getInstance().Write(aceMessage.str().c_str()); + + ServicePipeWriter::getInstance().Write("Redis successfully installed as a service."); } VOID ServiceStart(int argc, char ** argv) { @@ -337,16 +374,16 @@ VOID ServiceStart(int argc, char ** argv) { DWORD start = GetTickCount(); while (QueryServiceStatus(shService, &status) == TRUE) { if (status.dwCurrentState == SERVICE_RUNNING) { - WriteServiceInstallMessage("Redis service successfully started."); + ServicePipeWriter::getInstance().Write("Redis service successfully started."); break; } else if (status.dwCurrentState == SERVICE_STOPPED) { - WriteServiceInstallMessage("Redis service failed to start."); + ServicePipeWriter::getInstance().Write("Redis service failed to start."); break; } DWORD current = GetTickCount(); if (current - start >= cThirtySeconds) { - WriteServiceInstallMessage("Redis service start timed out."); + ServicePipeWriter::getInstance().Write("Redis service start timed out."); break; } } @@ -375,12 +412,12 @@ VOID ServiceStop(int argc, char ** argv) { DWORD start = GetTickCount(); while (QueryServiceStatus(shService, &status) == TRUE) { if (status.dwCurrentState == SERVICE_STOPPED) { - WriteServiceInstallMessage("Redis service successfully stopped."); + ServicePipeWriter::getInstance().Write("Redis service successfully stopped."); break; } DWORD current = GetTickCount(); if (current - start >= cThirtySeconds) { - WriteServiceInstallMessage("Redis service stop timed out."); + ServicePipeWriter::getInstance().Write("Redis service stop timed out."); break; } } @@ -405,7 +442,7 @@ VOID ServiceUninstall(int argc, char** argv) { RedisEventLog().UninstallEventLogSource(); - WriteServiceInstallMessage("Redis service successfully uninstalled."); + ServicePipeWriter::getInstance().Write("Redis service successfully uninstalled."); } DWORD WINAPI ServiceWorkerThread(LPVOID lpParam) { @@ -671,17 +708,17 @@ extern "C" BOOL HandleServiceCommands(int argc, char **argv) { } catch (std::system_error syserr) { stringstream ss; ss << "HandleServiceCommands: system error caught. error code=" << syserr.code().value() << ", message = " << syserr.what() << endl; - WriteServiceInstallMessage(ss.str()); + ServicePipeWriter::getInstance().Write(ss.str()); exit(1); } catch (std::runtime_error runerr) { stringstream err; err << "HandleServiceCommands: runtime error caught. message=" << runerr.what() << endl; - WriteServiceInstallMessage(err.str()); + ServicePipeWriter::getInstance().Write(err.str()); exit(1); } catch (...) { stringstream ss; ss << "HandleServiceCommands: other exception caught." << endl; - WriteServiceInstallMessage(ss.str()); + ServicePipeWriter::getInstance().Write(ss.str()); exit(1); } } diff --git a/src/anet.c b/src/anet.c index bc9b0939..b9bcd404 100644 --- a/src/anet.c +++ b/src/anet.c @@ -49,6 +49,7 @@ #ifdef _WIN32 #include "win32_Interop/win32fixes.h" #define ANET_NOTUSED(V) V +#include #endif #include "anet.h" @@ -81,20 +82,34 @@ int anetNonBlock(char *err, int fd) return ANET_OK; } -/* Set TCP keep alive option to detect dead peers. The interval option - * is only used for Linux as we are using Linux-specific APIs to set - * the probe send time, interval, and count. */ -int anetKeepAlive(char *err, int fd, int interval) -{ +/* Set TCP keep alive option to detect dead peers. */ +int anetKeepAlive(char *err, int fd, int interval) { +#ifdef _WIN32 int val = 1; - if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1) - { + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1) { anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno)); return ANET_ERR; } -#ifdef __linux__ + struct tcp_keepalive alive; + DWORD dwBytesRet = 0; + alive.onoff = TRUE; + alive.keepalivetime = interval * 1000; + /* According to http://msdn.microsoft.com/en-us/library/windows/desktop/ee470551(v=vs.85).aspx + On Windows Vista and later, the number of keep-alive probes (data retransmissions) is set to 10 and cannot be changed. + So we set the keep alive interval as interval/10, as 10 probes will be send before + detecting an error + */ + val = interval/10; + if (val == 0) val = 1; + alive.keepaliveinterval = val*1000; + if(WSAIoctl(fd, SIO_KEEPALIVE_VALS, &alive, sizeof(alive), + NULL, 0, &dwBytesRet, NULL, NULL) == SOCKET_ERROR) { + anetSetError(err, "WSAIotcl(SIO_KEEPALIVE_VALS) failed with error code %d\n", WSAGetLastError()); + return ANET_ERR; + } +#else /* Default settings are more or less garbage, with the keepalive time * set to 7200 by default on Linux. Modify settings to make the feature * actually useful. */