This commit is contained in:
jonathan pickett
2014-07-28 18:11:09 -07:00
11 changed files with 250 additions and 81 deletions
Binary file not shown.
+1 -1
View File
@@ -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
+2 -2
View File
@@ -3,7 +3,7 @@
<metadata>
<id>redis-64</id>
<title>redis-64</title>
<version>2.8.9</version>
<version>2.8.12</version>
<authors>Jonathan Pickett</authors>
<owners>Microsoft Open Technologies, Inc.</owners>
<summary>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.</summary>
@@ -14,7 +14,7 @@
<licenseUrl>https://github.com/MSOpenTech/redis/blob/2.8/license.txt</licenseUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<iconUrl>http://redis.io/images/redis.png</iconUrl>
<releaseNotes>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.</releaseNotes>
<releaseNotes>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.</releaseNotes>
</metadata>
<files>
<file src="..\signed_binaries\*.*" target=".\" />
+2 -1
View File
@@ -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 ./
+2 -2
View File
@@ -3,7 +3,7 @@
<metadata>
<id>redis-64</id>
<title>redis-64</title>
<version>2.8.9</version>
<version>2.8.12</version>
<authors>Jonathan Pickett</authors>
<owners>Microsoft Open Technologies, Inc.</owners>
<summary>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.</summary>
@@ -14,7 +14,7 @@
<licenseUrl>https://github.com/MSOpenTech/redis/blob/2.8/license.txt</licenseUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<iconUrl>http://redis.io/images/redis.png</iconUrl>
<releaseNotes>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.</releaseNotes>
<releaseNotes>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.</releaseNotes>
</metadata>
<files>
<file src="..\signed_binaries\*.*" target=".\" />
+116 -21
View File
@@ -32,6 +32,8 @@
// definition. #undef solves the warning messages.
#undef close
#include <Shlwapi.h>
#include <algorithm>
#include <fstream>
#include <iostream>
@@ -40,22 +42,26 @@
#include <functional>
using namespace std;
#pragma comment (lib, "Shlwapi.lib")
ArgumentMap g_argMap;
vector<string> 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<std::string> split(const std::string &s, char delim) {
return elems;
}
void ParseConfFile(string confFile, ArgumentMap& argMap) {
vector<string> Tokenize(string line) {
vector<string> 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<string> tokens = split(line, ' ');
vector<string> 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
}
}
vector<string> GetAccessPaths() {
return g_pathsAccessed;
}
+3 -1
View File
@@ -35,8 +35,9 @@ using namespace std;
typedef map<string, vector<vector<string>>> 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<string> 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";
+1 -1
View File
@@ -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));
+35 -16
View File
@@ -42,6 +42,7 @@
#include <sstream>
#include <stdint.h>
#include <exception>
#include <algorithm>
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,
+65 -28
View File
@@ -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);
}
}
+23 -8
View File
@@ -49,6 +49,7 @@
#ifdef _WIN32
#include "win32_Interop/win32fixes.h"
#define ANET_NOTUSED(V) V
#include <Mstcpip.h>
#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. */