Compare commits
13 Commits
win-2.8.24
...
msopentech
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d769620e8 | ||
|
|
ab667b8867 | ||
|
|
81f3e8d04e | ||
|
|
18cafadfc1 | ||
|
|
5ac9eb0590 | ||
|
|
65bfe8cb02 | ||
|
|
74e2ad1b8b | ||
|
|
2709b7783e | ||
|
|
3867227707 | ||
|
|
a13ad6f66d | ||
|
|
fe24e87f27 | ||
|
|
fa4676becd | ||
|
|
99e01c7e6b |
BIN
bin/release/redis-2.6.14.zip
Normal file
BIN
bin/release/redis-2.6.14.zip
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
deps/hiredis/sds.h
vendored
2
deps/hiredis/sds.h
vendored
@@ -44,7 +44,7 @@ typedef char *sds;
|
||||
struct sdshdr {
|
||||
int len;
|
||||
int free;
|
||||
char* buf;
|
||||
char buf[];
|
||||
};
|
||||
|
||||
static inline size_t sdslen(const sds s) {
|
||||
|
||||
@@ -86,6 +86,9 @@
|
||||
<RandomizedBaseAddress>false</RandomizedBaseAddress>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>powershell -executionpolicy unrestricted -command compressOutputToReleaseFolder.ps1</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\src\adlist.c" />
|
||||
|
||||
29
msvs/compressOutputToReleaseFolder.ps1
Normal file
29
msvs/compressOutputToReleaseFolder.ps1
Normal file
@@ -0,0 +1,29 @@
|
||||
[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
|
||||
|
||||
$Compression = [System.IO.Compression.CompressionLevel]::Optimal
|
||||
$IncludeBaseDirectory = $false
|
||||
|
||||
$CurDir = [System.IO.Directory]::GetCurrentDirectory()
|
||||
$PubDir = [System.IO.Path]::Combine($CurDir, "x64\Release\pub" )
|
||||
$SourceDir = [System.IO.Path]::Combine($CurDir, "x64\Release" )
|
||||
$Destination = [System.IO.Path]::Combine($CurDir, "..\bin\Release\redis-2.6.14.zip" )
|
||||
|
||||
[System.IO.Directory]::CreateDirectory($PubDir) | Out-Null
|
||||
|
||||
ForEach( $file in [System.IO.Directory]::EnumerateFiles($PubDir) ) {
|
||||
[System.IO.File]::Delete($file)
|
||||
}
|
||||
|
||||
ForEach( $file in [System.IO.Directory]::EnumerateFiles($SourceDir, "*.exe" ) ) {
|
||||
[System.IO.File]::Copy($file, [System.IO.Path]::Combine( $PubDir, [System.IO.Path]::GetFileName($file) ) )
|
||||
}
|
||||
|
||||
ForEach( $file in [System.IO.Directory]::EnumerateFiles($SourceDir, "*.dll" ) ) {
|
||||
[System.IO.File]::Copy($file, [System.IO.Path]::Combine( $PubDir, [System.IO.Path]::GetFileName($file) ) )
|
||||
}
|
||||
|
||||
If ( [System.IO.File]::Exists($Destination) ) {
|
||||
[System.IO.File]::Delete($Destination)
|
||||
}
|
||||
|
||||
[System.IO.Compression.ZipFile]::CreateFromDirectory($PubDir,$Destination,$Compression,$IncludeBaseDirectory)
|
||||
26
msvs/setups/chocolatey/Redis.nuspec
Normal file
26
msvs/setups/chocolatey/Redis.nuspec
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>redis-64</id>
|
||||
<title>redis-64</title>
|
||||
<version>2.6.14</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>
|
||||
<description>A production-ready Windows port of Redis, including 64-bit support, Chocolatey support, and much more.</description>
|
||||
<projectUrl>https://msopentech.com/opentech-projects/redis/</projectUrl>
|
||||
<tags>Redis nosql cache</tags>
|
||||
<copyright>Copyright Microsoft Open Technologies, Inc.</copyright>
|
||||
<licenseUrl>https://github.com/MSOpenTech/redis/blob/2.8.4_msopen/license.txt</licenseUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<iconUrl>http://redis.io/images/redis.png</iconUrl>
|
||||
<dependencies>
|
||||
<dependency id="vcredist2012" />
|
||||
</dependencies>
|
||||
<releaseNotes></releaseNotes>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\signed_binaries\*.*" target=".\" />
|
||||
<file src="..\documentation\*.*" target=".\" />
|
||||
</files>
|
||||
</package>
|
||||
BIN
msvs/setups/documentation/Redis Release Notes.docx
Normal file
BIN
msvs/setups/documentation/Redis Release Notes.docx
Normal file
Binary file not shown.
23
msvs/setups/nuget/Redis.nuspec
Normal file
23
msvs/setups/nuget/Redis.nuspec
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>redis-64</id>
|
||||
<title>redis-64</title>
|
||||
<version>2.6.14</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>
|
||||
<description>A production-ready Windows port of Redis, including 64-bit support, Chocolatey support, and much more.</description>
|
||||
<projectUrl>https://msopentech.com/opentech-projects/redis/</projectUrl>
|
||||
<tags>Redis nosql cache</tags>
|
||||
<copyright>Copyright Microsoft Open Technologies, Inc.</copyright>
|
||||
<licenseUrl>https://github.com/MSOpenTech/redis/blob/2.8.4_msopen/license.txt</licenseUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<iconUrl>http://redis.io/images/redis.png</iconUrl>
|
||||
<releaseNotes></releaseNotes>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\signed_binaries\*.*" target="redis-2.6.14\" />
|
||||
<file src="..\documentation\*.*" target="redis-2.6.14\" />
|
||||
</files>
|
||||
</package>
|
||||
631
redis.windows.conf
Normal file
631
redis.windows.conf
Normal file
@@ -0,0 +1,631 @@
|
||||
# Redis configuration file example
|
||||
|
||||
# Note on units: when memory size is needed, it is possible to specify
|
||||
# it in the usual form of 1k 5GB 4M and so forth:
|
||||
#
|
||||
# 1k => 1000 bytes
|
||||
# 1kb => 1024 bytes
|
||||
# 1m => 1000000 bytes
|
||||
# 1mb => 1024*1024 bytes
|
||||
# 1g => 1000000000 bytes
|
||||
# 1gb => 1024*1024*1024 bytes
|
||||
#
|
||||
# units are case insensitive so 1GB 1Gb 1gB are all the same.
|
||||
|
||||
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
|
||||
daemonize no
|
||||
|
||||
# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
|
||||
# default. You can specify a custom pid file location here.
|
||||
pidfile /var/run/redis.pid
|
||||
|
||||
# Accept connections on the specified port, default is 6379.
|
||||
# If port 0 is specified Redis will not listen on a TCP socket.
|
||||
port 6379
|
||||
|
||||
# If you want you can bind a single interface, if the bind option is not
|
||||
# specified all the interfaces will listen for incoming connections.
|
||||
#
|
||||
# bind 127.0.0.1
|
||||
|
||||
# Specify the path for the unix socket that will be used to listen for
|
||||
# incoming connections. There is no default, so Redis will not listen
|
||||
# on a unix socket when not specified.
|
||||
#
|
||||
# unixsocket /tmp/redis.sock
|
||||
# unixsocketperm 755
|
||||
|
||||
# Close the connection after a client is idle for N seconds (0 to disable)
|
||||
timeout 0
|
||||
|
||||
# TCP keepalive.
|
||||
#
|
||||
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
|
||||
# of communication. This is useful for two reasons:
|
||||
#
|
||||
# 1) Detect dead peers.
|
||||
# 2) Take the connection alive from the point of view of network
|
||||
# equipment in the middle.
|
||||
#
|
||||
# On Linux, the specified value (in seconds) is the period used to send ACKs.
|
||||
# Note that to close the connection the double of the time is needed.
|
||||
# On other kernels the period depends on the kernel configuration.
|
||||
#
|
||||
# A reasonable value for this option is 60 seconds.
|
||||
tcp-keepalive 0
|
||||
|
||||
# Specify the server verbosity level.
|
||||
# This can be one of:
|
||||
# debug (a lot of information, useful for development/testing)
|
||||
# verbose (many rarely useful info, but not a mess like the debug level)
|
||||
# notice (moderately verbose, what you want in production probably)
|
||||
# warning (only very important / critical messages are logged)
|
||||
loglevel notice
|
||||
|
||||
# Specify the log file name. Also 'stdout' can be used to force
|
||||
# Redis to log on the standard output. Note that if you use standard
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile stdout
|
||||
|
||||
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
|
||||
# and optionally update the other syslog parameters to suit your needs.
|
||||
# syslog-enabled no
|
||||
|
||||
# Specify the syslog identity.
|
||||
# syslog-ident redis
|
||||
|
||||
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
|
||||
# syslog-facility local0
|
||||
|
||||
# Set the number of databases. The default database is DB 0, you can select
|
||||
# a different one on a per-connection basis using SELECT <dbid> where
|
||||
# dbid is a number between 0 and 'databases'-1
|
||||
databases 16
|
||||
|
||||
################################ SNAPSHOTTING #################################
|
||||
#
|
||||
# Save the DB on disk:
|
||||
#
|
||||
# save <seconds> <changes>
|
||||
#
|
||||
# Will save the DB if both the given number of seconds and the given
|
||||
# number of write operations against the DB occurred.
|
||||
#
|
||||
# In the example below the behaviour will be to save:
|
||||
# after 900 sec (15 min) if at least 1 key changed
|
||||
# after 300 sec (5 min) if at least 10 keys changed
|
||||
# after 60 sec if at least 10000 keys changed
|
||||
#
|
||||
# Note: you can disable saving at all commenting all the "save" lines.
|
||||
#
|
||||
# It is also possible to remove all the previously configured save
|
||||
# points by adding a save directive with a single empty string argument
|
||||
# like in the following example:
|
||||
#
|
||||
# save ""
|
||||
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
# By default Redis will stop accepting writes if RDB snapshots are enabled
|
||||
# (at least one save point) and the latest background save failed.
|
||||
# This will make the user aware (in an hard way) that data is not persisting
|
||||
# on disk properly, otherwise chances are that no one will notice and some
|
||||
# distater will happen.
|
||||
#
|
||||
# If the background saving process will start working again Redis will
|
||||
# automatically allow writes again.
|
||||
#
|
||||
# However if you have setup your proper monitoring of the Redis server
|
||||
# and persistence, you may want to disable this feature so that Redis will
|
||||
# continue to work as usually even if there are problems with disk,
|
||||
# permissions, and so forth.
|
||||
stop-writes-on-bgsave-error yes
|
||||
|
||||
# Compress string objects using LZF when dump .rdb databases?
|
||||
# For default that's set to 'yes' as it's almost always a win.
|
||||
# If you want to save some CPU in the saving child set it to 'no' but
|
||||
# the dataset will likely be bigger if you have compressible values or keys.
|
||||
rdbcompression yes
|
||||
|
||||
# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
|
||||
# This makes the format more resistant to corruption but there is a performance
|
||||
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
|
||||
# for maximum performances.
|
||||
#
|
||||
# RDB files created with checksum disabled have a checksum of zero that will
|
||||
# tell the loading code to skip the check.
|
||||
rdbchecksum yes
|
||||
|
||||
# The filename where to dump the DB
|
||||
dbfilename dump.rdb
|
||||
|
||||
# The working directory.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Note that you must specify a directory here, not a file name.
|
||||
dir ./
|
||||
|
||||
################################# REPLICATION #################################
|
||||
|
||||
# Master-Slave replication. Use slaveof to make a Redis instance a copy of
|
||||
# another Redis server. Note that the configuration is local to the slave
|
||||
# so for example it is possible to configure the slave to save the DB with a
|
||||
# different interval, or to listen to another port, and so on.
|
||||
#
|
||||
# slaveof <masterip> <masterport>
|
||||
|
||||
# If the master is password protected (using the "requirepass" configuration
|
||||
# directive below) it is possible to tell the slave to authenticate before
|
||||
# starting the replication synchronization process, otherwise the master will
|
||||
# refuse the slave request.
|
||||
#
|
||||
# masterauth <master-password>
|
||||
|
||||
# When a slave loses its connection with the master, or when the replication
|
||||
# is still in progress, the slave can act in two different ways:
|
||||
#
|
||||
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
|
||||
# still reply to client requests, possibly with out of date data, or the
|
||||
# data set may just be empty if this is the first synchronization.
|
||||
#
|
||||
# 2) if slave-serve-stale-data is set to 'no' the slave will reply with
|
||||
# an error "SYNC with master in progress" to all the kind of commands
|
||||
# but to INFO and SLAVEOF.
|
||||
#
|
||||
slave-serve-stale-data yes
|
||||
|
||||
# You can configure a slave instance to accept writes or not. Writing against
|
||||
# a slave instance may be useful to store some ephemeral data (because data
|
||||
# written on a slave will be easily deleted after resync with the master) but
|
||||
# may also cause problems if clients are writing to it because of a
|
||||
# misconfiguration.
|
||||
#
|
||||
# Since Redis 2.6 by default slaves are read-only.
|
||||
#
|
||||
# Note: read only slaves are not designed to be exposed to untrusted clients
|
||||
# on the internet. It's just a protection layer against misuse of the instance.
|
||||
# Still a read only slave exports by default all the administrative commands
|
||||
# such as CONFIG, DEBUG, and so forth. To a limited extend you can improve
|
||||
# security of read only slaves using 'rename-command' to shadow all the
|
||||
# administrative / dangerous commands.
|
||||
slave-read-only yes
|
||||
|
||||
# Slaves send PINGs to server in a predefined interval. It's possible to change
|
||||
# this interval with the repl_ping_slave_period option. The default value is 10
|
||||
# seconds.
|
||||
#
|
||||
# repl-ping-slave-period 10
|
||||
|
||||
# The following option sets a timeout for both Bulk transfer I/O timeout and
|
||||
# master data or ping response timeout. The default value is 60 seconds.
|
||||
#
|
||||
# It is important to make sure that this value is greater than the value
|
||||
# specified for repl-ping-slave-period otherwise a timeout will be detected
|
||||
# every time there is low traffic between the master and the slave.
|
||||
#
|
||||
# repl-timeout 60
|
||||
|
||||
# Disable TCP_NODELAY on the slave socket after SYNC?
|
||||
#
|
||||
# If you select "yes" Redis will use a smaller number of TCP packets and
|
||||
# less bandwidth to send data to slaves. But this can add a delay for
|
||||
# the data to appear on the slave side, up to 40 milliseconds with
|
||||
# Linux kernels using a default configuration.
|
||||
#
|
||||
# If you select "no" the delay for data to appear on the slave side will
|
||||
# be reduced but more bandwidth will be used for replication.
|
||||
#
|
||||
# By default we optimize for low latency, but in very high traffic conditions
|
||||
# or when the master and slaves are many hops away, turning this to "yes" may
|
||||
# be a good idea.
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# The slave priority is an integer number published by Redis in the INFO output.
|
||||
# It is used by Redis Sentinel in order to select a slave to promote into a
|
||||
# master if the master is no longer working correctly.
|
||||
#
|
||||
# A slave with a low priority number is considered better for promotion, so
|
||||
# for instance if there are three slaves with priority 10, 100, 25 Sentinel will
|
||||
# pick the one wtih priority 10, that is the lowest.
|
||||
#
|
||||
# However a special priority of 0 marks the slave as not able to perform the
|
||||
# role of master, so a slave with priority of 0 will never be selected by
|
||||
# Redis Sentinel for promotion.
|
||||
#
|
||||
# By default the priority is 100.
|
||||
slave-priority 100
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Require clients to issue AUTH <PASSWORD> before processing any other
|
||||
# commands. This might be useful in environments in which you do not trust
|
||||
# others with access to the host running redis-server.
|
||||
#
|
||||
# This should stay commented out for backward compatibility and because most
|
||||
# people do not need auth (e.g. they run their own servers).
|
||||
#
|
||||
# Warning: since Redis is pretty fast an outside user can try up to
|
||||
# 150k passwords per second against a good box. This means that you should
|
||||
# use a very strong password otherwise it will be very easy to break.
|
||||
#
|
||||
# requirepass foobared
|
||||
|
||||
# Command renaming.
|
||||
#
|
||||
# It is possible to change the name of dangerous commands in a shared
|
||||
# environment. For instance the CONFIG command may be renamed into something
|
||||
# hard to guess so that it will still be available for internal-use tools
|
||||
# but not available for general clients.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
|
||||
#
|
||||
# It is also possible to completely kill a command by renaming it into
|
||||
# an empty string:
|
||||
#
|
||||
# rename-command CONFIG ""
|
||||
#
|
||||
# Please note that changing the name of commands that are logged into the
|
||||
# AOF file or transmitted to slaves may cause problems.
|
||||
|
||||
################################### LIMITS ####################################
|
||||
|
||||
# Set the max number of connected clients at the same time. By default
|
||||
# this limit is set to 10000 clients, however if the Redis server is not
|
||||
# able to configure the process file limit to allow for the specified limit
|
||||
# the max number of allowed clients is set to the current file limit
|
||||
# minus 32 (as Redis reserves a few file descriptors for internal uses).
|
||||
#
|
||||
# Once the limit is reached Redis will close all the new connections sending
|
||||
# an error 'max number of clients reached'.
|
||||
#
|
||||
# maxclients 10000
|
||||
|
||||
# The Linux version of Redis relies on the system call fork() to perform
|
||||
# point-in-time snapshots of the heap. In addition to the AOF and RDB backup
|
||||
# mechanism, the master-slave synchronization and clustering features are
|
||||
# dependent on this behavior of fork(). In order for the Windows version to
|
||||
# perform like the Linux version we had to simulate this aspect of fork().
|
||||
# Doing so meant moving the Redis heap into a memory mapped file that can
|
||||
# be shared with a child process.
|
||||
#
|
||||
# *** There must be disk space available for this file in order for Redis
|
||||
# to launch. ***
|
||||
#
|
||||
# The maxheap flag controls the maximum size of this memory mapped file,
|
||||
# as well as the total usable space for the Redis heap. Running Redis
|
||||
# without either maxheap or maxmemory will result in a memory mapped file
|
||||
# being created that is equal to the size of physical memory. During
|
||||
# fork() operations the total page file commit will max out at around:
|
||||
#
|
||||
# (size of physical memory) + (2 * size of maxheap)
|
||||
#
|
||||
# For instance, on a machine with 8GB of physical RAM, the max page file
|
||||
# commit with the default maxheap size will be (8)+(2*8) GB , or 24GB. The
|
||||
# default page file sizing of Windows will allow for this without having
|
||||
# to reconfigure the system. Larger heap sizes are possible, but the maximum
|
||||
# page file size will have to be increased accordingly.
|
||||
#
|
||||
# The Redis heap must be larger than the value specified by the maxmemory
|
||||
# flag, as the heap allocator has its own memory requirements and
|
||||
# fragmentation of the heap is inevitable. If only the maxmemory flag is
|
||||
# specified, maxheap will be set at 1.5*maxmemory. If the maxheap flag is
|
||||
# specified along with maxmemory, the maxheap flag will be automatically
|
||||
# increased if it is smaller than 1.5*maxmemory.
|
||||
#
|
||||
# maxheap <bytes>
|
||||
|
||||
# Don't use more memory than the specified amount of bytes.
|
||||
# When the memory limit is reached Redis will try to remove keys
|
||||
# accordingly to the eviction policy selected (see maxmemmory-policy).
|
||||
#
|
||||
# If Redis can't remove keys according to the policy, or if the policy is
|
||||
# set to 'noeviction', Redis will start to reply with errors to commands
|
||||
# that would use more memory, like SET, LPUSH, and so on, and will continue
|
||||
# to reply to read-only commands like GET.
|
||||
#
|
||||
# This option is usually useful when using Redis as an LRU cache, or to set
|
||||
# an hard memory limit for an instance (using the 'noeviction' policy).
|
||||
#
|
||||
# WARNING: If you have slaves attached to an instance with maxmemory on,
|
||||
# the size of the output buffers needed to feed the slaves are subtracted
|
||||
# from the used memory count, so that network problems / resyncs will
|
||||
# not trigger a loop where keys are evicted, and in turn the output
|
||||
# buffer of slaves is full with DELs of keys evicted triggering the deletion
|
||||
# of more keys, and so forth until the database is completely emptied.
|
||||
#
|
||||
# In short... if you have slaves attached it is suggested that you set a lower
|
||||
# limit for maxmemory so that there is some free RAM on the system for slave
|
||||
# output buffers (but this is not needed if the policy is 'noeviction').
|
||||
#
|
||||
# maxmemory <bytes>
|
||||
|
||||
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
||||
# is reached. You can select among five behaviors:
|
||||
#
|
||||
# volatile-lru -> remove the key with an expire set using an LRU algorithm
|
||||
# allkeys-lru -> remove any key accordingly to the LRU algorithm
|
||||
# volatile-random -> remove a random key with an expire set
|
||||
# allkeys-random -> remove a random key, any key
|
||||
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
|
||||
# noeviction -> don't expire at all, just return an error on write operations
|
||||
#
|
||||
# Note: with any of the above policies, Redis will return an error on write
|
||||
# operations, when there are not suitable keys for eviction.
|
||||
#
|
||||
# At the date of writing this commands are: set setnx setex append
|
||||
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
|
||||
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
|
||||
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
|
||||
# getset mset msetnx exec sort
|
||||
#
|
||||
# The default is:
|
||||
#
|
||||
# maxmemory-policy volatile-lru
|
||||
|
||||
# LRU and minimal TTL algorithms are not precise algorithms but approximated
|
||||
# algorithms (in order to save memory), so you can select as well the sample
|
||||
# size to check. For instance for default Redis will check three keys and
|
||||
# pick the one that was used less recently, you can change the sample size
|
||||
# using the following configuration directive.
|
||||
#
|
||||
# maxmemory-samples 3
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
# By default Redis asynchronously dumps the dataset on disk. This mode is
|
||||
# good enough in many applications, but an issue with the Redis process or
|
||||
# a power outage may result into a few minutes of writes lost (depending on
|
||||
# the configured save points).
|
||||
#
|
||||
# The Append Only File is an alternative persistence mode that provides
|
||||
# much better durability. For instance using the default data fsync policy
|
||||
# (see later in the config file) Redis can lose just one second of writes in a
|
||||
# dramatic event like a server power outage, or a single write if something
|
||||
# wrong with the Redis process itself happens, but the operating system is
|
||||
# still running correctly.
|
||||
#
|
||||
# AOF and RDB persistence can be enabled at the same time without problems.
|
||||
# If the AOF is enabled on startup Redis will load the AOF, that is the file
|
||||
# with the better durability guarantees.
|
||||
#
|
||||
# Please check http://redis.io/topics/persistence for more information.
|
||||
|
||||
appendonly no
|
||||
|
||||
# The name of the append only file (default: "appendonly.aof")
|
||||
# appendfilename appendonly.aof
|
||||
|
||||
# The fsync() call tells the Operating System to actually write data on disk
|
||||
# instead to wait for more data in the output buffer. Some OS will really flush
|
||||
# data on disk, some other OS will just try to do it ASAP.
|
||||
#
|
||||
# Redis supports three different modes:
|
||||
#
|
||||
# no: don't fsync, just let the OS flush the data when it wants. Faster.
|
||||
# always: fsync after every write to the append only log . Slow, Safest.
|
||||
# everysec: fsync only one time every second. Compromise.
|
||||
#
|
||||
# The default is "everysec", as that's usually the right compromise between
|
||||
# speed and data safety. It's up to you to understand if you can relax this to
|
||||
# "no" that will let the operating system flush the output buffer when
|
||||
# it wants, for better performances (but if you can live with the idea of
|
||||
# some data loss consider the default persistence mode that's snapshotting),
|
||||
# or on the contrary, use "always" that's very slow but a bit safer than
|
||||
# everysec.
|
||||
#
|
||||
# More details please check the following article:
|
||||
# http://antirez.com/post/redis-persistence-demystified.html
|
||||
#
|
||||
# If unsure, use "everysec".
|
||||
|
||||
# appendfsync always
|
||||
appendfsync everysec
|
||||
# appendfsync no
|
||||
|
||||
# When the AOF fsync policy is set to always or everysec, and a background
|
||||
# saving process (a background save or AOF log background rewriting) is
|
||||
# performing a lot of I/O against the disk, in some Linux configurations
|
||||
# Redis may block too long on the fsync() call. Note that there is no fix for
|
||||
# this currently, as even performing fsync in a different thread will block
|
||||
# our synchronous write(2) call.
|
||||
#
|
||||
# In order to mitigate this problem it's possible to use the following option
|
||||
# that will prevent fsync() from being called in the main process while a
|
||||
# BGSAVE or BGREWRITEAOF is in progress.
|
||||
#
|
||||
# This means that while another child is saving, the durability of Redis is
|
||||
# the same as "appendfsync none". In practical terms, this means that it is
|
||||
# possible to lose up to 30 seconds of log in the worst scenario (with the
|
||||
# default Linux settings).
|
||||
#
|
||||
# If you have latency problems turn this to "yes". Otherwise leave it as
|
||||
# "no" that is the safest pick from the point of view of durability.
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
# Automatic rewrite of the append only file.
|
||||
# Redis is able to automatically rewrite the log file implicitly calling
|
||||
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
|
||||
#
|
||||
# This is how it works: Redis remembers the size of the AOF file after the
|
||||
# latest rewrite (if no rewrite has happened since the restart, the size of
|
||||
# the AOF at startup is used).
|
||||
#
|
||||
# This base size is compared to the current size. If the current size is
|
||||
# bigger than the specified percentage, the rewrite is triggered. Also
|
||||
# you need to specify a minimal size for the AOF file to be rewritten, this
|
||||
# is useful to avoid rewriting the AOF file even if the percentage increase
|
||||
# is reached but it is still pretty small.
|
||||
#
|
||||
# Specify a percentage of zero in order to disable the automatic AOF
|
||||
# rewrite feature.
|
||||
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
################################ LUA SCRIPTING ###############################
|
||||
|
||||
# Max execution time of a Lua script in milliseconds.
|
||||
#
|
||||
# If the maximum execution time is reached Redis will log that a script is
|
||||
# still in execution after the maximum allowed time and will start to
|
||||
# reply to queries with an error.
|
||||
#
|
||||
# When a long running script exceed the maximum execution time only the
|
||||
# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
|
||||
# used to stop a script that did not yet called write commands. The second
|
||||
# is the only way to shut down the server in the case a write commands was
|
||||
# already issue by the script but the user don't want to wait for the natural
|
||||
# termination of the script.
|
||||
#
|
||||
# Set it to 0 or a negative value for unlimited execution without warnings.
|
||||
lua-time-limit 5000
|
||||
|
||||
################################## SLOW LOG ###################################
|
||||
|
||||
# The Redis Slow Log is a system to log queries that exceeded a specified
|
||||
# execution time. The execution time does not include the I/O operations
|
||||
# like talking with the client, sending the reply and so forth,
|
||||
# but just the time needed to actually execute the command (this is the only
|
||||
# stage of command execution where the thread is blocked and can not serve
|
||||
# other requests in the meantime).
|
||||
#
|
||||
# You can configure the slow log with two parameters: one tells Redis
|
||||
# what is the execution time, in microseconds, to exceed in order for the
|
||||
# command to get logged, and the other parameter is the length of the
|
||||
# slow log. When a new command is logged the oldest one is removed from the
|
||||
# queue of logged commands.
|
||||
|
||||
# The following time is expressed in microseconds, so 1000000 is equivalent
|
||||
# to one second. Note that a negative number disables the slow log, while
|
||||
# a value of zero forces the logging of every command.
|
||||
slowlog-log-slower-than 10000
|
||||
|
||||
# There is no limit to this length. Just be aware that it will consume memory.
|
||||
# You can reclaim memory used by the slow log with SLOWLOG RESET.
|
||||
slowlog-max-len 128
|
||||
|
||||
############################### ADVANCED CONFIG ###############################
|
||||
|
||||
# Hashes are encoded using a memory efficient data structure when they have a
|
||||
# small number of entries, and the biggest entry does not exceed a given
|
||||
# threshold. These thresholds can be configured using the following directives.
|
||||
hash-max-ziplist-entries 512
|
||||
hash-max-ziplist-value 64
|
||||
|
||||
# Similarly to hashes, small lists are also encoded in a special way in order
|
||||
# to save a lot of space. The special representation is only used when
|
||||
# you are under the following limits:
|
||||
list-max-ziplist-entries 512
|
||||
list-max-ziplist-value 64
|
||||
|
||||
# Sets have a special encoding in just one case: when a set is composed
|
||||
# of just strings that happens to be integers in radix 10 in the range
|
||||
# of 64 bit signed integers.
|
||||
# The following configuration setting sets the limit in the size of the
|
||||
# set in order to use this special memory saving encoding.
|
||||
set-max-intset-entries 512
|
||||
|
||||
# Similarly to hashes and lists, sorted sets are also specially encoded in
|
||||
# order to save a lot of space. This encoding is only used when the length and
|
||||
# elements of a sorted set are below the following limits:
|
||||
zset-max-ziplist-entries 128
|
||||
zset-max-ziplist-value 64
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation Redis uses (see dict.c)
|
||||
# performs a lazy rehashing: the more operation you run into an hash table
|
||||
# that is rehashing, the more rehashing "steps" are performed, so if the
|
||||
# server is idle the rehashing is never complete and some more memory is used
|
||||
# by the hash table.
|
||||
#
|
||||
# The default is to use this millisecond 10 times every second in order to
|
||||
# active rehashing the main dictionaries, freeing memory when possible.
|
||||
#
|
||||
# If unsure:
|
||||
# use "activerehashing no" if you have hard latency requirements and it is
|
||||
# not a good thing in your environment that Redis can reply form time to time
|
||||
# to queries with 2 milliseconds delay.
|
||||
#
|
||||
# use "activerehashing yes" if you don't have such hard requirements but
|
||||
# want to free memory asap when possible.
|
||||
activerehashing yes
|
||||
|
||||
# The client output buffer limits can be used to force disconnection of clients
|
||||
# that are not reading data from the server fast enough for some reason (a
|
||||
# common reason is that a Pub/Sub client can't consume messages as fast as the
|
||||
# publisher can produce them).
|
||||
#
|
||||
# The limit can be set differently for the three different classes of clients:
|
||||
#
|
||||
# normal -> normal clients
|
||||
# slave -> slave clients and MONITOR clients
|
||||
# pubsub -> clients subcribed to at least one pubsub channel or pattern
|
||||
#
|
||||
# The syntax of every client-output-buffer-limit directive is the following:
|
||||
#
|
||||
# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
|
||||
#
|
||||
# A client is immediately disconnected once the hard limit is reached, or if
|
||||
# the soft limit is reached and remains reached for the specified number of
|
||||
# seconds (continuously).
|
||||
# So for instance if the hard limit is 32 megabytes and the soft limit is
|
||||
# 16 megabytes / 10 seconds, the client will get disconnected immediately
|
||||
# if the size of the output buffers reach 32 megabytes, but will also get
|
||||
# disconnected if the client reaches 16 megabytes and continuously overcomes
|
||||
# the limit for 10 seconds.
|
||||
#
|
||||
# By default normal clients are not limited because they don't receive data
|
||||
# without asking (in a push way), but just after a request, so only
|
||||
# asynchronous clients may create a scenario where data is requested faster
|
||||
# than it can read.
|
||||
#
|
||||
# Instead there is a default limit for pubsub and slave clients, since
|
||||
# subscribers and slaves receive data in a push fashion.
|
||||
#
|
||||
# Both the hard or the soft limit can be disabled by setting them to zero.
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit slave 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
|
||||
# Redis calls an internal function to perform many background tasks, like
|
||||
# closing connections of clients in timeot, purging expired keys that are
|
||||
# never requested, and so forth.
|
||||
#
|
||||
# Not all tasks are perforemd with the same frequency, but Redis checks for
|
||||
# tasks to perform accordingly to the specified "hz" value.
|
||||
#
|
||||
# By default "hz" is set to 10. Raising the value will use more CPU when
|
||||
# Redis is idle, but at the same time will make Redis more responsive when
|
||||
# there are many keys expiring at the same time, and timeouts may be
|
||||
# handled with more precision.
|
||||
#
|
||||
# The range is between 1 and 500, however a value over 100 is usually not
|
||||
# a good idea. Most users should use the default of 10 and raise this up to
|
||||
# 100 only in environments where very low latency is required.
|
||||
hz 10
|
||||
|
||||
# When a child rewrites the AOF file, if the following option is enabled
|
||||
# the file will be fsync-ed every 32 MB of data generated. This is useful
|
||||
# in order to commit the file to the disk more incrementally and avoid
|
||||
# big latency spikes.
|
||||
aof-rewrite-incremental-fsync yes
|
||||
|
||||
################################## INCLUDES ###################################
|
||||
|
||||
# Include one or more other config files here. This is useful if you
|
||||
# have a standard template that goes to all Redis server but also need
|
||||
# to customize a few per-server settings. Include files can include
|
||||
# other files, so use this wisely.
|
||||
#
|
||||
# include /path/to/local.conf
|
||||
# include /path/to/other.conf
|
||||
@@ -34,8 +34,17 @@
|
||||
#include "Win32_SmartHandle.h"
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <stdint.h>
|
||||
using namespace std;
|
||||
|
||||
extern "C"
|
||||
{
|
||||
// forward def from util.h.
|
||||
long long memtoll(const char *p, int *err);
|
||||
}
|
||||
|
||||
//#define DEBUG_WITH_PROCMON
|
||||
#ifdef DEBUG_WITH_PROCMON
|
||||
#define FILE_DEVICE_PROCMON_LOG 0x00009535
|
||||
@@ -117,18 +126,16 @@ How the parent invokes the QFork process:
|
||||
#define HIDWORD(_qw) ((DWORD)(((_qw) >> (sizeof(DWORD)*8)) & DWORD(~0)))
|
||||
#endif
|
||||
|
||||
const SIZE_T cAllocationGranularity = 1 << 26; // 64MB per dlmalloc heap block
|
||||
const int cMaxBlocks = 1 << 16; // 64KB*64K sections = 4TB. 4TB is the largest memory config Windows supports at present.
|
||||
const SIZE_T cSystemReserve = 3 * 1024i64 * 1024i64 * 1024i64; // Reserve left for Windows to operate on when we are heavily loaded.
|
||||
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* qforkFlag = "--QFork";
|
||||
const char* maxmemoryFlag = "maxmemory";
|
||||
const char* maxheapFlag = "maxheap";
|
||||
const int cDeadForkWait = 30000;
|
||||
size_t pageSize = 0;
|
||||
|
||||
typedef enum BlockState {
|
||||
bsINVALID = 0,
|
||||
bsUNMAPPED = 1,
|
||||
bsMAPPED = 2
|
||||
}BlockState;
|
||||
enum class BlockState : std::uint8_t {bsINVALID = 0, bsUNMAPPED = 1, bsMAPPED = 2};
|
||||
|
||||
struct QForkControl {
|
||||
HANDLE heapMemoryMapFile;
|
||||
@@ -154,10 +161,11 @@ struct QForkControl {
|
||||
QForkControl* g_pQForkControl;
|
||||
HANDLE g_hQForkControlFileMap;
|
||||
HANDLE g_hForkedProcess;
|
||||
DWORD g_systemAllocationGranularity;
|
||||
|
||||
BOOL QForkSlaveInit(HANDLE QForkConrolMemoryMapHandle, DWORD ParentProcessID) {
|
||||
try {
|
||||
SmartHandle shParent(
|
||||
SmartHandle shParent(
|
||||
OpenProcess(SYNCHRONIZE | PROCESS_DUP_HANDLE, TRUE, ParentProcessID),
|
||||
string("Could not open parent process"));
|
||||
|
||||
@@ -188,19 +196,22 @@ BOOL QForkSlaveInit(HANDLE QForkConrolMemoryMapHandle, DWORD ParentProcessID) {
|
||||
g_pQForkControl->heapMemoryMapFile,
|
||||
PAGE_WRITECOPY,
|
||||
HIDWORD(mmSize), LODWORD(mmSize),
|
||||
string("QForkSlaveInit: Could not open file mapping object in slave"));
|
||||
string("Could not open file mapping object in slave"));
|
||||
g_pQForkControl->heapMemoryMap = sfmhMapFile;
|
||||
|
||||
SmartFileView<byte> sfvHeap(
|
||||
|
||||
// The key to mapping a heap larger than physical memory is to not map it all at once.
|
||||
SmartFileView<byte> sfvHeap(
|
||||
g_pQForkControl->heapMemoryMap,
|
||||
FILE_MAP_COPY,
|
||||
0, 0, 0,
|
||||
0, 0,
|
||||
cAllocationGranularity, // Only map a portion of the heap . Deal with the unmapped pages with a VEH.
|
||||
g_pQForkControl->heapStart,
|
||||
string("QForkSlaveInit: Could not map heap in forked process. Is system swap file large enough?"));
|
||||
string("Could not map heap in forked process. Is system swap file large enough?"));
|
||||
|
||||
// setup DLMalloc global data
|
||||
if( SetDLMallocGlobalState(g_pQForkControl->DLMallocGlobalStateSize, g_pQForkControl->DLMallocGlobalState) != 0) {
|
||||
throw std::runtime_error("QForkSlaveInit: DLMalloc global state copy failed.");
|
||||
throw std::runtime_error("DLMalloc global state copy failed.");
|
||||
}
|
||||
|
||||
// signal parent that we are ready
|
||||
@@ -212,7 +223,7 @@ BOOL QForkSlaveInit(HANDLE QForkConrolMemoryMapHandle, DWORD ParentProcessID) {
|
||||
// copy redis globals into fork process
|
||||
SetupGlobals(g_pQForkControl->globalData.globalData, g_pQForkControl->globalData.globalDataSize, g_pQForkControl->globalData.dictHashSeed);
|
||||
|
||||
// execute requiested operation
|
||||
// execute requested operation
|
||||
if (g_pQForkControl->typeOfOperation == OperationType::otRDB) {
|
||||
do_rdbSave(g_pQForkControl->globalData.filename);
|
||||
} else if (g_pQForkControl->typeOfOperation == OperationType::otAOF) {
|
||||
@@ -221,7 +232,7 @@ BOOL QForkSlaveInit(HANDLE QForkConrolMemoryMapHandle, DWORD ParentProcessID) {
|
||||
throw runtime_error("unexpected operation type");
|
||||
}
|
||||
|
||||
// let parent know weare done
|
||||
// let parent know we are done
|
||||
SetEvent(g_pQForkControl->operationComplete);
|
||||
|
||||
// parent will notify us when to quit
|
||||
@@ -231,6 +242,7 @@ BOOL QForkSlaveInit(HANDLE QForkConrolMemoryMapHandle, DWORD ParentProcessID) {
|
||||
return TRUE;
|
||||
}
|
||||
catch(std::system_error syserr) {
|
||||
printf("QForkSlaveInit: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what());
|
||||
g_pQForkControl = NULL;
|
||||
if(g_pQForkControl != NULL) {
|
||||
if(g_pQForkControl->operationFailed != NULL) {
|
||||
@@ -240,6 +252,7 @@ BOOL QForkSlaveInit(HANDLE QForkConrolMemoryMapHandle, DWORD ParentProcessID) {
|
||||
return FALSE;
|
||||
}
|
||||
catch(std::runtime_error runerr) {
|
||||
printf("QForkSlaveInit: runtime error caught. message=%s\n", runerr.what());
|
||||
g_pQForkControl = NULL;
|
||||
SetEvent(g_pQForkControl->operationFailed);
|
||||
return FALSE;
|
||||
@@ -247,177 +260,247 @@ BOOL QForkSlaveInit(HANDLE QForkConrolMemoryMapHandle, DWORD ParentProcessID) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
BOOL QForkMasterInit() {
|
||||
// allocate file map for qfork control so it can be passed to the forked process
|
||||
g_hQForkControlFileMap = CreateFileMappingW(
|
||||
INVALID_HANDLE_VALUE,
|
||||
NULL,
|
||||
PAGE_READWRITE,
|
||||
0, sizeof(QForkControl),
|
||||
NULL);
|
||||
if (g_hQForkControlFileMap == NULL) {
|
||||
printf( "Problem with CreateFileMapping\n");
|
||||
errno = EBADF;
|
||||
goto err;
|
||||
}
|
||||
|
||||
g_pQForkControl = (QForkControl*)MapViewOfFile(
|
||||
g_hQForkControlFileMap,
|
||||
FILE_MAP_ALL_ACCESS,
|
||||
0, 0,
|
||||
0);
|
||||
if (g_pQForkControl == NULL) {
|
||||
printf( "Problem with MapViewOfFile\n");
|
||||
errno = ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
// This must be called only once per process! Calling it more times than that will not recreate existing
|
||||
// section, and dlmalloc will ultimately fail with an access violation. Once is good.
|
||||
if (dlmallopt(M_GRANULARITY, cAllocationGranularity) == 0) {
|
||||
printf( "DLMalloc failed initializing allocation granularity.\n");
|
||||
errno = ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
g_pQForkControl->heapBlockSize = cAllocationGranularity;
|
||||
|
||||
// determine the number of blocks we can allocate
|
||||
MEMORYSTATUSEX ms;
|
||||
ms.dwLength = sizeof(MEMORYSTATUSEX);
|
||||
GlobalMemoryStatusEx(&ms);
|
||||
SIZE_T maxPhysicalMapping = ms.ullTotalPhys - cSystemReserve;
|
||||
g_pQForkControl->availableBlocksInHeap = (int)(maxPhysicalMapping / cAllocationGranularity);
|
||||
if (g_pQForkControl->availableBlocksInHeap <= 0) {
|
||||
printf( "Not enough physical memory to initialize Redis. Physical memory must be greater than 3GB.\n");
|
||||
errno = ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
wchar_t heapMemoryMapPath[MAX_PATH];
|
||||
swprintf_s(
|
||||
heapMemoryMapPath,
|
||||
MAX_PATH,
|
||||
L"%s_%d.dat",
|
||||
cMapFileBaseName,
|
||||
GetCurrentProcessId());
|
||||
|
||||
g_pQForkControl->heapMemoryMapFile =
|
||||
CreateFileW(
|
||||
heapMemoryMapPath,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
NULL,
|
||||
CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL| FILE_FLAG_DELETE_ON_CLOSE,
|
||||
NULL );
|
||||
if (g_pQForkControl->heapMemoryMapFile == INVALID_HANDLE_VALUE) {
|
||||
printf( "Problem creating memory mapped file.\n");
|
||||
errno = EBADF;
|
||||
goto err;
|
||||
}
|
||||
|
||||
SIZE_T mmSize = g_pQForkControl->availableBlocksInHeap * cAllocationGranularity;
|
||||
g_pQForkControl->heapMemoryMap =
|
||||
CreateFileMappingW(
|
||||
g_pQForkControl->heapMemoryMapFile,
|
||||
BOOL QForkMasterInit( __int64 maxheapBytes ) {
|
||||
try {
|
||||
// allocate file map for qfork control so it can be passed to the forked process
|
||||
g_hQForkControlFileMap = CreateFileMappingW(
|
||||
INVALID_HANDLE_VALUE,
|
||||
NULL,
|
||||
PAGE_READWRITE,
|
||||
HIDWORD(mmSize),
|
||||
LODWORD(mmSize),
|
||||
0, sizeof(QForkControl),
|
||||
NULL);
|
||||
if (g_pQForkControl->heapMemoryMap == NULL) {
|
||||
printf( "Problem mapping heap.\n");
|
||||
errno = EBADF;
|
||||
goto err;
|
||||
}
|
||||
|
||||
// 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
|
||||
// the forked process and negotiate for a shared memory address here.)
|
||||
LPVOID pHigh = VirtualAllocEx(
|
||||
GetCurrentProcess(),
|
||||
NULL,
|
||||
mmSize,
|
||||
MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
|
||||
PAGE_READWRITE);
|
||||
if (pHigh == NULL) {
|
||||
printf( "Viirtual memory reservation failed.\n");
|
||||
DWORD err = GetLastError();
|
||||
errno = ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
if (VirtualFree(pHigh, 0, MEM_RELEASE) == FALSE) {
|
||||
DWORD err = GetLastError();
|
||||
errno = ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
if (g_hQForkControlFileMap == NULL) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"CreateFileMapping failed");
|
||||
}
|
||||
|
||||
g_pQForkControl->heapStart =
|
||||
MapViewOfFileEx(
|
||||
g_pQForkControl->heapMemoryMap,
|
||||
g_pQForkControl = (QForkControl*)MapViewOfFile(
|
||||
g_hQForkControlFileMap,
|
||||
FILE_MAP_ALL_ACCESS,
|
||||
0,0,
|
||||
0,
|
||||
pHigh);
|
||||
if (g_pQForkControl->heapStart == NULL) {
|
||||
printf( "Mapping view of heap failed.\n");
|
||||
DWORD err = GetLastError();
|
||||
errno = ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
0, 0,
|
||||
0);
|
||||
if (g_pQForkControl == NULL) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"MapViewOfFile failed");
|
||||
}
|
||||
|
||||
for (int n = 0; n < cMaxBlocks; n++) {
|
||||
g_pQForkControl->heapBlockMap[n] =
|
||||
((n < g_pQForkControl->availableBlocksInHeap) ?
|
||||
BlockState::bsUNMAPPED : BlockState::bsINVALID);
|
||||
}
|
||||
// This must be called only once per process! Calling it more times than that will not recreate existing
|
||||
// section, and dlmalloc will ultimately fail with an access violation. Once is good.
|
||||
if (dlmallopt(M_GRANULARITY, cAllocationGranularity) == 0) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"DLMalloc failed initializing allocation granularity.");
|
||||
}
|
||||
g_pQForkControl->heapBlockSize = cAllocationGranularity;
|
||||
|
||||
g_pQForkControl->typeOfOperation = OperationType::otINVALID;
|
||||
g_pQForkControl->forkedProcessReady = CreateEvent(NULL,TRUE,FALSE,NULL);
|
||||
if (g_pQForkControl->forkedProcessReady == NULL) {
|
||||
errno = EBADF;
|
||||
goto err;
|
||||
}
|
||||
g_pQForkControl->startOperation = CreateEvent(NULL,TRUE,FALSE,NULL);
|
||||
if (g_pQForkControl->startOperation == NULL) {
|
||||
errno = EBADF;
|
||||
goto err;
|
||||
}
|
||||
g_pQForkControl->operationComplete = CreateEvent(NULL,TRUE,FALSE,NULL);
|
||||
if (g_pQForkControl->operationComplete == NULL) {
|
||||
errno = EBADF;
|
||||
goto err;
|
||||
}
|
||||
g_pQForkControl->operationFailed = CreateEvent(NULL,TRUE,FALSE,NULL);
|
||||
if (g_pQForkControl->operationFailed == NULL) {
|
||||
errno = EBADF;
|
||||
goto err;
|
||||
}
|
||||
g_pQForkControl->terminateForkedProcess = CreateEvent(NULL,TRUE,FALSE,NULL);
|
||||
if (g_pQForkControl->terminateForkedProcess == NULL) {
|
||||
errno = EBADF;
|
||||
goto err;
|
||||
}
|
||||
// ensure the number of blocks is a multiple of cAllocationGranularity
|
||||
SIZE_T allocationBlocks = maxheapBytes / cAllocationGranularity;
|
||||
allocationBlocks += ((maxheapBytes % cAllocationGranularity) != 0);
|
||||
|
||||
return TRUE;
|
||||
g_pQForkControl->availableBlocksInHeap = (int)allocationBlocks;
|
||||
if (g_pQForkControl->availableBlocksInHeap <= 0) {
|
||||
throw std::runtime_error(
|
||||
"Invalid number of heap blocks.");
|
||||
}
|
||||
|
||||
err:
|
||||
printf( "Error ocurred initializing Redis.");
|
||||
if( GetLastError() != 0 ) {
|
||||
printf( " GetLastError() returns 0x%08x", GetLastError());
|
||||
wchar_t heapMemoryMapPath[MAX_PATH];
|
||||
swprintf_s(
|
||||
heapMemoryMapPath,
|
||||
MAX_PATH,
|
||||
L"%s_%d.dat",
|
||||
cMapFileBaseName,
|
||||
GetCurrentProcessId());
|
||||
|
||||
g_pQForkControl->heapMemoryMapFile =
|
||||
CreateFileW(
|
||||
heapMemoryMapPath,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
NULL,
|
||||
CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL| FILE_FLAG_DELETE_ON_CLOSE,
|
||||
NULL );
|
||||
if (g_pQForkControl->heapMemoryMapFile == INVALID_HANDLE_VALUE) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"CreateFileW failed.");
|
||||
}
|
||||
|
||||
// There is a strange random failure toawrds the end of mapping the heap in the forked process in the VEH if
|
||||
// the underlying MMF is not larger than the MM space we are using. This seems to be some sort of
|
||||
// memory->file allocation granularity issue. Increasing the size of the file (by 16MB) takes care of the
|
||||
// issue in all cases.
|
||||
const size_t extraMMF = 64 * cAllocationGranularity;
|
||||
|
||||
SIZE_T mmSize = g_pQForkControl->availableBlocksInHeap * cAllocationGranularity + extraMMF;
|
||||
g_pQForkControl->heapMemoryMap =
|
||||
CreateFileMappingW(
|
||||
g_pQForkControl->heapMemoryMapFile,
|
||||
NULL,
|
||||
PAGE_READWRITE,
|
||||
HIDWORD(mmSize),
|
||||
LODWORD(mmSize),
|
||||
NULL);
|
||||
if (g_pQForkControl->heapMemoryMap == NULL) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"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
|
||||
// the forked process and negotiate for a shared memory address here.)
|
||||
LPVOID pHigh = VirtualAllocEx(
|
||||
GetCurrentProcess(),
|
||||
NULL,
|
||||
mmSize,
|
||||
MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
|
||||
PAGE_READWRITE);
|
||||
if (pHigh == NULL) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"VirtualAllocEx failed.");
|
||||
}
|
||||
if (VirtualFree(pHigh, 0, MEM_RELEASE) == FALSE) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"VirtualFree failed.");
|
||||
}
|
||||
|
||||
g_pQForkControl->heapStart =
|
||||
MapViewOfFileEx(
|
||||
g_pQForkControl->heapMemoryMap,
|
||||
FILE_MAP_ALL_ACCESS,
|
||||
0,0,
|
||||
0,
|
||||
pHigh);
|
||||
if (g_pQForkControl->heapStart == NULL) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"MapViewOfFileEx failed.");
|
||||
}
|
||||
|
||||
for (int n = 0; n < cMaxBlocks; n++) {
|
||||
g_pQForkControl->heapBlockMap[n] =
|
||||
((n < g_pQForkControl->availableBlocksInHeap) ?
|
||||
BlockState::bsUNMAPPED : BlockState::bsINVALID);
|
||||
}
|
||||
|
||||
g_pQForkControl->typeOfOperation = OperationType::otINVALID;
|
||||
g_pQForkControl->forkedProcessReady = CreateEvent(NULL,TRUE,FALSE,NULL);
|
||||
if (g_pQForkControl->forkedProcessReady == NULL) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"CreateEvent failed.");
|
||||
}
|
||||
g_pQForkControl->startOperation = CreateEvent(NULL,TRUE,FALSE,NULL);
|
||||
if (g_pQForkControl->startOperation == NULL) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"CreateEvent failed.");
|
||||
}
|
||||
g_pQForkControl->operationComplete = CreateEvent(NULL,TRUE,FALSE,NULL);
|
||||
if (g_pQForkControl->operationComplete == NULL) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"CreateEvent failed.");
|
||||
}
|
||||
g_pQForkControl->operationFailed = CreateEvent(NULL,TRUE,FALSE,NULL);
|
||||
if (g_pQForkControl->operationFailed == NULL) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"CreateEvent failed.");
|
||||
}
|
||||
g_pQForkControl->terminateForkedProcess = CreateEvent(NULL,TRUE,FALSE,NULL);
|
||||
if (g_pQForkControl->terminateForkedProcess == NULL) {
|
||||
throw std::system_error(
|
||||
GetLastError(),
|
||||
system_category(),
|
||||
"CreateEvent failed.");
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
catch(std::system_error syserr) {
|
||||
printf("QForkMasterInit: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what());
|
||||
}
|
||||
catch(std::runtime_error runerr) {
|
||||
printf("QForkMasterInit: runtime error caught. message=%s\n", runerr.what());
|
||||
}
|
||||
catch(...) {
|
||||
printf("QForkMasterInit: other exception caught.\n");
|
||||
}
|
||||
printf( "\n");
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
LONG CALLBACK VectoredHeapMapper(PEXCEPTION_POINTERS info) {
|
||||
if( info->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION &&
|
||||
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 * g_pQForkControl->heapBlockSize);
|
||||
if( failingMemoryAddress >= heapStart && failingMemoryAddress <= heapEnd )
|
||||
{
|
||||
intptr_t startOfMapping = failingMemoryAddress - failingMemoryAddress % g_systemAllocationGranularity;
|
||||
intptr_t mmfOffset = startOfMapping - heapStart;
|
||||
size_t bytesToMap = min( g_systemAllocationGranularity, heapEnd - startOfMapping);
|
||||
LPVOID pMapped = MapViewOfFileEx(
|
||||
g_pQForkControl->heapMemoryMap,
|
||||
FILE_MAP_COPY,
|
||||
HIDWORD(mmfOffset),
|
||||
LODWORD(mmfOffset),
|
||||
bytesToMap,
|
||||
(LPVOID)startOfMapping);
|
||||
if(pMapped != NULL)
|
||||
{
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\nF(0x%p)", startOfMapping);
|
||||
printf( "\t MapViewOfFileEx failed with error 0x%08X. \n", GetLastError() );
|
||||
printf( "\t heapStart 0x%p\n", heapStart);
|
||||
printf( "\t heapEnd 0x%p\n", heapEnd);
|
||||
printf( "\t failing access location 0x%p\n", failingMemoryAddress);
|
||||
printf( "\t offset into mmf to start mapping 0x%016X\n", mmfOffset);
|
||||
printf( "\t start of new mapping 0x%p \n", startOfMapping);
|
||||
printf( "\t bytes to map 0x%08x \n", bytesToMap);
|
||||
printf( "\t continuing exception handler search \n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
|
||||
// QFork API
|
||||
StartupStatus QForkStartup(int argc, char** argv) {
|
||||
bool foundSlaveFlag = false;
|
||||
HANDLE QForkConrolMemoryMapHandle = NULL;
|
||||
DWORD PPID = 0;
|
||||
__int64 maxheapBytes = -1;
|
||||
__int64 maxmemoryBytes = -1;
|
||||
int memtollerr = 0;
|
||||
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
g_systemAllocationGranularity = si.dwAllocationGranularity;
|
||||
|
||||
if ((argc == 3) && (strcmp(argv[0], qforkFlag) == 0)) {
|
||||
// slave command line looks like: --QFork [QForkConrolMemoryMap handle] [parent process id]
|
||||
foundSlaveFlag = true;
|
||||
@@ -425,18 +508,145 @@ StartupStatus QForkStartup(int argc, char** argv) {
|
||||
QForkConrolMemoryMapHandle = (HANDLE)strtoul(argv[1],&endPtr,10);
|
||||
char* end = NULL;
|
||||
PPID = strtoul(argv[2], &end, 10);
|
||||
} else {
|
||||
for (int n = 1; n < argc; n++) {
|
||||
// check for flags in .conf file
|
||||
if( n == 1 && strncmp(argv[n],"--",2) != 0 ) {
|
||||
ifstream config;
|
||||
config.open(argv[n]);
|
||||
if (config.fail())
|
||||
continue;
|
||||
while (!config.eof()) {
|
||||
string line;
|
||||
getline(config,line);
|
||||
istringstream iss(line);
|
||||
string token;
|
||||
if (getline(iss, token, ' ')) {
|
||||
if (_stricmp(token.c_str(), maxmemoryFlag) == 0) {
|
||||
string maxmemoryString;
|
||||
if (getline(iss, maxmemoryString, ' ')) {
|
||||
maxmemoryBytes = memtoll(maxmemoryString.c_str(),&memtollerr);
|
||||
if( memtollerr != 0) {
|
||||
printf (
|
||||
"%s specified. Unable to convert %s to the number of bytes for the maxmemory flag.\n",
|
||||
maxmemoryBytes,
|
||||
argv[n+1] );
|
||||
printf( "Failing startup.\n");
|
||||
return StartupStatus::ssFAILED;
|
||||
}
|
||||
}
|
||||
} else if( _stricmp(token.c_str(), maxheapFlag) == 0 ) {
|
||||
string maxheapString;
|
||||
if (getline(iss, maxheapString, ' ')) {
|
||||
maxheapBytes = memtoll(maxheapString.c_str(),&memtollerr);
|
||||
if( memtollerr != 0) {
|
||||
printf (
|
||||
"%s specified. Unable to convert %s to the number of bytes for the maxmemory flag.\n",
|
||||
maxmemoryBytes,
|
||||
argv[n+1] );
|
||||
printf( "Failing startup.\n");
|
||||
return StartupStatus::ssFAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if( strncmp(argv[n],"--", 2) == 0) {
|
||||
if (_stricmp(argv[n]+2,maxmemoryFlag) == 0) {
|
||||
maxmemoryBytes = memtoll(argv[n+1],&memtollerr);
|
||||
if( memtollerr != 0) {
|
||||
printf (
|
||||
"%s specified. Unable to convert %s to the number of bytes for the maxmemory flag.\n",
|
||||
maxmemoryBytes,
|
||||
argv[n+1] );
|
||||
printf( "Failing startup.\n");
|
||||
return StartupStatus::ssFAILED;
|
||||
}
|
||||
} else if(_stricmp(argv[n]+2,maxheapFlag) == 0) {
|
||||
maxheapBytes = memtoll(argv[n+1],&memtollerr);
|
||||
if( memtollerr != 0) {
|
||||
printf (
|
||||
"%s specified. Unable to convert %s to the number of bytes for the maxmemory flag.\n",
|
||||
maxmemoryBytes,
|
||||
argv[n+1] );
|
||||
printf( "Failing startup.\n");
|
||||
return StartupStatus::ssFAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PERFORMANCE_INFORMATION perfinfo;
|
||||
perfinfo.cb = sizeof(PERFORMANCE_INFORMATION);
|
||||
if (FALSE == GetPerformanceInfo(&perfinfo, sizeof(PERFORMANCE_INFORMATION))) {
|
||||
printf ( "GetPerformanceInfo failed.\n" );
|
||||
printf( "Failing startup.\n" );
|
||||
return StartupStatus::ssFAILED;
|
||||
}
|
||||
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 ) {
|
||||
maxheapBytes = (maxheapBytes > maxMemoryPlusHalf) ? maxheapBytes : maxMemoryPlusHalf;
|
||||
}
|
||||
if( maxheapBytes == -1 )
|
||||
{
|
||||
maxheapBytes = perfinfo.PhysicalTotal * pageSize;
|
||||
}
|
||||
|
||||
|
||||
if (foundSlaveFlag) {
|
||||
return QForkSlaveInit( QForkConrolMemoryMapHandle, PPID ) ? StartupStatus::ssSLAVE_EXIT : StartupStatus::ssFAILED;
|
||||
LPVOID exceptionHandler = AddVectoredExceptionHandler( 1, VectoredHeapMapper );
|
||||
StartupStatus retVal = StartupStatus::ssFAILED;
|
||||
try {
|
||||
retVal = QForkSlaveInit( QForkConrolMemoryMapHandle, PPID ) ? StartupStatus::ssSLAVE_EXIT : StartupStatus::ssFAILED;
|
||||
} catch (...) { }
|
||||
RemoveVectoredExceptionHandler(exceptionHandler);
|
||||
return retVal;
|
||||
} else {
|
||||
return QForkMasterInit() ? StartupStatus::ssCONTINUE_AS_MASTER : StartupStatus::ssFAILED;
|
||||
return QForkMasterInit(maxheapBytes) ? StartupStatus::ssCONTINUE_AS_MASTER : StartupStatus::ssFAILED;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL QForkShutdown() {
|
||||
if(g_hForkedProcess != NULL) {
|
||||
TerminateProcess(g_hForkedProcess, -1);
|
||||
CloseHandle(g_hForkedProcess);
|
||||
g_hForkedProcess = NULL;
|
||||
}
|
||||
|
||||
@@ -599,8 +809,8 @@ BOOL BeginForkOperation(OperationType type, char* fileName, LPVOID globalData, i
|
||||
catch(std::system_error syserr) {
|
||||
printf("BeginForkOperation: system error caught. error code=0x%08x, message=%s\n", syserr.code().value(), syserr.what());
|
||||
}
|
||||
catch(std::runtime_error syserr) {
|
||||
printf("BeginForkOperation: runtime error caught. message=%s\n", syserr.what());
|
||||
catch(std::runtime_error runerr) {
|
||||
printf("BeginForkOperation: runtime error caught. message=%s\n", runerr.what());
|
||||
}
|
||||
catch(...) {
|
||||
printf("BeginForkOperation: other exception caught.\n");
|
||||
@@ -667,6 +877,7 @@ BOOL EndForkOperation() {
|
||||
"EndForkOperation: Killing forked process failed.");
|
||||
}
|
||||
}
|
||||
CloseHandle(g_hForkedProcess);
|
||||
g_hForkedProcess = 0;
|
||||
}
|
||||
|
||||
@@ -735,7 +946,6 @@ BOOL EndForkOperation() {
|
||||
typedef COWList::iterator COWListIterator;
|
||||
COWList cowList;
|
||||
HANDLE hProcess = GetCurrentProcess();
|
||||
const size_t pageSize = 4096;
|
||||
size_t mmSize = g_pQForkControl->availableBlocksInHeap * g_pQForkControl->heapBlockSize;
|
||||
int pages = (int)(mmSize / pageSize);
|
||||
PSAPI_WORKING_SET_EX_INFORMATION* pwsi =
|
||||
@@ -770,9 +980,6 @@ BOOL EndForkOperation() {
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
// cout << cowList.size() << " of " << (mmSize / pageSize) << " are modified" << endl;
|
||||
#endif
|
||||
|
||||
if (cowList.size() > 0) {
|
||||
LPBYTE cowBuffer = (LPBYTE)malloc(cowList.size() * pageSize);
|
||||
@@ -875,7 +1082,7 @@ int totalFreeCalls = 0;
|
||||
|
||||
LPVOID AllocHeapBlock(size_t size, BOOL allocateHigh) {
|
||||
totalAllocCalls++;
|
||||
LPVOID retPtr = (LPVOID)-1;
|
||||
LPVOID retPtr = (LPVOID)NULL;
|
||||
if (size % g_pQForkControl->heapBlockSize != 0 ) {
|
||||
errno = EINVAL;
|
||||
return retPtr;
|
||||
@@ -884,7 +1091,7 @@ LPVOID AllocHeapBlock(size_t size, BOOL allocateHigh) {
|
||||
|
||||
size_t mapped = 0;
|
||||
int startIndex = allocateHigh ? g_pQForkControl->availableBlocksInHeap - 1 : contiguousBlocksToAllocate - 1;
|
||||
int endIndex = allocateHigh ? 0 : g_pQForkControl->availableBlocksInHeap - contiguousBlocksToAllocate;
|
||||
int endIndex = allocateHigh ? -1 : g_pQForkControl->availableBlocksInHeap - contiguousBlocksToAllocate + 1;
|
||||
int direction = allocateHigh ? -1 : 1;
|
||||
int blockIndex = 0;
|
||||
int contiguousBlocksFound = 0;
|
||||
|
||||
@@ -435,7 +435,12 @@ void loadServerConfigFromString(char *config) {
|
||||
err = sentinelHandleConfiguration(argv+1,argc-1);
|
||||
if (err) goto loaderr;
|
||||
}
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
} else if (!strcasecmp(argv[0],"maxheap")) {
|
||||
// ignore. This is taken care of in the qfork code.
|
||||
|
||||
#endif
|
||||
} else {
|
||||
err = "Bad directive or wrong number of arguments"; goto loaderr;
|
||||
}
|
||||
sdsfreesplitres(argv,argc);
|
||||
|
||||
106
src/sentinel.c
106
src/sentinel.c
@@ -217,7 +217,12 @@ typedef struct sentinelScriptJob {
|
||||
execution at any time. If the script is not
|
||||
running and it's not 0, it means: do not run
|
||||
before the specified time. */
|
||||
#ifdef _WIN32
|
||||
HANDLE hScriptProcess; /* handle of process executing script */
|
||||
pid_t processId; /* Script execution pid. (for reporting only) */
|
||||
#else
|
||||
pid_t pid; /* Script execution pid. */
|
||||
#endif
|
||||
} sentinelScriptJob;
|
||||
|
||||
/* ======================= hiredis ae.c adapters =============================
|
||||
@@ -595,7 +600,12 @@ void sentinelScheduleScriptExecution(char *path, ...) {
|
||||
sj->retry_num = 0;
|
||||
sj->argv = zmalloc(sizeof(char*)*(argc+1));
|
||||
sj->start_time = 0;
|
||||
#ifdef _WIN32
|
||||
sj->hScriptProcess = INVALID_HANDLE_VALUE;
|
||||
sj->processId = 0;
|
||||
#else
|
||||
sj->pid = 0;
|
||||
#endif
|
||||
memcpy(sj->argv,argv,sizeof(char*)*(argc+1));
|
||||
|
||||
listAddNodeTail(sentinel.scripts_queue,sj);
|
||||
@@ -622,6 +632,7 @@ void sentinelScheduleScriptExecution(char *path, ...) {
|
||||
|
||||
/* Lookup a script in the scripts queue via pid, and returns the list node
|
||||
* (so that we can easily remove it from the queue if needed). */
|
||||
#ifndef _WIN32
|
||||
listNode *sentinelGetScriptListNodeByPid(pid_t pid) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
@@ -635,6 +646,7 @@ listNode *sentinelGetScriptListNodeByPid(pid_t pid) {
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Run pending scripts if we are not already at max number of running
|
||||
* scripts. */
|
||||
@@ -647,8 +659,7 @@ void sentinelRunPendingScripts(void) {
|
||||
* tail of the queue, so we run older jobs first. */
|
||||
listRewind(sentinel.scripts_queue,&li);
|
||||
while (sentinel.running_scripts < SENTINEL_SCRIPT_MAX_RUNNING &&
|
||||
(ln = listNext(&li)) != NULL)
|
||||
{
|
||||
(ln = listNext(&li)) != NULL) {
|
||||
sentinelScriptJob *sj = ln->value;
|
||||
pid_t pid;
|
||||
|
||||
@@ -661,6 +672,38 @@ void sentinelRunPendingScripts(void) {
|
||||
sj->flags |= SENTINEL_SCRIPT_RUNNING;
|
||||
sj->start_time = mstime();
|
||||
sj->retry_num++;
|
||||
|
||||
#ifdef _WIN32
|
||||
{
|
||||
PROCESS_INFORMATION pi;
|
||||
char args[1024];
|
||||
int j = 1;
|
||||
int pos = 0;
|
||||
while(sj->argv[j]) {
|
||||
int arglen = strlen(sj->argv[j]);
|
||||
memcpy(args+pos, sj->argv[j], arglen);
|
||||
pos += arglen;
|
||||
memcpy(args+pos, " ", 1);
|
||||
pos += 1;
|
||||
j++;
|
||||
}
|
||||
if(TRUE == CreateProcessA(sj->argv[0], args, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi)) {
|
||||
sj->hScriptProcess = pi.hProcess;
|
||||
sj->processId = pi.dwProcessId;
|
||||
CloseHandle( pi.hThread );
|
||||
|
||||
sentinel.running_scripts++;
|
||||
sentinelEvent(REDIS_DEBUG,"+script-child",NULL,"%ld",(long)sj->processId);
|
||||
} else {
|
||||
sentinelEvent(REDIS_WARNING,"-script-error",NULL,
|
||||
"%s %d %d", sj->argv[0], 99, 0);
|
||||
sj->flags &= ~SENTINEL_SCRIPT_RUNNING;
|
||||
sj->processId = 0;
|
||||
sj->hScriptProcess = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
pid = fork();
|
||||
|
||||
if (pid == -1) {
|
||||
@@ -673,11 +716,7 @@ void sentinelRunPendingScripts(void) {
|
||||
sj->pid = 0;
|
||||
} else if (pid == 0) {
|
||||
/* Child */
|
||||
#ifdef _WIN32
|
||||
_execv(sj->argv[0],sj->argv);
|
||||
#else
|
||||
execve(sj->argv[0],sj->argv,environ);
|
||||
#endif
|
||||
/* If we are here an error occurred. */
|
||||
_exit(2); /* Don't retry execution. */
|
||||
} else {
|
||||
@@ -686,6 +725,7 @@ void sentinelRunPendingScripts(void) {
|
||||
sentinelEvent(REDIS_DEBUG,"+script-child",NULL,"%ld",(long)pid);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* How much to delay the execution of a script that we need to retry after
|
||||
@@ -707,6 +747,49 @@ mstime_t sentinelScriptRetryDelay(int retry_num) {
|
||||
* a signal, or returned exit code "1", it is scheduled to run again if
|
||||
* the max number of retries did not already elapsed. */
|
||||
void sentinelCollectTerminatedScripts(void) {
|
||||
#ifdef _WIN32
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
DWORD exitCode;
|
||||
|
||||
listRewind(sentinel.scripts_queue,&li);
|
||||
while (sentinel.running_scripts < SENTINEL_SCRIPT_MAX_RUNNING &&
|
||||
(ln = listNext(&li)) != NULL) {
|
||||
sentinelScriptJob *sj = ln->value;
|
||||
|
||||
if(sj->hScriptProcess == INVALID_HANDLE_VALUE)
|
||||
continue;
|
||||
|
||||
if(WaitForSingleObject(sj->hScriptProcess,0) == WAIT_OBJECT_0) {
|
||||
GetExitCodeProcess(sj->hScriptProcess,&exitCode);
|
||||
sentinelEvent(REDIS_DEBUG,"-script-child",NULL,"%ld %d %d",
|
||||
(long)sj->processId, exitCode, 0);
|
||||
|
||||
/* at this point the process ID may be recycled by Windows */
|
||||
CloseHandle(sj->hScriptProcess);
|
||||
|
||||
/* If the script returns an exit code of "1" (that means: please retry),
|
||||
* we reschedule it if the max number of retries is not already reached. */
|
||||
if (exitCode == 1 && sj->retry_num != SENTINEL_SCRIPT_MAX_RETRY) {
|
||||
sj->hScriptProcess = INVALID_HANDLE_VALUE;
|
||||
sj->processId = 0;
|
||||
sj->flags &= ~SENTINEL_SCRIPT_RUNNING;
|
||||
sj->start_time = mstime() +
|
||||
sentinelScriptRetryDelay(sj->retry_num);
|
||||
} else {
|
||||
/* Otherwise let's remove the script, but log the event if the
|
||||
* execution did not terminated in the best of the ways. */
|
||||
if (exitCode != 0) {
|
||||
sentinelEvent(REDIS_WARNING,"-script-error",NULL,
|
||||
"%s %d", sj->argv[0], exitCode);
|
||||
}
|
||||
listDelNode(sentinel.scripts_queue,ln);
|
||||
sentinelReleaseScriptJob(sj);
|
||||
sentinel.running_scripts--;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
int statloc;
|
||||
pid_t pid;
|
||||
|
||||
@@ -749,6 +832,7 @@ void sentinelCollectTerminatedScripts(void) {
|
||||
sentinel.running_scripts--;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Kill scripts in timeout, they'll be collected by the
|
||||
@@ -765,9 +849,15 @@ void sentinelKillTimedoutScripts(void) {
|
||||
if (sj->flags & SENTINEL_SCRIPT_RUNNING &&
|
||||
(now - sj->start_time) > SENTINEL_SCRIPT_MAX_RUNTIME)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
sentinelEvent(REDIS_WARNING,"-script-timeout",NULL,"%s %ld",
|
||||
sj->argv[0], (long)sj->processId);
|
||||
TerminateProcess(sj->hScriptProcess,1);
|
||||
#else
|
||||
sentinelEvent(REDIS_WARNING,"-script-timeout",NULL,"%s %ld",
|
||||
sj->argv[0], (long)sj->pid);
|
||||
kill(sj->pid,SIGKILL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -796,7 +886,11 @@ void sentinelPendingScriptsCommand(redisClient *c) {
|
||||
(sj->flags & SENTINEL_SCRIPT_RUNNING) ? "running" : "scheduled");
|
||||
|
||||
addReplyBulkCString(c,"pid");
|
||||
#ifdef _WIN32
|
||||
addReplyBulkLongLong(c,sj->processId);
|
||||
#else
|
||||
addReplyBulkLongLong(c,sj->pid);
|
||||
#endif
|
||||
|
||||
if (sj->flags & SENTINEL_SCRIPT_RUNNING) {
|
||||
addReplyBulkCString(c,"run-time");
|
||||
|
||||
Reference in New Issue
Block a user