Compare commits

..
945 Commits
Author SHA1 Message Date
antirez b0f3922383 Redis 2.8.11. 2014-06-11 10:30:05 +02:00
antirez a3b0dbccf5 Scripting: Fix for a #1118 regression simplified.
It is more straightforward to just test for a numerical type avoiding
Lua's automatic conversion. The code is technically more correct now,
however Lua should automatically convert to number only if the original
type is a string that "looks like a number", and not from other types,
so practically speaking the fix is identical AFAIK.
2014-06-11 10:14:26 +02:00
Matt Stancliff ba76daa42e Scripting: Fix regression from #1118
The new check-for-number behavior of Lua arguments broke
users who use large strings of just integers.

The Lua number check would convert the string to a number, but
that breaks user data because
Lua numbers have limited precision compared to an arbitrarily
precise number wrapped in a string.

Regression fixed and new test added.

Fixes #1118 again.
2014-06-11 10:14:26 +02:00
Matt Stancliff 8f774ee7ca Fix lack of strtold under Cygwin
Renaming strtold to strtod then casting
the result is the standard way of dealing with
no strtold in Cygwin.
2014-06-09 11:43:19 +02:00
Matt Stancliff 2af2414344 Fix lack of SA_ONSTACK under Cygwin
Fixes #232
2014-06-09 11:43:19 +02:00
Matt Stancliff b4f9761d85 Fix blocking operations from missing new lists
Behrad Zari discovered [1] and Josiah reported [2]: if you block
and wait for a list to exist, but the list creates from
a non-push command, the blocked client never gets notified.

This commit adds notification of blocked clients into
the DB layer and away from individual commands.

Lists can be created by [LR]PUSH, SORT..STORE, RENAME, MOVE,
and RESTORE.  Previously, blocked client notifications were
only triggered by [LR]PUSH.  Your client would never get
notified if a list were created by SORT..STORE or RENAME or
a RESTORE, etc.

Blocked client notification now happens in one unified place:
  - dbAdd() triggers notification when adding a list to the DB

Two new tests are added that fail prior to this commit.

All test pass.

Fixes #1668

[1]: https://groups.google.com/forum/#!topic/redis-db/k4oWfMkN1NU
[2]: #1668
2014-06-09 11:39:44 +02:00
Andy Grunwald 6ab4775a2a Fixed typo in word avarege in result message of --intrinsic-latency analyzer 2014-06-06 11:20:04 +02:00
Jan-Erik Rediger b98ee12f61 Small typo fixed 2014-06-06 10:45:46 +02:00
yoav 3783d90e53 Fix eval usage in tests to conform with eval semantics 2014-06-06 10:44:08 +02:00
zionwu e3112bed8d fix issue 1787 2014-06-06 10:38:04 +02:00
antirez 987a47b823 Redis 2.8.10. 2014-06-05 11:02:23 +02:00
antirez 430bec3461 Don't process min-slaves-to-write for slaves.
Replication is totally broken when a slave has this option, since it
stops accepting updates from masters.

This fixes issue #1434.
2014-06-05 10:50:31 +02:00
antirez 3a30be51aa Tests for min-slaves-* feature. 2014-06-05 10:50:31 +02:00
antirez 42504169c7 Fixed dbuf variable scope in luaRedisGenericCommand().
I'm not sure if while the visibility is the inner block, the fact we
point to 'dbuf' is a problem or not, probably the stack var isx
guaranteed to live until the function returns. However obvious code is
better anyway.
2014-06-04 18:58:58 +02:00
antirez 26359d570b Regression test for issue #1118. 2014-06-04 18:51:31 +02:00
antirez 768994b683 Scripting: better Lua number -> string conversion in luaRedisGenericCommand().
The lua_to*string() family of functions use a non optimal format
specifier when converting integers to strings. This has both the problem
of the number being converted in exponential notation, which we don't
use as a Redis return value when floating point numbers are involed,
and, moreover, there is a loss of precision since the default format
specifier is not able to represent numbers that must be represented
exactly in the IEEE 754 number mantissa.

The new code handles it as a special case using a saner conversion.

This fixes issue #1118.
2014-06-04 18:45:49 +02:00
antirez 1c0c42e668 More trailing spaces in sentinel.c removed. 2014-05-28 15:46:14 +02:00
Matt Stancliff 75266018ee Disable recursive watchdog signal handler
If we are in the signal handler, we don't want to handle
the signal again.  In extreme cases, this can cause a stack overflow
and segfault Redis.

Fixes #1771
2014-05-26 17:53:40 +02:00
antirez edc41baca5 Sentinel example config: explain you don't need to specify slaves. 2014-05-26 10:18:07 +02:00
antirez 1a14eba2d5 Test: fixed scripting.tcl test false positive. 2014-05-22 15:46:06 +02:00
antirez f482349707 Process events with processEventsWhileBlocked() when blocked.
When we are blocked and a few events a processed from time to time, it
is smarter to call the event handler a few times in order to handle the
accept, read, write, close cycle of a client in a single pass, otherwise
there is too much latency added for clients to receive a reply while the
server is busy in some way (for example during the DB loading).
2014-05-22 15:46:02 +02:00
antirez f3d3c606f1 Accept multiple clients per iteration.
When the listening sockets readable event is fired, we have the chance
to accept multiple clients instead of accepting a single one. This makes
Redis more responsive when there is a mass-connect event (for example
after the server startup), and in workloads where a connect-disconnect
pattern is used often, so that multiple clients are waiting to be
accepted continuously.

As a side effect, this commit makes the LOADING, BUSY, and similar
errors much faster to deliver to the client, making Redis more
responsive when there is to return errors to inform the clients that the
server is blocked in an not interruptible operation.
2014-05-22 15:45:58 +02:00
antirez 4113473997 While ANET_ERR is -1, check syscall retval for -1 itself. 2014-05-22 15:45:54 +02:00
antirez e8669ca8a7 Regression test for issue #1764. 2014-05-20 16:20:45 +02:00
michael-grunder d491a479bc Fix LUA_OBJCACHE segfault.
When scanning the argument list inside of a redis.call() invocation
for pre-cached values, there was no check being done that the
argument we were on was in fact within the bounds of the cache size.

So if a redis.call() command was ever executed with more than 32
arguments (current cache size #define setting) redis-server could
segfault.
2014-05-20 16:20:38 +02:00
antirez d575f7a147 Remove trailing spaces from scripting.c 2014-05-20 16:11:28 +02:00
antirez 6d2fddd2a3 Remove trailing spaces from sentinel.c. 2014-05-20 14:22:34 +02:00
antirez ad935769ae HyperLogLog regression test for issue #1762. 2014-05-19 15:45:24 +02:00
Mike Trinkala 43278af63e Correct the HyperLogLog stale cache flag to prevent unnecessary computations.
Set the MSB as documented.
2014-05-19 15:45:24 +02:00
antirez 3203e2dcdd Fix 2.8 backport of fastscript branch.
No REDIS_ENCODING_EMBSTR in 2.8 internals.
2014-05-14 18:39:42 +02:00
Akos Vandra 6e478ec890 Fixed possible buffer overflow bug if RDB file is corrupted.
(Note: commit message modified by @antirez for clarity).
2014-05-12 11:49:16 +02:00
Akos Vandra c0eaac9572 fixed possible buffer overflow error 2014-05-12 11:19:36 +02:00
antirez 23011dea1f Sentinel: Add "dir /tmp" directive in example sentinel.conf. 2014-05-12 10:46:56 +02:00
antirez 6ae4c1fcbc DEBUG POPULATE: call dictExpand() to avoid useless rehashing. 2014-05-09 15:05:31 +02:00
antirez 13d8b2b096 Sentinel: log when a failover will be attempted again.
When a Sentinel performs a failover (successful or not), or when a
Sentinel votes for a different Sentinel trying to start a failover, it
sets a min delay before it will try to get elected for a failover.

While not strictly needed, because if multiple Sentinels will try
to failover the same master at the same time, only one configuration
will eventually win, this serialization is practically very useful.
Normal failovers are cleaner: one Sentinel starts to failover, the
others update their config when the Sentinel performing the failover
is able to get the selected slave to move from the role of slave to the
one of master.

However currently this timeout was implicit, so users could see
Sentinels not reacting, after a failed failover, for some time, without
giving any feedback in the logs to the poor sysadmin waiting for clues.

This commit makes Sentinels more verbose about the delay: when a master
is down and a failover attempt is not performed because the delay has
still not elaped, something like that will be logged:

    Next failover delay: I will not start a failover
    before Thu May  8 16:48:59 2014
2014-05-08 16:49:45 +02:00
antirez 909d1883e7 Sentinel: generate +config-update-from event when a new config is received.
This event makes clear, before the switch-master event is generated,
that a Sentinel received a configuration update from another Sentinel.
2014-05-08 16:49:45 +02:00
antirez 80b96bc4a7 Scripting test: check that Lua can call commands rewirting argv.
SPOP, tested in the new test, is among the commands rewritng the
client->argv argument vector (it gets rewritten as SREM) for command
replication purposes.

Because of recent optimizations to client->argv caching in the context
of the Lua internal Redis client, it is important to test for SPOP to be
callable from Lua without bad effects to the other commands.
2014-05-07 16:17:11 +02:00
antirez 093eab56de Test: handle new osx 'leaks' error.
Sometimes the process is still there but no longer in a state that can
be checked (after being killed). This used to happen after a call to
SHUTDOWN NOSAVE in the scripting unit, causing a false positive.
2014-05-07 16:17:11 +02:00
antirez 32971f6251 Scripting: objects caching for Lua c->argv creation.
Reusing small objects when possible is a major speedup under certain
conditions, since it is able to avoid the malloc/free pattern that
otherwise is performed for every argument in the client command vector.
2014-05-07 16:17:11 +02:00
antirez 87516f1a4d Scripting: Use faster API for Lua client c->argv creation.
Replace the three calls to Lua API lua_tostring, lua_lua_strlen,
and lua_isstring, with a single call to lua_tolstring.

~ 5% consistent speed gain measured.
2014-05-07 16:17:11 +02:00
antirez 1b5524f697 Scripting: don't call lua_gc() after Lua script run.
Calling lua_gc() after every script execution is too expensive, and
apparently does not make the execution smoother: the same peak latency
was measured before and after the commit.

This change accounts for scripts execution speedup in the order of 10%.
2014-05-07 16:17:11 +02:00
antirez 8237d88f48 Scripting: cache argv in luaRedisGenericCommand().
~ 4% consistently measured speed improvement.
2014-05-07 16:17:11 +02:00
antirez 6938a3d58f Fixed missing c->bufpos reset in luaRedisGenericCommand().
Bug introduced when adding a fast path to avoid copying the reply buffer
for small replies that fit into the client static buffer.
2014-05-07 16:17:11 +02:00
antirez 08ea98c9e9 Scripting: replace tolower() with faster code in evalGenericCommand().
The function showed up consuming a non trivial amount of time in the
profiler output. After this change benchmarking gives a 6% speed
improvement that can be consistently measured.
2014-05-07 16:17:11 +02:00
antirez 3a1a0c3240 Scripting: luaRedisGenericCommand() fast path for buffer-only replies.
When the reply is only contained in the client static output buffer, use
a fast path avoiding the dynamic allocation of an SDS string to
concatenate the client reply objects.
2014-05-07 16:17:11 +02:00
antirez d70281575c Define HAVE_ATOMIC for clang. 2014-05-07 16:17:11 +02:00
antirez 67e350367d Scripting: simpler reply buffer creation in luaRedisGenericCommand().
It if faster to just create the string with a single sdsnewlen() call.
If c->bufpos is zero, the call will simply be like sdsemtpy().
2014-05-07 16:17:11 +02:00
Matt Stancliff b06de30dd7 Add test for deleting an expired key
Verify proper expire-before-delete behavior.

This test passes with the expire-before-delete commit and fails
without it.
2014-04-23 16:14:40 +02:00
Matt Stancliff e9d0d86dbf Check key expiration before deleting
Deleting an expired key should return 0, not success.

Fixes #1648
2014-04-23 16:14:40 +02:00
Glauber Costa 96b5dc0b98 fix null pointer access with no file pointer
I happen to be working on a system that lacks urandom. While the code does try
to handle this case and artificially create some bytes if the file pointer is
empty, it does try to close it unconditionally, leading to a segfault.
2014-04-23 15:35:12 +02:00
Salvatore Sanfilippo 3d2af57974 Merge pull request #1700 from nirvdrum/patch-1
Fixed typos.
2014-04-23 11:55:50 +02:00
antirez 35252037fd Missing return REDIS_ERR added to processMultibulkBuffer().
When we set a protocol error we should return with REDIS_ERR to let the
caller know it should stop processing the client.

Bug found in a code auditing related to issue #1699.
2014-04-23 10:21:26 +02:00
Kevin Menard 662f505727 Fixed typos. 2014-04-22 10:51:19 -04:00
antirez de7ebf83e1 redis-cli help.h updated. 2014-04-22 16:15:01 +02:00
antirez 91dcc5b12f generate-command-help.rb updated with new hyperloglog group. 2014-04-22 16:15:01 +02:00
antirez 26375709c5 Redis 2.8.9. 2014-04-22 10:14:57 +02:00
antirez c5ee48a9d9 Fuzzy test for ZREMRANGEBYLEX added. 2014-04-18 16:16:20 +02:00
antirez be145453c6 ZREMRANGEBYLEX memory leak removed calling zslFreeLexRange(). 2014-04-18 16:16:20 +02:00
antirez 115782b1d7 PFCOUNT multi-key test added. 2014-04-18 16:16:20 +02:00
antirez 2cd20d3c4c Speedup hllRawSum() processing 8 bytes per iteration.
The internal HLL raw encoding used by PFCOUNT when merging multiple keys
is aligned to 8 bits (1 byte per register) so we can exploit this to
improve performances by processing multiple bytes per iteration.

In benchmarks the new code was several times faster with HLLs with many
registers set to zero, while no slowdown was observed with populated
HLLs.
2014-04-18 16:16:20 +02:00
antirez 3f5cd0c660 Speedup SUM(2^-reg[m]) in HyperLogLog computation.
When the register is set to zero, we need to add 2^-0 to E, which is 1,
but it is faster to just add 'ez' at the end, which is the number of
registers set to zero, a value we need to compute anyway.
2014-04-18 16:16:20 +02:00
antirez f0eca148ff PFCOUNT support for multi-key union. 2014-04-18 16:16:20 +02:00
antirez d25fd8be64 HyperLogLog low level merge extracted from PFMERGE. 2014-04-18 16:16:20 +02:00
antirez 4a26648a22 ZREMRANGEBYLEX implemented. 2014-04-18 16:16:20 +02:00
antirez 3fc22561e7 Always pass sorted set range objects by reference. 2014-04-18 16:16:04 +02:00
antirez ea0af9ace3 ZREMRANGE* commands refactored into a single generic function. 2014-04-18 16:14:38 +02:00
antirez 019e6f1889 Sorted set lex ranges stress tester. 2014-04-18 16:14:38 +02:00
antirez 9c9aef24a2 Basic ZRANGEBYLEX / ZLEXCOUNT tests. 2014-04-17 00:08:38 +02:00
antirez 11f89497b4 Pass by pointer and release of lex ranges.
Given that the code was written with a 2 years pause... something
strange happened in the middle. So there was no function to free a
lex range min/max objects, and in some places the range was passed by
value.
2014-04-17 00:08:37 +02:00
antirez d975bb587f ZLEXCOUNT implemented.
Like ZCOUNT for lexicographical ranges.
2014-04-16 15:26:28 +02:00
antirez 71dfb87f76 More HyperLogLog tests. 2014-04-16 15:26:28 +02:00
antirez f2e59eae44 HyperLogLog invalid representation error code set to INVALIDOBJ. 2014-04-16 15:26:28 +02:00
antirez 469b7c5168 PFDEBUG TODENSE added.
Converts HyperLogLogs from sparse to dense. Used for testing.
2014-04-16 15:26:28 +02:00
antirez 614fcd491e User-defined switch point between sparse-dense HLL encodings. 2014-04-16 15:26:28 +02:00
antirez 08da2b79fc PFSELFTEST improved with sparse encoding checks. 2014-04-16 15:26:27 +02:00
antirez 5a0bab2a8f PFDEBUG ENCODING added. 2014-04-16 15:26:27 +02:00
antirez 05563310f4 Set HLL_SPARSE_MAX to 3000.
After running a few benchmarks, 3000 looks like a reasonable value to
keep HLLs with a few thousand elements small while the CPU cost is
still not huge.

This covers all the cases where the dense representation would use N
orders of magnitude more space, like in the case of many HLLs with
carinality of a few tens or hundreds.

It is not impossible that in the future this gets user configurable,
however it is easy to pick an unreasoable value just looking at savings
in the space dimension without checking what happens in the time
dimension.
2014-04-16 15:26:27 +02:00
antirez e77a3e40f2 Error message for invalid HLL objects unified. 2014-04-16 15:26:27 +02:00
antirez c4b2a7a7f4 PFMERGE fixed to work with sparse encoding. 2014-04-16 15:26:27 +02:00
antirez ee682c4c48 Mark PFDEBUG as write command in the commands table.
It is safer since it is able to have side effects.
2014-04-16 15:26:27 +02:00
antirez 9ef8ef410b Correctly replicate PFDEBUG GETREG.
Even if it is a debugging command, make sure that when it forces a
change in encoding, the command is propagated.
2014-04-16 15:26:27 +02:00
antirez 91e05e1618 Added assertion in hllSparseAdd() when promotion to dense occurs.
If we converted to dense, a register must be updated in the dense
representation.
2014-04-16 15:26:27 +02:00
antirez 68a4bf70e0 hllSparseAdd(): speed optimization.
Mostly by reordering opcodes check conditional by frequency of opcodes
in larger sparse-encoded HLLs.
2014-04-16 15:26:27 +02:00
antirez 21e328a6d7 Detect corrupted sparse HLLs in hllSparseSum(). 2014-04-16 15:26:27 +02:00
antirez 4244aa29f9 hllSparseAdd(): faster code removing conditional.
Bottleneck found profiling. Big run time improvement found when testing
after the change.
2014-04-16 15:26:27 +02:00
antirez 4cd11077d2 Comment typo in hllSparseAdd(). first -> fits. 2014-04-16 15:26:27 +02:00
antirez 08183cfef5 Merge adjacent VAL opcodes in hllSparseAdd().
As more values are added splitting ZERO or XZERO opcodes, try to merge
adjacent VAL opcodes if they have the same value.
2014-04-16 15:26:27 +02:00
antirez d428569d13 More robust HLL_SPARSE macros protecting 'p' with parens.
Now the macros will work with arguments such as "ptr+1".
2014-04-16 15:26:27 +02:00
antirez 2d1e35a58a hllSparseAdd() opcode seek stop condition fixed. 2014-04-16 15:26:27 +02:00
antirez 29c7d1974b Fixed error message generation in PFDEBUG GETREG.
Bulk length for registers was emitted too early, so if there was a bug
the reply looked like a long array with just one element, blocking the
client as result.
2014-04-16 15:26:27 +02:00
antirez 023f2f7a10 Fixed memmove() count in hllSparseAdd(). 2014-04-16 15:26:27 +02:00
antirez 91391760ca hllSparseAdd(): more correct dense conversion conditional.
We want to promote if the total string size exceeds the resulting size
after the upgrade.
2014-04-16 15:26:27 +02:00
antirez 5d87160f8a hllSparseToDense(): sanity check added.
The function checks if all the HLL_REGISTERS were processed during the
convertion from sparse to dense encoding, returning REDIS_OK or
REDIS_ERR to signal a corruption problem.

A bug in PFDEBUG GETREG was fixed: when the object is converted to the
dense representation we need to reassign the new pointer to the header
structure pointer.
2014-04-16 15:26:27 +02:00
antirez a18e7afbe8 PFDEBUG DECODE added.
Provides a human readable description of the opcodes composing a
run-length encoded HLL (sparse encoding).
The command is only useful for debugging / development tasks.
2014-04-16 15:26:27 +02:00
antirez 5c86d9e7b0 PFDEBUG added, PFGETREG removed.
PFDEBUG will be the interface to do debugging tasks with a key
containing an HLL object.
2014-04-16 15:26:27 +02:00
antirez 0e0ddcc4fa hllSparseToDense API changed to take ref to object.
The new API takes directly the object doing everything needed to
turn it into a dense representation, including setting the new
representation as object->ptr.
2014-04-16 15:26:27 +02:00
antirez 533235f7e3 hllSparseAdd() sanity check for span != 0 added. 2014-04-16 15:26:27 +02:00
antirez f12fb49bb2 Fix hllSparseAdd() new sequence replacement when next is NULL.
sdsIncrLen() must be called anyway even if we are replacing the last
oppcode of the sparse representation.
2014-04-16 15:26:27 +02:00
antirez a3e2adc0ae Fix seqlen computation in hllSparseAdd(). 2014-04-16 15:26:27 +02:00
antirez eaa73ef73a Abstract hllSparseAdd() / hllDenseAdd() via hllAdd(). 2014-04-16 15:26:27 +02:00
antirez 95de3eaea0 hllSparseSum(): multiply 1 * runlen for zero entries. 2014-04-16 15:26:27 +02:00
antirez c277169ed7 Macro HLL_SPARSE_XZERO_LEN fixed. 2014-04-16 15:26:27 +02:00
antirez b6ca71a40a Fix HLL sparse object creation #2.
Two vars initialized to wrong values in createHLLObject().
2014-04-16 15:26:27 +02:00
antirez 8b39fee4c8 Increment pointer while iterating sparse HLL object. 2014-04-16 15:26:27 +02:00
antirez c1ddead779 Fix HLL sparse object creation.
The function didn't considered the fact that each XZERO opcode is
two bytes.
2014-04-16 15:26:27 +02:00
antirez 0a7ba6faef Create HyperLogLog objects with sparse encoding. 2014-04-16 15:26:27 +02:00
antirez 8e889728bf HyperLogLog sparse to dense conversion function. 2014-04-16 15:26:27 +02:00
antirez aadd513160 HyperLogLog sparse representation initial implementation.
Code never tested, but the basic layout is shaped in this commit.
Also missing:

1) Sparse -> Dense conversion function.
2) New HLL object creation using the sparse representation.
3) Implementation of PFMERGE for the sparse representation.
2014-04-16 15:26:27 +02:00
antirez e5e5facba1 hllCount() refactored to support multiple representations. 2014-04-16 15:26:27 +02:00
antirez 88847a216f hllAdd() refactored into two functions.
Also dense representation access macro renamed accordingly.
2014-04-16 15:26:27 +02:00
antirez 21c97e71cd HyperLogLog refactoring to support different encodings.
Metadata are now placed at the start of the representation as an header.
There is a proper structure to access the representation.
Still work to do in order to truly abstract the implementation from the
representation, commands still work assuming dense representation.
2014-04-16 15:26:27 +02:00
antirez ed96e115d0 HyperLogLog sparse representation slightly modified.
After running a few simulations with different alternative encodings,
it was found that the VAL opcode performs better using 5 bits for the
value and 2 bits for the run length, at least for cardinalities in the
range of interest.
2014-04-16 15:26:27 +02:00
antirez 93c75f1bf4 HyperLogLog sparse representation description and macros. 2014-04-16 15:26:26 +02:00
antirez 2b1385b9ec Add casting to match printf format.
adjustOpenFilesLimit() and clusterUpdateSlotsWithConfig() that were
assuming uint64_t is the same as unsigned long long, which is true
probably for all the systems out there that we target, but still GCC
emitted a warning since technically they are two different types.
2014-04-16 15:26:22 +02:00
antirez 12d1d18b59 ZRANGEBYLEX and ZREVRANGEBYLEX implementation. 2014-04-16 15:26:09 +02:00
antirez a10bfded15 PFCOUNT: always unshare/decode the object.
This will be a non-op most of the times since the object will be
unshared / decoded, however it is more technically correct to start this
way since the object may be decoded even in the read-only code path.
2014-04-16 15:26:09 +02:00
antirez ee764b0f8d Changed HyperLogLog hash seed to a non-zero value.
Using a seed of zero has the side effect of having the empty string
hashing to what is a very special case in the context of HyperLogLog: a
very long run of zeroes.

This did not influenced the correctness of the result with 16k registers
because of the harmonic mean, but still it is inconvenient that a so
obvious value maps to a so special hash.

The seed 0xadc83b19 is used instead, which is the first 64 bits of the
SHA1 of the empty string.

Reference: issue #1657.
2014-04-16 15:26:09 +02:00
antirez 091f5677a0 Initial HyperLogLog tests. 2014-04-16 15:26:09 +02:00
antirez a97675923d Return "WRONGTYPE" error on PF* type mismatch. 2014-04-16 15:26:09 +02:00
antirez faa7f259cc Fix PFADD infinite loop.
We need to guarantee that the last bit is 1, otherwise an element may
hash to just zeroes with probability 1/(2^64) and trigger an infinite
loop.

See issue #1657.
2014-04-16 15:26:09 +02:00
antirez 8ae6887091 Make hll-gnuplot-graph.rb callable from cli. 2014-04-16 15:26:09 +02:00
antirez bdd8701c60 Remove HyperLogLog type checking duplicated code. 2014-04-16 15:26:09 +02:00
antirez b3baa51403 PFGETREG added for testing purposes.
The new command allows to get a dump of the registers stored
into an HyperLogLog data structure for testing / debugging purposes.
2014-04-16 15:26:09 +02:00
antirez 348d5ea246 PFCOUNT: unshare the object when cached cardinality is modified. 2014-04-16 15:26:09 +02:00
antirez ba0dc0be2c PFSELFTEST improved to test the approximation error. 2014-04-16 15:26:09 +02:00
antirez c2cd48718d hll-gnuplot-graph.rb improved with new filter.
The function to generate graphs is also more flexible as now includes
step and max value. The step of the samples generation function is no
longer limited to min step of 1000.
2014-04-16 15:26:09 +02:00
antirez ff551fad24 HyperLogLog: added magic / version.
This will allow future changes like compressed representations.
Currently the magic is not checked for performance reasons but this may
change in the future, for example if we add new types encoded in strings
that may have the same size of HyperLogLogs.
2014-04-16 15:26:09 +02:00
Raymond Myers e08100a9ba Fixed pfadd/pfcount commands emitting hll* events instead of pf* events 2014-04-16 15:26:09 +02:00
Raymond Myers 23acae6e1c Change HLL* to PF* in error messages 2014-04-16 15:26:09 +02:00
antirez e3773b0a0e Include redis.h before other stuff in hyperloglog.c.
Otherwise fmacros.h is included later and this may break compilation on
different systems.
2014-04-16 15:26:09 +02:00
antirez badf23f57b HyperLogLog API prefix modified from "P" to "PF".
Using both the initials of Philippe Flajolet instead of just "P".
2014-04-16 15:26:03 +02:00
antirez 6e71459c73 Makefile.dep updated with hyperloglog.o deps. 2014-04-16 15:25:28 +02:00
antirez 2ee2bec2d6 HyperLogLog: make API use the P prefix in honor of Philippe Flajolet. 2014-04-16 15:24:15 +02:00
antirez 1e90c0066c HLLMERGE fixed by adding a... missing loop! 2014-04-16 15:23:21 +02:00
antirez be7eef911b hll-gnuplot-graph.rb: added new filter "all". 2014-04-16 15:23:21 +02:00
antirez f2e707b679 HyperLogLog apply bias correction using a polynomial.
Better results can be achieved by compensating for the bias of the raw
approximation just after 2.5m (when LINEARCOUNTING is no longer used) by
using a polynomial that approximates the bias at a given cardinality.

The curve used was found using this web page:

    http://www.xuru.org/rt/PR.asp

That performs polynomial regression given a set of values.
2014-04-16 15:23:21 +02:00
antirez be7fe2b92b HLLMERGE implemented.
Merge N HLL data structures by selecting the max value for every
M[i] register among the set of HLLs.
2014-04-16 15:23:21 +02:00
antirez 39dd1f648d HLLCOUNT is technically a write command
When we update the cached value, we need to propagate the command and
signal the key as modified for WATCH.
2014-04-16 15:23:21 +02:00
antirez f07b1514bb HLLADD: propagate write when only variable name is given.
The following form is given:

    HLLADD myhll

No element is provided in the above case so if 'myhll' var does not
exist the result is to just create an empty HLL structure, and no update
will be performed on the registers.

In this case, the DB should still be set dirty and the command
propagated.
2014-04-16 15:23:21 +02:00
antirez 32e9996846 hll-gnuplot-graph.rb: Use |error| when filter is :max 2014-04-16 15:23:21 +02:00
antirez 9faa80ee8c Ignore txt files inside utils/hyperloglog.
Those are generated to trace graphs using gnuplot.
2014-04-16 15:23:21 +02:00
antirez aad5959ddc HyperLogLog: use LINEARCOUNTING up to 3m.
The HyperLogLog original paper suggests using LINEARCOUNTING for
cardinalities < 2.5m, however for P=14 the median / max error
curves show that a value of '3' is the best pick for m = 16384.
2014-04-16 15:23:21 +02:00
antirez d6ce659ffd hll-gnuplot-graph.rb added to plot HyperLogLog error graphs. 2014-04-16 15:23:21 +02:00
antirez c3df5965f3 HyperLogLog approximated cardinality caching.
The more we add elements to an HyperLogLog counter, the smaller is
the probability that we actually update some register.

From this observation it is easy to see how it is possible to use
caching of a previously computed cardinality and reuse it to serve
HLLCOUNT queries as long as no register was updated in the data
structure.

This commit does exactly this by using just additional 8 bytes for the
data structure to store a 64 bit unsigned integer value cached
cardinality. When the most significant bit of the 64 bit integer is set,
it means that the value computed is no longer usable since at least a
single register was modified and we need to recompute it at the next
call of HLLCOUNT.

The value is always stored in little endian format regardless of the
actual CPU endianess.
2014-04-16 15:23:21 +02:00
antirez 9e178afae9 String value unsharing refactored into proper function.
All the Redis functions that need to modify the string value of a key in
a destructive way (APPEND, SETBIT, SETRANGE, ...) require to make the
object unshared (if refcount > 1) and encoded in raw format (if encoding
is not already REDIS_ENCODING_RAW).

This was cut & pasted many times in multiple places of the code. This
commit puts the small logic needed into a function called
dbUnshareStringValue().
2014-04-16 15:22:56 +02:00
antirez 1c795db9d0 Use endian neutral hash function for HyperLogLog.
We need to be sure that you can save a dataset in a Redis instance,
reload it in a different architecture, and continue to count in the same
HyperLogLog structure.

So 32 and 64 bit, little or bit endian, must all guarantee to output the
same hash for the same element.
2014-04-16 15:19:40 +02:00
antirez 7f30998432 HyperLogLog internal representation modified.
The new representation is more obvious, starting from the LSB of the
first byte and using bits going to MSB, and passing to next byte as
needed.

There was also a subtle error: first two bits were unused, everything
was carried over on the right of two bits, even if it worked because of
the code requirement of always having a byte more at the end.

During the rewrite the code was made safer trying to avoid undefined
behavior due to shifting an uint8_t for more than 8 bits.
2014-04-16 15:19:40 +02:00
antirez 430d0ade75 Remove a few useless operations from hllCount() fast path. 2014-04-16 15:19:40 +02:00
antirez 83650a72a1 HLLCOUNT 3x faster taking fast path for default params. 2014-04-16 15:19:40 +02:00
antirez 3afd25ecc7 Use processor base types in HLL_(GET|SET)_REGISTER.
This speedups the macros by a noticeable factor.
2014-04-16 15:19:40 +02:00
antirez 0ffd5e4a30 HyperLogLog: use precomputed table for 2^(-M[i]). 2014-04-16 15:19:40 +02:00
antirez ca6973d3b6 hll-err.rb: speedup using pipelining. 2014-04-16 15:19:40 +02:00
antirez 2500f2a334 hll-err.rb added to test error rate of Redis HyperLogLog. 2014-04-16 15:19:40 +02:00
antirez 42592a7faa HyperLogLog algorithm fixed in two ways.
There was an error in the computation of 2^register, and the sequence of
zeroes computed after the hashing did not included the "1".
2014-04-16 15:19:40 +02:00
antirez 3035075b2c HLLCOUNT implemented. 2014-04-16 15:19:40 +02:00
antirez 959d0f012a HLLADD implemented. 2014-04-16 15:19:33 +02:00
antirez ac29bb2ae4 hllAdd() low level HyperLogLog "add" implemented. 2014-04-16 15:19:05 +02:00
antirez 2a91f548c4 HyperLogLog: redefine constants using "P". 2014-04-16 15:19:05 +02:00
antirez 3f159bbd44 HLL_SET_REGISTER fixed.
There was an error in the first version of the macro.
Now the HLLSELFTEST test reports success.
2014-04-16 15:19:05 +02:00
antirez a18e880bf1 Use REDIS_HLL_REGISTER_MAX when possible. 2014-04-16 15:19:05 +02:00
antirez 00409d8742 HLL_(SET|GET)_REGISTER types fixed. 2014-04-16 15:19:05 +02:00
antirez de3f821af6 HLLSELFTEST command implemented.
To test the bitfield array of counters set/get macros from the Redis Tcl
suite is hard, so a specialized command that is able to test the
internals was developed.
2014-04-16 15:18:57 +02:00
antirez 02d88fb201 HyperLogLog: initial sketch of registers access. 2014-04-16 15:17:26 +02:00
antirez ddbdd3b0df Document TTL changes in the 2.6 -> 2.8 migration doc. 2014-03-27 15:53:14 +01:00
antirez d2e59c2715 Redis 2.8.8. 2014-03-25 11:30:42 +01:00
antirez 99fc582f8c Test: do not complain when "leaks" can't run because process died. 2014-03-25 09:34:11 +01:00
antirez 3580bb485a adjustOpenFilesLimit() refactoring.
In this commit:
* Decrement steps are semantically differentiated from the reserved FDs.
  Previously both values were 32 but the meaning was different.
* Make it clear that we save setrlimit errno.
* Don't explicitly handle wrapping of 'f', but prevent it from
  happening.
* Add comments to make the function flow more readable.

This integrates PR #1630
2014-03-25 09:07:21 +01:00
Matt Stancliff c3510af1c0 Fix potentially incorrect errno usage
errno may be reset by the previous call to redisLog, so capture
the original value for proper error reporting.
2014-03-25 09:07:21 +01:00
Matt Stancliff 771f8ad0e7 Add REDIS_MIN_RESERVED_FDS define for open fds
Also update the original REDIS_EVENTLOOP_FDSET_INCR to
include REDIS_MIN_RESERVED_FDS. REDIS_EVENTLOOP_FDSET_INCR
exists to make sure more than (maxclients+RESERVED) entries
are allocated, but we can only guarantee that if we include
the current value of REDIS_MIN_RESERVED_FDS as a minimum
for the INCR size.
2014-03-25 09:07:21 +01:00
Matt Stancliff 3ce742d1d5 Fix infinite loop on startup if ulimit too low
Fun fact: rlim_t is an unsigned long long on all platforms.

Continually subtracting from a rlim_t makes it get smaller
and smaller until it wraps, then you're up to 2^64-1.

This was causing an infinite loop on Redis startup if
your ulimit was extremely (almost comically) low.

The case of (f > oldlimit) would never be met in a case like:

    f = 150
    while (f > 20) f -= 128

Since f is unsigned, it can't go negative and would
take on values of:

    Iteration 1: 150 - 128 => 22
    Iteration 2:  22 - 128 => 18446744073709551510
    Iterations 3-∞: ...

To catch the wraparound, we use the previous value of f
stored in limit.rlimit_cur.  If we subtract from f and
get a larger number than the value it had previously,
we print an error and exit since we don't have enough
file descriptors to help the user at this point.

Thanks to @bs3g for the inspiration to fix this problem.
Patches existed from @bs3g at antirez#1227, but I needed to repair a few other
parts of Redis simultaneously, so I didn't get a chance to use them.
2014-03-25 09:07:21 +01:00
Matt Stancliff b59585396b Improve error handling around setting ulimits
The log messages about open file limits have always
been slightly opaque and confusing.  Here's an attempt to
fix their wording, detail, and meaning.  Users will have a
better understanding of how to fix very common problems
with these reworded messages.

Also, we handle a new error case when maxclients becomes less
than one, essentially rendering the server unusable.  We
now exit on startup instead of leaving the user with a server
unable to handle any connections.

This fixes antirez#356 as well.
2014-03-25 09:07:21 +01:00
Matt Stancliff 6826af1b50 Replace magic 32 with REDIS_EVENTLOOP_FDSET_INCR
32 was the additional number of file descriptors Redis
would reserve when managing a too-low ulimit.  The
number 32 was in too many places statically, so now
we use a macro instead that looks more appropriate.

When Redis sets up the server event loop, it uses:
    server.maxclients+REDIS_EVENTLOOP_FDSET_INCR

So, when reserving file descriptors, it makes sense to
reserve at least REDIS_EVENTLOOP_FDSET_INCR FDs instead
of only 32.  Currently, REDIS_EVENTLOOP_FDSET_INCR is
set to 128 in redis.h.

Also, I replaced the static 128 in the while f < old loop
with REDIS_EVENTLOOP_FDSET_INCR as well, which results
in no change since it was already 128.

Impact: Users now need at least maxclients+128 as
their open file limit instead of maxclients+32 to obtain
actual "maxclients" number of clients.  Redis will carve
the extra REDIS_EVENTLOOP_FDSET_INCR file descriptors it
needs out of the "maxclients" range instead of failing
to start (unless the local ulimit -n is too low to accomidate
the request).
2014-03-25 09:07:21 +01:00
Matt Stancliff 611372fa97 Fix maxclients error handling
Everywhere in the Redis code base, maxclients is treated
as an int with (int)maxclients or `maxclients = atoi(source)`,
so let's make maxclients an int.

This fixes a bug where someone could specify a negative maxclients
on startup and it would work (as well as set maxclients very high)
because:

    unsigned int maxclients;
    char *update = "-300";
    maxclients = atoi(update);
    if (maxclients < 1) goto fail;

But, (maxclients < 1) can only catch the case when maxclients
is exactly 0.  maxclients happily sets itself to -300, which isn't
-300, but rather 4294966996, which isn't < 1, so... everything
"worked."

maxclients config parsing checks for the case of < 1, but maxclients
CONFIG SET parsing was checking for case of < 0 (allowing
maxclients to be set to 0).  CONFIG SET parsing is now updated to
match config parsing of < 1.

It's tempting to add a MINIMUM_CLIENTS define, but... I didn't.

These changes were inspired by antirez#356, but this doesn't
fix that issue.
2014-03-25 09:07:21 +01:00
Matt Stancliff 3bd3240660 Sentinel: remove variable causing warning
GCC-4.9 warned about this, but clang didn't.

This commit fixes warning:
sentinel.c: In function 'sentinelReceiveHelloMessages':
sentinel.c:2156:43: warning: variable 'master' set but not used [-Wunused-but-set-variable]
     sentinelRedisInstance *ri = c->data, *master;
2014-03-25 08:06:04 +01:00
antirez 79349affd5 Fixed undefined variable value with certain code paths.
In sentinelFlushConfig() fd could be undefined when the following if
statement was true:

        if (rewrite_status == -1) goto werr;

This could cause random file descriptors to get closed.
2014-03-25 08:03:11 +01:00
Matt Stancliff 80dec5e4df Sentinel: Notify user when config can't be saved 2014-03-25 08:03:07 +01:00
Jan-Erik Rediger a2ec9a9068 Small typo fixed 2014-03-25 08:02:56 +01:00
Matt Stancliff 88c6c669c3 Fix data loss when save AOF/RDB with no free space
Previously, the (!fp) would only catch lack of free space
under OS X.  Linux waits to discover it can't write until
it actually writes contents to disk.

(fwrite() returns success even if the underlying file
has no free space to write into.  All the errors
only show up at flush/sync/close time.)

Fixes antirez/redis#1604
2014-03-24 21:56:22 +01:00
Jan-Erik Rediger 6182dad37c Finally fix the install_server.sh script.
Includes changes from a dozen bug reports and pull requests.
Was tested on Ubuntu, Debian and CentOS.
2014-03-24 18:12:58 +01:00
antirez 4ebc7e3738 Sample and cache RSS in serverCron().
Obtaining the RSS (Resident Set Size) info is slow in Linux and OSX.
This slowed down the generation of the INFO 'memory' section.

Since the RSS does not require to be a real-time measurement, we
now sample it with server.hz frequency (10 times per second by default)
and use this value both to show the INFO rss field and to compute the
fragmentation ratio.

Practically this does not make any difference for memory profiling of
Redis but speeds up the INFO call significantly.
2014-03-24 12:03:44 +01:00
antirez 72257f4b87 sdscatvprintf(): Try to use a static buffer.
For small content the function now tries to use a static buffer to avoid
a malloc/free cycle that is too costly when the function is used in the
context of performance critical code path such as INFO output generation.

This change was verified to have positive effects in the execution speed
of the INFO command.
2014-03-24 10:23:58 +01:00
antirez 7054f2a029 Cache uname() output across INFO calls.
Uname was profiled to be a slow syscall. It produces always the same
output in the context of a single execution of Redis, so calling it at
every INFO output generation does not make too much sense.

The uname utsname structure was modified as a static variable. At the
same time a static integer was added to check if we need to call uname
the first time.
2014-03-24 10:02:58 +01:00
antirez 7a20f09647 sdscatvprintf(): guess buflen using format length.
sdscatvprintf() uses a loop where it tries to output the formatted
string in a buffer of the initial length, if there was not enough room,
a buffer of doubled size is tried and so forth.

The initial guess for the buffer length was very poor, an hardcoded
"16". This caused the printf to be processed multiple times without a
good reason. Given that printf functions are already not fast, the
overhead was significant.

The new heuristic is to use a buffer 4 times the length of the format
buffer, and 32 as minimal size. This appears to be a good balance for
typical uses of the function inside the Redis code base.

This change improved INFO command performances 3 times.
2014-03-24 09:45:18 +01:00
antirez 001775f5fb Use 24 bits for the lru object field and improve resolution.
There were 2 spare bits inside the Redis object structure that are now
used in order to enlarge 4x the range of the LRU field.

At the same time the resolution was improved from 10 to 1 second: this
still provides 194 days before the LRU counter overflows (restarting from
zero).

This is not a problem since it only causes lack of eviction precision for
objects not touched for a very long time, and the lack of precision is
only temporary.
2014-03-21 11:19:28 +01:00
antirez c68189a19f Specify lruclock in redisServer structure via REDIS_LRU_BITS.
The padding field was totally useless: removed.
2014-03-21 11:17:52 +01:00
antirez ff8c81873a Set LRU parameters via REDIS_LRU_BITS define. 2014-03-21 11:17:36 +01:00
antirez e3b71a1c02 Unify stats reset for CONFIG RESETSTAT / initServer().
Now CONFIG RESETSTAT makes sure to reset all the fields, and in the
future it will be simpler to avoid missing new fields.
2014-03-21 11:16:12 +01:00
antirez 0937377aa2 Sentinel: sentinelRefreshInstanceInfo() minor refactoring.
Test sentinel.tilt condition on top and return if it is true.
This allows to remove the check for the tilt condition in the remaining
code paths of the function.
2014-03-21 11:16:12 +01:00
antirez 686839b477 Sentinel test: 02 unit better coverage + refactoring. 2014-03-21 11:16:12 +01:00
antirez 6d0e408a27 Sentinel test: foreach_instance_id implements 'break'. 2014-03-21 11:16:12 +01:00
antirez ba2edc4191 Sentinel: instance_is_killed proc added to sentinel.tcl. 2014-03-21 11:16:12 +01:00
antirez 9c2063fb8f Sentinel: propagate down-after-ms changes to slaves and sentinels. 2014-03-21 11:16:12 +01:00
antirez ffa8f479a5 Sentinel: down-after-milliseconds is not master-specific.
addReplySentinelRedisInstance() modified so that this field is displayed
for all the kind of instances: Sentinels, Masters, Slaves.
2014-03-21 11:16:12 +01:00
antirez 42091a79bb Sentinel failure detection implementation improved.
Failure detection in Sentinel is ping-pong based. It used to work by
remembering the last time a valid PONG reply was received, and checking
if the reception time was too old compared to the current current time.

PINGs were sent at a fixed interval of 1 second.

This works in a decent way, but does not scale well when we want to set
very small values of "down-after-milliseconds" (this is the node
timeout basically).

This commit reiplements the failure detection making a number of
changes. Some changes are inspired to Redis Cluster failure detection
code:

* A new last_ping_time field is added in representation of instances.
  If non zero, we have an active ping that was sent at the specified
  time. When a valid reply to ping is received, the field is zeroed
  again.
* last_ping_time is not reset when we reconnect the link or send a new
  ping, so from our point of view it represents the time we started
  waiting for the instance to reply to our pings without receiving a
  reply.
* last_ping_time is now used in order to check if the instance is
  timed out. This means that we can have a node timeout of 100
  milliseconds and yet the system will work well since the new check is
  not bound to the period used to send pings.
* Pings are now sent every second, or often if the value of
  down-after-milliseconds is less than one second. With a lower limit of
  10 HZ ping frequency.
* Link reconnection code was improved. This is used in order to try to
  reconnect the link when we are at 50% of the node timeout without a
  valid reply received yet. However the old code triggered unnecessary
  reconnections when the node timeout was very small. Now that should be
  ok.

The new code passes the tests but more testing is needed and more unit
tests stressing the failure detector, so currently this is merged only
in the unstable branch.
2014-03-21 11:16:11 +01:00
antirez 38241c4b1e Sentinel: use CLIENT SETNAME when connecting to Redis.
This makes debugging / monitoring of Sentinels simpler since you can
identify sentinels in CLIENT LIST output of Redis instances.
2014-03-21 11:16:11 +01:00
Matt Stancliff 9de0755869 Fix segfault from accessing array out of bounds
argc == 2; argv[2] == crash
2014-03-14 22:57:10 +01:00
antirez a31a0b4336 Sentinel: be safe under crash-recovery assumptions.
Sentinel's main safety argument is that there are no two configurations
for the same master with the same version (configuration epoch).

For this to be true Sentinels require to be authorized by a majority.
Additionally Sentinels require to do two important things:

* Never vote again for the same epoch.
* Never exchange an old vote for a fresh one.

The first prerequisite, in a crash-recovery system model, requires to
persist the master->leader_epoch on durable storage before to reply to
messages. This was not the case.

We also make sure to persist the current epoch in order to never reply
to stale votes requests from other Sentinels, after a recovery.

The configuration is persisted by making use of fsync(), this is
considered in the context of this code a good enough guarantee that
after a restart our durable state is restored, however this may not
always be the case depending on the kind of hardware and operating
system used.
2014-03-14 22:57:06 +01:00
antirez 6b0e36ffc6 Sentinel: fake PUBLISH command to receive HELLO messages.
Now the way HELLO messages are received is unified.
Now it is no longer needed for Sentinels to converge to the higher
configuration for a master to be able to chat via some Redis instance,
the are able to directly exchanges configurations.

Note that this commit does not include the (trivial) change needed to
send HELLO messages to Sentinel instances as well, since for an error I
committed the change in the previous commit that refactored hello
messages processing into a separated function.
2014-03-14 11:04:54 +01:00
antirez bd48ff69b0 Sentinel: HELLO processing refactored into sentinelProcessHelloMessage(). 2014-03-14 10:56:56 +01:00
antirez 3703112671 Linenoise updated, multiline mode enabled in redis-cli. 2014-03-13 15:12:04 +01:00
zhanghailei ccd2c18c1c According to context,the size should be 16 rather than 64 2014-03-11 10:12:19 +01:00
zhanghailei daa5d2a6c9 FIXED a typo more thank should be more than 2014-03-11 10:12:13 +01:00
zhanghailei ca0720b694 refer to updateLRUClock's comment REDIS_LRU_CLOCK_MAX is 22 bits,but #define REDIS_LRU_CLOCK_MAX ((1<<21)-1) only 21 bits 2014-03-11 10:12:00 +01:00
Matt Stancliff ffe742f92a Reset op_sec_last_sample_ops when reset requested
This value needs to be set to zero (in addition to
stat_numcommands) or else people may see
a negative operations per second count after they
run CONFIG RESETSTAT.

Fixes antirez/redis#1577
2014-03-11 10:10:54 +01:00
antirez 6fb9e2d717 Typo in sentinel.conf, exists -> exits. 2014-03-11 10:10:30 +01:00
antirez eb9e1526e6 DEBUG ERROR implemented.
The new "error" subcommand of the DEBUG command can reply with an user
selected error, specified as its sole argument:

    DEBUG ERROR "LOADING please wait..."

The error is generated just prefixing the command argument with a "-"
character, and replacing newlines with spaces (since error replies can't
include newlines).

The goal of the command is to help in Client libraries unit tests by
making simple to simulate a command call triggering a given error.
2014-03-10 23:04:37 +01:00
Matt Stancliff a6970570e3 Fix return value check for anetTcpAccept
anetTcpAccept returns ANET_ERR, not AE_ERR.

This isn't a physical error since both ANET_ERR
and AE_ERR are -1, but better to be consistent.
2014-03-10 15:47:20 +01:00
antirez fe0ab7d234 Fixed memory leak in SORT LIMIT option argument parsing on error. 2014-03-10 15:45:29 +01:00
antirez 464fef9bf8 Redis 2.8.7. 2014-03-05 14:42:50 +01:00
antirez aab16ead92 Cast saveparams[].seconds to long for %ld format specifier. 2014-03-05 11:26:50 +01:00
antirez 86afc0706d Merge branch '2.8' of github.com:/antirez/redis into 2.8 2014-03-05 10:22:56 +01:00
antirez 55b8f6ec1c Document why we update peak memory in INFO. 2014-03-05 10:16:20 +01:00
Matt Stancliff 647a261465 Force INFO used_memory_peak to match peak memory
used_memory_peak only updates in serverCron every server.hz,
but Redis can use more memory and a user can request memory
INFO before used_memory_peak gets updated in the next
cron run.

This patch updates used_memory_peak to the current
memory usage if the current memory usage is higher
than the recorded used_memory_peak value.

(And it only calls zmalloc_used_memory() once instead of
twice as it was doing before.)
2014-03-05 10:16:16 +01:00
antirez 9c66cd91be Sentinel test: Makefile target added. 2014-03-05 10:16:12 +01:00
michael-grunder 6991792943 Improved bigkeys with progress, pipelining and summary
This commit reworks the redis-cli --bigkeys command to provide more
information about our progress as well as output summary information
when we're done.

 - We now show an approximate percentage completion as we go
 - Hiredis pipelining is used for TYPE and SIZE retreival
 - A summary of keyspace distribution and overall breakout at the end
2014-03-05 10:16:06 +01:00
antirez 7d65b7199a BITPOS fuzzy testing. 2014-03-05 10:16:02 +01:00
antirez c19cfde65d Basic BITPOS tests. 2014-03-05 10:15:55 +01:00
antirez 55f4b20f31 Sentinel test: set less time sensitive defaults.
This commit sets the failover timeout to 30 seconds instead of the 180
seconds default, and allows to reconfigure multiple slaves at the same
time.

This makes tests less sensible to timing, with the result that there are
less false positives due to normal behaviors that require time to
succeed or to be retried.

However the long term solution is probably some way in order to detect
when a test failed because of timing issues (for example split brain
during leader election) and retry it.
2014-03-05 10:15:32 +01:00
antirez 1606978a0b Sentinel: more aggressive failover start desynchronization.
Sentinel needs to avoid split brain conditions due to multiple sentinels
trying to get voted at the exact same time.

So far some desynchronization was provided by fluctuating server.hz,
that is the frequency of the timer function call. However the
desynchonization provided in this way was not enough when using many
Sentinel instances, especially when a large quorum value is used in
order to force a greater degree of agreement (more than N/2+1).

It was verified that it was likely to trigger a split brain
condition, forcing the system to try again after a timeout.
Usually the system will succeed after a few retries, but this is not
optimal.

This commit desynchronizes instances in a more effective way to make it
likely that the first attempt will be successful.
2014-03-05 10:15:32 +01:00
antirez 6200191943 CONFIG REWRITE should be logged at WARNING level. 2014-03-05 10:15:32 +01:00
antirez 2af2173a60 Sentinel test: debugging console improved. 2014-03-05 10:15:32 +01:00
antirez 1dc1e31c2b Sentinel test: initial debugging console. 2014-03-05 10:15:32 +01:00
antirez b132121a71 Sentinel test: be more patient in create_redis_master_slave_cluster. 2014-03-05 10:15:32 +01:00
antirez d1d706c923 Sentiel test: add test start time in output. 2014-03-05 10:15:32 +01:00
antirez c99cd2fd40 Sentinel test: use 1000 as retry in initial 00 unit test. 2014-03-05 10:15:32 +01:00
antirez f33d4a6c24 Sentinel test: initial tests in 03 unit. 2014-03-05 10:15:32 +01:00
antirez c2d99d49b5 Sentinel test: foreach_instance_id now supports 'continue'. 2014-03-05 10:15:32 +01:00
antirez e40f8378bd Sentienl test: fixed typo in unit 03 top comment. 2014-03-05 10:15:32 +01:00
antirez a0335e18ba Sentinel test: Makefile target added. 2014-02-28 16:00:17 +01:00
antirez 950cb76e7b BITPOS fuzzy testing. 2014-02-27 15:58:34 +01:00
antirez 42e3630d77 Basic BITPOS tests. 2014-02-27 15:58:30 +01:00
antirez 1892b56224 warnigns -> warnings in redisBitpos(). 2014-02-27 15:58:27 +01:00
antirez 2c8036f7b2 More consistent BITPOS behavior with bit=0 and ranges.
With the new behavior it is possible to specify just the start in the
range (the end will be assumed to be the first byte), or it is possible
to specify both start and end.

This is useful to change the behavior of the command when looking for
zeros inside a string.

1) If the user specifies both start and end, and no 0 is found inside
   the range, the command returns -1.

2) If instead no range is specified, or just the start is given, even
   if in the actual string no 0 bit is found, the command returns the
   first bit on the right after the end of the string.

So for example if the string stored at key foo is "\xff\xff":

    BITPOS foo (returns 16)
    BITPOS foo 0 -1 (returns -1)
    BITPOS foo 0 (returns 16)

The idea is that when no end is given the user is just looking for the
first bit that is zero and can be set to 1 with SETBIT, as it is
"available". Instead when a specific range is given, we just look for a
zero within the boundaries of the range.
2014-02-27 15:58:21 +01:00
antirez 3294f74fef Initial implementation of BITPOS.
It appears to work but more stress testing, and both unit tests and
fuzzy testing, is needed in order to ensure the implementation is sane.
2014-02-27 15:58:14 +01:00
antirez 82d2e295b8 Added two more BITCOUNT tests stressing misaligned access. 2014-02-27 15:57:56 +01:00
antirez a3eb3f9c3b BITCOUNT fuzzy test with random start/end added.
It was verified in practice that this test is able to stress much more
the implementation by introducing errors that were only trivially to
detect with different offsets but impossible to detect starting always
at zero and counting bits the full length of the string.
2014-02-27 15:57:53 +01:00
antirez 30a92b6c76 Fix misaligned word access in redisPopcount(). 2014-02-27 15:57:49 +01:00
antirez c1cc28f230 warnigns -> warnings in redisBitpos(). 2014-02-27 15:55:56 +01:00
antirez d79f9ebdb5 More consistent BITPOS behavior with bit=0 and ranges.
With the new behavior it is possible to specify just the start in the
range (the end will be assumed to be the first byte), or it is possible
to specify both start and end.

This is useful to change the behavior of the command when looking for
zeros inside a string.

1) If the user specifies both start and end, and no 0 is found inside
   the range, the command returns -1.

2) If instead no range is specified, or just the start is given, even
   if in the actual string no 0 bit is found, the command returns the
   first bit on the right after the end of the string.

So for example if the string stored at key foo is "\xff\xff":

    BITPOS foo (returns 16)
    BITPOS foo 0 -1 (returns -1)
    BITPOS foo 0 (returns 16)

The idea is that when no end is given the user is just looking for the
first bit that is zero and can be set to 1 with SETBIT, as it is
"available". Instead when a specific range is given, we just look for a
zero within the boundaries of the range.
2014-02-27 15:55:47 +01:00
antirez 25e2791ec3 Initial implementation of BITPOS.
It appears to work but more stress testing, and both unit tests and
fuzzy testing, is needed in order to ensure the implementation is sane.
2014-02-27 15:54:31 +01:00
antirez c955b47d62 Added two more BITCOUNT tests stressing misaligned access. 2014-02-27 15:53:40 +01:00
antirez bfe4c560e6 BITCOUNT fuzzy test with random start/end added.
It was verified in practice that this test is able to stress much more
the implementation by introducing errors that were only trivially to
detect with different offsets but impossible to detect starting always
at zero and counting bits the full length of the string.
2014-02-27 15:53:40 +01:00
antirez 580ed0768f Fix misaligned word access in redisPopcount(). 2014-02-27 15:53:40 +01:00
antirez d90e3e3a70 Sentinel test: add stub for unit 04. 2014-02-25 15:55:37 +01:00
antirez 89d95ec2e7 Sentinel test: added TODO items in 02 unit. 2014-02-25 15:55:37 +01:00
antirez 65194d11c3 redis-cli: also remove useless uint8_t. 2014-02-25 15:09:15 +01:00
antirez 16c2189d4e redis-cli: don't use uint64_t where actually not needed.
The computation is just something to take the CPU busy, no need to use a
specific type. Since stdint.h was not included this prevented
compilation on certain systems.
2014-02-25 15:09:15 +01:00
antirez 1b2bcd4210 redis-cli: check argument existence for --pattern. 2014-02-25 15:09:15 +01:00
antirez 6ec3dc2c42 redis-cli: --intrinsic-latency run mode added. 2014-02-25 15:09:15 +01:00
antirez 02ae6cb01a redis-cli: added comments to split program in parts. 2014-02-25 15:09:15 +01:00
antirez fe05112029 Sentinel test: check role at end of unit 01. 2014-02-25 15:08:41 +01:00
michael-grunder cf76fdd04f Update --bigkeys to use SCAN
This commit changes the findBigKeys() function in redis-cli.c to use the new
SCAN command for iterating the keyspace, rather than RANDOMKEY.  Because we
can know when we're done using SCAN, it will exit after exhausting the keyspace.
2014-02-25 15:08:38 +01:00
antirez 9e34e296cf Sentinel test: kill masters instead of using DEBUG SLEEP in all tests. 2014-02-25 15:08:34 +01:00
antirez dfacfafc2a Sentinel test: restart instances left killed by previous unit.
An unit can abort in the middle for an error. The next unit should not
assume that the instances are in a clean state, and must restart what
was left killed.
2014-02-25 10:30:35 +01:00
antirez 2fab983c69 Sentinel test: jump to next unit on test failure.
Sentinel tests are designed to be dependent on the previous tests in the
same unit, so usually we can't continue with the next test in the same
unit if a previous test failed.
2014-02-25 10:30:35 +01:00
antirez a6d560db64 Sentinel test: test majority crashing Sentinels.
The test was previously performed by removing the master from the
Sentinel monitored masters. The test with the Sentinels crashed is
more similar to real-world partitions / failures.
2014-02-25 10:30:35 +01:00
antirez 3f16fee39a Sentinel test: restart_instance should refresh pid attrib.
Also kill_instance was modified to warn when a test will try to kill the
same instance multiple times for error.
2014-02-25 10:30:35 +01:00
antirez e8e2ac072c Sentinel test: more stuff mored from 00-base to init.
The area a number of mandatory tests to craete a stable setup for
testing that is not too sensitive to timing issues. All those tests
moved to includes/init-tests, and marked as (init).
2014-02-25 10:30:35 +01:00
antirez 85fa77e02e Sentinel: log quorum with +monitor event. 2014-02-25 10:30:35 +01:00
antirez 2d48d7c2fc Sentinel test: removed useless code to set SDOWN timeout.
The new common initialization code used to start a new unit already set
the timeout to 2000 milliseconds.
2014-02-25 10:30:35 +01:00
antirez 6e6106797e Sentinel: generate +monitor events at startup. 2014-02-25 10:30:35 +01:00
antirez 96162c0c58 Sentinel: log +monitor and +set events.
Now that we have a runtime configuration system, it is very important to
be able to log how the Sentinel configuration changes over time because
of API calls.
2014-02-25 10:30:35 +01:00
antirez 39eacde136 Sentinel: added missing exit(1) after checking for config file. 2014-02-25 10:30:35 +01:00
antirez 28aa15a6ea Sentinel test: tmp dir and gitignore added. 2014-02-25 10:30:35 +01:00
antirez 3917224f7c Sentinel test: minor fixes to --pause-on-error. 2014-02-25 10:30:35 +01:00
antirez 001ab29915 Sentinel test: --pause-on-error option added.
Pause the test with running instances available for state inspection on
error.
2014-02-25 10:30:35 +01:00
antirez d31a394148 Sentinel test: added empty units to fill later. 2014-02-25 10:30:35 +01:00
antirez d83ab8f9d4 Sentinel: IDONTKNOW error removed.
This error was conceived for the older version of Sentinel that worked
via master redirection and that was not able to get configuration
updates from other Sentinels via the Pub/Sub channel of masters or
slaves.

This reply does not make sense today, every Sentinel should reply with
the best information it has currently. The error will make even more
sense in the future since the plan is to allow Sentinels to update the
configuration of other Sentinels via gossip with a direct chat without
the prerequisite that they have at least a monitored instance in common.
2014-02-25 10:30:35 +01:00
antirez 1dfab3d107 Sentinel test: framework improved and conf-update unit added.
It is now possible to kill and restart sentinel or redis instances for
more real-world testing.

The 01 unit tests the capability of Sentinel to update the configuration
of Sentinels rejoining the cluster, however the test is pretty trivial
and more tests should be added.
2014-02-25 10:30:35 +01:00
antirez aee165e084 Sentinel test: move init tests as includes.
Most units will start with these two basic tests to create an
environment where the real tests are ran.
2014-02-25 10:30:35 +01:00
antirez 73b290cc5b Sentinel test: ability to run just a subset of test files. 2014-02-25 10:30:35 +01:00
Matt Stancliff 3ced78dc6c Auto-enter slaveMode when SYNC from redis-cli
If someone asks for SYNC or PSYNC from redis-cli,
automatically enter slaveMode (as if they ran
redis-cli --slave) and continue printing the replication
stream until either they Ctrl-C or the master gets disconnected.
2014-02-25 10:30:35 +01:00
antirez 6441a41f94 Sentinel: report instances role switch events.
This is useful mostly for debugging of issues.
2014-02-20 12:28:03 +01:00
antirez 05dde71919 Sentinel test: some reliability fixes to 00-base tests. 2014-02-20 12:28:03 +01:00
antirez c499d82f28 Sentinel test: check that role matches at end of 00-base. 2014-02-20 12:28:03 +01:00
antirez c01aff1234 Sentinel test: ODOWN and agreement. 2014-02-20 12:28:03 +01:00
antirez 56cb037a86 Sentinel test: check reconfig of slaves and old master. 2014-02-20 12:28:03 +01:00
antirez d4edf63bc4 Sentinel test: basic failover tested. Framework improvements. 2014-02-20 12:28:02 +01:00
antirez 722d4f0c41 Sentinel test: basic tests for MONITOR and auto-discovery. 2014-02-20 12:28:02 +01:00
antirez b26660e130 Sentinel test: info fields, master-slave setup, fixes. 2014-02-20 12:28:02 +01:00
antirez ac5fd5a519 Prefix test file names with numbers to force exec order. 2014-02-20 12:28:02 +01:00
antirez 890559ea1c Sentinel test: provide basic commands to access instances. 2014-02-20 12:28:02 +01:00
antirez 905c55d5ae Sentinel: SENTINEL_SLAVE_RECONF_RETRY_PERIOD -> RECONF_TIMEOUT
Rename define to match the new meaning.
2014-02-18 10:30:29 +01:00
antirez 1b345ec3f0 Sentinel: fix slave promotion timeout.
If we can't reconfigure a slave in time during failover, go forward as
anyway the slave will be fixed by Sentinels in the future, once they
detect it is misconfigured.

Otherwise a failover in progress may never terminate if for some reason
the slave is uncapable to sync with the master while at the same time
it is not disconnected.
2014-02-18 10:30:28 +01:00
antirez 351e790032 Sentinel: initial testing framework.
Nothing tested at all so far... Just the infrastructure spawning N
Sentinels and N Redis instances that the test will use again and again.
2014-02-17 17:38:58 +01:00
antirez f71c7de88e Test: colorstr moved to util.tcl. 2014-02-17 17:38:54 +01:00
antirez 0886c98fef Test: code to test server availability refactored.
Some inline test moved into server_is_up procedure.
Also find_available_port was moved into util since it is going
to be used for the Sentinel test as well.
2014-02-17 12:29:54 +01:00
antirez 4237f14a8a Get absoulte config file path before processig 'dir'.
The code tried to obtain the configuration file absolute path after
processing the configuration file. However if config file was a relative
path and a "dir" statement was processed reading the config, the absolute
path obtained was wrong.

With this fix the absolute path is obtained before processing the
configuration while the server is still in the original directory where
it was executed.
2014-02-17 12:14:19 +01:00
antirez 5efee4f0bb Sentinel: better specify startup errors due to config file.
Now it logs the file name if it is not accessible. Also there is a
different error for the missing config file case, and for the non
writable file case.
2014-02-17 12:10:12 +01:00
antirez 1d4d9e7b14 Redis 2.8.6. 2014-02-13 15:43:40 +01:00
antirez 85492dcfef Update cached time in rdbLoad() callback.
server.unixtime and server.mstime are cached less precise timestamps
that we use every time we don't need an accurate time representation and
a syscall would be too slow for the number of calls we require.

Such an example is the initialization and update process of the last
interaction time with the client, that is used for timeouts.

However rdbLoad() can take some time to load the DB, but at the same
time it did not updated the time during DB loading. This resulted in the
bug described in issue #1535, where in the replication process the slave
loads the DB, creates the redisClient representation of its master, but
the timestamp is so old that the master, under certain conditions, is
sensed as already "timed out".

Thanks to @yoav-steinberg and Redis Labs Inc for the bug report and
analysis.
2014-02-13 15:13:35 +01:00
antirez eb10bb31ac Log when CONFIG REWRITE goes bad. 2014-02-13 14:33:50 +01:00
antirez ebdb37cea7 Test: regression for issue #1549.
It was verified that reverting the commit that fixes the bug, the test
no longer passes.
2014-02-13 12:27:08 +01:00
antirez 9b73a274ac Fix script cache bug in the scripting engine.
This commit fixes a serious Lua scripting replication issue, described
by Github issue #1549. The root cause of the problem is that scripts
were put inside the script cache, assuming that slaves and AOF already
contained it, even if the scripts sometimes produced no changes in the
data set, and were not actaully propagated to AOF/slaves.

Example:

    eval "if tonumber(KEYS[1]) > 0 then redis.call('incr', 'x') end" 1 0

Then:

    evalsha <sha1 step 1 script> 1 0

At this step sha1 of the script is added to the replication script cache
(the script is marked as known to the slaves) and EVALSHA command is
transformed to EVAL. However it is not dirty (there is no changes to db),
so it is not propagated to the slaves. Then the script is called again:

    evalsha <sha1 step 1 script> 1 1

At this step master checks that the script already exists in the
replication script cache and doesn't transform it to EVAL command. It is
dirty and propagated to the slaves, but they fail to evaluate the script
as they don't have it in the script cache.

The fix is trivial and just uses the new API to force the propagation of
the executed command regardless of the dirty state of the data set.

Thank you to @minus-infinity on Github for finding the issue,
understanding the root cause, and fixing it.
2014-02-13 12:16:34 +01:00
antirez 96973a7c33 AOF write error: retry with a frequency of 1 hz. 2014-02-12 16:57:17 +01:00
antirez fadbbdd3f4 AOF: don't abort on write errors unless fsync is 'always'.
A system similar to the RDB write error handling is used, in which when
we can't write to the AOF file, writes are no longer accepted until we
are able to write again.

For fsync == always we still abort on errors since there is currently no
easy way to avoid replying with success to the user otherwise, and this
would violate the contract with the user of only acknowledging data
already secured on disk.
2014-02-12 16:57:13 +01:00
antirez 688d32e16b Don't count time to feed MONITORs in SLOWLOG. 2014-02-07 18:29:26 +01:00
antirez 3e4968339b Sentinel: allow SHUTDOWN command in Sentinel mode. 2014-02-07 11:22:30 +01:00
antirez 301a0cfc69 Check for EAGAIN in sendBulkToSlave().
Sometime an osx master with a Linux server over a slow link caused
a strange error where osx called the writable function for
the socket but actually apparently there was no room in the socket
buffer to accept the write: write(2) call returned an EAGAIN error,
that was not checked, so we considered write(2) == 0 always as a connection
reset, which was unfortunate since the bulk transfer has to start again.

Also more errors are logged with the WARNING level in the same code path
now.
2014-02-05 16:41:04 +01:00
antirez 4e809a9a19 Redis 2.8.5. 2014-02-04 11:17:21 +01:00
antirez ddcf160309 Move mstime_t define outside sentinel.c.
The define is now used in other parts of Redis 2.8 tree instead of long
long.

A nice side effect is that now 2.8 and unstable sentinel.c files are
identical as it should be.
2014-02-03 16:34:46 +01:00
antirez c5bc592650 Scripting: expire keys in scripts only at first access.
Keys expiring in the middle of the execution of Lua scripts are to
create inconsistencies in masters and / or AOF files. See the following
example:

    if redis.call("exists",KEYS[1]) == 1
    then
        redis.call("incr","mycounter")
    end

    if redis.call("exists",KEYS[1]) == 1
    then
        return redis.call("incr","mycounter")
    end

The script executes two times the same *if key exists then incrementcounter*
logic. However the two executions will work differently in the master and
the slaves, provided some unlucky timing happens.

In the master the first time the key may still exist, while the second time
the key may no longer exist. This will result in the key incremented just one
time. However as a side effect the master will generate a synthetic
`DEL` command in the replication channel in order to force the slaves to
expire the key (given that key expiration is master-driven).

When the same script will run in the slave, the key will no longer be
there, so the script will not increment the key.

The key idea used to implement the expire-at-first-lookup semantics was
provided by Marc Gravell.
2014-02-03 16:29:25 +01:00
antirez 5201ca0ca1 Allow CONFIG and SHUTDOWN while in stale-slave state. 2014-02-03 15:51:07 +01:00
antirez 3da5cbe5bb Scripting: use mstime() and mstime_t for lua_time_start.
server.lua_time_start is expressed in milliseconds. Use mstime_t instead
of long long, and populate it with mstime() instead of ustime()/1000.

Functionally identical but more natural.
2014-02-03 15:46:47 +01:00
PatrickJS 0be31e2d22 update copyright year 2014-02-03 11:19:25 +01:00
antirez 66304fb122 Test: fixed osx msg passing issue in testing framework.
The Redis test uses a server-clients model in order to parallelize the
execution of different tests. However in recent versions of osx not
setting the channel to a binary encoding caused issues even if AFAIK no
binary data is really sent via this channel. However now the channels
are deliberately set to a binary encoding and this solves the issue.

The exact issue was the test not terminating and giving the impression
of running forever, since test clients or servers were unable to
exchange the messages to continue.
2014-01-31 16:25:13 +01:00
antirez 1406112ec4 Redis.conf comment about tcp-backlog option improved. 2014-01-31 15:03:25 +01:00
antirez 917b851491 Option "backlog" renamed "tcp-backlog".
This is especially important since we already have a concept of backlog
(the replication backlog).
2014-01-31 15:03:22 +01:00
Nenad Merdanovic 8dda9dbef0 Add support for listen(2) backlog definition
In high RPS environments, the default listen backlog is not sufficient, so
giving users the power to configure it is the right approach, especially
since it requires only minor modifications to the code.
2014-01-31 15:03:19 +01:00
antirez f0652c37a5 Sentinel: check arity for SENTINEL MASTER command.
This fixes issue #1530.
2014-01-31 10:14:07 +01:00
antirez a2c9d38a9f SENTINEL SET master quorum implemented. 2014-01-28 11:23:51 +01:00
antirez 89a731b3aa Fixed inverted if condition in MISCONF error code path. 2014-01-28 10:10:56 +01:00
antirez ecfefde760 Don't log MONITOR clients as disconnecting slaves. 2014-01-25 11:54:02 +01:00
antirez 43503ae5d6 redis-cli --help output improved with --scan and periods. 2014-01-22 12:08:08 +01:00
antirez b9c84d4aef redis-cli: support for --scan option. 2014-01-22 12:08:04 +01:00
antirez 248e916550 Use fflush() before fsync() in rio.c.
Incremental flushing in rio.c is only used to avoid huge kernel buffers
synched to slow disks creating big latency spikes, so this fix has no
durability implications, however it is certainly more correct to make
sure that the FILE buffers are flushed to the kernel before calling
fsync on the file descriptor.

Thanks to Li Shao Kai for reporting this issue in the Redis mailing
list.
2014-01-22 09:56:35 +01:00
antirez d401022d14 Fix typo in aofRewriteBufferAppend() comment. 2014-01-14 15:38:26 +01:00
antirez 4ea91aadfb Set REDIS_AOF_REWRITE_MIN_SIZE to 64mb.
64mb is the default value in redis.conf. For some reason instead the
hard-coded default was 1mb that is too small.
2014-01-14 11:28:18 +01:00
antirez 71f7eb2590 Redis 2.8.4. 2014-01-13 17:09:58 +01:00
antirez df00b6ac05 SENTINEL SET: error on bad option name + flush config on error. 2014-01-13 16:39:56 +01:00
antirez b8db8a0c48 SENTINEL SET implemented.
The new command allows to change master-specific configurations
at runtime. All the settable parameters can be retrivied via the
SENTINEL MASTER command, so there is no equivalent "GET" command.
2014-01-13 16:39:51 +01:00
antirez 8ed0b49525 Sentinel: fix wrong arity error message. 2014-01-13 16:39:47 +01:00
antirez 560e548dc6 Sentinel: SENTINEL REMOVE command added.
The command totally removes a monitored master.
2014-01-13 16:39:44 +01:00
antirez 0ca750d94a Sentinel: releaseSentinelRedisInstance() top comment fixed.
The claim about unlinking the instance from the connected hash tables
was the opposite of the reality. Also the current actual behavior is
safer in most cases, so it is better to manually unlink when needed.
2014-01-13 16:39:40 +01:00
antirez 1f9580c125 Sentinel: flush config on disk when new master is added. 2014-01-13 16:39:36 +01:00
antirez bb207007ca anetResolveIP() prototype added to anet.h. 2014-01-13 16:39:31 +01:00
antirez b7dc3204a6 Sentinel: SENTINEL MONITOR command implemented.
It allows to add new masters to monitor at runtime.
2014-01-13 16:39:27 +01:00
antirez 2e0ba7bb5b anetResolveIP() added to anet.c.
The new function is used when we want to normalize an IP address without
performing a DNS lookup if the string to resolve is not a valid IP.

This is useful every time only IPs are valid inputs or when we want to
skip DNS resolution that is slow during runtime operations if we are
required to block.
2014-01-13 16:39:24 +01:00
antirez 52cf0975c7 Sentinel: added SENTINEL MASTER <name> command.
With SENTINEL MASTERS it was already possible to list all the configured
masters, but not a specific one.
2014-01-13 16:39:18 +01:00
antirez 6bcc370c9d Add all the configurable fields to addReplySentinelRedisInstance().
Note: the auth password with the master is voluntarily not exposed.
2014-01-13 16:39:15 +01:00
antirez ea2bffa030 Trip comment to 80 cols in SentinelCommand(). 2014-01-13 16:39:11 +01:00
antirez bca65866ff Test: regression for issues #1483. 2014-01-09 11:19:11 +01:00
antirez e4c51759f5 Fix RESTORE ttl handling in 32 bit archs.
long was used instead of long long in order to handle a 64 bit
resolution millisecond timestamp.

This fixes issue #1483.
2014-01-09 11:12:53 +01:00
antirez 51f71e2d8e Fix keyspace events flags-to-string conversion.
Fixes issue #1491 on Github.
2014-01-08 17:18:00 +01:00
antirez 05d219a6fa Test: stress events flags to/from string conversion. 2014-01-08 17:16:09 +01:00
antirez 2a1a31ca9d Don't send REPLCONF ACK to old masters.
Masters not understanding REPLCONF ACK will reply with errors to our
requests causing a number of possible issues.

This commit detects a global replication offest set to -1 at the end of
the replication, and marks the client representing the master with the
REDIS_PRE_PSYNC flag.

Note that this flag was called REDIS_PRE_PSYNC_SLAVE but now it is just
REDIS_PRE_PSYNC as it is used for both slaves and masters starting with
this commit.

This commit fixes issue #1488.
2014-01-08 14:27:49 +01:00
antirez 418d3d358a Clarify a comment in slaveTryPartialResynchronization(). 2014-01-08 14:11:02 +01:00
antirez 0a1a236e3e Log disconnection with slave only when ip:port is available. 2013-12-25 18:41:10 +01:00
antirez 27d06111db anetPeerToString / SockName: port can be NULL on errors too. 2013-12-25 18:39:49 +01:00
antirez 5b7c16137d anetTcpGenericConnect() bug introduced in 9d19977 fixed.
Durign a refactoring I mispelled _port for port.
This is one of the reasons I never used _varname myself.
2013-12-25 18:38:33 +01:00
antirez d07d4a876c Remove useless goto from anetTcpGenericConnect(). 2013-12-25 18:24:04 +01:00
antirez 9d1997706c anetTcpGenericConnect() code improved + 1 bug fix.
Now the socket is closed if anetNonBlock() fails, and in general the
code structure makes it harder to introduce this kind of bugs in the
future.

Reference: pull request #1059.
2013-12-25 18:16:46 +01:00
antirez 4ad219adc8 Fix CONFIG REWRITE handling of unknown options.
There were two problems with the implementation.

1) "save" was not correctly processed when no save point was configured,
   as reported in issue #1416.
2) The way the code checked if an option existed in the "processed"
   dictionary was wrong, as we add the element with as a key associated
   with a NULL value, so dictFetchValue() can't be used to check for
   existance, but dictFind() must be used, that returns NULL only if the
   entry does not exist at all.
2013-12-23 12:50:52 +01:00
antirez c6db326d1d Configuring port to 0 disables IP socket as specified.
This was no longer the case with 2.8 becuase of a bug introduced with
the IPv6 support. Now it is fixed.

This fixes issue #1287 and #1477.
2013-12-23 11:34:15 +01:00
antirez 4456ee1173 Make new masters inherit replication offsets.
Currently replication offsets could be used into a limited way in order
to understand, out of a set of slaves, what is the one with the most
updated data. For example this comparison is possible of N slaves
were replicating all with the same master.

However the replication offset was not transferred from master to slaves
(that are later promoted as masters) in any way, so for instance if
there were three instances A, B, C, with A master and B and C
replication from A, the following could happen:

C disconnects from A.
B is turned into master.
A is switched to master of B.
B receives some write.

In this context there was no way to compare the offset of A and C,
because B would use its own local master replication offset as
replication offset to initialize the replication with A.

With this commit what happens is that when B is turned into master it
inherits the replication offset from A, making A and C comparable.
In the above case assuming no inconsistencies are created during the
disconnection and failover process, A will show to have a replication
offset greater than C.

Note that this does not mean offsets are always comparable to understand
what is, in a set of instances, since in more complex examples the
replica with the higher replication offset could be partitioned away
when picking the instance to elect as new master. However this in
general improves the ability of a system to try to pick a good replica
to promote to master.
2013-12-22 11:54:10 +01:00
antirez 5fa937d956 Slave disconnection is an event worth logging. 2013-12-22 10:16:02 +01:00
antirez 9ba9d78ada Log when a slave lose the connection with its master. 2013-12-21 00:24:29 +01:00
antirez d33f3689d7 Clarify include directive behavior in example redis.conf. 2013-12-19 16:03:52 +01:00
antirez fb8a480f54 CONFIG REWRITE: no special handling or include and rename-command.
CONFIG REWRITE is now wiser and does not touch what it does not
understand inside redis.conf.
2013-12-19 16:03:49 +01:00
Yubao Liu d62bbfda16 CONFIG REWRITE: don't throw some options on config rewrite
Those options will be thrown without this patch:
  include, rename-command, min-slaves-to-write, min-slaves-max-lag,
appendfilename.
2013-12-19 16:03:37 +01:00
antirez 090bcc946f CONFIG REWRITE: old development comments removed. 2013-12-19 16:02:50 +01:00
antirez ddd529bc3b CONFIG REWRITE: don't wipe unknown options.
With this commit options not explicitly rewritten by CONFIG REWRITE are
not touched at all. These include new options that may not have support
for REWRITE, and other special cases like rename-command and include.
2013-12-19 16:02:46 +01:00
antirez 673c42bb4e Example redis.conf formatted to better show appendfilename option. 2013-12-19 10:19:13 +01:00
antirez 33f6f35f34 Makefile.dep updated. 2013-12-13 13:14:47 +01:00
antirez 8eb1cb3b52 SDIFF iterator misuse bug regression test added.
See commit c00453d for more info about the bug.
2013-12-13 11:37:35 +01:00
antirez 993e0ede76 SDIFF iterator misuse fixed in diff algorithm #1.
The bug could be easily triggered by:

    SADD foo a b c 1 2 3 4 5 6
    SDIFF foo foo

When the key was the same in two sets, an unsafe iterator was used to
check existence of elements in the same set we were iterating.
Usually this would just result into a wrong output, however with the
dict.c API misuse protection we have in place, the result was actually
an assertion failed that was triggered by the CI test, while creating
random datasets for the "MASTER and SLAVE consistency" test.
2013-12-13 11:29:59 +01:00
antirez 2507e366b2 Sentinel: dead code removed. 2013-12-13 11:01:20 +01:00
antirez f9e9448a5b Makefile: remove odd syntax not compatible with some make versions.
See issue #1448.
2013-12-12 15:19:29 +01:00
Yubao Liu 45c60a903a fix typo in redis.conf and sentinel.conf 2013-12-12 11:32:05 +01:00
codeeply 5e75e681fe comment mistake fixed 2013-12-12 11:25:48 +01:00
antirez 8995634014 Redis 2.8.3. 2013-12-11 15:31:57 +01:00
antirez 9a8ae5a553 Replication: publish the slave_repl_offset when disconnected from master.
When a slave was disconnected from its master the replication offset was
reported as -1. Now it is reported as the replication offset of the
previous master, so that failover can be performed using this value in
order to try to select a slave with more processed data from a set of
slaves of the old master.
2013-12-11 15:24:50 +01:00
Yossi Gottlieb 0ff078d8d0 Return proper error on requests with an unbalanced number of quotes. 2013-12-11 13:21:55 +01:00
Yossi Gottlieb 25ba2e9607 Fix wrong repldboff type which causes dropped replication in rare cases. 2013-12-11 11:37:58 +01:00
antirez 563d6b3f98 Slaves heartbeats during sync improved.
The previous fix for false positive timeout detected by master was not
complete. There is another blocking stage while loading data for the
first synchronization with the master, that is, flushing away the
current data from the DB memory.

This commit uses the newly introduced dict.c callback in order to make
some incremental work (to send "\n" heartbeats to the master) while
flushing the old data from memory.

It is hard to write a regression test for this issue unfortunately. More
support for debugging in the Redis core would be needed in terms of
functionalities to simulate a slow DB loading / deletion.
2013-12-10 18:42:22 +01:00
antirez b6610a569d dict.c: added optional callback to dictEmpty().
Redis hash table implementation has many non-blocking features like
incremental rehashing, however while deleting a large hash table there
was no way to have a callback called to do some incremental work.

This commit adds this support, as an optiona callback argument to
dictEmpty() that is currently called at a fixed interval (one time every
65k deletions).
2013-12-10 18:18:36 +01:00
antirez 26cf5c8ac6 Log empty DB + Loading data into two separated messages. 2013-12-10 17:51:16 +01:00
antirez 303cc97ff9 Don't send more than 1 newline/sec while loading RDB. 2013-12-10 17:50:26 +01:00
antirez 75bf5a4a4a Slaves heartbeat while loading RDB files.
Starting with Redis 2.8 masters are able to detect timed out slaves,
while before 2.8 only slaves were able to detect a timed out master.

Now that timeout detection is bi-directional the following problem
happens as described "in the field" by issue #1449:

1) Master and slave setup with big dataset.
2) Slave performs the first synchronization, or a full sync
   after a failed partial resync.
3) Master sends the RDB payload to the slave.
4) Slave loads this payload.
5) Master detects the slave as timed out since does not receive back the
   REPLCONF ACK acknowledges.

Here the problem is that the master has no way to know how much the
slave will take to load the RDB file in memory. The obvious solution is
to use a greater replication timeout setting, but this is a shame since
for the 0.1% of operation time we are forced to use a timeout that is
not what is suited for 99.9% of operation time.

This commit tries to fix this problem with a solution that is a bit of
an hack, but that modifies little of the replication internals, in order
to be back ported to 2.8 safely.

During the RDB loading time, we send the master newlines to avoid
being sensed as timed out. This is the same that the master already does
while saving the RDB file to still signal its presence to the slave.

The single newline is used because:

1) It can't desync the protocol, as it is only transmitted all or
nothing.
2) It can be safely sent while we don't have a client structure for the
master or in similar situations just with write(2).
2013-12-10 15:40:36 +01:00
antirez 8d0083ba25 Handle inline requested terminated with just \n. 2013-12-10 15:40:33 +01:00
antirez fba0b23e72 Sentinel: fix reported role info sampling.
The way the role change was recoded was not sane and too much
convoluted, causing the role information to be not always updated.

This commit fixes issue #1445.
2013-12-06 12:49:27 +01:00
antirez dceaca1f69 Sentinel: fix reported role fields when master is reset.
When there is a master address switch, the reported role must be set to
master so that we have a chance to re-sample the INFO output to check if
the new address is reporting the right role.

Otherwise if the role was wrong, it will be sensed as wrong even after
the address switch, and for enough time according to the role change
time, for Sentinel consider the master SDOWN.

This fixes isue #1446, that describes the effects of this bug in
practice.
2013-12-06 12:49:23 +01:00
antirez bf307cfb4d Fixed typo in redis.conf. 2013-12-06 10:48:40 +01:00
Anurag Ramdasan 4f9d30b33b Grammar fix. 2013-12-05 18:54:01 +01:00
Anurag Ramdasan b67f39da09 fixed typo 2013-12-05 17:18:37 +01:00
Anurag Ramdasan 4c53178c6c Fixed grammar: 'usually' to 'usual' 2013-12-05 16:42:28 +01:00
antirez 7f6743a581 Fixed grammar: before H the article is a, not an. 2013-12-05 16:37:21 +01:00
antirez edae78999c Fixed typos in redis.conf file. 2013-12-05 16:30:15 +01:00
antirez 3d7263aa41 Removed old comments and dead code from freeClient(). 2013-12-03 13:54:15 +01:00
antirez 333453646c Grammar fix in freeClient(). 2013-12-03 13:40:51 +01:00
antirez 4d650a3baa Redis 2.8.2. 2013-12-02 16:07:46 +01:00
antirez dd0ac4ac72 Sentinel: don't write HZ when flushing config.
See issue #1419.
2013-12-02 15:56:42 +01:00
antirez 75347ada7f Sentinel: better time desynchronization.
Sentinels are now desynchronized in a better way changing the time
handler frequency between 10 and 20 HZ. This way on average a
desynchronization of 25 milliesconds is produced that should be larger
enough compared to network latency, avoiding most split-brain condition
during the vote.

Now that the clocks are desynchronized, to have larger random delays when
performing operations can be easily achieved in the following way.
Take as example the function that starts the failover, that is
called with a frequency between 10 and 20 HZ and will start the
failover every time there are the conditions. By just adding as an
additional condition something like rand()%4 == 0, we can amplify the
desynchronization between Sentinel instances easily.

See issue #1419.
2013-12-02 15:56:39 +01:00
antirez 83333b08d0 Stop writes on MISCONF only if instance is a master.
From the point of view of the slave not accepting writes from the master
can only create a bigger consistency issue.
2013-11-28 16:25:49 +01:00
antirez 50d140e90b SLAVEOF command refactored into a proper API.
We now have replicationSetMaster() and replicationUnsetMaster() that can
be called in other contexts (for instance Redis Cluster).
2013-11-28 16:19:16 +01:00
antirez 2eb8a46061 Reply to PING with error when there is a MISCONF state. 2013-11-28 16:16:58 +01:00
antirez a46f841df3 Sentinel: log vote received from other Sentinels. 2013-11-28 15:23:51 +01:00
huangz1990 e5c577e679 fix a bug in sentinel.c about pub/sub link 2013-11-26 15:11:58 +01:00
antirez dd239c37db Redis 2.8.1. 2013-11-25 11:21:49 +01:00
Salvatore Sanfilippo c61cc761d5 Merge pull request #1370 from yaauie/deprecate-redis-copy-script-2.8
Deprecate redis copy script 2.8
2013-11-25 02:10:32 -08:00
antirez d13635b2a9 Sentinel: fixes inverted strcmp() test preventing config updates.
The result of this one-char bug was pretty serious, if the new master
had the same port of the previous master, but just a different IP
address, non-leader Sentinels would not be able to recognize the
configuration change.

This commit fixes issue #1394.

Many thanks to @shanemadden that reported the bug and helped
investigating it.
2013-11-25 10:57:20 +01:00
antirez d240202261 Sentinel: fix type specifier for Hello msg generation.
This fixes issue #1395.
2013-11-25 10:25:10 +01:00
antirez e83741746c Fix false positive in memory efficiency test.
Fixes issue #1298.
2013-11-25 10:21:18 +01:00
antirez 4e6d34bd2c Redis 2.8.0. 2013-11-22 10:25:29 +01:00
antirez f75fccafe7 Sentinel: example sentinel.conf updated. 2013-11-21 17:07:10 +01:00
antirez 312ca4dacc Sentinel: different comments updated to new implementation. 2013-11-21 16:23:11 +01:00
antirez c84275ee60 Sentinel: cleanup around SENTINEL_INFO_VALIDITY_TIME. 2013-11-21 16:06:07 +01:00
antirez 0b2639123b Sentinel: removed mem leak and useless code. 2013-11-21 15:44:10 +01:00
antirez 750b007d7c Sentinel: manual failover works again. 2013-11-21 15:24:27 +01:00
antirez 4bd1dd1c53 Sentinel: test for writable config file.
This commit introduces a funciton called when Sentinel is ready for
normal operations to avoid putting Sentinel specific stuff in redis.c.
2013-11-21 15:24:22 +01:00
antirez 46e2f3468c Sentinel: check for disconnected links in sentinelSendHello().
Does not fix any bug as the test is performed by the caller, but better
to have the check.
2013-11-21 15:24:15 +01:00
antirez 87098a39f0 Sentinel: Hello message sending code refactored. 2013-11-21 15:24:11 +01:00
antirez 9ecd819ecb Sentinel: select slave with best (greater) replication offset. 2013-11-21 15:24:08 +01:00
antirez eeb8cb3305 Sentinel: take the replication offset in slaves state. 2013-11-21 15:24:03 +01:00
antirez c517f72343 CONFIG REWRITE: don't add the signature if it already exists.
At the end of the file, CONFIG REWRITE adds a comment line that:

    # Generated by CONFIG REWRITE

Followed by the additional config options required. However this was
added again and again at every rewrite in praticular conditions (when a
given set of options change in a given time during the time).

Now if it was alrady encountered, it is not added a second time.

This is especially important for Sentinel that rewrites the config at
every state change.
2013-11-21 15:23:47 +01:00
antirez 812f76a850 Sentinel: distinguish between is-master-down-by-addr requests.
Some are just to know if the master is down, and in this case the runid
in the request is set to "*", others are actually in order to seek for a
vote and get elected. In the latter case the runid is set to the runid
of the instance seeking for the vote.
2013-11-21 15:22:55 +01:00
antirez fc93198ff9 Sentinel: various fixes to leader election implementation. 2013-11-21 15:22:52 +01:00
antirez 44b3684633 Sentinel: failover script execution fixed. 2013-11-21 15:22:48 +01:00
antirez 6d0400f569 Sentinel: no longer used defines removed. 2013-11-21 15:22:44 +01:00
antirez 1bbce5bf01 Sentinel: when writing config on disk, remember sentinels runid. 2013-11-21 15:22:27 +01:00
antirez 679dc8b09d Sentinel: arity of known-sentinel/slave is 4 not 3. 2013-11-21 15:22:21 +01:00
antirez 3e1fc6278d Sentinel: rewriteConfigSentinelOption() sub-iterators var typo fixed. 2013-11-21 15:22:18 +01:00
antirez 9a9c0cfaa6 Sentinel: call sentinelFlushConfig() to persist state when needed.
Also the sentinel configuration rewriting was modified in order to
account for failover in progress, where we need to provide the promoted
slave address as master address, and the old master address as one of
the slaves address.
2013-11-21 15:22:14 +01:00
antirez 93d924ff1c Sentinel: sentinelFlushConfig() to CONFIG REWRITE + fsync. 2013-11-21 15:22:11 +01:00
antirez a52909c5f2 Sentinel: CONFIG REWRITE support for Sentinel config. 2013-11-21 15:22:07 +01:00
antirez 8c3e197040 Sentinel: can-failover option removed, many comments fixed. 2013-11-21 15:22:02 +01:00
antirez 04b1fb0b1a Fix typo 'configuraiton' in rewriteConfigRewriteLine() comment. 2013-11-21 15:21:58 +01:00
antirez 0b9853ecdc Sentinel: added config options useful to take state on config rewrite.
We'll use CONFIG REWRITE (internally) in order to store the new
configuration of a Sentinel after the internal state changes. In order
to do so, we need configuration options (that usually the user will not
touch at all) about config epoch of the master, Sentinels and Slaves
known for this master, and so forth.
2013-11-21 15:21:55 +01:00
antirez 737062745d Sentinel: failover abort function simplified. 2013-11-21 15:21:50 +01:00
antirez 66b03c1a40 Sentinel: slaves reconfig delay modified.
The time Sentinel waits since the slave is detected to be configured to
the wrong master, before reconfiguring it, is now the failover_timeout
time as this makes more sense in order to give the Sentinel performing
the failover enoung time to reconfigure the slaves slowly (if required
by the configuration).

Also we now PUBLISH more frequently the new configuraiton as this allows
to switch the reapprearing master back to slave faster.
2013-11-21 15:21:46 +01:00
antirez 8ba31c218b Sentinel: failover restart time is now multiple of failover timeout.
Also defaulf failover timeout changed to 3 minutes as the failover is a
fairly fast procedure most of the times, unless there are a very big
number of slaves and the user picked to configure them sequentially (in
that case the user should change the failover timeout accordingly).
2013-11-21 15:21:41 +01:00
antirez ccaba966bc Sentinel: state machine and timeouts simplified. 2013-11-21 15:21:37 +01:00
antirez 3c4497e83c Sentinel: election timeout define. 2013-11-21 15:21:34 +01:00
antirez e15ba6a697 Sentinel: fix address of master in Hello messages.
Once we switched configuration during a failover, we should advertise
the new address.

This was a serious race condition as the Sentinel performing the
failover for a moment advertised the old address with the new
configuration epoch: once trasmitted to the other Sentinels the broken
configuration would remain there forever, until the next failover
(because a greater configuration epoch is required to overwrite an older
one).
2013-11-21 15:21:30 +01:00
antirez 1a6abe7d79 Sentinel: master address selection in get-master-address refactored. 2013-11-21 15:21:26 +01:00
antirez 0eeb0a0782 Sentinel: fix conditional to only affect slaves with wrong master. 2013-11-21 15:21:21 +01:00
antirez 64c8de8657 Sentinel: simplify and refactor slave reconfig code. 2013-11-21 15:20:56 +01:00
antirez 782f9cacaf Sentinel: reconfigure slaves to right master. 2013-11-21 15:20:44 +01:00
antirez 7dbc0a63f5 Sentinel: remember last time slave changed master. 2013-11-21 15:20:40 +01:00
antirez 612dbb2a91 Sentinel: redirect-to-master is not ok with new algorithm.
Now Sentinel believe the current configuration is always the winner and
should be applied by Sentinels instead of trying to adapt our view of
the cluster based on what we observe.

So the only way to modify what a Sentinel believe to be the truth is to
win an election and advertise the new configuration via Pub / Sub with a
greater configuration epoch.
2013-11-21 15:20:36 +01:00
antirez 4ccf807abc Sentinel: safer slave reconfig, master reported role should match. 2013-11-21 15:20:31 +01:00
antirez e98d82c639 Sentinel: role reporting fixed and added in SENTINEL output. 2013-11-21 15:20:25 +01:00
antirez be19e5450c Sentinel: being a master and reporting as slave is considered SDOWN. 2013-11-21 15:20:20 +01:00
antirez 8c551d65a1 Sentinel: make sure role_reported is always updated. 2013-11-21 15:20:15 +01:00
antirez 9577fed8a3 Sentinel: track role change time. Wait before reconfigurations. 2013-11-21 15:20:11 +01:00
antirez fc10fb17da Sentinel: fix no-down check in master->slave conversion code. 2013-11-21 15:20:07 +01:00
antirez b02ef3d59a Sentinel: readd slaves back after a master reset. 2013-11-21 15:19:56 +01:00
antirez 1a1dc3de38 Sentinel: sentinelResetMaster() new flag to avoid removing set of sentinels.
This commit also removes some dead code and cleanup generic flags.
2013-11-21 15:19:52 +01:00
antirez 9f0e52a13d Sentinel: receive Pub/Sub messages from slaves. 2013-11-21 15:19:49 +01:00
antirez f7604b4c07 Sentinel: change event name when converting master to slave. 2013-11-21 15:19:45 +01:00
antirez 1569326277 Sentinel: added config-epoch to SENTINEL masters output. 2013-11-21 15:19:41 +01:00
antirez 9f20780de6 Sentinel: new failover algo, desync slaves and update config epoch. 2013-11-21 15:19:34 +01:00
antirez 2488257ad8 Sentinel: when starting failover seek for votes ASAP. 2013-11-21 15:19:30 +01:00
antirez 6593f33222 Sentinel: +new-epoch events. 2013-11-21 15:19:26 +01:00
antirez 48acc675dd Sentinel: wait some time between failover attempts. 2013-11-21 15:19:22 +01:00
antirez 447c2787a0 Sentinel: allow to vote for myself. 2013-11-21 15:19:16 +01:00
antirez eba4775b5d Sentinel: fix PUBLISH to masters and slaves. 2013-11-21 15:19:11 +01:00
antirez b95c6ed7b7 Sentinel: epoch introduced in leader vote. 2013-11-21 15:18:52 +01:00
antirez 663d79c0d5 Sentinel: leadership handling changes WIP.
Changes to leadership handling.

Now the leader gets selected by every Sentinel, for a specified epoch,
when the SENTINEL is-master-down-by-addr is sent.

This command now includes the runid and the currentEpoch of the instance
seeking for a vote. The Sentinel only votes a single time in a given
epoch.

Still a work in progress, does not even compile at this stage.
2013-11-21 15:18:45 +01:00
antirez b72985d7f7 Sentinel: handle Hello messages received via slaves correctly.
Even when messages are received via the slave, we should perform
operations (like adding a new Sentinel) in the context of the master.
2013-11-21 15:18:41 +01:00
antirez fe7f96f18c Sentinel: remove code not useful in the new design. 2013-11-21 15:18:36 +01:00
antirez be2ef1b59f Sentinel: epoch introduced.
Sentinel state now includes the idea of current epoch and config epoch.
In the Hello message, that is now published both on masters and slaves,
a Sentinel no longer just advertises itself but also broadcasts its
current view of the configuration: the master name / ip / port and its
current epoch.

Sentinels receiving such information switch to the new master if the
configuration epoch received is newer and the ip / port of the master
are indeed different compared to the previos ones.
2013-11-21 15:18:31 +01:00
antirez 98682cd178 Log to what master a slave is going to connect to. 2013-11-11 09:25:41 +01:00
antirez 68b911478f Fixed typo in release candidate notes. 2013-11-10 10:25:09 +01:00
Ryan Biesemeyer 7d30b3121d Deprecate utils/redis-copy.rb in favor of redis-copy gem 2013-11-08 06:33:04 +00:00
antirez 875bc51909 Fix broken rdbWriteRaw() return value check in rdb.c.
Thanks to @PhoneLi for reporting.
2013-11-07 23:54:46 +01:00
antirez c874e39c45 Sentinel: sentinelSendSlaveOf() was missing a var and the prototype. 2013-11-06 11:29:57 +01:00
antirez 1767998751 Sentinel: increment pending_commands counter in two more places.
AUTH and SCRIPT KILL were sent without incrementing the pending commands
counter. Clearly this needs some kind of wrapper doing it for the caller
in order to be less bug prone.
2013-11-06 11:29:53 +01:00
antirez 97810c45e8 Sentinel: always send CONFIG REWRITE when changing instance role.
This change makes Sentinel less fragile about a number of failure modes.

This commit also fixes a different bug as a side effect, SLAVEOF command
was sent multiple times without incrementing the pending commands count.
2013-11-06 11:29:49 +01:00
antirez f7f97bf730 Redis 2.7.106 (2.8 Release Candidate 6). 2013-11-06 09:24:26 +01:00
antirez 3a66e0c157 Fixed typo in parseScanCursorOrReply(): ad -> at.
Thanks to @badboy for reporting it.
2013-11-05 17:25:34 +01:00
antirez 060d56e7eb SCAN code refactored to parse cursor first.
The previous implementation of SCAN parsed the cursor in the generic
function implementing SCAN, SSCAN, HSCAN and ZSCAN.

The actual higher-level command implementation only checked for empty
keys and return ASAP in that case. The result was that inverting the
arguments of, for instance, SSCAN for example and write:

    SSCAN 0 key

Instead of

    SSCAN key 0

Resulted into no error, since 0 is a non-existing key name very likely.
Just the iterator returned no elements at all.

In order to fix this issue the code was refactored to extract the
function to parse the cursor and return the error. Every higher level
command implementation now parses the cursor and later checks if the key
exist or not.
2013-11-05 17:25:29 +01:00
antirez 162acd8ac2 SCAN: when iterating ziplists or intsets always return cursor of 0.
The previous implementation assumed that the first call always happens
with cursor set to 0, this may not be the case, and we want to return 0
anyway otherwise the (broken) client code will loop forever.
2013-11-05 17:24:33 +01:00
antirez 406be0ea28 Use strtoul() instead of sscanf() in SCAN implementation. 2013-11-05 17:24:30 +01:00
antirez 4b615eea8f Added tests for [SHZ]SCAN with MATCH. 2013-11-05 17:24:26 +01:00
antirez 54a5a7dff8 HSCAN/ZSCAN: skip value when matching.
This fixes issue #1360 and #1362.
2013-11-05 17:24:22 +01:00
antirez bebbc7f9f7 Pass int64_t to intsetGet() instead of long long. 2013-11-05 17:24:17 +01:00
antirez 736e343509 removed not used vars in dictScan(). 2013-11-05 17:24:12 +01:00
antirez 59b30b327d SCAN: no sdsEncodedObject() API in Redis 2.8. 2013-10-31 18:22:29 +01:00
antirez 3d3e350b12 SSCAN with integer encoded object test improved. 2013-10-31 10:38:56 +01:00
antirez 3cc7d486bc Inverted variable boolean value and name after scanGenericCommand() refactoring. 2013-10-31 10:38:52 +01:00
antirez 06455014aa scanGenericCommand() refactoring and handling of integer encoded elements.
This commit fixes issue #1354.
2013-10-31 10:38:48 +01:00
antirez 141f30f421 Regression test added for [SHZ]SCAN issue #1354. 2013-10-31 10:38:45 +01:00
antirez c53cab5dde Test: added a SCAN test trying to trigger HT resize. 2013-10-30 16:51:48 +01:00
antirez ab6f4195dd Test: added ZSCAN test. 2013-10-30 16:27:43 +01:00
antirez 13e879c9cf Test: added HSCAN test. 2013-10-30 16:27:39 +01:00
antirez 9ea69a58ac Test: added SSCAN test. 2013-10-30 16:27:36 +01:00
antirez bd1962acc4 SCAN test keys sorting turned into more idiomatic Tcl. 2013-10-30 16:27:31 +01:00
antirez 02617b6e92 SCAN: tests moved to unit/scan.tcl. 2013-10-30 16:27:28 +01:00
antirez bb2df795e0 Aesthetic fix (missing space) into HSCAN and ZSCAN implementations.
Thanks to @badboy for reporting.
2013-10-29 16:21:21 +01:00
antirez 35816a2e4d ZSCAN implemented. 2013-10-29 16:21:18 +01:00
antirez 1cc7e646af HSCAN implemented. 2013-10-29 16:21:13 +01:00
antirez 24ecabc995 SSCAN implemented. 2013-10-29 16:21:01 +01:00
antirez 17785f40b5 dictScan(): empty hash table requires special handling. 2013-10-29 16:20:13 +01:00
antirez 7f063b1c8c SCAN is a random command and does not require output sorting.
Sorting the output helps when we want to turn a non-deterministic into a
deterministic command, in that case this is not possible.
2013-10-29 16:20:10 +01:00
antirez 9e62d9e79c SCAN: refactored into scanGenericCommand.
The new implementation is capable of iterating the keyspace but also
sets, hashes, and sorted sets, and can be used to implement SSCAN, ZSCAN
and HSCAN.
2013-10-29 16:20:06 +01:00
antirez 1a06bbe430 Fixed typos in dictScan() comment. 2013-10-29 16:20:02 +01:00
antirez 0880bec4e2 dictScan() algorithm documented. 2013-10-29 16:19:57 +01:00
antirez 1d659784f4 SCAN: stay inside 80 cols. 2013-10-29 16:19:54 +01:00
antirez 854b8be616 Revert "Fixed typo in SCAN comment. iff -> if."
Probably here Pieter means "if and only if".

This reverts commit 43fdf3b404.
2013-10-29 16:19:49 +01:00
antirez 5fa5153bf5 SCAN: simplify keys list cleanup using listSetFreeMethod(). 2013-10-29 16:19:45 +01:00
antirez 108c6e6734 SCAN: Fix test after option renamed from PATTERN to MATCH. 2013-10-29 16:19:41 +01:00
antirez 21da9b06a4 SCAN: improve variable names for readability. 2013-10-29 16:19:37 +01:00
antirez 907e06cd61 SCAN: remove additional newlines to conform to Redis code base. 2013-10-29 16:19:33 +01:00
antirez b44f1589a4 SCAN: remove useless assertion, already enforced by command table. 2013-10-29 16:19:28 +01:00
antirez d1c1f62ed5 SCAN: use define REDIS_LONGSTR_SIZE instead of fixed len. 2013-10-29 16:19:25 +01:00
antirez 8cdaf604ce Fixed typo in SCAN comment. iff -> if. 2013-10-29 16:19:21 +01:00
antirez 33409b4c6c SCAN option name changed: pattern -> match. 2013-10-29 16:19:17 +01:00
Pieter Noordhuis fd22106a98 Fix error in scan algorithm
The irrelevant bits shouldn't be masked to 1. This can result in slots being
skipped when the hash table is resized between calls to the iterator.
2013-10-29 16:19:12 +01:00
Pieter Noordhuis 8c4ff679fe SCAN requires at least 1 argument 2013-10-29 16:19:08 +01:00
Pieter Noordhuis 0dd95e23ab Add SCAN command 2013-10-29 16:18:24 +01:00
antirez 9546f7846b redis-benchmark: update help for new __rand_int__ form. 2013-10-28 18:14:11 +01:00
antirez 508d3c1bb7 Redis 2.7.105 (2.8 Release Candidate 5). 2013-10-09 13:08:42 +02:00
antirez df0c96002d Replication: install the write handler when reusing a cached master.
Sometimes when we resurrect a cached master after a successful partial
resynchronization attempt, there is pending data in the output buffers
of the client structure representing the master (likely REPLCONF ACK
commands).

If we don't reinstall the write handler, it will never be installed
again by addReply*() family functions as they'll assume that if there is
already data pending, the write handler is already installed.

This bug caused some slaves after a successful partial sync to never
send REPLCONF ACK, and continuously being detected as timing out by the
master, with a disconnection / reconnection loop.
2013-10-04 16:14:57 +02:00
antirez d7fa6d9aba Replication: fix master timeout.
Since we started sending REPLCONF ACK from slaves to masters, the
lastinteraction field of the client structure is always refreshed as
soon as there is room in the socket output buffer, so masters in timeout
are detected with too much delay (the socket buffer takes a lot of time
to be filled by small REPLCONF ACK <number> entries).

This commit only counts data received as interactions with a master,
solving the issue.
2013-10-04 13:01:49 +02:00
antirez c8c1006cf4 PSYNC: safer handling of PSYNC requests.
There was a bug that over-esteemed the amount of backlog available,
however this could only happen when a slave was asking for an offset
that was in the "future" compared to the master replication backlog.

Now this case is handled well and logged as an incident in the master
log file.
2013-10-04 12:27:40 +02:00
antirez 81f614ef6f Add REWRITE to CONFIG subcommands help message. 2013-10-04 12:27:36 +02:00
Michel Martens 20c506f432 Document the redis-cli --csv option. 2013-09-26 10:12:51 +02:00
antirez af967ff9cb Allow AUTH / PING when disconnected from slave and serve-stale-data is no. 2013-09-17 09:46:40 +02:00
antirez 2d55f1ca7f redis-cli: fix big keys search when the key no longer exist.
The code freed a reply object that was never created, resulting in a
segfault every time randomkey returned a key that was deleted before we
queried it for size.
2013-09-04 10:37:55 +02:00
Maxim Zakharov 9a0f27a5d0 mistype fixed 2013-09-03 15:15:42 +02:00
Maxim Zakharov c713b1ebdd A mistype fixed 2013-09-03 15:15:34 +02:00
antirez 56ce068664 Redis 2.7.104 (2.8 Release Candidate 4). 2013-08-30 09:52:18 +02:00
antirez 23acf93f97 Test: Lua stack leak regression test added. 2013-08-30 08:59:16 +02:00
antirez 0ea9a20d47 Test: added a memory efficiency test. 2013-08-30 08:48:07 +02:00
antirez 752b1fca90 Fixed critical memory leak from EVAL.
Multiple missing calls to lua_pop prevented the error handler function
pushed on the stack for lua_pcall() to be popped before returning,
causing a memory leak in almost all the code paths of EVAL (both
successful calls and calls returning errors).

This caused two issues: Lua leaking memory (and this was very visible
from INFO memory output, as the 'used_memory_lua' field reported an
always increasing amount of memory used), and as a result slower and
slower GC cycles resulting in all the CPU being used.

Thanks to Tanguy Le Barzic for noticing something was wrong with his 2.8
slave, and for creating a testing EC2 environment where I was able to
investigate the issue.
2013-08-29 11:49:28 +02:00
Salvatore Sanfilippo 3e62d7c308 Merge pull request #1244 from yihuang/2.8
fix lua_cmsgpack pack map as array
2013-08-27 06:15:32 -07:00
antirez a57294b339 Fix an hypothetical issue in processMultibulkBuffer(). 2013-08-27 13:00:14 +02:00
antirez facc5c0661 tryObjectEncoding(): optimize sds strings if possible.
When no encoding is possible, at least try to reallocate the sds string
with one that does not waste memory (with free space at the end of the
buffer) when the string is large enough.
2013-08-27 12:36:09 +02:00
antirez 79a1d335c8 tryObjectEncoding(): don't call stringl2() for too big strings.
We are sure that a string that is longer than 21 chars cannot be
represented by a 64 bit signed integer, as -(2^64) is 21 chars:

strlen(-18446744073709551616) => 21
2013-08-27 12:30:42 +02:00
antirez 0f32c37fc9 Fix DEBUG SDSLEN after 2.8 back port. 2013-08-27 12:26:02 +02:00
antirez df21771c69 Don't over-allocate the sds string for large bulk requests.
The call to sdsMakeRoomFor() did not accounted for the amount of data
already present in the query buffer, resulting into over-allocation.
2013-08-27 12:22:12 +02:00
antirez f05b1f6889 DEBUG SDSLEN added.
This command is only useful for low-level debugging of memory issues due
to sds wasting memory as empty buffer at the end of the string.
2013-08-27 12:22:07 +02:00
antirez 9c3e6c3aa0 Update server.lastbgsave_status when fork() fails. 2013-08-27 10:16:25 +02:00
antirez 9df63fcabf Only run the fast active expire cycle if master & enabled. 2013-08-27 09:31:43 +02:00
antirez f5903846bc Opening TCP listening ports refactored into a function. 2013-08-22 14:06:54 +02:00
antirez bd6327855b Print error message when can't bind * on any address. 2013-08-22 13:03:07 +02:00
antirez 2c94d80f58 Fix for issue #1214 simplified. 2013-08-21 11:41:35 +02:00
Allan 778f753deb fixed initServer fail while having no IPv6 nor IPv4 2013-08-21 11:41:30 +02:00
Allan 6c2b34a465 fixed initServer failed if no IPV4 or no IPV6 2013-08-21 11:41:25 +02:00
Allan 0a4656d63f fixed bug issue of #1213 2013-08-21 11:41:21 +02:00
antirez 7c861340b1 Use printf %zu specifier to print private_dirty. 2013-08-20 12:09:36 +02:00
antirez 2c018b14fb dictFingerprint(): cast pointers to integer of same size. 2013-08-20 12:09:31 +02:00
antirez 6a88a84ec7 Redis 2.7.103 (2.8 Release Candidate 3). 2013-08-19 18:04:32 +02:00
antirez 5621272575 Revert "Fixed type in dict.c comment: 265 -> 256."
This reverts commit 009515f914.
2013-08-19 17:25:57 +02:00
antirez 009515f914 Fixed type in dict.c comment: 265 -> 256. 2013-08-19 15:11:07 +02:00
antirez 837817b2b7 assert.h replaced with redisassert.h when appropriate.
Also a warning was suppressed by including unistd.h in redisassert.h
(needed for _exit()).
2013-08-19 15:02:16 +02:00
antirez bd353d9a3c Added redisassert.h as drop in replacement for assert.h.
By using redisassert.h version of assert() you get stack traces in the
log instead of a process disappearing on assertions.
2013-08-19 15:02:13 +02:00
antirez c7da5fc6c1 dictFingerprint() fingerprinting made more robust.
The previous hashing used the trivial algorithm of xoring the integers
together. This is not optimal as it is very likely that different
hash table setups will hash the same, for instance an hash table at the
start of the rehashing process, and at the end, will have the same
fingerprint.

Now we hash N integers in a smarter way, by summing every integer to the
previous hash, and taking the integer hashing again (see the code for
further details). This way it is a lot less likely that we get a
collision. Moreover this way of hashing explicitly protects from the
same set of integers in a different order to hash to the same number.

This commit is related to issue #1240.
2013-08-19 15:02:10 +02:00
antirez 8368550733 Fix comments for correctness in zunionInterGenericCommand().
Related to issue #1240.
2013-08-19 15:02:06 +02:00
antirez 3ad87c652b Properly init/release iterators in zunionInterGenericCommand().
This commit does mainly two things:

1) It fixes zunionInterGenericCommand() by removing mass-initialization
of all the iterators used, so that we don't violate the unsafe iterator
API of dictionaries. This fixes issue #1240.

2) Since the zui* APIs required the allocator to be initialized in the
zsetopsrc structure in order to use non-iterator related APIs, this
commit fixes this strict requirement by accessing objects directly via
the op->subject->ptr pointer we have to the object.
2013-08-19 15:02:02 +02:00
antirez 05379f49ee dict.c iterator API misuse protection.
dict.c allows the user to create unsafe iterators, that are iterators
that will not touch the dictionary data structure in any way, preventing
copy on write, but at the same time are limited in their usage.

The limitation is that when itearting with an unsafe iterator, no call
to other dictionary functions must be done inside the iteration loop,
otherwise the dictionary may be incrementally rehashed resulting into
missing elements in the set of the elements returned by the iterator.

However after introducing this kind of iterators a number of bugs were
found due to misuses of the API, and we are still finding
bugs about this issue. The bugs are not trivial to track because the
effect is just missing elements during the iteartion.

This commit introduces auto-detection of the API misuse. The idea is
that an unsafe iterator has a contract: from initialization to the
release of the iterator the dictionary should not change.

So we take a fingerprint of the dictionary state, xoring a few important
dict properties when the unsafe iteartor is initialized. We later check
when the iterator is released if the fingerprint is still the same. If it
is not, we found a misuse of the iterator, as not allowed API calls
changed the internal state of the dictionary.

This code was checked against a real bug, issue #1240.

This is what Redis prints (aborting) when a misuse is detected:

Assertion failed: (iter->fingerprint == dictFingerprint(iter->d)),
function dictReleaseIterator, file dict.c, line 587.
2013-08-19 15:01:58 +02:00
yihuang 706056ca16 fix lua_cmsgpack pack map as array 2013-08-13 17:48:40 +08:00
antirez 14a1cba3e3 Use precomptued objects for bulk and mbulk prefixes. 2013-08-12 12:43:26 +02:00
antirez 004f00bfa4 replicationFeedSlaves() func name typo: feedReplicationBacklogWithObject -> feedReplicationBacklog. 2013-08-12 12:38:52 +02:00
antirez 45d4e06e93 replicationFeedSlave() reworked for correctness and speed.
The previous code using a static buffer as an optimization was lame:

1) Premature optimization, actually it was *slower* than naive code
   because resulted into the creation / destruction of the object
   encapsulating the output buffer.
2) The code was very hard to test, since it was needed to have specific
   tests for command lines exceeding the size of the static buffer.
3) As a result of "2" the code was bugged as the current tests were not
   able to stress specific corner cases.

It was replaced with easy to understand code that is safer and faster.
2013-08-12 12:31:20 +02:00
antirez 3eab283b65 Fix a PSYNC bug caused by a variable name typo. 2013-08-12 11:51:03 +02:00
antirez 0e72cc601f Fix sdsempty() prototype in sds.h. 2013-08-12 11:38:28 +02:00
antirez 689829f2e1 redis-benchmark: changes to random arguments substitution.
Before this commit redis-benchmark supported random argumetns in the
form of :rand:000000000000. In every string of that form, the zeros were
replaced with a random number of 12 digits at every command invocation.

However this was far from perfect as did not allowed to generate simply
random numbers as arguments, there was always the :rand: prefix.

Now instead every argument in the form __rand_int__ is replaced with a
12 digits number. Note that "__rand_int__" is 12 characters itself.

In order to implement the new semantic, it was needed to change a few
thigns in the internals of redis-benchmark, as new clients are created
cloning old clients, so without a stable prefix such as ":rand:" the old
way of cloning the client was no longer able to understand, from the old
command line, what was the position of the random strings to substitute.

Now instead a client structure is passed as a reference for cloning, so
that we can directly clone the offsets inside the command line.
2013-08-08 16:47:18 +02:00
antirez 4fe67cc103 redis-benchmark: replace snprintf()+memcpy with faster code.
This change was profiler-driven, but the actual effect is hard to
measure in real-world redis benchmark runs.
2013-08-08 14:31:54 +02:00
Jan-Erik Rediger a256b83448 Little typo 2013-08-07 16:09:33 +02:00
antirez a79862de2a redis-benchmark: fix memory leak introduced by 346256f 2013-08-07 16:09:13 +02:00
antirez bcc7965931 redis-benchmark: max pipeline length hardcoded limit removed. 2013-08-07 16:09:09 +02:00
antirez 46c1bafeab redis-benchmark: fix db selection when :rand: feature is used. 2013-08-07 11:22:33 +02:00
antirez 151ad68540 redis-benchmark: ability to SELECT a specifid db number. 2013-08-07 11:22:28 +02:00
antirez fa48b1fa32 Add per-db average TTL information in INFO output.
Example:

db0:keys=221913,expires=221913,avg_ttl=655

The algorithm uses a running average with only two samples (current and
previous). Keys found to be expired are considered at TTL zero even if
the actual TTL can be negative.

The TTL is reported in milliseconds.
2013-08-06 15:36:43 +02:00
antirez 31d0f34100 activeExpireCycle(): fix about fast cycle early start.
We don't want to repeat a fast cycle too soon, the previous code was
broken, we need to wait two times the period *since* the start of the
previous cycle in order to avoid there is an even space between cycles:

.-> start                   .-> second start
|                           |
+-------------+-------------+--------------+
| first cycle |    pause    | second cycle |
+-------------+-------------+--------------+

The second and first start must be PERIOD*2 useconds apart hence the *2
in the new code.
2013-08-06 15:36:39 +02:00
antirez 00c8cfef74 Some activeExpireCycle() refactoring. 2013-08-06 15:36:35 +02:00
antirez 8686a70303 Remove dead code and fix comments for new expire code. 2013-08-06 15:36:31 +02:00
antirez d54e373b84 Darft #2 for key collection algo: more improvements.
This commit makes the fast collection cycle time configurable, at
the same time it does not allow to run a new fast collection cycle
for the same amount of time as the max duration of the fast
collection cycle.
2013-08-06 15:36:27 +02:00
antirez 500155b91b Draft #1 of a new expired keys collection algorithm.
The main idea here is that when we are no longer to expire keys at the
rate the are created, we can't block more in the normal expire cycle as
this would result in too big latency spikes.

For this reason the commit introduces a "fast" expire cycle that does
not run for more than 1 millisecond but is called in the beforeSleep()
hook of the event loop, so much more often, and with a frequency bound
to the frequency of executed commnads.

The fast expire cycle is only called when the standard expiration
algorithm runs out of time, that is, consumed more than
REDIS_EXPIRELOOKUPS_TIME_PERC of CPU in a given cycle without being able
to take the number of already expired keys that are yet not collected
to a number smaller than 25% of the number of keys.

You can test this commit with different loads, but a simple way is to
use the following:

Extreme load with pipelining:

redis-benchmark -r 100000000 -n 100000000  \
        -P 32 set ele:rand:000000000000 foo ex 2

Remove the -P32 in order to avoid the pipelining for a more real-world
load.

In another terminal tab you can monitor the Redis behavior with:

redis-cli -i 0.1 -r -1 info keyspace

and

redis-cli --latency-history

Note: this commit will make Redis printing a lot of debug messages, it
is not a good idea to use it in production.
2013-08-06 15:36:23 +02:00
antirez cc94697284 Redis 2.7.102 (2.8 Release Candidate 2). 2013-07-30 20:20:02 +02:00
antirez f8e43aba78 Test: regression test for issue #1221. 2013-07-29 17:40:18 +02:00
antirez 13f7ade551 Fix replicationFeedSlaves() off-by-one bug.
This fixes issue #1221.
2013-07-28 12:50:35 +02:00
antirez 78644f5e3e Remove dead variable bothsds from object.c.
Thanks to @run and @badboy for spotting this.
Triva: clang was not able to provide me a warning about that when
compiling.

This closes #1024 and #1207, committing the change myself as the pull
requests no longer apply cleanly after other changes to the same
function.
2013-07-28 11:02:20 +02:00
antirez aea8020554 Use latest sds.c in the hiredis library under deps. 2013-07-25 10:34:02 +02:00
antirez c3e1724a57 Ignore sdsrange return value. 2013-07-24 18:59:36 +02:00
antirez f899ab55ca sdsrange() does not need to return a value.
Actaully the string is modified in-place and a reallocation is never
needed, so there is no need to return the new sds string pointer as
return value of the function, that is now just "void".
2013-07-24 11:22:52 +02:00
antirez a1d37ba469 Inline protocol improved to accept quoted strings. 2013-07-24 10:38:05 +02:00
antirez 57c8e026b9 Every function inside sds.c is now commented. 2013-07-23 16:36:14 +02:00
antirez f3b44f291d Merge remote-tracking branch 'origin/2.8' into 2.8 2013-07-19 14:55:49 +02:00
antirez 5d0ad2f983 Fixed typo in 2.8 release notes. 2013-07-18 16:10:31 +02:00
Salvatore Sanfilippo 349f6349be Merge pull request #1203 from ronnix/patch-1
Fix a few typos in release notes
2013-07-18 03:27:41 -07:00
Ronan Amicel 5b19e9d433 Fix a few typos in release notes 2013-07-18 12:23:47 +02:00
antirez 3cb3714e99 Redis 2.7.101 (2.8 Release Candidate 1). 2013-07-18 11:26:53 +02:00
antirez a50635bb6c addReplyDouble(): format infinite in a libc agnostic way.
There are systems that when printing +/- infinte with printf-family
functions will not use the usual "inf" "-inf", but different strings.
Handle that explicitly.

Fixes issue #930.
2013-07-17 15:04:22 +02:00
antirez 2e449d42e0 Fixed typo in rio.h, simgle -> single. 2013-07-16 15:43:41 +02:00
yoav 9f6f436a51 Chunked loading of RDB to prevent redis from stalling reading very large keys. 2013-07-16 15:41:59 +02:00
antirez 112e763618 Make sure that ZADD can accept the full range of double values.
This fixes issue #1194, that contains many details.

However in short, it was possible for ZADD to not accept as score values
that was however possible to obtain with multiple calls to ZINCRBY, like
in the following example:

redis 127.0.0.1:6379> zadd k 2.5e-308 m
(integer) 1
redis 127.0.0.1:6379> zincrby k -2.4e-308 m
"9.9999999999999694e-310"
redis 127.0.0.1:6379> zscore k m
"9.9999999999999694e-310"
redis 127.0.0.1:6379> zadd k 9.9999999999999694e-310 m1
(error) ERR value is not a valid float

The problem was due to strtod() returning ERANGE in the following case
specified by POSIX:

"If the correct value would cause an underflow, a value whose magnitude
is no greater than the smallest normalized positive number in the return
type shall be returned and errno set to [ERANGE].".

Now instead the returned value is accepted even when ERANGE is returned
as long as the return value of the function is not negative or positive
HUGE_VAL or zero.
2013-07-16 15:08:57 +02:00
Ted Nyman fe04710908 Make sure the log standardizes on 'timeout' 2013-07-12 23:12:27 +02:00
antirez 98757b4a6e Use the environment locale for strcoll() collation. 2013-07-12 13:39:48 +02:00
antirez 18fabeb264 SORT ALPHA: use collation instead of binary comparison.
Note that we only do it when STORE is not used, otherwise we want an
absolutely locale independent and binary safe sorting in order to ensure
AOF / replication consistency.

This is probably an unexpected behavior violating the least surprise
rule, but there is currently no other simple / good alternative.
2013-07-12 13:39:44 +02:00
antirez d8fcbb6645 Fixed compareStringObject() and introduced collateStringObject().
compareStringObject was not always giving the same result when comparing
two exact strings, but encoded as integers or as sds strings, since it
switched to strcmp() when at least one of the strings were not sds
encoded.

For instance the two strings "123" and "123\x00456", where the first
string was integer encoded, would result into the old implementation of
compareStringObject() to return 0 as if the strings were equal, while
instead the second string is "greater" than the first in a binary
comparison.

The same compasion, but with "123" encoded as sds string, would instead
return a value < 0, as it is correct. It is not impossible that the
above caused some obscure bug, since the comparison was not always
deterministic, and compareStringObject() is used in the implementation
of skiplists, hash tables, and so forth.

At the same time, collateStringObject() was introduced by this commit, so
that can be used by SORT command to return sorted strings usign
collation instead of binary comparison. See next commit.
2013-07-12 13:39:40 +02:00
Jan-Erik Rediger 1bcbb7a90c Wrap IPv6 in brackets in the prompt. 2013-07-12 10:58:47 +02:00
antirez 1e23848ed3 Sentinel: embed IPv6 address into [] when naming slave/sentinel instance. 2013-07-11 17:10:09 +02:00
antirez 076f6395b9 Sentinel: use comma as separator to publish hello messages.
We use comma to play well with IPv6 addresses, but the implementation is
still able to parse the old messages separated by colons.
2013-07-11 17:09:44 +02:00
antirez 4cfd70e903 hiredis: minimal IPv6 support. 2013-07-11 17:09:35 +02:00
antirez 98d0abcecd Sentinel: make sure published addr/id buffer is large enough.
With ipv6 support we need more space, so we account for the IP address
max size plus what we need for the Run ID, port, flags.
2013-07-11 17:09:30 +02:00
antirez 8669e70921 anet.c: save some vertical space. 2013-07-11 17:09:25 +02:00
antirez 9e089e7c5d anet.c: use SO_REUSEADDR when creating listening sockets.
It used to be ok, but the socket option was removed when adding IPv6
support.
2013-07-11 17:09:22 +02:00
antirez cdf801d92e Use getClientPeerId() for MONITOR implementation. 2013-07-11 17:09:18 +02:00
antirez 3472d045d8 getClientPeerId() refactored into two functions. 2013-07-11 17:09:14 +02:00
antirez da18366609 getClientPeerId() now reports errors.
We now also use it in CLIENT KILL implementation.
2013-07-11 17:09:10 +02:00
antirez 4fa68b2815 getClientPeerID introduced.
The function returns an unique identifier for the client, as ip:port for
IPv4 and IPv6 clients, or as path:0 for Unix socket clients.

See the top comment in the function for more info.
2013-07-11 17:09:04 +02:00
antirez a7451c1b6d All IP string repr buffers are now REDIS_IP_STR_LEN bytes. 2013-07-11 17:07:52 +02:00
antirez 2e75d3947c IPv6: bind IPv4 and IPv6 interfaces by default. 2013-07-11 17:07:41 +02:00
antirez b5423b099c Fix old anetPeerToString() API call in replication.c 2013-07-11 17:07:38 +02:00
antirez 1e7949227c Revert "Document port6 and bind6 config options."
IPv6 support is not going to use IPv6 specific options, just it will be
possible to specify all the ipv4 / ipv6 addresses of the interfaces to
bind, otherwise connections will be accepted from all the interfaces in
both IPv4 and IPv6 addresses.

This reverts commit 93570e179e.
2013-07-11 17:07:34 +02:00
Geoff Garside 0d8f254359 Add IPv6 support to sentinel.c.
This has been done by exposing the anetSockName() function anet.c
to be used when the sentinel is publishing its existence to the masters.

This implementation is very unintelligent as it will likely break if used
with IPv6 as the nested colons will break any parsing of the PUBLISH string
by the master.
2013-07-11 17:07:31 +02:00
Geoff Garside 5938126c98 Cleanup main() and BACKTRACE mistaken pulled while rebasing. 2013-07-11 17:07:27 +02:00
Geoff Garside d3bf85423a Fix calls to anetPeerToString() missing buffer size. 2013-07-11 17:07:22 +02:00
Geoff Garside 47620a3186 Document port6 and bind6 config options.
Add commented port6 and bind6 options to default redis.conf file.
2013-07-11 17:07:18 +02:00
Geoff Garside fd8a6ae7a6 Add anetTcp6Server() function.
Refactor the common code from anetTcpServer into internal function which
can be used by both anetTcpServer and anetTcp6Server.
2013-07-11 17:07:13 +02:00
Geoff Garside f638236902 Add static anetV6Only() function.
This function sets the IPV6_V6ONLY option to 1 to use separate stack
IPv6 sockets.
2013-07-11 17:07:07 +02:00
Geoff Garside 71795d4e7e Change anetTcpGenericConnect to use AF_UNSPEC.
This allows anetTcpGenericConnect to try to connect to AF_INET6
addresses in addition to any resolved AF_INET addresses.
2013-07-11 17:07:03 +02:00
Geoff Garside d37d006cd2 Mark places that might want changing for IPv6.
Any places which I feel might want to be updated to work differently
with IPv6 have been marked with a comment starting "IPV6:".

Currently the only comments address places where an IP address is
combined with a port using the standard : separated form. These may want
to be changed when printing IPv6 addresses to wrap the address in []
such as

	[2001:db8::c0:ffee]:6379

instead of

	2001:db8::c0:ffee:6379

as the latter format is a technically valid IPv6 address and it is hard
to distinguish the IPv6 address component from the port unless you know
the port is supposed to be there.
2013-07-11 17:06:55 +02:00
Geoff Garside 57e2b23dfb Expand ip char buffers which are too small for v6.
Increase the size of character buffers being used to store printable IP
addresses so that they can safely store IPv6 addresses.
2013-07-11 17:06:43 +02:00
Geoff Garside 39d57c057f Mark ip string buffers which could be reduced.
In two places buffers have been created with a size of 128 bytes which
could be reduced to INET6_ADDRSTRLEN to still hold a full IP address.
These places have been marked as they are presently big enough to handle
the needs of storing a printable IPv6 address.
2013-07-11 17:06:35 +02:00
Geoff Garside f1151f869e Update anetPeerToString to handle AF_INET6 addrs.
Change the sockaddr_in to sockaddr_storage which is capable of storing
both AF_INET and AF_INET6 sockets. Uses the sockaddr_storage ss_family
to correctly return the printable IP address and port.
2013-07-11 17:05:21 +02:00
Geoff Garside fefa14914e Update anetTcpAccept to handle AF_INET6 addresses.
Change the sockaddr_in to sockaddr_storage which is capable of storing
both AF_INET and AF_INET6 sockets. Uses the sockaddr_storage ss_family
to correctly return the printable IP address and port.
2013-07-11 17:05:16 +02:00
Geoff Garside 77ddec8582 Update anetResolve to resolve AF_INET6 as well.
Change the getaddrinfo(3) hints family from AF_INET to AF_UNSPEC to
allow resolution of IPv6 addresses as well as IPv4 addresses. The
function will return the IP address of whichever address family is
preferenced by the operating system. Most current operating systems
will preference AF_INET6 over AF_INET.

Unfortunately without attempting to establish a connection to the
remote address we can't know if the host is capable of using the
returned IP address. It might be desirable to have anetResolve
accept an additional argument specifying the AF_INET/AF_INET6 address
the caller would like to receive. Currently though it does not appear
as though the anetResolve function is ever used within Redis.
2013-07-11 17:05:12 +02:00
Geoff Garside 4b2e374e4a Update calls to anetResolve to include buffer size 2013-07-11 17:05:08 +02:00
Geoff Garside 3570411def Update calls to anetPeerToString to include ip_len. 2013-07-11 17:05:03 +02:00
Geoff Garside 68d72aa5b1 Add macro to define clusterNode.ip buffer size.
Add REDIS_CLUSTER_IPLEN macro to define the size of the clusterNode ip
character array. Additionally use this macro in inet_ntop(3) calls where
the size of the array was being defined manually.

The REDIS_CLUSTER_IPLEN is defined as INET_ADDRSTRLEN which defines the
correct size of a buffer to store an IPv4 address in. The
INET_ADDRSTRLEN macro itself is defined in the <netinet/in.h> header
file and should be portable across the majority of systems.
2013-07-11 17:04:32 +02:00
Geoff Garside 30836721f2 Update anetTcpAccept & anetPeerToString calls.
Add the additional ip buffer length argument to function calls of
anetTcpAccept and anetPeerToString in network.c and cluster.c
2013-07-11 17:02:29 +02:00
Geoff Garside 920ab4c926 Use inet_ntop(3) in anet. #apichange
Replace inet_ntoa(3) calls with the more future proof inet_ntop(3)
function which is capable of handling additional address families.

API Change: anetTcpAccept() & anetPeerToString() additional argument
  additional argument required to specify the length of the character
  buffer the IP address is written to in order to comply with
  inet_ntop(3) function semantics.
2013-07-11 17:02:12 +02:00
Geoff Garside aee5ee70fa Use getaddrinfo(3) in a anetTcpServer.
Change anetTcpServer() function to use getaddrinfo(3) to perform
address resolution, socket creation and binding. Resolved addresses
are limited to those reachable by the AF_INET address family.
2013-07-11 17:02:08 +02:00
Geoff Garside e50f2c93bc Use getaddrinfo(3) in anetTcpGenericConnect.
Change anetTcpGenericConnect() function to use getaddrinfo(3) to
perform address resolution, socket creation and connection. Resolved
addresses are limited to those reachable by the AF_INET family.
2013-07-11 17:02:04 +02:00
Geoff Garside d36dc1fc82 Add anetSetReuseAddr(err, fd) static function.
Extract setting SO_REUSEADDR socket option into separate function
so the same code can be more easily used by anetCreateSocket and
other functions.
2013-07-11 17:01:59 +02:00
Geoff Garside 855ff8df9f Use getaddrinfo(3) in anetResolve. #apichange
Change anetResolve() function to use getaddrinfo(3) to resolve hostnames.
Resolved hostnames are limited to those reachable by the AF_INET address
family.

API Change: anetResolve requires additional argument.
  additional argument required to specify the length of the character
  buffer the IP address is written to in order to comply with
  inet_ntop(3) function semantics. inet_ntop(3) replaces inet_ntoa(3)
  as it has been designed to be compatible with more address families.
2013-07-11 17:01:55 +02:00
antirez 33c5f0a243 Example redis.conf: bind to multiple interfaces documented. 2013-07-08 10:42:37 +02:00
antirez 323e5e2058 Remove cluster stuff from closeListeningSockets() for 2.8. 2013-07-08 10:31:49 +02:00
antirez fc022ca300 Binding multiple IPs done properly with multiple sockets. 2013-07-08 10:30:54 +02:00
antirez 00063bea33 Revert "anet.c: Allow creation of TCP listening sockets bound to N addresses."
Bind() can't be called multiple times against the same socket, multiple
sockets are required to bind multiple interfaces, silly me.

This reverts commit bd234d62bb.
2013-07-08 10:28:02 +02:00
antirez 6dabd34ad0 Ability to bind multiple addresses. 2013-07-08 10:27:21 +02:00
antirez 14857decfa anet.c: Allow creation of TCP listening sockets bound to N addresses. 2013-07-08 10:25:51 +02:00
antirez 2150cfebb7 sds.c: new function sdsjoin() to join strings. 2013-07-08 10:25:47 +02:00
antirez 3e3501262b redis-cli: introduced --pipe-timeout.
When in --pipe mode, after all the data transfer to the server is
complete, now redis-cli waits at max the specified amount of
seconds (30 by default, use 0 to wait forever) without receiving any
reply at all from the server. After this time limit the operation is
aborted with an error.

That's related to issue #681.
2013-07-03 12:18:55 +02:00
antirez 24b3799238 redis-cli --pipe: send final ECHO in a safer way.
If the protocol read from stdin happened to contain grabage (invalid
random chars), in the previous implementation it was possible to end
with something like:

dksfjdksjflskfjl*2\r\n$4\r\nECHO....

That is invalid as the *2 should start into a new line. Now we prefix
the ECHO with a CRLF that has no effects on the server but prevents this
issues most of the times.

Of course if the offending wrong sequence is something like:

$3248772349\r\n

No one is going to save us as Redis will wait for data in the context of
a big argument, so this fix does not cover all the cases.

This partially fixes issue #681.
2013-07-03 12:02:09 +02:00
antirez 2f6560f351 pqsort.c: remove the "switch to insertion sort" optimization.
It causes catastrophic performance for certain inputs.

Relevant NetBSD commit:

http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/stdlib/qsort.c?rev=1.20&content-type=text/x-cvsweb-markup&only_with_tag=MAIN

This fixes issue #968.
2013-07-02 17:48:49 +02:00
charsyam 847a40805f fix randstring bug 2013-07-02 12:25:30 +02:00
antirez aaf5623535 Only allow basenames for dbfilename and appendfilename.
This fixes issue #1094.
2013-07-02 12:24:12 +02:00
antirez 25a3ad7f74 pathIsBaseName() added to utils.c
The function is used to test that the specified string looks like just
as the basename of a path, without any absolute or relative path.
2013-07-02 12:24:08 +02:00
antirez 028fdbb70b getAbsolutePath() moved into utils.c 2013-07-02 12:24:05 +02:00
antirez 8090c37f71 CONFIG SET maxclients. 2013-07-01 10:44:08 +02:00
antirez 75dc48f04a ae.c event loop: API to resize the fd set size on the run. 2013-07-01 10:44:04 +02:00
antirez 0a0fd37e5b Allow SHUTDOWN in loading state. 2013-06-27 12:14:23 +02:00
antirez 7bfbc38068 Redis 2.7.3 2013-06-26 15:26:11 +02:00
antirez bb0d0fd479 function renamed: popcount_binary -> redisPopcount. 2013-06-26 15:24:58 +02:00
antirez cdf79c063f Don't disconnect pre PSYNC replication clients for timeout.
Clients using SYNC to replicate are older implementations, such as
redis-cli --slave, and are not designed to acknowledge the master with
REPLCONF ACK commands, so we don't have any feedback and should not
disconnect them on timeout.
2013-06-26 15:24:30 +02:00
antirez 7cca200603 Test: add some AOF testing to EVALSHA replication test. 2013-06-26 15:24:26 +02:00
antirez 29308077a8 Flush the replication script cache after SCRIPT FLUSH. 2013-06-26 15:24:22 +02:00
antirez 9faa6b9dc3 Test: EVALSHA replication. 2013-06-26 15:24:19 +02:00
antirez b0e0f3c5b2 Test: randomInt() behavior commented. 2013-06-26 15:24:16 +02:00
antirez 4e2b97bb89 Test: replication-3 test speedup in master-slave setup. 2013-06-26 15:24:12 +02:00
antirez d34c1ac208 Force propagation of SCRIPT LOAD to AOF. 2013-06-26 15:24:09 +02:00
antirez b7233f40fb SCRIPT FLUSH comment minor pedantic improvement. 2013-06-26 15:24:04 +02:00
antirez 8450d6e24d Move Replication Script Cache initialization in safer place.
It should be called just one time at startup and not every time the Lua
scripting engine is re-initialized, otherwise memory is leaked.
2013-06-26 15:23:52 +02:00
antirez 545fe0c318 Use the RSC to replicate EVALSHA unmodified.
This commit uses the Replication Script Cache in order to avoid
translating EVALSHA into EVAL whenever possible for both the AOF and
slaves.
2013-06-26 15:23:29 +02:00
antirez 9d894b1b8c Replication of scripts as EVALSHA: sha1 caching implemented.
This code is only responsible to take an LRU-evicted fixed length cache
of SHA1 that we are sure all the slaves received.

In this commit only the implementation is provided, but the Redis core
does not use it to actually send EVALSHA to slaves when possible.
2013-06-26 15:23:15 +02:00
antirez 8328d993e1 New API to force propagation.
The old REDIS_CMD_FORCE_REPLICATION flag was removed from the
implementation of Redis, now there is a new API to force specific
executions of a command to be propagated to AOF / Replication link:

    void forceCommandPropagation(int flags);

The new API is also compatible with Lua scripting, so a script that will
execute commands that are forced to be propagated, will also be
propagated itself accordingly even if no change to data is operated.

As a side effect, this new design fixes the issue with scripts not able
to propagate PUBLISH to slaves (issue #873).
2013-06-26 15:21:55 +02:00
antirez aeab473dfb Allow PUBSUB NUMSUB without channels.
The result is an empty list but it is handy to call it programmatically.
2013-06-26 15:21:12 +02:00
antirez a8f1474dfd PUBSUB command implemented.
Currently it implements three subcommands:

PUBSUB CHANNELS [<pattern>]    List channels with non-zero subscribers.
PUBSUB NUMSUB [channel_1 ...]  List number of subscribers for channels.
PUBSUB NUMPAT                  Return number of subscribed patterns.
2013-06-26 15:21:08 +02:00
antirez 72d3fbea65 Fix comment typo in integration/aof.tcl. 2013-06-26 15:21:05 +02:00
YAMAMOTO Takashi 3642557ae6 don't assume time_t == long
time_t is always 64bit on recent versions of NetBSD.
2013-06-26 15:20:59 +02:00
YAMAMOTO Takashi 30f3ae03a5 use nanosleep instead of usleep
SUSv3 says that:
	The useconds argument shall be less than one million. If the value of
	useconds is 0, then the call has no effect.
and actually NetBSD's implementation rejects such a value with EINVAL.
use nanosleep which has no such a limitation instead.
2013-06-26 15:20:56 +02:00
YAMAMOTO Takashi f27896a17d rename popcount to popcount_binary to avoid a conflict with NetBSD libc
NetBSD-current's libc has a function named popcount.
hiding these extensions using feature macros is not possible because
redis uses other extensions covered by the same feature macro.
eg. inet_aton
2013-06-26 15:20:32 +02:00
YAMAMOTO Takashi 9a7b09ed35 don't define _XOPEN_SOURCE for NetBSD
on NetBSD, defining _XOPEN_SOURCE hides extensions
like inet_aton, strcasecmp, etc.
2013-06-26 15:20:03 +02:00
Jan-Erik Rediger 90c3a14ac0 Initialize char* to NULL to remove compiler warning 2013-06-20 16:59:04 +02:00
antirez 9af8125c7d Sentinel: parse new INFO replication output correctly.
Sentinel was not able to detect slaves when connected to a very recent
version of Redis master since a previos non-backward compatible change
to INFO broken the parsing of the slaves ip:port INFO output.

This fixes issue #1164
2013-06-20 10:24:31 +02:00
antirez be8d5fd954 Test: regression test for #1163. 2013-06-19 18:53:14 +02:00
antirez 86147afd08 Allow writes from scripts called by AOF loading in read-only slaves.
This fixes issue #1163
2013-06-19 18:29:05 +02:00
antirez 449ec38fe4 Fix logStackTrace() when logging to stdout.
When the semantics changed from logfile = NULL to logfile = "" to log
into standard output, no proper change was made to logStackTrace() to
make it able to work with the new setup.

This commit fixes the issue.
2013-06-19 14:45:38 +02:00
antirez bf8e078aa2 Lua script errors format more unified.
lua_pcall error handler now formats errors in a way more similar to
luaPushError() so that errors generated in different contexts look alike.
2013-06-18 19:38:23 +02:00
antirez b3fbc84cc1 Lua scripting: improve error reporting.
When calling Lua scripts we try to report not just the error but
information about the code line causing the error.
2013-06-18 19:38:19 +02:00
Marc-Antoine Perennou 1cf839a216 test-server: only listen to 127.0.0.1
Signed-off-by: Marc-Antoine Perennou <Marc-Antoine@Perennou.com>
2013-06-18 19:38:16 +02:00
ioddly e34f00842f Try to report source of bad Lua API calls 2013-06-18 19:38:10 +02:00
antirez b435817b17 Binary safe dump of object content in redisLogObjectDebugInfo(). 2013-06-04 15:55:35 +02:00
antirez 054632f551 CONFIG SET: accept slave-priority zero, it is valid. 2013-05-31 19:31:51 +02:00
antirez d34c623e10 Test: avoid a false positive in min-slaves test. 2013-05-31 11:43:23 +02:00
antirez fe4b62fcaf Refresh good slaves count after CONFIG SET min-slaves-...
This way just after the CONFIG SET enabling the min-slaves feature it is
possible to write to the database without delays.
2013-05-31 07:20:19 +02:00
antirez 971217c7b4 New INFO field "min_slaves_good_slaves".
When min-slaves-to-write feature is active, this field reports the
number of slaves considered good (online state, lag within the specified
range).
2013-05-31 07:19:45 +02:00
antirez f5275da6e9 Refresh good slaves count when setting slave state as online. 2013-05-31 07:19:42 +02:00
antirez 9a7ea3dae9 Tests added for min-slaves feature. 2013-05-30 18:54:39 +02:00
antirez 4333e6ce20 Make tests compatible with new INFO replication output. 2013-05-30 11:43:53 +02:00
antirez 9d1bc3efcf min-replicas-to-write: only deny write commands.
I guess I needed another coffee...
2013-05-30 11:32:00 +02:00
antirez d0d67f8d42 min-slaves-to-write: don't accept writes with less than N replicas.
This feature allows the user to specify the minimum number of
connected replicas having a lag less or equal than the specified
amount of seconds for writes to be accepted.
2013-05-30 11:31:46 +02:00
antirez fc2587ec6e min-slaves-to-write: initial description of the feature in redis.conf 2013-05-30 11:30:34 +02:00
antirez 75fcf5c936 repl_offset field in INFO replication is now just offset. 2013-05-29 19:56:40 +02:00
antirez c64338aa8f Slaves list in INFO output: lag added, format changed.
There is a new 'lag' information in the list of slaves, in the
"replication" section of the INFO output.

Also the format was changed in a backward incompatible way in order to
make it more easy to parse if new fields are added in the future, as the
new format is comma separated but has named fields (no longer positional
fields).
2013-05-29 19:55:11 +02:00
antirez 889d39b5ba Accept REPLCONF in any state. 2013-05-28 15:26:55 +02:00
antirez 1b87b3ef38 A comment about BLPOP timeout did not reflected actual behavior. 2013-05-27 19:34:12 +02:00
antirez 2d497014ea Version bumped to 2.7.2. 2013-05-27 11:43:47 +02:00
antirez 75bf91abea redis.conf updated: repl-timeout now uesd by masters as well. 2013-05-27 11:43:21 +02:00
antirez 308940aa2c Close connection with timedout slaves.
Now masters, using the time at which the last REPLCONF ACK was received,
are able to explicitly disconnect slaves that are no longer responding.

Previously the only chance was to see a very long output buffer, that
was highly suboptimal.
2013-05-27 11:43:17 +02:00
antirez 45e6a4023a Send ACK to master once every second.
ACKs can be also used as a base for synchronous replication. However in
that case they'll be explicitly requested by the master when the client
sends a request that needs to be replicated synchronously.
2013-05-27 11:43:14 +02:00
antirez a74f8fe1ad Don't ACK the master after every command.
Sending an ACK is now moved into the replicationSendAck() function.
2013-05-27 11:43:10 +02:00
antirez 0000d5334d Make sure that REPLCONF ACK really has no return value. 2013-05-27 11:43:06 +02:00
antirez 146f1d7d86 Replication: send REPLCONF ACK to master. 2013-05-27 11:43:03 +02:00
antirez 1e77b77de4 REPLCONF ACK command.
This special command is used by the slave to inform the master the
amount of replication stream it currently consumed.

it does not return anything so that we not need to consume additional
bandwidth needed by the master to reply something.

The master can do a number of things knowing the amount of stream
processed, such as understanding the "lag" in bytes of the slave, verify
if a given command was already processed by the slave, and so forth.
2013-05-27 11:43:00 +02:00
antirez 0387f86947 Fixed a bug in no queueing replies to master. 2013-05-25 01:00:16 +02:00
antirez 7ae29d5d9e Replication: don't even queue replies to master commands.
When master send commands, there is no need for the slave to reply.
Redis used to queue the reply in the output buffer and discard the reply
later, this is a waste of work and it is not clear why it was this way
(I sincerely don't remember).

This commit changes it in order to don't queue the reply at all.

All tests passing.
2013-05-24 19:48:03 +02:00
antirez 302b0a5c7b Top comment for prepareClientToWrite() clarified.
We don't write the output buffer to the client socket for slaves only if
the slave is not online.
2013-05-24 18:42:18 +02:00
antirez 0b398b6ffa Don't stop reading redis.conf if line has no args.
Should be "continue" and was "return".

This fixes issue #1110
2013-05-18 16:22:24 +02:00
antirez 059018c4cd CONFIG REWRITE: remove cluster stuff for 2.8 back port. 2013-05-15 12:02:44 +02:00
antirez 414d5db85c Use memtoll() when parsing the backlog-size option. 2013-05-15 12:01:32 +02:00
antirez 604efb02ab CONFIG REWRITE: backlog size is a bytes option. 2013-05-15 12:01:27 +02:00
antirez 7d338fd982 CONFIG REWRITE: bindaddr -> bind. 2013-05-15 12:01:23 +02:00
antirez 2173f30836 CONFIG REWRITE: when rewriting amount of bytes use GB, MB, KB if possible. 2013-05-15 12:01:14 +02:00
antirez 901ceab20b CONFIG REWRITE: correctly escape the notify-keyspace-events option. 2013-05-15 12:01:06 +02:00
antirez b4ce02988c CONFIG REWRITE: "active-rehashing" -> "activerehashing". 2013-05-15 12:01:02 +02:00
antirez 2a29cf430a CONFIG REWRITE: fixed typo in AOF fsync policy. 2013-05-15 12:00:57 +02:00
antirez c95a60960b CONFIG REWRITE: repl-disable-tcp-nodelay is a boolean option. 2013-05-15 12:00:52 +02:00
antirez e8cc959300 Test default config modified to set logfile to empty string.
Required because of recent changes in the way logfile is set to standard
output.
2013-05-15 12:00:47 +02:00
antirez 180cfaae8e Added a define for most configuration defaults.
Also the logfile option was modified to always have an explicit value
and to log to stdout when an empty string is used as log file.

Previously there was special handling of the string "stdout" that set
the logfile to NULL, this always required some special handling.
2013-05-15 12:00:43 +02:00
antirez 417253afc3 CONFIG REWRITE: Use sane perms when creating redis.conf from scratch. 2013-05-15 12:00:38 +02:00
antirez 8feb3d261a CONFIG REWRITE: actually rewrite the config file, atomically. 2013-05-15 12:00:32 +02:00
antirez a461376ca9 CONFIG REWRITE: remove orphaned lines. 2013-05-15 12:00:28 +02:00
antirez 8cf0b66606 CONFIG REWRITE: strip multiple empty lines. 2013-05-15 12:00:24 +02:00
antirez cb8433f313 CONFIG REWRITE: support for client-output-buffer-limit. 2013-05-15 12:00:21 +02:00
antirez f62851aea9 CONFIG REWRITE: support for dir and slaveof. 2013-05-15 12:00:17 +02:00
antirez 91ef10ef45 CONFIG REWRITE: support for save and syslog-facility. 2013-05-15 12:00:13 +02:00
antirez 973f793b04 CONFIG REWRITE: Initial support code and design. 2013-05-15 12:00:03 +02:00
antirez 7a5d3d91a1 Obtain absoute path of configuration file, expose it in INFO. 2013-05-15 11:59:26 +02:00
antirez d8dd70c2dd redis-cli: help.h updated. 2013-05-14 11:22:58 +02:00
antirez 41babac68d Test: more PSYNC tests (backlog TTL). 2013-05-09 12:52:16 +02:00
antirez cb55ad991b Version bumped to 2.7.1 2013-05-08 15:24:54 +02:00
antirez c847c73e78 Test: check that replication partial sync works if we break the link.
The test checks both successful syncs and unsuccessful ones by changing
the backlog size.
2013-05-08 13:02:53 +02:00
antirez 2865bb7f1f Test: various issues with the replication-4.tcl test fixed.
The test actually worked, but vars for master and slave were inverted
and sometimes used incorrectly.
2013-05-08 11:59:42 +02:00
Jiahao Huang 3d93dffd5d in 32bit machine, popcount don't work with a input string length up to 512 MB,
bitcount commant may return negtive integer with string length more than 256 MB
2013-05-08 09:56:10 +02:00
antirez 85671d0e3b Added more info about 32 bit builds into README. 2013-05-08 09:48:46 +02:00
antirez b05d2516fe CONFIG SET server.masterauth aesthetic change.
This is just to make the code exactly like the above instance used for
requirepass. No actual change nor the original code violated the Redis
coding style.
2013-05-02 17:22:30 +02:00
Michel Martens b60465eb78 Reset masterauth if an empty string is configured. 2013-05-02 17:21:55 +02:00
charsyam a94db7908a Fix AOF bug: expire could be removed from key on AOF rewrite.
There was a race condition in the AOF rewrite code that, with bad enough
timing, could cause a volatile key just about to expire to be turned
into a non-volatile key. The bug was never reported to cause actualy
issues, but was found analytically by an user in the Redis mailing list:

https://groups.google.com/forum/?fromgroups=#!topic/redis-db/Kvh2FAGK4Uk

This commit fixes issue #1079.
2013-05-02 15:46:56 +02:00
antirez e7bcec829c Sentinel: changes to tilt mode.
Tilt mode was too aggressive (not processing INFO output), this
resulted in a few problems:

1) Redirections were not followed when in tilt mode. This opened a
   window to misinform clients about the current master when a Sentinel
   was in tilt mode and a fail over happened during the time it was not
   able to update the state.

2) It was possible for a Sentinel exiting tilt mode to detect a false
   fail over start, if a slave rebooted with a wrong configuration
   about at the same time. This used to happen since in tilt mode we
   lose the information that the runid changed (reboot).

   Now instead the Sentinel in tilt mode will still remove the instance
   from the list of slaves if it changes state AND runid at the same
   time.

Both are edge conditions but the changes should overall improve the
reliability of Sentinel.
2013-04-30 15:09:14 +02:00
antirez 4028a777b6 Sentinel: more sensible delay in master demote after tilt. 2013-04-30 15:09:10 +02:00
antirez 70845320cc Sentinel: only demote old master into slave under certain conditions.
We used to always turn a master into a slave if the DEMOTE flag was set,
as this was a resurrecting master instance.

However the following race condition is possible for a Sentinel that
got partitioned or internal issues (tilt mode), and was not able to
refresh the state in the meantime:

1) Sentinel X is running, master is instance "A".
3) "A" fails, sentinels will promote slave "B" as master.
2) Sentinel X goes down because of a network partition.
4) "A" returns available, Sentinels will demote it as a slave.
5) "B" fails, other Sentinels will promote slave "A" as master.
6) At this point Sentinel X comes back.

When "X" comes back he thinks that:

"B" is the master.
"A" is the slave to demote.

We want to avoid that Sentinel "X" will demote "A" into a slave.
We also want that Sentinel "X" will detect that the conditions changed
and will reconfigure itself to monitor the right master.

There are two main ways for the Sentinel to reconfigure itself after
this event:

1) If "B" is reachable and already configured as a slave by other
sentinels, "X" will perform a redirection to "A".
2) If there are not the conditions to demote "A", the fact that "A"
reports to be a master will trigger a failover detection in "X", that
will end into a reconfiguraiton to monitor "A".

However if the Sentinel was not reachable, its state may not be updated,
so in case it titled, or was partiitoned from the master instance of the
slave to demote, the new implementation waits some time (enough to
guarantee we can detect the new INFO, and new DOWN conditions).

If after some time still there are not the right condiitons to demote
the instance, the DEMOTE flag is cleared.
2013-04-30 15:09:06 +02:00
antirez 2b427a89fb Lua updated to version 5.1.5. 2013-04-29 12:02:01 +02:00
antirez d2ff5ed603 Sentinel: always redirect on master->slave transition.
Sentinel redirected to the master if the instance changed runid or it
was the first time we got INFO, and a role change was detected from
master to slave.

While this is a good idea in case of slave->master, since otherwise we
could detect a failover without good reasons just after a reboot with a
slave with a wrong configuration, in the case of master->slave
transition is much better to always perform the redirection for the
following reasons:

1) A Sentinel may go down for some time. When it is back online there is
no other way to understand there was a failover.
2) Pointing clients to a slave seems to be always the wrong thing to do.
3) There is no good rationale about handling things differently once an
instance is rebooted (runid change) in that case.
2013-04-24 11:34:02 +02:00
antirez b06f13e7b7 Config option to turn AOF rewrite incremental fsync on/off. 2013-04-24 10:57:35 +02:00
antirez 9ca306d874 AOF: sync data on disk every 32MB when rewriting.
This prevents the kernel from putting too much stuff in the output
buffers, doing too heavy I/O all at once. So the goal of this commit is
to split the disk pressure due to the AOF rewrite process into smaller
spikes.

Please see issue #1019 for more information.
2013-04-24 10:27:02 +02:00
antirez 5276996cf1 rio.c: added ability to fdatasync() from time to time while writing. 2013-04-24 10:26:58 +02:00
antirez e426ea5245 Test: fix RDB test checking file permissions.
When the test is executed using the root account, setting the permission
to 222 does not work as expected, as root can read files with 222
permission.

Now we skip the test if root is detected.

This fixes issue #1034 and the duplicated #1040 issue.

Thanks to Jan-Erik Rediger (@badboy on Github) for finding a way to reproduce the issue.
2013-04-23 14:16:47 +02:00
antirez d0c9a2a767 Sentinel: turn old master into a slave when it comes back. 2013-04-22 11:26:29 +02:00
antirez a500c120ba Test: split conceptually unrelated comments in RDB test. 2013-04-22 11:26:02 +02:00
antirez 3a865829a7 More explicit panic message on out of memory. 2013-04-19 15:11:55 +02:00
antirez 0e3baa5241 Redis/Jemalloc Gitignore were too aggressive.
Redis gitignore was too aggressive since simply broken.

Jemalloc gitignore was too agressive because it is conceived to just
keep the files that allow to generate all the rest in development
environments (so for instance the "configure" file is excluded).
2013-04-18 16:23:12 +02:00
antirez 42ba5d3788 redis-cli: raise error on bad command line switch.
Previously redis-cli never tried to raise an error when an unrecognized
switch was encountered, as everything after the initial options is to be
transmitted to the server.

However this is too liberal, as there are no commands starting with "-".
So the new behavior is to produce an error if there is an unrecognized
switch starting with "-". This should not break past redis-cli usages
but should prevent broken options to be silently discarded.

As far the first token not starting with "-" is encountered, all the
rest is considered to be part of the command, so you cna still use
strings starting with "-" as values, like in:

    redis-cli --port 6380 set foo --my-value
2013-04-11 13:19:52 +02:00
antirez 0d7b10ee6e redis-cli: --latency-history mode implemented. 2013-04-11 13:19:48 +02:00
antirez 8d81e68d18 Test: remove useless statements and comments from test default config. 2013-04-04 14:31:30 +02:00
antirez d83d72f380 Make rio.c comment 80-columns friendly. 2013-04-03 12:41:27 +02:00
antirez d6b0c18c51 Throttle BGSAVE attempt on saving error.
When a BGSAVE fails, Redis used to flood itself trying to BGSAVE at
every next cron call, that is either 10 or 100 times per second
depending on configuration and server version.

This commit does not allow a new automatic BGSAVE attempt to be
performed before a few seconds delay (currently 5).

This avoids both the auto-flood problem and filling the disk with
logs at a serious rate.

The five seconds limit, considering a log entry of 200 bytes, will use
less than 4 MB of disk space per day that is reasonable, the sysadmin
should notice before of catastrofic events especially since by default
Redis will stop serving write queries after the first failed BGSAVE.

This fixes issue #849
2013-04-02 14:12:28 +02:00
Johan Bergström 0bc7dbda1c use install as default installer (except on SunOS) 2013-04-02 11:54:20 +02:00
Johan Bergström 034d94193d Silence mkdir output 2013-04-02 11:30:16 +02:00
Johan Bergström 2094eb36e7 Only pass -rdynamic as linker option 2013-04-02 11:30:13 +02:00
Johan Bergström 31844fe138 Remove extra spaces 2013-04-02 11:29:56 +02:00
Johan Bergström c72459bf18 make check is a common naming convention for tests 2013-04-02 11:27:38 +02:00
Johan Bergström ca0b1c2436 Inherit CC for Lua 2013-04-02 11:26:50 +02:00
Johan Bergström 49eef7738e Spaces to tabs 2013-04-02 11:26:41 +02:00
Johan Bergström 0c8690d46e Slightly refactor CFLAGS/LDFLAGS/LIBS
This way, we can avoid -rdynamic and -pthread warnings on darwin.
2013-04-02 11:26:37 +02:00
charsyam cf8484b723 Support for case unsensitive SET options. 2013-03-29 10:37:29 +01:00
antirez 0c447d3506 Test: Extended SET tests. 2013-03-28 16:39:39 +01:00
antirez 24aa61785d Extended SET command implemented (issue #931). 2013-03-28 16:39:34 +01:00
antirez e6e07042a1 EXPIRE should not resurrect keys. Issue #1026. 2013-03-28 12:48:38 +01:00
antirez be832929c8 Test: regression test for issue #1026. 2013-03-28 12:48:35 +01:00
antirez 4de4080ea5 Better DEBUG error message when num of arguments is wrong. 2013-03-28 12:48:30 +01:00
antirez 978185bf67 Test: verify that lazy-expire works. 2013-03-28 12:48:26 +01:00
antirez 10d8e6a712 DEBUG set-active-expire added.
We need the ability to disable the activeExpireCycle() (active
expired key collection) call for testing purposes.
2013-03-28 12:48:19 +01:00
antirez c6a9a20d3b Test: test replication of MULTI/EXEC. 2013-03-27 12:05:22 +01:00
antirez 61b1d6da18 Test: Restore DB back to 9 after testing MULTI/EXEC with DB 5. 2013-03-27 12:05:19 +01:00
antirez 7009ff98b8 Test: new functions to capture and analyze the replication stream. 2013-03-27 12:05:15 +01:00
antirez a452b54849 TTL / PTTL commands: two bugs fixed.
This commit fixes two corner cases for the TTL command.

1) When the key was already logically expired (expire time older
than current time) the command returned -1 instead of -2.

2) When the key was existing and the expire was found to be exactly 0
(the key was just about to expire), the command reported -1 (that is, no
expire) instead of a TTL of zero (that is, about to expire).
2013-03-26 13:59:33 +01:00
antirez 10c6195e3f Flag PUBLISH as read-only in the command table. 2013-03-26 13:59:26 +01:00
antirez 2618101ad5 Transactions: propagate MULTI/EXEC only when needed.
MULTI/EXEC is now propagated to the AOF / Slaves only once we encounter
the first command that is not a read-only one inside the transaction.

The old behavior was to always propagate an empty MULTI/EXEC block when
the transaction was composed just of read only commands, or even
completely empty. This created two problems:

1) It's a bandwidth waste in the replication link and a space waste
   inside the AOF file.

2) We used to always increment server.dirty to force the propagation of
   the EXEC command, resulting into triggering RDB saves more often
   than needed.

Note: even read-only commands may also trigger writes that will be
propagated, when we access a key that is found expired and Redis will
synthesize a DEL operation. However there is no need for this to stay
inside the transaction itself, but only to be ordered.

So for instance something like:

    MULTI
    GET foo
    SET key zap
    EXEC

May be propagated into:

    DEL foo
    MULTI
    SET key zap
    EXEC

While the DEL is outside the transaction, the commands are delivered in
the right order and it is not possible for other commands to be inserted
between DEL and MULTI.
2013-03-26 13:59:21 +01:00
antirez 82516e8168 Transactions: use discardTransaction() in EXEC implementation. 2013-03-26 13:59:18 +01:00
antirez d91dfaf6dc Transactions: use the propagate() API to propagate MULTI.
The behavior is the same, but the code is now cleaner and uses the
proper interface instead of dealing directly with AOF/replication
functions.
2013-03-26 13:59:14 +01:00
antirez 150f7e43ca Allow SELECT while loading the DB.
Fixes issue #1024.
2013-03-26 13:59:05 +01:00
NanXiao 382420ec6e Update config.c
Fix bug in configGetCommand function: get correct masterauth value.
2013-03-25 19:32:20 +01:00
antirez 6b7562e204 Test: obuf-limits test false positive removed.
Fixes #621.
2013-03-25 11:56:31 +01:00
antirez ec18d4bfba redis-cli --stat, stolen from redis-tools.
Redis-tools is a connection of tools no longer mantained that was
intented as a way to economically make sense of Redis in the pre-vmware
sponsorship era. However there was a nice redis-stat utility, this
commit imports one of the functionalities of this tool here in redis-cli
as it seems to be pretty useful.

Usage: redis-cli --stat

The output is similar to vmstat in the format, but with Redis specific
stuff of course.

From the point of view of the monitored instance, only INFO is used in
order to grab data.
2013-03-25 11:52:25 +01:00
antirez 58708fa65a Replication: master_link_down_since_seconds initial value should be huge.
server.repl_down_since used to be initialized to the current time at
startup. This is wrong since the replication never started. Clients
testing this filed to check if data is uptodate should never believe
data is recent if we never ever connected to our master.
2013-03-13 12:55:02 +01:00
antirez ba8367ee49 Test: make sure broken RDB checksum is detected. 2013-03-13 11:15:31 +01:00
antirez 67a745515d Test: more RDB loading checks.
A test for issue #1001 is included.
2013-03-13 10:08:40 +01:00
antirez 7b6fb960cc Test: check that Redis starts empty without an RDB file. 2013-03-13 10:08:36 +01:00
antirez 64b91c1493 rdbLoad(): rework code to save vertical space. 2013-03-13 10:08:30 +01:00
Damian Janowski edb3c10361 Abort when opening the RDB file results in an error other than ENOENT.
This fixes cases where the RDB file does exist but can't be accessed for
any reason. For instance, when the Redis process doesn't have enough
permissions on the file.
2013-03-13 10:08:26 +01:00
antirez b2e42a90d3 Set default for stop_writes_on_bgsave_err in initServerConfig().
It was placed for error in initServer() that's called after the
configuation is already loaded, causing issue #1000.
2013-03-12 18:36:02 +01:00
antirez 378fcaad55 redis-cli --bigkeys: don't crash with empty DBs. 2013-03-12 09:57:57 +01:00
antirez 8e7d962291 activeExpireCycle() smarter with many DBs and under expire pressure.
activeExpireCycle() tries to test just a few DBs per iteration so that
it scales if there are many configured DBs in the Redis instance.
However this commit makes it a bit smarter when one a few of those DBs
are under expiration pressure and there are many many keys to expire.

What we do is to remember if in the last iteration had to return because
we ran out of time. In that case the next iteration we'll test all the
configured DBs so that we are sure we'll test again the DB under
pressure.

Before of this commit after some mass-expire in a given DB the function
tested just a few of the next DBs, possibly empty, a few per iteration,
so it took a long time for the function to reach again the DB under
pressure. This resulted in a lot of memory being used by already expired
keys and never accessed by clients.
2013-03-11 11:30:13 +01:00
antirez c5afbb6e2a In databasesCron() never test more DBs than we have. 2013-03-11 11:30:09 +01:00
antirez 7e7ee815b0 Make comment name match var name in activeExpireCycle(). 2013-03-11 11:30:04 +01:00
antirez 6a8288766c Optimize inner loop of activeExpireCycle() for no-expires case. 2013-03-11 11:30:01 +01:00
antirez 9afb7789b3 REDIS_DBCRON_DBS_PER_SEC -> REDIS_DBCRON_DBS_PER_CALL 2013-03-11 11:29:57 +01:00
antirez 1a73260dce activeExpireCycle(): process only a small number of DBs per iteration.
This small number of DBs is set to 16 so actually in the default
configuraiton Redis should behave exactly like in the past.
However the difference is that when the user configures a very large
number of DBs we don't do an O(N) operation, consuming a non trivial
amount of CPU per serverCron() iteration.
2013-03-11 11:29:53 +01:00
antirez 8abfe5fe8b Use unsigned integers for DB ids, for defined wrap-to-zero. 2013-03-11 11:29:49 +01:00
antirez a4bb4b29fb Only resize/rehash a few databases per cron iteration.
This is the first step to lower the CPU usage when many databases are
configured. The other is to also process a limited number of DBs per
call in the active expire cycle.
2013-03-11 11:29:45 +01:00
antirez aa7f74c7e9 Actually call databasesCron() inside serverCron(). 2013-03-11 11:29:41 +01:00
antirez 21822fd0e9 Move Redis databases background processing to databasesCron(). 2013-03-11 11:29:34 +01:00
Gengliang Wang 6ae8fb477a Removed useless "return" statements in pubsub.c
(original commit message edited)
2013-03-06 16:50:53 +01:00
antirez bc1b2e8f96 API to lookup commands with their original name.
A new server.orig_commands table was added to the server structure, this
contains a copy of the commant table unaffected by rename-command
statements in redis.conf.

A new API lookupCommandOrOriginal() was added that checks both tables,
new first, old later, so that rewriteClientCommandVector() and friends
can lookup commands with their new or original name in order to fix the
client->cmd pointer when the argument vector is renamed.

This fixes the segfault of issue #986, but does not fix a wider range of
problems resulting from renaming commands that actually operate on data
and are registered into the AOF file or propagated to slaves... That is
command renaming should be handled with care.
2013-03-06 16:36:57 +01:00
antirez deccd104d8 Add a warning about command renaming in redis.conf. 2013-03-06 16:36:57 +01:00
antirez 5bcfa2ebbc Handle a non-impossible empty argv in loadServerConfigFromString().
Usually this does not happens since we trim for " \t\r\n", but if there
are other chars that return true with isspace(), we may end with an
empty argv. Better to handle the condition in an explicit way.
2013-03-06 12:43:49 +01:00
antirez 00a5e86e44 redis-cli: use sdsfreesplitres() instead of hand-coding it. 2013-03-06 12:43:45 +01:00
antirez f72a3406a7 sds.c: sdssplitargs_free() removed as it was a duplicate. 2013-03-06 12:43:41 +01:00
antirez 68532cf01e More specific error message in loadServerConfigFromString(). 2013-03-06 12:43:37 +01:00
antirez a70e3ffb16 sdssplitargs(): on error set *argc to 0.
This makes programs not checking the return value for NULL much safer
since with this change:

1) It is still possible to iterate the zero-length result without
crashes.
2) sdssplitargs_free will work against NULL and 0 count.
2013-03-06 12:43:32 +01:00
antirez a6f557657c sdssplitargs(): now returns NULL only on error.
An empty input string also resulted into the function returning NULL
making it harder for the caller to distinguish between error and empty
string without checking the original input string length.
2013-03-06 12:43:27 +01:00
charsyam 5aeb1f71a3 Don't segfault on unbalanced quotes. 2013-03-06 12:43:22 +01:00
antirez aadb8aaa08 Allow AUTH while loading the DB in memory.
While Redis is loading the AOF or RDB file in memory only a subset of
commands are allowed. This commit adds AUTH to this subset.
2013-03-06 11:51:23 +01:00
antirez df3b492045 redis-cli: use keepalive socket option.
This should improve things in two ways:

1) Prevent timeouts caused by the execution of long commands.
2) Improve detection of real connection errors.

This is mostly effective only on Linux because of the bogus default
keepalive settings. In Linux we have OS-specific calls to set the
keepalive interval to reasonable values.
2013-03-04 11:16:21 +01:00
0x20h e618eb4fb3 suppress external diff program when using git diff. 2013-03-04 10:58:02 +01:00
Stam He a74056f1e0 point 2 of slave-serve-stale-data miss '-' between 'stale' and 'data' 2013-03-04 10:49:19 +01:00
antirez 02cd545f7d A comment in main() clarified. 2013-02-27 12:47:51 +01:00
antirez b148513fec Remove warning when printing redisBuildId(). 2013-02-27 12:47:38 +01:00
antirez 5e9e96999f Remove too agressive/spamming log in rdb.c. 2013-02-27 12:47:33 +01:00
antirez d2a37badc2 Use GCC printf format attribute for redisLog().
This commit also fixes redisLog() statements producing warnings.
2013-02-27 12:47:16 +01:00
antirez 0d8d7f106a Better panic message for failed time event creation. 2013-02-27 12:00:51 +01:00
Stam He 438dfdd13d add a check for aeCreateTimeEvent
1) Add a check for aeCreateTimeEvent in function initServer.
2013-02-27 12:00:48 +01:00
Stam He 6f96ac1c1c Set proctitle: avoid the use of __attribute__((constructor)).
This cased a segfault in some Linux system and was GCC-specific.

Commit modified by @antirez:

1) Stripped away the part to set the proc title via config for now.
2) Handle initialization of setproctitle only when the replacement
   is used.
3) Don't require GCC now that the attribute constructor is no
   longer used.
2013-02-27 12:00:43 +01:00
antirez f69b0a0db8 setproctitle.c: declar tmp as static so valgrind will not detect a leak. 2013-02-26 15:51:15 +01:00
antirez ac3100bc3b Set process name in ps output to make operations safer.
This commit allows Redis to set a process name that includes the binding
address and the port number in order to make operations simpler.

Redis children processes doing AOF rewrites or RDB saving change the
name into redis-aof-rewrite and redis-rdb-bgsave respectively.

This in general makes harder to kill the wrong process because of an
error and makes simpler to identify saving children.

This feature was suggested by Arnaud GRANAL in the Redis Google Group,
Arnaud also pointed me to the setproctitle.c implementation includeed in
this commit.

This feature should work on all the Linux, OSX, and all the three major
BSD systems.
2013-02-26 12:03:48 +01:00
Arnaud Granal fedcd51185 Fix error "repl-backlog-size must be 1 or greater"
The parameter repl-backlog-size is not parsed correctly in the configuration file. argv[0] is parsed instead of argv[1].
2013-02-25 18:38:48 +01:00
antirez ff56772115 PSYNC: another change to unexpected reply from PSYNC. 2013-02-13 18:43:45 +01:00
antirez 964ee00de0 PSYNC: More robust handling of unexpected reply to PSYNC. 2013-02-13 18:34:18 +01:00
antirez 3b4c0d80d5 Avoid compiler warning by casting to match printf() specifier. 2013-02-13 13:38:32 +01:00
antirez d96a66f531 Replication: more strict error checking for master PING reply. 2013-02-12 16:59:27 +01:00
antirez 31f0a6ec50 Replication: added new stats counting full and partial resynchronizations. 2013-02-12 16:26:42 +01:00
antirez 5fe2577a19 Return a specific NOAUTH error if authentication is required. 2013-02-12 16:25:47 +01:00
antirez 9854252798 Test: avoid false positives in CLIENT SETNAME closed connection test. 2013-02-12 13:27:30 +01:00
Steven Penny 5a8cf340a4 Format to fit 80 columns
This makes it readable on GitHub and editors without auto wrapping.
2013-02-12 13:11:44 +01:00
antirez 0ae1a6b1c3 Add missing bracket removed for error after rebase of PSYNC. 2013-02-12 12:58:07 +01:00
antirez 2b1398e870 PSYNC: debugging printf() calls are now logs at DEBUG level. 2013-02-12 12:58:03 +01:00
antirez 5a4ef3e5b6 Remove harmless warning in slaveTryPartialResynchronization(). 2013-02-12 12:57:59 +01:00
antirez 4fe7672dfc PSYNC: don't use the client buffer to send +CONTINUE and +FULLRESYNC.
When we are preparing an handshake with the slave we can't touch the
connection buffer as it'll be used to accumulate differences between
the sent RDB file and what arrives next from clients.

So in short we can't use addReply() family functions.

However we just use write(2) because we know that the socket buffer is
empty, since a prerequisite for SYNC to work is that the static buffer
and the output list are empty, and in general it is not expected that a
client SYNCs after doing some heavy I/O with the master.

However a short write connection is explicitly handled to avoid
fragility (we simply close the connection and the slave will retry).
2013-02-12 12:57:56 +01:00
antirez 8529dd218f SYNC not allowed with pending data on the static output buffer. 2013-02-12 12:57:52 +01:00
antirez 6726ea5b53 Log the unexpected string received in place of the SYNC payload length. 2013-02-12 12:57:48 +01:00
antirez f5edd535d1 After SLAVEOF <newslave> don't allow chained slaves to PSYNC. 2013-02-12 12:57:44 +01:00
antirez 700e5eb4fc PSYNC: work in progress, preview #2, rebased to unstable. 2013-02-12 12:57:40 +01:00
antirez 4d8655cfd3 Use the new unified protocol to send SELECT to slaves.
SELECT was still transmitted to slaves using the inline protocol, that
is conceived mostly for humans to type into telnet sessions, and is
notably not understood by redis-cli --slave.

Now the new protocol is used instead.
2013-02-12 12:57:36 +01:00
antirez 01c21f9943 Use replicationFeedSlaves() to send PING to slaves.
A Redis master sends PING commands to slaves from time to time: doing
this ensures that even if absence of writes, the master->slave channel
remains active and the slave can feel the master presence, instead of
closing the connection for timeout.

This commit changes the way PINGs are sent to slaves in order to use the
standard interface used to replicate all the other commands, that is,
the function replicationFeedSlaves().

With this change the stream of commands sent to every slave is exactly
the same regardless of their exact state (Transferring RDB for first
synchronization or slave already online). With the previous
implementation the PING was only sent to online slaves, with the result
that the output stream from master to slaves was not identical for all
the slaves: this is a problem if we want to implement partial resyncs in
the future using a global replication stream offset.

TL;DR: this commit should not change the behaviour in practical terms,
but is just something in preparation for partial resynchronization
support.
2013-02-12 12:57:31 +01:00
antirez 5a35e485f9 Emit SELECT to slaves in a centralized way.
Before this commit every Redis slave had its own selected database ID
state. This was not actually useful as the emitted stream of commands
is identical for all the slaves.

Now the the currently selected database is a global state that is set to
-1 when a new slave is attached, in order to force the SELECT command to
be re-emitted for all the slaves.

This change is useful in order to implement replication partial
resynchronization in the future, as makes sure that the stream of
commands received by slaves, including SELECT commands, are exactly the
same for every slave connected, at any time.

In this way we could have a global offset that can identify a specific
piece of the master -> slaves stream of commands.
2013-02-12 12:57:26 +01:00
antirez c970816e4d Makefile: valgrind target added (forces -O0 and libc malloc). 2013-02-11 12:11:26 +01:00
antirez 8a22934c68 TCP keep-alive. Better documentation in redis.conf. 2013-02-11 11:44:35 +01:00
antirez b06b90b5d8 Tcp keep-alive: send three probes before detectin an error.
Otherwise we end with less reliable connections because it's too easy
that a single packet gets lost.
2013-02-11 11:44:31 +01:00
antirez 0a14a6542c tcp-keepalive option documented in redis.conf. 2013-02-11 11:44:27 +01:00
antirez 2ed3fc1502 Set SO_KEEPALIVE on client sockets if configured to do so. 2013-02-11 11:44:23 +01:00
antirez f2817cbd9e Add SO_KEEPALIVE support to anet.c. 2013-02-11 11:44:18 +01:00
antirez fcfdbda104 Sentinel: advertise the promoted slave address only after successful setup. 2013-02-11 11:44:14 +01:00
Pierre Chapuis 0d470b4c43 fix comments forgotten in #285 (zipmap -> ziplist) 2013-02-11 11:44:06 +01:00
antirez cc55a4525a Make all WATCHers dirty when the slave reloads the DB. 2013-02-08 10:27:21 +01:00
antirez 5eb0ec142d LASTSAVE is a "random" command. 2013-02-07 19:13:06 +01:00
antirez 5f7dff4d16 TCP_NODELAY after SYNC: changes to the implementation. 2013-02-05 12:05:39 +01:00
charsyam 1d80acae54 Turn off TCP_NODELAY on the slave socket after SYNC.
Further details from @antirez:

It was reported by @StopForumSpam on Twitter that the Redis replication
link was strangely using multiple TCP packets for multiple commands.
This wastes a lot of bandwidth and is due to the TCP_NODELAY option we
enable on the socket after accepting a new connection.

However the master -> slave channel is a one-way channel since Redis
replication is asynchronous, so there is no point in trying to reduce
the latency, we should aim to reduce the bandwidth. For this reason this
commit introduces the ability to disable the nagle algorithm on the
socket after a successful SYNC.

This feature is off by default because the delay can be up to 40
milliseconds with normally configured Linux kernels.
2013-02-05 12:05:24 +01:00
antirez 14daa01c52 Test: No clients timeout while testing. 2013-02-05 12:02:41 +01:00
Johan Bergström d2a8bca82b Use info nameofexectuable to find current executable 2013-02-05 11:44:55 +01:00
Johan Bergström 1124cda37a Enforce tcl 8.5 or newer 2013-02-05 11:44:51 +01:00
Johan Bergström e92edd3cee Check available tcl versions 2013-02-05 11:44:44 +01:00
Rock Li 2543fa6476 retval doesn't initalized
If each if conditions are all fail, variable retval will under uninitlized
2013-02-05 10:49:24 +01:00
David Celis 44d0eac568 Fix a few typos and improve grammar of redis.conf
Make several edits to the example redis.conf configuration file for
improved flow and grammar.

Signed-off-by: David Celis <me@davidcel.is>
2013-02-04 10:34:05 +01:00
Gengliang Wang 57e411c687 Fix a bug in srandmemberWithCountCommand()
In CASE 2, the call sunionDiffGenericCommand will involve the string "srandmember"
> sadd foo one
(integer 1)
> sadd srandmember two
(integer 2)
> srandmember foo 3
1)"one"
2)"two"
2013-02-04 10:33:21 +01:00
antirez 8024de7eb0 Z*STORE event fixed: generate del only if resulting sorted set is empty. 2013-01-29 13:50:09 +01:00
antirez ca8e7d4f8a Generate del events when S*STORE commands delete the destination key. 2013-01-29 13:43:24 +01:00
antirez 664de412af Tests for keyspace notifications. 2013-01-28 13:19:04 +01:00
antirez d3cf778d82 Send 'expired' events when a key expires by lookup. 2013-01-28 13:18:57 +01:00
antirez 9db8fbcfdd Keyspace notifications: fixed a leak and a bug introduced in the latest commit. 2013-01-28 13:18:53 +01:00
antirez b9bc4f9132 Keyspace events: it is now possible to select subclasses of events.
When keyspace events are enabled, the overhead is not sever but
noticeable, so this commit introduces the ability to select subclasses
of events in order to avoid to generate events the user is not
interested in.

The events can be selected using redis.conf or CONFIG SET / GET.
2013-01-28 13:18:36 +01:00
antirez 6de052cd77 decrRefCount -> decrRefCountVoid in list constructor. 2013-01-28 13:17:37 +01:00
antirez 81af92b023 Enable keyspace events notification when testing. 2013-01-28 13:17:33 +01:00
antirez 4ff138fbbe Keyspace events added for more commands. 2013-01-28 13:17:30 +01:00
antirez 2825f21fd8 Fix decrRefCount() prototype from void to robj pointer.
decrRefCount used to get its argument as a void* pointer in order to be
used as destructor where a 'void free_object(void*)' prototype is
expected. However this made simpler to introduce bugs by freeing the
wrong pointer. This commit fixes the argument type and introduces a new
wrapper called decrRefCountVoid() that can be used when the void*
argument is needed.
2013-01-28 13:17:26 +01:00
antirez a64383c861 notifyKeyspaceEvent(): release channel names using the right pointers. 2013-01-28 13:17:22 +01:00
antirez fdfb59beae Initial test events for the new keyspace notification API. 2013-01-28 13:17:18 +01:00
antirez dd2ce743ca Fixed over-80-cols comment in db.c 2013-01-28 13:17:15 +01:00
antirez 6e64525cc2 Two fixes to initial keyspace notifications API. 2013-01-28 13:17:11 +01:00
antirez 212edbc409 Keyspace events notification API. 2013-01-28 13:17:00 +01:00
antirez 153766e137 Lua struct library updated to version 0.2.
There was a bug in the previous version of this library that caused a
crash under the circumstances described in issue #901.

The newer version of the library appears to be fixed (I tested it
manually with valgrind and everything seems fine now).

For more information about this library please visit this web site:

    http://www.inf.puc-rio.br/~roberto/struct/
2013-01-23 10:53:31 +01:00
antirez 767a53aa84 redis-cli --bigkeys output is now simpler to understand. 2013-01-21 19:16:11 +01:00
antirez 3ff75e58e8 UNSUBSCRIBE and PUNSUBSCRIBE: always provide a reply.
UNSUBSCRIBE and PUNSUBSCRIBE commands are designed to mass-unsubscribe
the client respectively all the channels and patters if called without
arguments.

However when these functions are called without arguments, but there are
no channels or patters we are subscribed to, the old behavior was to
don't reply at all.

This behavior is broken, as every command should always reply.
Also it is possible that we are no longer subscribed to a channels but we
are subscribed to patters or the other way around, and the client should
be notified with the correct number of subscriptions.

Also it is not pretty that sometimes we did not receive a reply at all
in a redis-cli session from these commands, blocking redis-cli trying
to read the reply.

This fixes issue #714.
2013-01-21 19:02:24 +01:00
antirez 850117a8a5 Fixed a bug in memtest progress bar, that had no actual effects.
This closes issue #859, thanks to @erbenmo.
2013-01-21 12:38:46 +01:00
Bilal Husain 2f1318ab29 s/adiacent/adjacent/
fixed typo in a comment (step 2 memcheck)
2013-01-21 12:20:07 +01:00
antirez f6f43d7da0 Not every __sun has backtrace().
I don't know how to test for Open Solaris that has support for
backtrace() so for now removing the #ifdef that breaks compilation under
other Solaris flavors.
2013-01-21 12:07:48 +01:00
antirez 635c532c89 Additionally two typos fixed thanks to @jodal 2013-01-19 13:46:22 +01:00
antirez 39f0a33f78 Whitelist SIGUSR1 to avoid auto-triggering errors.
This commit fixes issue #875 that was caused by the following events:

1) There is an active child doing BGSAVE.
2) flushall is called (or any other condition that makes Redis killing
the saving child process).
3) An error is sensed by Redis as the child exited with an error (killed
by a singal), that stops accepting write commands until a BGSAVE happens
to be executed with success.

Whitelisting SIGUSR1 and making sure Redis always uses this signal in
order to kill its own children fixes the issue.
2013-01-19 13:30:46 +01:00
antirez 1e20c939fe Clear server.shutdown_asap on failed shutdown.
When a SIGTERM is received Redis schedules a shutdown. However if it
fails to perform the shutdown it must be clear the shutdown_asap flag
otehrwise it will try again and again possibly making the server
unusable.
2013-01-19 13:20:56 +01:00
antirez d766907cfb Slowlog: don't log EXEC but just the executed commands.
The Redis Slow Log always used to log the slow commands executed inside
a MULTI/EXEC block. However also EXEC was logged at the end, which is
perfectly useless.

Now EXEC is no longer logged and a test was added to test this behavior.

This fixes issue #759.
2013-01-19 12:55:15 +01:00
guiquanz 1caf09399e Fixed many typos.
Conflicts fixed, mainly because 2.8 has no cluster support / files:
	00-RELEASENOTES
	src/cluster.c
	src/crc16.c
	src/redis-trib.rb
	src/redis.h
2013-01-19 11:03:19 +01:00
charsyam ff1e4d22a6 redis-cli prompt bug fix 2013-01-19 10:34:22 +01:00
Jan-Erik Rediger a8f9cec16a Always exit if connection fails.
This avoids unnecessary core dumps. Fixes antirez/redis#894
2013-01-19 10:29:42 +01:00
bitterb aadcda9977 Fix an error reply for CLIENT command 2013-01-19 10:26:32 +01:00
Nathan Parry f2bc198d40 redis-cli --rdb fails if server sends a ping
Redis pings slaves in "pre-synchronization stage" with newlines. (See
https://github.com/antirez/redis/blob/2.6.9/src/replication.c#L814)
However, redis-cli does not expect this - it sees the newline as the end
of the bulk length line, and ends up returning 0 as bulk the length.
This manifests as the following when running redis-cli:

    $ ./src/redis-cli --rdb some_file
    SYNC sent to master, writing 0 bytes to 'some_file'
    Transfer finished with success.

With this commit, we just ignore leading newlines while reading the bulk
length line.

To reproduce the problem, load enough data into Redis so that the
preparation of the RDB snapshot takes long enough for a ping to occur
while redis-cli is waiting for the data.
2013-01-18 11:50:06 +01:00
antirez c48584b85d redis-cli: save an RDB dump from remote server to local file. 2013-01-16 19:48:30 +01:00
antirez 7897e0a0d6 Tests for CLIENT GETNAME/SETNAME. 2013-01-15 13:34:26 +01:00
antirez f31d10b9a6 Typo fixed, ASCI -> ASCII. 2013-01-15 13:34:24 +01:00
antirez 786bd3938e CLIENT GETNAME and CLIENT SETNAME introduced.
Sometimes it is much simpler to debug complex Redis installations if it
is possible to assign clients a name that is displayed in the CLIENT
LIST output.

This is the case, for example, for "leaked" connections. The ability to
provide a name to the client makes it quite trivial to understand what
is the part of the code implementing the client not releasing the
resources appropriately.

Behavior:

    CLIENT SETNAME: set a name for the client, or remove the current
                    name if an empty name is set.
    CLIENT GETNAME: get the current name, or a nil.
    CLIENT LIST: now displays the client name if any.

Thanks to Mark Gravell for pushing this idea forward.
2013-01-15 13:34:22 +01:00
antirez aa9497fdb3 Undo slave-master handshake when SLAVEOF sets a new slave.
Issue #828 shows how Redis was not correctly undoing a non-blocking
connection attempt with the previous master when the master was set to a
new address using the SLAVEOF command.

This was also a result of lack of refactoring, so now there is a
function to cancel the non blocking handshake with the master.
The new function is now used when SLAVEOF NO ONE is called or when
SLAVEOF is used to set the master to a different address.
2013-01-15 13:33:27 +01:00
antirez 64ee9c861f Makefile.dep updated. 2013-01-11 23:51:12 +01:00
antirez 8a70007f9e Comment in the call() function clarified a bit. 2013-01-10 12:04:24 +01:00
antirez e2e36cf19b Test: added regression for issue #872. 2013-01-10 12:04:19 +01:00
antirez 414e06653c Multiple fixes for EVAL (issue #872).
1) The event handler was no restored after a timeout condition if the
   command was eventually executed with success.
2) The command was not converted to EVAL in case of errors in the middle
   of the execution.
3) Terrible duplication of code without any apparent reason.
2013-01-10 12:04:15 +01:00
antirez cefff3a1b3 Better error reporting when fd event creation fails. 2013-01-03 14:32:32 +01:00
antirez 6570d2f89e ae.c: set errno when error is not a failing syscall.
In this way the caller is able to perform better error checking or to
use strerror() without the risk of meaningless error messages being
displayed.
2013-01-03 14:32:27 +01:00
antirez 858b5576ac Fix overflow in mstime() in redis-cli and benchmark.
The problem does not exist in the Redis server implementation of mstime()
but is only limited to redis-cli and redis-benchmark.

Thix fixes issue #839.
2012-12-20 15:21:40 +01:00
antirez 61fb0aacf5 CONTRIBUTING updated with request to add BSD license. 2012-12-17 11:17:55 +01:00
antirez 6fa2610ac9 Added missing license and copyright in deps/hiredis. 2012-12-17 11:11:36 +01:00
antirez a6d117b6c0 serverCron() frequency is now a runtime parameter (was REDIS_HZ).
REDIS_HZ is the frequency our serverCron() function is called with.
A more frequent call to this function results into less latency when the
server is trying to handle very expansive background operations like
mass expires of a lot of keys at the same time.

Redis 2.4 used to have an HZ of 10. This was good enough with almost
every setup, but the incremental key expiration algorithm was working a
bit better under *extreme* pressure when HZ was set to 100 for Redis
2.6.

However for most users a latency spike of 30 milliseconds when million
of keys are expiring at the same time is acceptable, on the other hand a
default HZ of 100 in Redis 2.6 was causing idle instances to use some
CPU time compared to Redis 2.4. The CPU usage was in the order of 0.3%
for an idle instance, however this is a shame as more energy is consumed
by the server, if not important resources.

This commit introduces HZ as a runtime parameter, that can be queried by
INFO or CONFIG GET, and can be modified with CONFIG SET. At the same
time the default frequency is set back to 10.

In this way we default to a sane value of 10, but allows users to
easily switch to values up to 500 for near real-time applications if
needed and if they are willing to pay this small CPU usage penalty.
2012-12-14 17:20:21 +01:00
Patrick TJ McPhee 5207c03c49 Define _XOPEN_SOURCE appropriately on NetBSD. 2012-12-12 18:42:41 +01:00
antirez bbee226ecd Fix config.h endianess detection to work on Linux / PPC64.
Config.h performs endianess detection including OS-specific headers to
define the endianess macros, or when this is not possible, checking the
processor type via ifdefs.

Sometimes when the OS-specific macro is included, only __BYTE_ORDER is
defined, while BYTE_ORDER remains undefined. There is code at the end of
config.h endianess detection in order to define the macros without the
underscore, but it was not working correctly.

This commit fixes endianess detection fixing Redis on Linux / PPC64 and
possibly other systems.
2012-12-11 17:19:54 +01:00
Brian J. McManus 17e31eb03a Issue 804 Add Default-Start and Default-Stop LSB tags for RedHat startup and update-rc.d compatability. 2012-12-03 12:31:12 +01:00
antirez 124cb6dda1 Memory leak fixed: release client's bpop->keys dictionary.
Refactoring performed after issue #801 resolution (see commit
2f87cf8b01) introduced a memory leak that
is fixed by this commit.

I simply forgot to free the new allocated dictionary in the client
structure trusting the output of "make test" on OSX.

However due to changes in the "leaks" utility the test was no longer
testing memory leaks. This problem was also fixed.

Fortunately the CI test running at ci.redis.io spotted the bug in the
valgrind run.

The leak never ended into a stable release.
2012-12-03 12:17:33 +01:00
antirez e39735214d Test: fixed osx "leaks" support in test.
Due to changes in recent releases of osx leaks utility, the osx leak
detection no longer worked. Now it is fixed in a way that should be
backward compatible.
2012-12-03 12:17:29 +01:00
antirez 07a9f85405 Blocking POP: use a dictionary to store keys clinet side.
To store the keys we block for during a blocking pop operation, in the
case the client is blocked for more data to arrive, we used a simple
linear array of redis objects, in the blockingState structure:

    robj **keys;
    int count;

However in order to fix issue #801 we also use a dictionary in order to
avoid to end in the blocked clients queue for the same key multiple
times with the same client.

The dictionary was only temporary, just to avoid duplicates, but since
we create / destroy it there is no point in doing this duplicated work,
so this commit simply use a dictionary as the main structure to store
the keys we are blocked for. So instead of the previous fields we now
just have:

    dict *keys;

This simplifies the code and reduces the work done by the server during
a blocking POP operation.
2012-12-02 20:42:59 +01:00
antirez fbf3e33d18 Test: regression for issue #801. 2012-12-02 20:42:56 +01:00
antirez 64e69c0d5f Client should not block multiple times on the same key.
Sending a command like:

BLPOP foo foo foo foo 0

Resulted into a crash before this commit since the client ended being
inserted in the waiting list for this key multiple times.
This resulted into the function handleClientsBlockedOnLists() to fail
because we have code like that:

    if (de) {
        list *clients = dictGetVal(de);
        int numclients = listLength(clients);

        while(numclients--) {
            listNode *clientnode = listFirst(clients);

            /* server clients here... */
        }
    }

The code to serve clients used to remove the served client from the
waiting list, so if a client is blocking multiple times, eventually the
call to listFirst() will return NULL or worse will access random memory
since the list may no longer exist as it is removed by the function
unblockClientWaitingData() if there are no more clients waiting for this
list.

To avoid making the rest of the implementation more complex, this commit
modifies blockForKeys() so that a client will be put just a single time
into the waiting list for a given key.

Since it is Saturday, I hope this fixes issue #801.
2012-12-02 20:42:51 +01:00
antirez c1eda786d6 SDIFF fuzz test added. 2012-11-30 17:14:08 +01:00
antirez ccc974d90b SDIFF is now able to select between two algorithms for speed.
SDIFF used an algorithm that was O(N) where N is the total number
of elements of all the sets involved in the operation.

The algorithm worked like that:

ALGORITHM 1:

1) For the first set, add all the members to an auxiliary set.
2) For all the other sets, remove all the members of the set from the
auxiliary set.

So it is an O(N) algorithm where N is the total number of elements in
all the sets involved in the diff operation.

Cristobal Viedma suggested to modify the algorithm to the following:

ALGORITHM 2:

1) Iterate all the elements of the first set.
2) For every element, check if the element also exists in all the other
remaining sets.
3) Add the element to the auxiliary set only if it does not exist in any
of the other sets.

The complexity of this algorithm on the worst case is O(N*M) where N is
the size of the first set and M the total number of sets involved in the
operation.

However when there are elements in common, with this algorithm we stop
the computation for a given element as long as we find a duplicated
element into another set.

I (antirez) added an additional step to algorithm 2 to make it faster,
that is to sort the set to subtract from the biggest to the
smallest, so that it is more likely to find a duplicate in a larger sets
that are checked before the smaller ones.

WHAT IS BETTER?

None of course, for instance if the first set is much larger than the
other sets the second algorithm does a lot more work compared to the
first algorithm.

Similarly if the first set is much smaller than the other sets, the
original algorithm will less work.

So this commit makes Redis able to guess the number of operations
required by each algorithm, and select the best at runtime according
to the input received.

However, since the second algorithm has better constant times and can do
less work if there are duplicated elements, an advantage is given to the
second algorithm.
2012-11-30 17:13:34 +01:00
antirez 2572bb13c6 redis-benchmark: seed the PRNG with time() at startup. 2012-11-30 17:13:25 +01:00
antirez e34c14df1a Make an EXEC test more latency proof. 2012-11-29 16:12:23 +01:00
antirez 3b71404d70 Introduced the Build ID in INFO and --version output.
The idea is to be able to identify a build in a unique way, so for
instance after a bug report we can recognize that the build is the one
of a popular Linux distribution and perform the debugging in the same
environment.
2012-11-29 14:22:15 +01:00
antirez cd99c14e38 On crash memory test rewrote so that it actaully works.
1) We no longer test location by location, otherwise the CPU write cache
completely makes our business useless.
2) We still need a memory test that operates in steps from the first to
the last location in order to never hit the cache, but that is still
able to retain the memory content.

This was tested using a Linux box containing a bad memory module with a
zingle bit error (always zero).

So the final solution does has an error propagation step that is:

1) Invert bits at every location.
2) Swap adiacent locations.
3) Swap adiacent locations again.
4) Invert bits at every location.
5) Swap adiacent locations.
6) Swap adiacent locations again.

Before and after these steps, and after step 4, a CRC64 checksum is computed.
If the three CRC64 checksums don't match, a memory error was detected.
2012-11-29 10:24:41 +01:00
antirez 053f1f357d Jemalloc updated to version 3.2.0. 2012-11-28 18:42:35 +01:00
charsyam 1f0d0b094a Remove unnecessary condition in _dictExpandIfNeeded (dict.c) 2012-11-28 11:45:52 +01:00
Matt Arsenault ccb500cf36 It's a watchdog, not a watchdong. 2012-11-28 11:42:24 +01:00
charsyam ed48550fe6 remove compile warning bioKillThreads 2012-11-28 11:42:20 +01:00
antirez d0570c9693 EVALSHA is now case insensitive.
EVALSHA used to crash if the SHA1 was not lowercase (Issue #783).
Fixed using a case insensitive dictionary type for the sha -> script
map used for replication of scripts.
2012-11-22 15:51:03 +01:00
antirez cc85ed41df Fix integer overflow in zunionInterGenericCommand().
This fixes issue #761.
2012-11-22 15:28:48 +01:00
antirez ca9321afe2 Test: MULTI state is cleared after EXECABORT error. 2012-11-22 10:36:06 +01:00
antirez a527235f33 Test: make sure EXEC fails after previous transaction errors. 2012-11-22 10:36:01 +01:00
antirez 53600e34db Test: MULTI/EXEC tests moved into multi.tcl. 2012-11-22 10:35:50 +01:00
antirez 60f9dac672 Safer handling of MULTI/EXEC on errors.
After the transcation starts with a MULIT, the previous behavior was to
return an error on problems such as maxmemory limit reached. But still
to execute the transaction with the subset of queued commands on EXEC.

While it is true that the client was able to check for errors
distinguish QUEUED by an error reply, MULTI/EXEC in most client
implementations uses pipelining for speed, so all the commands and EXEC
are sent without caring about replies.

With this change:

1) EXEC fails if at least one command was not queued because of an
error. The EXECABORT error is used.
2) A generic error is always reported on EXEC.
3) The client DISCARDs the MULTI state after a failed EXEC, otherwise
pipelining multiple transactions would be basically impossible:
After a failed EXEC the next transaction would be simply queued as
the tail of the previous transaction.
2012-11-22 10:35:47 +01:00
antirez dff1ec4a36 Make bio.c threads killable ASAP if needed.
We use this new bio.c feature in order to stop our I/O threads if there
is a memory test to do on crash. In this case we don't want anything
else than the main thread to run, otherwise the other threads may mess
with the heap and the memory test will report a false positive.
2012-11-22 10:28:53 +01:00
antirez d4b5c4803b Fast memory test on Redis crash. 2012-11-22 10:28:48 +01:00
antirez dd905417ec Use more fine grained HAVE macros instead of HAVE_PROCFS. 2012-11-22 10:28:43 +01:00
antirez c342b075df Children creating AOF or RDB files now report memory used by COW.
Finally Redis is able to report the amount of memory used by
copy-on-write while saving an RDB or writing an AOF file in background.

Note that this information is currently only logged (at NOTICE level)
and not shown in INFO because this is less trivial (but surely doable
with some minor form of interprocess communication).

The reason we can't capture this information on the parent before we
call wait3() is that the Linux kernel will release the child memory
ASAP, and only retain the minimal state for the process that is useful
to report the child termination to the parent.

The COW size is obtained by summing all the Private_Dirty fields found
in the "smap" file inside the proc filesystem for the process.

All this is Linux specific and is not available on other systems.
2012-11-19 15:26:02 +01:00
antirez 1d8af6074c zmalloc_get_private_dirty() function added (Linux only).
For non Linux systmes it just returns 0.

This function is useful to estimate copy-on-write because of childs
saving stuff on disk.
2012-11-19 15:25:57 +01:00
antirez 1a706c729a zmalloc: kill unused __size parameter in update_zmalloc_stat_alloc() macro. 2012-11-14 13:07:54 +01:00
antirez 50c41de752 TTL API change: TTL returns -2 for non existing keys.
The previous behavior was to return -1 if:

1) Existing key but without an expire set.
2) Non existing key.

Now the second case is handled in a different, and TTL will return -2
if the key does not exist at all.

PTTL follows the same behavior as well.
2012-11-12 23:05:56 +01:00
antirez 388cc20418 Copyright date fixed in COPYING file. 2012-11-08 19:14:37 +01:00
antirez cda5c0ba06 Make clear that contributing code to the Redis project means to release it under the terms of the BSD license. 2012-11-08 18:43:41 +01:00
antirez 8ddb23b90c BSD license added to every C source and header file. 2012-11-08 18:34:04 +01:00
antirez 46c5d39660 Type mismatch errors are now prefixed with WRONGTYPE.
So instead to reply with a generic error like:

-ERR ... wrong kind of value ...

now it replies with:

-WRONGTYPE ... wrong kind of value ...

This makes this particular error easy to check without resorting to
(fragile) pattern matching of the error string (however the error string
used to be consistent already).

Client libraries should return a specific exeption type for this error.

Most of the commit is about fixing unit tests.
2012-11-06 20:28:15 +01:00
Runzhen Wang 2b28ef0e61 fix a typo in redis.h line 595 comment 2012-11-02 12:12:08 +01:00
antirez 64be5e365a More robust handling of AOF rewrite child.
After the wait3() syscall we used to do something like that:

    if (pid == server.rdb_child_pid) {
        backgroundSaveDoneHandler(exitcode,bysignal);
    } else {
        ....
    }

So the AOF rewrite was handled in the else branch without actually
checking if the pid really matches. This commit makes the check explicit
and logs at WARNING level if the pid returned by wait3() does not match
neither the RDB or AOF rewrite child.
2012-11-01 22:41:54 +01:00
Yecheng Fu fecc8797c2 fix typo in comments (redis.c, networking.c) 2012-11-01 22:26:49 +01:00
antirez d775b35715 Unix socket clients properly displayed in MONITOR and CLIENT LIST.
This also fixes issue #745.
2012-11-01 22:12:50 +01:00
antirez 655e6838fb 32 bit build fixed on Linux.
It failed because of the way jemalloc was compiled (without passing the
right flags to make, but just to configure). Now the same set of flags
are also passed to the make command, fixing the issue.

This fixes issue #744
2012-11-01 15:40:46 +01:00
YAMAMOTO Takashi 428b706308 fix a typo in a comment 2012-10-31 09:30:00 +01:00
antirez 9fab63b334 Invert two sides of if expression in SET to avoid a lookup.
Because of the short circuit behavior of && inverting the two sides of
the if expression avoids an hash table lookup if the non-EX variant of
SET is called.

Thanks to Weibin Yao (@yaoweibin on github) for spotting this.
2012-10-31 09:27:46 +01:00
antirez 537235bbbb No longer used macro rdbIsOpcode() removed. 2012-10-30 19:12:01 +01:00
antirez 38272f961a help.h update (adds bitop, bitcount, evalsha...) 2012-10-30 19:11:57 +01:00
antirez 6c7e957d17 Ctrl+w support in linenoise. 2012-10-30 19:11:46 +01:00
antirez 6bd105032c Marginally more robust glibc version test for sync_file_range detection. 2012-10-30 19:11:41 +01:00
charsyam 72f42cf40c patch config.h for sync_file_range 2012-10-30 19:11:34 +01:00
antirez 2a49aa1da2 Fix compilation on Linux kernels or glibc versions lacking sync_file_range().
This fixes issue #667.

Many thanks to Didier Spezia for the fix.
2012-10-30 19:11:29 +01:00
antirez 24ee20e5ca Update memory peak stats while loading RDB / AOF. 2012-10-30 19:11:23 +01:00
antirez f7b42b1749 2.7.0 branch created as a fork of 2.6.0. 2012-10-22 23:32:26 +02:00
121 changed files with 14392 additions and 3099 deletions
+354 -362
View File
@@ -1,9 +1,9 @@
Redis 2.6 release notes
Redis 2.8 release notes
=======================
** IMPORTANT ** Check the 'Migrating from 2.4 to 2.6' section at the end of
this file for information about what changed between 2.4 and
2.6 and how this may affect your application.
** IMPORTANT ** Check the 'Migrating from 2.6 to 2.8' section at the end of
this file for information about what changed between 2.6 and
2.8 and how this may affect your application.
--------------------------------------------------------------------------------
Upgrade urgency levels:
@@ -14,386 +14,378 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
--------------------------------------------------------------------------------
--[ Redis 2.6.13 ]
UPGRADE URGENCY: MODERATE, nothing very critical but upgrading is suggested
if you experienced:
1) Strange issues with Lua scripting.
2) Not reconfigured reappearing master using Sentinel.
3) Server continusly trying to save on save error.
This version of Redis may also help with AOF and slow / busy
disks and latency issues.
* [FIX] Throttle BGSAVE attempt on saving error.
* [FIX] redis-cli: raise error on bad command line switch.
* [FIX] Redis/Jemalloc Gitignore were too aggressive.
* [FIX] Test: fix RDB test checking file permissions.
* [FIX] Sentinel: always redirect on master->slave transition.
* [FIX] Lua updated to version 5.1.5. Fixes rare scripting issues.
* [NEW] AOF: improved latency figures with slow/busy disks.
* [NEW] Sentinel: turn old master into a slave when it comes back.
* [NEW] More explicit panic message on out of memory.
* [NEW] redis-cli: --latency-history mode implemented.
--[ Redis 2.6.12 ]
UPGRADE URGENCY: MODERATE, nothing very critical but a few non trivial bugs.
* [BUGFIX] redis-cli --bigkeys: don't crash with empty DB.
* [BUGFIX] stop-writes-on-bgsave-error now works in redis.conf
* [BUGFIX] Don't crash at startup if RDB is there but can't be opened.
* [BUGFIX] Initial value for master_link_down_since_seconds is now huge.
* [BUGFIX] Allow SELECT while loading the DB.
* [BUGFIX] Don't replicate/AOF an empty MULTI/EXEC if the transaction
is empty or containing just read-only commands.
* [BUGFIX] EXPIRE should not be able to resurrect keys (see issue #1026).
* [IMPROVED] Extended SET back ported from Redis 2.8 / unstable
See http://redis.io/commands/set for more information.
* [IMPROVED] Test suite improved.
--[ Redis 2.8.11 ] Release date: 11 Jun 2014
# UPGRADE URGENCY: HIGH if you use Lua scripting, LOW otherwise.
* [FIX] A previous fix for Lua -> Redis numerical precision enhancement
introduced a new problem. In Redis 2.8.10 commands called from Lua
passing a string that "looks like" a very large number, may actually
use as argument the string converted as a float. This bug is now
fixed.
* [FIX] Now commands other than *PUSH* adding elements to a list will be able
to awake clients blocked in a blocking POP operation.
* [FIX] Cygwin compilation fixes.
--[ Redis 2.8.10 ] Release date: 5 Jun 2014
# UPGRADE URGENCY: HIGH if you use min-slaves-to-write option.
* [FIX] IMPORTANT! A min-slaves-to-write option active in a slave totally
prevented the slave from acception the master stream of commands.
This release includes testes for min-slaves-to-write, and a fix
for this issue.
* [FIX] Sometimes DEL returned 1 for already expired keys. Fixed.
* [FIX] Fix test false positive because new osx 'leaks' output.
* [FIX] PFCOUNT HLL cache invalidation fixed: no wrong value was reported
but the cache was not used at all, leading to lower performances.
* [FIX] Accept(2) multiple clients per readable-event invocation, and better
processing of I/O while loading or busy running a timedout script.
Basically now the LOADING / BUSY errors are reported at a decent
speed.
* [FIX] A softwaer watchdog crash fixed.
* [FIX] Fixed a Lua -> Redis numerical precision loss.
* [NEW] Lua scripting engine speed improved.
* [NEW] Sentinel generates one new event for humans to understand better
what is happening during a failover: +config-update-from.
Also the time at which a failover will be re-attempted is logged.
--[ Redis 2.8.9 ] Release date: 22 Apr 2014
# UPGRADE URGENCY: LOW, only new features introduced, no bugs fixed.
* [NEW] The HyperLogLog data structure. You can read more about it
in this blog post. http://antirez.com/news/75
* [NEW] The Sorted Set data type has now support for lexicographic range
queries, check the new commands ZRANGEBYLEX, ZLEXCOUNT and
ZREMRANGEBYLEX, which are documented at http://redis.io.
--[ Redis 2.8.8 ] Release date: 25 Mar 2014
# UPGRADE URGENCY: HIGH for Redis, LOW for Sentinel. There is a potentially
critical bug fix causing data loss in Redis but it requires
a combination of disk full and the use of the
SHUTDOWN command.
* [FIX] Fixed data loss when SHUTDOWN was used with a disk full condition.
* [FIX] Fixed a memory leak in the SORT syntax error processing.
* [FIX] When Sentinel down-after-milliseconds parameter is modified at runtime
now it gets propagated to all the slaves and sentinel instances
of the master.
* [FIX] `install_server.sh` script finally fixed.
* [FIX] Different fixes to maxclients handling.
* [NEW] Sentinels are now able to send update messages in a peer-to-peer
fashion even if no Redis instances are available. Now the Sentinel
liveness property that the most updated configuration in a given
partition is propagated to all the Sentinels is extended to partitions
without reachable instances.
* [NEW] Sentinel safety properties are now ensured in a crash-recovery system
model since some state is persisted on disk before replying to other
nodes, and reloaded at startup.
* [NEW] Sentinel now uses CLIENT SETNAME so that it is easy to identify
Sentinels using CLIENT LIST among other clients.
* [NEW] Sentinel failure detection and reconnection code improved.
* [NEW] Use all 24 bits (instead of 22) for the Redis objects LRU field.
Note that the new LRU algorithm using eviction pools was not backported
from unstable for safery / code maturity concerns.
* [NEW] Majory speedup for the INFO command (it is now 6 times faster).
* [NEW] More Sentinel unit tests.
* [NEW] New command DEBUG ERROR returns the specified error. Example:
DEBUG ERROR "LOADING database". This is handy to write Redis client
libraries unit tests.
* [NEW] redis-cli now supports multi-line editing via updated linenoise lib.
Thanks to Matt Stancliff and Jan-Erik Rediger for the work done in the context
of this release.
--[ Redis 2.8.7 ] Release date: 5 Mar 2014
# UPGRADE URGENCY: LOW for Redis, LOW for Sentinel. However this release adds
new features so users may want to upgrade in order to
exploit the new functionalities.
* [FIX] Sometimes the absolute config file path was obtained in a wrong way.
This happened when there was a "dir" directive inside the config file
and at the same time the configuration file was given as a relative
path to redis-server or redis-sentinel executables.
* [FIX] redis-cli: Automatically enter --slave mode when SYNC or PSYNC are
called during an interactive session.
* [FIX] Sentinel "IDONTKNOW" error removed as it does not made sense with the
new Sentinel design. This error was actually a fix for a design error
in the first implementation of Sentinel.
* [FIX] Sentinel: added a missing exit() call to abort after config file
checks at startup. This error was introduced with an improvement in
a previous 2.8 release.
* [FIX] BITCOUNT: fixed unaligned access causing issues in sparc and other
archs not capable of dealing with unaligned accesses. This also makes
the code faster in archs where unaligned accesses are allowed.
* [FIX] Sentinel: better nodes fail over start time desynchronization to avoid
split-brain during the voting process needed to get authorization to
fail over. This means the system is less likely to need to retry
and will fail over faster. No changes in behavior / correctness.
* [FIX] Force INFO used_memory_peak to match peak memory. This generated some
confusion among users even if it was not an actual bug.
* [NEW] Sentinel unit tests and framework. More tests needed and units must
be improved in order to have less false positives, but it is a start
and features a debugging console that is useful to fix tests or to
inspect bugs causing tests failures.
* [NEW] New Sentinel events: +/-monitor and +set used to monitor when an
instance to monitor is added or removed, or when a configuration
is modified via SENTINEL SET.
* [NEW] Redis-cli updated to use SCAN instead of random sampling via
RANDOMKEY in order to implement --bigkeys feature. Moreover the
implementation now supports pipelining and reports more information
at the end of the scan. Much faster, much better. A special thank
you to Michael Grunder for this improvement.
* [NEW] redis-cli now supports a new --intrinsic-latency mode that is able
to meter the latency of a system due to kernel / hypervisor.
How to use it is explained at http://redis.io/topics/latency.
* [NEW] New command BITPOS: find first bit set or clear in a bitmap.
* [NEW] CONFIG REWRITE calls are now logged.
--[ Redis 2.8.6 ] Release date: 13 Feb 2014
# UPGRADE URGENCY: HIGH for Redis, LOW for Sentinel. Redis users using Lua
scripts with EVALSHA and attached slaves and/or AOF
persistence should consider upgrading ASAP.
* [FIX] Fixed an critical EVALSHA script cache bug: scripts executed may not
propagate to AOF / Slaves correctly under certain conditions.
See issue #1549 at Github for more information.
* [FIX] Fixed multiple bugs resulting into closing the link with master or slave
during replication without good reasons. This will result in useless
resynchronizations, or infinite loops where the replication link can't
be established.
* [FIX] Don't count the time needed to populate the buffers of clients waiting
in MONITOR mode when populating the Slow Log entries.
* [NEW] AOF write errors (like no space on device) no longer abort Redis if the
fsync policy is none or every second. The database enters a read-only
mode where every write is refused with an error. Normal operations are
restored as soon as Redis is able to append again data to the AOF file.
* [NEW] Sentinel now accepts SHUTDOWN command.
--[ Redis 2.8.5 ] Release date: 4 Feb 2014
# UPGRADE URGENCY: HIGH for Redis, LOW for Sentinel. Redis users using Lua
scripts with expires, and Redis users relying on the
ability of Redis to block writes on RDB saving errors
should plan to upgrade ASAP.
* [FIX] Fixed a replication bug caused by Lua scripts + expired keys: keys could
expire in the middle of scripts causing non-deterministic behavior.
* [FIX] MISCONFIG error if condition fixed, the server was no longer able
to stop writes on RDB misconfiguration after this error was introduced.
* [FIX] REDIS_AOF_REWRITE_MIN_SIZE is now 64mb like example redis.conf default.
* [FIX] Perform fflush() before fsync() in rio.c (bug without actual effects).
* [FIX] Don't log MONITOR clients as disconnecting slaves.
* [FIX] SENTINEL MASTER arity check fixed. Crashed the Sentinel instance when
the command was given without arguments.
* [NEW] Allow CONFIG and SHUTDOWN while in stale-slave state.
* [NEW] Support for configurable TCP listen(2) backlog size.
* [NEW] redis-cli supports SCAN via the --scan and --pattern options.
* [NEW] SENTINEL SET master quorum via runtime API implemented.
--[ Redis 2.8.4 ] Release date: 13 Jan 2014
# UPGRADE URGENCY: MODERATE for Redis and Sentinel.
* [FIX] Makefile compatibility with non common make variants improved.
* [FIX] SDIFF crash in very unlikely to trigger state fixed.
* [FIX] Config rewriting fixed: don't wipe options unknown to the rewrite
process.
* [FIX] Set TCP port to 0 works again to disable TCP networking.
* [FIX] Fixed replication with old Redis instances as masters by not
sending REPLCONF ACK to them.
* [FIX] Fix keyspace notifications rewrite and CONFIG GET output.
* [FIX] Fix RESTORE TTL handling in 32 bit systems (32 bit overflow).
* [NEW] Sentinel now has a run time configuration API.
* [NEW] Log when we lost connection with master or slave.
* [NEW] When instance is turned from slave to master now inherits the
old master replication offset when possible. This improves the
Sentinel failover procedure.
--[ Redis 2.8.3 ] Release date: 11 Dec 2013
# UPGRADE URGENCY: MODERATE for Redis, HIGH for Sentinel.
* [FIX] Sentinel instance role sampling fixed, the system is now more
reliable during failover and when reconfiguring instances with
non matching configuration.
* [FIX] Inline requests are now handled even when terminated with just LF.
* [FIX] Replication timeout handling greatly improved, now the slave is able
to ping the master while removing the old data from memory, and while
loading the new RDB file. This avoid false timeouts sensed by
masters.
* [FIX] Fixed a replication bug involving 32 bit instances and big datasets
hard to compress that resulted into more than 2GB of RDB file sent.
* [FIX] Return error for inline requests with unbalanced quotes.
* [FIX] Publish the slave replication offset even when disconnected from the
master if there is still a cached master instance.
--[ Redis 2.8.2 ] Release date: 2 Dec 2013
# UPGRADE URGENCY: MODERATE for both Redis and Sentinel.
* [FIX] Sentinel better desynchronization to avoid split-brain elections
where no Sentinel managed to get elected.
* [FIX] Stop accepting writes on "MISCONF" error only if master, not slave.
* [FIX] Reply to PING with an error on "MISCONF" errors.
--[ Redis 2.8.1 ] Release date: 25 Nov 2013
# UPGRADE URGENCY: LOW for Redis, CRITICAL for Senitnel. You don't need to
upgrade your Redis instances but it is highly recommended
to upgrade and restart all the Sentinel processes.
* [FIX] Fixed a bug in "new Sentinel" config propagation.
* [FIX] Fixed a false positive in Redis tests.
--[ Redis 2.8.0 ] Release date: 22 Nov 2013
# UPGRADE URGENCY: LOW, unless you want to upgrade to new Sentinel code.
* [FIX] Fixed an error in rdbWriteRaw() that should have no practical impact.
* [NEW] Log the new master when SLAVEOF command is used.
* [NEW] Sentinel code synchronized with the unstable branch, the new Sentinel
is a reimplementation that uses more reliable algorithms.
--[ Redis 2.8 Release Candidate 6 (2.7.106) ] Release date: 6 Nov 2013
This is the 6th release candidate of Redis 2.8 (official version is 2.7.106).
# UPGRADE URGENCY: LOW, only new features back ported, no fixes.
* [NEW] SCAN, SSCAN, HSCAN, ZSCAN commands.
--[ Redis 2.8 Release Candidate 5 (2.7.105) ] Release date: 9 Oct 2013
This is the 5th release candidate of Redis 2.8 (official version is 2.7.105).
Important bugs fixed inside.
--[ Redis 2.6.11 ]
# UPGRADE URGENCY: HIGH because of many non critical replication bugs fixed.
UPGRADE URGENCY: LOW, however updating is encouraged if you have many instances
per server and you want to lower the CPU / energy usage.
* [FIX] redis-cli: don't crash with --bigkeys when the key no longer exist.
* [FIX] Allow AUTH / PING when disconnected from slave and serve-stale-data is no.
* [FIX] PSYNC: safer handling of PSYNC requests with offsets in the future.
* [FIX] Replication: Fix master timeout detection.
* [FIX] Replication: Correctly install the write handler after successful PSYNC.
--[ Redis 2.8 Release Candidate 4 (2.7.104) ] Release date: 30 Aug 2013
* [BUGFIX] Replication: more strict error checking for master PING reply.
* [BUGFIX] redis-cli: use keepalive socket option for improved reliability.
* [BUGFIX] Allow AUTH while loading the DB in memory.
* [BUGFIX] Don't segfault on unbalanced quotes while parsing config file.
* [BUGFIX] Don't segfault if command gets propagated to AOF / replication
link as another command name that was renamed in redis.conf
* [IMPROVED] serverCron() frequency is now a runtime parameter (was REDIS_HZ).
* [IMPROVED] Use a lot less CPU when idle, even with many configured DBs.
This is the fourth release candidate of Redis 2.8 (official version is 2.7.104).
Important bugs fixed inside.
--[ Redis 2.6.10 ]
# UPGRADE URGENCY: HIGH because of the EVAL memory leak.
UPGRADE URGENCY: MODERATE, this release contains many non-critical fixes
and many small improvements.
* [FIX] Fixed a serious EVAL memory leak in the Lua stack.
* [FIX] Fixed server startup when no IPv6 address exists in any interface.
* [FIX] Send MISCONFIG error when BGSAVE fails because can't fork.
* [FIX] Memory efficiency with large (> a few kbytes) values improved considerably.
* [NEW] DEBUG SDSLEN for sds memory debugging.
* [BUGFIX] redis-cli --rdb, fixed when the server sends newlines to ping.
* [BUGFIX] redis-cli, minor fixes on connection handling, prompt.
* [BUGFIX] Slow log: don't log EXEC, just executed commands.
* [BUGFIX] On failed shutdown don't try again and again compulsively.
* [BUGFIX] Fix build on sunos without backtrace().
* [BUGFIX] UNSUBSCRIBE and PUNSUBSCRIBE: always provide a reply (see 742e580)
* [BUGFIX] Lua struct library was broken, upgraded.
* [BUGFIX] Fix a bug in srandmemberWithCountCommand() with count argument.
* [BUGFIX] Test: disable clients timeout to prevent issues on slow systems.
* [BUGFIX] Sentinel: don't advertise the promoted slave as master too early.
* [IMPROVED] Whitelist SIGUSR1, see http://redis.io/topics/signals.
* [IMPROVED] Simpler to understand redis-cli --bigkeys output.
* [IMPROVED] Test now works with tclsh > 8.5.
* [IMPROVED] Added option to turn of the Nagle algorithm in slave socket.
* [IMPROVED] Optionally use SO_KEEPALIVE to detect dead peers.
--[ Redis 2.8 Release Candidate 3 (2.7.103) ] Release date: 19 Aug 2013
--[ Redis 2.6.9 ]
This is the third release candidate of Redis 2.8 (official version is 2.7.103).
Important bugs fixed inside.
UPGRADE URGENCY: MODERATE if you use replication.
# UPGRADE URGENCY: HIGH
* [BUGFIX] Changing master at runtime (SLAVEOF command) in presence of
network problems, or in very rapid succession, could result
in non-critical problems (GitHub Issue #828).
* [IMPROVED] CLINGET GETNAME and SETNAME to set and query connection names
reported by CLIENT LIST. Very useful for debugging of
problems.
* [IMPROVED] redis-cli is now able to transfer an RDB file from a remote
server to a local file using the --rdb <filename> command
line option.
* [FIX] Improved expired keys collection algorithm. Even under heavy load keys
to be expired can't accumulate because of lack of CPU time.
* [FIX] Replication speed regression fixed (issue #1238).
* [FIX] Fixed an hard to trigger PSYNC bug.
* [FIX] Fixed Issue #1240, ZUNIONSTORE could lead to wrong result.
* [NEW] Add per-db average TTL information in INFO output.
* [NEW] redis-benchmark improvements.
* [NEW] dict.c API wrong usage detection.
--[ Redis 2.6.8 ]
--[ Redis 2.8 Release Candidate 2 (2.7.102) ] Release date: 30 Jul 2013
UPGRADE URGENCY: MODERATE if you use Lua scripting. Otherwise LOW.
* [BUGFIX] Multiple fixes for EVAL (issue #872).
* [BUGFIX] Fix overflow in mstime() in redis-cli and benchmark.
* [BUGFIX] Fix Linux / PPC64 behavior by correcting endianess detection.
* [BUGFIX] Fix NetBSD build by defining _XOPEN_SOURCE appropriately.
* [BUGFIX] Added missing license and copyright in a few places.
* [BUGFIX] Better error reporting when fd event creation fails.
--[ Redis 2.6.7 ]
UPGRADE URGENCY: MODERATE (unless you BLPOP using the same key multiple times).
* [BUGFIX] Don't crash if BLPOP & co are called with the same key repeated
multiple times (Issue #801).
--[ Redis 2.6.6 ]
UPGRADE URGENCY: CRITICAL if you experienced one more more crashes.
MODERATE if Redis is running fine for you.
* [BUGFIX] Jemalloc updated to 3.2.0.
--[ Redis 2.6.5 ]
UPGRADE URGENCY: MODERATE
Warning: this release of Redis introduces a different behavior in MULTI/EXEC
handling of errors. This was done because the new behavior is safer
compared to the old one, and should not break any code targeting
Redis 2.6 in a critical way.
For more information check http://redis.io/topics/transactions
* [IMPROVED] RDB/AOF childern now log amount of additional memory used
because of copy on write.
* [BUGFIX] MIGRATE non critical fixes (see commits for details).
* [BUGFIX] MULTI/EXEC: now EXEC aborts on errors before EXEC.
* [BUGFIX] Fix integer overflow in zunionInterGenericCommand resulting
into Z[INTER|UNION][STORE] commands to crash under extremely
unlikely conditions (almost impossible in real world).
* [BUGFIX] EVALSHA is now case insensitive (and will not crash).
--[ Redis 2.6.4 ]
UPGRADE URGENCY: LOW
* [IMPROVED] BSD license and copyright notice added to every .c and .h file.
--[ Redis 2.6.2 ]
UPGRADE URGENCY: LOW
* [BUGFIX] The compilation fix for RHLE5 in 2.6.1 was broken. Fixed.
* [IMPROVED] Linenoise updated, now supports Ctrl+w.
---[ Redis 2.6.1 ]
UPGRADE URGENCY: LOW
* [BUGFIX] Compilation on Linux < 2.6.17 or glibc < 2.6 fixed (RHLE5 & co).
---[ Redis 2.6.0 ]
UPGRADE URGENCY: HIGH
* [BUGFIX] Allow AUTH when server is in -BUSY state because of a slow script.
* [BUGFIX] MULTI/EXEC flow now makes sense when observed in MONITOR
* [BUGFIX] SCRIPT KILL now uses different error prefixes for different errors.
* [BUGFIX] Default memory limit for 32bit archs lowered from 3.5 to 3 GB.
* [BUGFIX] redis-check-dump is now compatible with RDB files generated by 2.6.
* [IMPROVED] New field in INFO: slave_read_only.
---[ Redis 2.5.14 (2.6 Release Candidate 8) ]
* [BUGFIX] Fixed compilation on FreeBSD.
* [IMPROVED] SRANDMEMBER <count> that returns multiple random elements.
* [IMPROVED] Sentinel backported to 2.6. It will be taken in sync with 2.8.
* [IMPROVED] Helper function for scripting to return errors and status replies.
* [IMPROVED] SORT by nosort [ASC|DESC] uses sorted set elements ordering.
* [BUGFIX] Better resistence to system clock skew.
* [IMPROVED] Warn the user when the configured maxmemory seems odd.
* [BUGFIX] Hashing function is now murmurhash2 for security purposes.
* [IMPROVED] Install script no longer uses a template but redis.conf itself.
---[ Redis 2.5.13 (2.6 Release Candidate 7) ]
UPGRADE URGENCY: HIGH
* [BUGFIX] Theoretical bug in ziplist fixed.
* [BUGFIX] Better out of memory handling (Log produced in log file).
* [BUGFIX] Incrementally flush RDB file on slave side while performing the
first synchronization with the master. This makes Redis less
blocking in environments where disk I/O is slow.
* [BUGFIX] Don't crash with Lua's redis.call() without arguments.
* [BUGFIX] Don't crash after a big number of Lua calls on 32 bit systems
because of a failed assertion.
* [BUGFIX] Fix SORT behaviour when called from scripting.
* [BUGFIX] Adjust slave PING period accordingly to REDIS_HZ define.
* [BUGFIX] BITCOUNT: fix crash on overflowing arguments.
* [BUGFIX] Return an error when SELECT argument is not an integer.
* [BUGFIX] Blocking operations on lists were completely reimplemented for
correctness. Now blocking list ops and pushes originated from
Lua scripts will play well together and will be replicated
and transmitted to the AOF correctly.
* [IMPROVED] Send async PING before starting replication to avoid blocking if
master allows us to connect but it is actually not able to reply.
* [IMPROVED] Support slave-priority for Redis Sentinel.
* [IMPROVED] Hiredis library updated.
---[ Redis 2.5.12 (2.6 Release Candidate 6) ]
UPGRADE URGENCY: MODERATE.
* [BUGFIX] Fixed a timing attack on AUTH (Issue #560).
* [BUGFIX] Don't assume that "char" is signed.
* [BUGFIX] Check that we have connection before enabling pipe mode.
* [BUGFIX] Use the optimized version of the function to convert a double to
its string representation. Compilation was disabled because of
a typo in the #if statement.
* [IMPROVED} REPLCONF internal command introduced, now INFO shows slaves with
correct port numbers. This makes 2.5.12 Redis Sentinel compatible.
* [IMPROVED] Truncate short write from the AOF for a cleaner restart. On short
writes (for instance out of space) Redis will now try to remove
the half-written data so that the next restart will work without
the need for the "redis-check-aof" utility.
* [IMPROVED] New in INFO: aof_last_bgrewrite_status
* [IMPROVED] Allow Pub/Sub in contexts where other commands are blocked.
* [BUGFIX] mark fd as writable when EPOLLERR or EPOLLHUP is returned by
epoll_wait.
---[ Redis 2.5.11 (2.6 Release Candidate 5) ]
UPGRADE URGENCY: HIGH.
* [BUGFIX] Fixed Hash corruption when loading an RDB file generated by
previous versions of Redis that encoded hashes using
a different ziplist encoding format for small integers.
All the fileds that are integers in the range 0-255 may not
be recognized, or duplicated un updates, causing a crash
when the ziplist is converted to a real hash. (Issue #547).
* [BUGFIX] Fixed the count of memory used by output buffers in the
setDeferredMultiBulkLength() function.
---[ Redis 2.5.10 (2.6 Release Candidate 4) ]
UPGRADE URGENCY: HIGH.
* [BUGFIX] Allow PREFIX to be overwritten on "make install".
* [BUGFIX] Run the test with just one client if the computer is slow.
* [BUGFIX] Event port support in our event driven libray.
* [BUGFIX] Jemalloc updated to 3.0.0. This fixes a possibly AOF rewrite issue.
See https://github.com/antirez/redis/issues/504 for info.
* [BUGFIX] Fixed issue #516: ZINTERSTORE / ZUNIONSTORE with mixed sets/zsets.
* [BUGFIX] Set fd to writable when poll(2) detects POLLERR or POLLHUP event.
* [BUGFIX] Fixed RESTORE hash failure (Issue #532).
* [IMPROVED] Allow an AOF rewrite buffer > 2GB (Related to issue #504).
* [IMPROVED] Server cron function frequency is now configurable (REDIS_HZ).
* [IMPROVED] Better, less blocking expired keys collection algorithm.
* [FEATURE] New commands: BITOP and BITCOUNT.
* [FEATURE] redis-cli --pipe for mass import.
What's new in Redis 2.5.9 (aka 2.6 Release Candidate 3)
=======================================================
UPGRADE URGENCY: critical, upgrade ASAP.
* [BUGFIX] Fix for issue #500 (https://github.com/antirez/redis/pull/500).
Redis 2.6-RC1 and RC2 may corrupt ziplist-encoded sorted sets
produced by Redis 2.4.x.
* [BUGFIX] Fixed several bugs in init.d script.
* [BUGFIX] syncio.c functions modified for speed and correctness. On osx
(and possibly other BSD-based systems) the slave would block on
replication to send the SYNC command when the master was not
available. This is fixed now, but was not affecting Linux installs.
* Now when slave-serve-stale-data is set to yes and the master is down, instead
of reporting a generic error Redis replies with -MASTERDOWN.
What's new in Redis 2.5.8 (aka 2.6 Release Candidate 2)
=======================================================
UPGRADE URGENCY: high for all the users of the KEYS command, otherwise low.
* [BUGFIX] Fix for KEYS command: if the DB contains keys with expires the KEYS
command may return the wrong output, having duplicated or missing
keys. See issue #487 and #488 on github for details.
What's new in Redis 2.5.7 (aka 2.6 Release Candidate 1)
=======================================================
UPGRADE URGENCY: upgrade not recommended because this is an RC release.
* This is the first release candidate for Redis 2.6. We are not aware of
bugs, but part of this code is young and was never tested in production
environments, so handle with care.
An overview of new features and changes in Redis 2.6.x
======================================================
* Server side Lua scripting, see http://redis.io/commands/eval
* Virtual Memory removed (was deprecated in 2.4)
* Hardcoded limits about max number of clients removed.
* AOF low level semantics is generally more sane, and especially when used
in slaves.
* Milliseconds resolution expires, also added new commands with milliseconds
precision (PEXPIRE, PTTL, ...).
* Better memory usage for "small" lists, ziplists and hashes when fields or
values contain small integers.
* Read only slaves.
* New bit opeations: BITCOUNT and BITOP commands.
* Clients max output buffer soft and hard limits. You can specifiy different
limits for different classes of clients (normal,pubsub,slave).
* More incremental (less blocking) expired keys collection algorithm, in
practical terms this means that Redis is more responsive when a very
big number of keys expire about at the same time.
* AOF is now able to rewrite aggregate data types using variadic commands,
often producing an AOF that is faster to save, load, and is smaller in size.
* Every redis.conf directive is now accepted as a command line option for the
redis-server binary, with the same name and number of arguments.
* Hash table seed randomization for protection against collisions attacks.
* Performances improved when writing large objects to Redis.
* Integrated memory test, see redis-server --test-memory.
* INCRBYFLOAT and HINCRBYFLOAT commands.
* New DUMP, RESTORE, MIGRATE commands (back ported from Redis Cluster to 2.6).
* CRC64 checksump in RDB files.
* Better MONITOR output and behavior (now commands are logged before execution).
* "Software Watchdog" feature to debug latency issues.
* Significant parts of the core refactored or rewritten. New internal APIs
and core changes allowed to develop Redis Cluster on top of the new code,
however for 2.6 all the cluster code was removed, and will be released with
Redis 3.0 when it is more complete and stable.
* Redis ASCII art logo added at startup.
* Crash report on memory violation or failed asserts improved significantly
to make debugging of hard to catch bugs simpler.
* redis-benchmark improvements: ability to run selected tests,
CSV output, faster, better help.
* redis-cli improvements: --eval for comfortable development of Lua scripts.
* SHUTDOWN now supports two optional arguments: "SAVE" and "NOSAVE".
* INFO output split into sections, the command is now able to just show
pecific sections.
* New statistics about how many time a command was called, and how much
execution time it used (INFO commandstats).
* More predictable SORT behavior in edge cases.
* Better support for big endian and *BSD systems.
* Build system improved.
Migrating from 2.4 to 2.6
This is the second release candidate of Redis 2.8 (official version is 2.7.102).
Important bugs fixed inside.
# UPGRADE URGENCY: HIGH
* [FIX] Fixed a critical replication bug, see issue #1221.
* [NEW] The new inline protocol now accepts quoted strings like, for example
you can now type in a telnet session: set 'foo bar' "hello world\n".
--[ Redis 2.8 Release Candidate 1 (2.7.101) ] Release date: 18 Jul 2013
This is the first release candidate of Redis 2.8 (official version is 2.7.101).
The following is a list of improvements in Redis 2.8, compared to Redis 2.6.
* [NEW] Slaves are now able to partially resynchronize with the master, so most
of the times a full resynchronization with the RDB creation in the master
side is not needed when the master-slave link is disconnected for a short
amount of time.
* [NEW] Experimental IPv6 support.
* [NEW] Slaves explicitly ping masters now, a master is able to detect a timed out
slave independently.
* [NEW] Masters can stop accepting writes if not enough slaves with a given
maximum latency are connected.
* [NEW] Keyspace changes notifications via Pub/Sub.
* [NEW] CONFIG SET maxclients is now available.
* [NEW] Ability to bind multiple IP addresses.
* [NEW] Set process names so that you can recognize, in the "ps" command output,
the listening port of an instance, or if it is a saving child.
* [NEW] Automatic memory check on crash.
* [NEW] CONFIG REWRITE is able to materialize the changes in the configuration
operated using CONFIG SET into the redis.conf file.
* [NEW] More NetBSD friendly code base.
* [NEW] PUBSUB command for Pub/Sub introspection capabilities.
* [NEW] EVALSHA can now be replicated as such, without requiring to be expanded
to a full EVAL for the replication link.
* [NEW] Better Lua scripts error reporting.
* [NEW] SDIFF performance improved.
* [FIX] A number of bugfixes.
Migrating from 2.6 to 2.8
=========================
Redis 2.4 is mostly a strict subset of 2.6. However there are a few things
Redis 2.6 is mostly a strict subset of 2.8. However there are a few things
that you should be aware of:
* You can't use .rdb and AOF files generated with 2.6 into a 2.4 instance.
* 2.6 slaves can be attached to 2.4 masters, but not the contrary, and only
for the time needed to perform the version upgrade.
The following commands changed behavior:
There are also a few API differences, that are unlikely to cause problems,
but it is better to keep them in mind:
* SORT now will refuse to sort in numerical mode elements that can't be parsed
as numbers.
* EXPIREs now all have millisecond resolution (but this is very unlikely to
break code that was not conceived exploting the previous resolution error
in some way.)
* INFO output is a bit different now, and contains empty lines and comments
starting with '#'. All the major clients should be already fixed to work
with the new INFO format.
* Slaves are only read-only by default (but you can change this easily
setting the "slave-read-only" configuration option to "no" editing your
redis.conf or using CONFIG SET.
The following INFO fields were renamed for consistency:
changes_since_last_save -> rdb_changes_since_last_save
bgsave_in_progress -> rdb_bgsave_in_progress
last_save_time -> rdb_last_save_time
last_bgsave_status -> rdb_last_bgsave_status
bgrewriteaof_in_progress -> aof_rewrite_in_progress
bgrewriteaof_scheduled -> aof_rewrite_scheduled
* The TTL and PTTL commands now return -2 if the key does not exist and
-1 if it exists but has no associated expire. Redis 2.6 and previous
versions used to return -1 for both the conditions.
* SORT with ALPHA now sorts according to local collation locale if no STORE
option is used.
* ZADD/ZINCRBY are now able to accept a bigger range of values as valid
scores, that is, all the values you may end having as a result of
calling ZINCRBY multiple times.
* Many errors are now prefixed by a more specific error code instead of
the generic -ERR, for example -WRONGTYPE, -NOAUTH, ...
* PUBLISH called inside Lua scripts is now correctly propagated to slaves.
The following redis.conf and CONFIG GET / SET parameters changed:
* hash-max-zipmap-entries, now replaced by hash-max-ziplist-entries
* hash-max-zipmap-value, now replaced by hash-max-ziplist-value
* glueoutputbuf option was now completely removed (was deprecated)
* logfile now uses the empty string in order to log to standard output,
so 'logfile stdout' is now invalid, use 'logfile ""' instead.
The following INFO fields changed format in a non-backward compatible way:
* The list of slaves in INFO is now in field=value format.
Replication:
Redis 2.8 can be used as slave for Redis 2.6, but doing this is only
a good idea for the short amount of time needed to upgrade your servers.
We suggest to update both master and slaves at about the same time.
--------------------------------------------------------------------------------
Credits: Where not specified the implementation and design are done by
Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all
Credits: Where not specified the implementation and design is done by
Salvatore Sanfilippo. Thanks to VMware and Pivotal for making all
this possible. Also many thanks to all the other contributors and the amazing
community we have.
+1 -1
View File
@@ -22,7 +22,7 @@ each source file that you contribute.
1. Drop a message to the Redis Google Group with a proposal of semantics/API.
2. If in steps 1 you get an acknowledge from the project leaders, use the
2. If in step 1 you get an acknowledge from the project leaders, use the
following procedure to submit a patch:
a. Fork Redis on github ( http://help.github.com/fork-a-repo/ )
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2006-2012, Salvatore Sanfilippo
Copyright (c) 2006-2014, Salvatore Sanfilippo
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+15 -4
View File
@@ -26,9 +26,20 @@ After building Redis is a good idea to test it, using:
% make test
NOTE: if after building Redis with a 32 bit target you need to rebuild it
with a 64 bit target you need to perform a "make clean" in the root
directory of the Redis distribution.
Fixing problems building 32 bit binaries
---------
If after building Redis with a 32 bit target you need to rebuild it
with a 64 bit target, or the other way around, you need to perform a
"make distclean" in the root directory of the Redis distribution.
In case of build errors when trying to build a 32 bit binary of Redis, try
the following steps:
* Install the packages libc6-dev-i386 (also try g++-multilib).
* Try using the following command line instead of "make 32bit":
make CFLAGS="-m32 -march=native" LDFLAGS="-m32"
Allocator
---------
@@ -119,7 +130,7 @@ it the proper way for a production system, we have a script doing this
for Ubuntu and Debian systems:
% cd utils
% ./install_server
% ./install_server.sh
The script will ask you a few questions and will setup everything you need
to run Redis properly as a background daemon that will start again on
+2 -2
View File
@@ -54,8 +54,8 @@ linenoise: .make-prerequisites
.PHONY: linenoise
ifeq ($(uname_S),SunOS)
# Make isinf() available
LUA_CFLAGS= -D__C99FEATURES__=1
# Make isinf() available
LUA_CFLAGS= -D__C99FEATURES__=1
endif
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI $(CFLAGS)
+2 -2
View File
@@ -650,7 +650,7 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Discard part of the buffer when we've consumed at least 1k, to avoid
* doing unnecessary calls to memmove() in sds.c. */
if (r->pos >= 1024) {
r->buf = sdsrange(r->buf,r->pos,-1);
sdsrange(r->buf,r->pos,-1);
r->pos = 0;
r->len = sdslen(r->buf);
}
@@ -1125,7 +1125,7 @@ int redisBufferWrite(redisContext *c, int *done) {
sdsfree(c->obuf);
c->obuf = sdsempty();
} else {
c->obuf = sdsrange(c->obuf,nwritten,-1);
sdsrange(c->obuf,nwritten,-1);
}
}
}
+10 -2
View File
@@ -215,9 +215,17 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct t
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
/* Try with IPv6 if no IPv4 address was found. We do it in this order since
* in a Redis client you can't afford to test if you have IPv6 connectivity
* as this would add latency to every connect. Otherwise a more sensible
* route could be: Use IPv6 if both addresses are available and there is IPv6
* connectivity. */
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
return REDIS_ERR;
hints.ai_family = AF_INET6;
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
return REDIS_ERR;
}
}
for (p = servinfo; p != NULL; p = p->ai_next) {
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
+386 -109
View File
@@ -1,6 +1,6 @@
/* SDSLib, A C dynamic strings library
*
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -32,52 +32,76 @@
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include "sds.h"
#include "zmalloc.h"
#ifdef SDS_ABORT_ON_OOM
static void sdsOomAbort(void) {
fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
abort();
}
#endif
/* Create a new sds string with the content specified by the 'init' pointer
* and 'initlen'.
* If NULL is used for 'init' the string is initialized with zero bytes.
*
* The string is always null-termined (all the sds strings are, always) so
* even if you create an sds string with:
*
* mystring = sdsnewlen("abc",3");
*
* You can print the string with printf() as there is an implicit \0 at the
* end of the string. However the string is binary safe and can contain
* \0 characters in the middle, as the length is stored in the sds header. */
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
sh = malloc(sizeof(struct sdshdr)+initlen+1);
#ifdef SDS_ABORT_ON_OOM
if (sh == NULL) sdsOomAbort();
#else
if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
if (sh == NULL) return NULL;
#endif
sh->len = initlen;
sh->free = 0;
if (initlen) {
if (init) memcpy(sh->buf, init, initlen);
else memset(sh->buf,0,initlen);
}
if (initlen && init)
memcpy(sh->buf, init, initlen);
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}
/* Create an empty (zero length) sds string. Even in this case the string
* always has an implicit null term. */
sds sdsempty(void) {
return sdsnewlen("",0);
}
/* Create a new sds string starting from a null termined C string. */
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
/* Duplicate an sds string. */
sds sdsdup(const sds s) {
return sdsnewlen(s, sdslen(s));
}
/* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) {
if (s == NULL) return;
free(s-sizeof(struct sdshdr));
zfree(s-sizeof(struct sdshdr));
}
/* Set the sds string length to the length as obtained with strlen(), so
* considering as content only up to the first null term character.
*
* This function is useful when the sds string is hacked manually in some
* way, like in the following example:
*
* s = sdsnew("foobar");
* s[2] = '\0';
* sdsupdatelen(s);
* printf("%d\n", sdslen(s));
*
* The output will be "2", but if we comment out the call to sdsupdatelen()
* the output will be "6" as the string was modified but the logical length
* remains 6 bytes. */
void sdsupdatelen(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
int reallen = strlen(s);
@@ -85,7 +109,24 @@ void sdsupdatelen(sds s) {
sh->len = reallen;
}
static sds sdsMakeRoomFor(sds s, size_t addlen) {
/* Modify an sds string on-place to make it empty (zero length).
* However all the existing buffer is not discarded but set as free space
* so that next append operations will not require allocations up to the
* number of bytes previously available. */
void sdsclear(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
sh->free += sh->len;
sh->len = 0;
sh->buf[0] = '\0';
}
/* Enlarge the free space at the end of the sds string so that the caller
* is sure that after calling this function can overwrite up to addlen
* bytes after the end of the string, plus one more byte for nul term.
*
* Note: this does not change the *length* of the sds string as returned
* by sdslen(), but only the free buffer space we have. */
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
size_t free = sdsavail(s);
size_t len, newlen;
@@ -93,20 +134,84 @@ static sds sdsMakeRoomFor(sds s, size_t addlen) {
if (free >= addlen) return s;
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
newlen = (len+addlen)*2;
newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
#ifdef SDS_ABORT_ON_OOM
if (newsh == NULL) sdsOomAbort();
#else
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
if (newsh == NULL) return NULL;
#endif
newsh->free = newlen - len;
return newsh->buf;
}
/* Reallocate the sds string so that it has no free space at the end. The
* contained string remains not altered, but next concatenation operations
* will require a reallocation.
*
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdsRemoveFreeSpace(sds s) {
struct sdshdr *sh;
sh = (void*) (s-(sizeof(struct sdshdr)));
sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);
sh->free = 0;
return sh->buf;
}
/* Return the total size of the allocation of the specifed sds string,
* including:
* 1) The sds header before the pointer.
* 2) The string.
* 3) The free buffer at the end if any.
* 4) The implicit null term.
*/
size_t sdsAllocSize(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
return sizeof(*sh)+sh->len+sh->free+1;
}
/* Increment the sds length and decrements the left free space at the
* end of the string according to 'incr'. Also set the null term
* in the new end of the string.
*
* This function is used in order to fix the string length after the
* user calls sdsMakeRoomFor(), writes something after the end of
* the current string, and finally needs to set the new length.
*
* Note: it is possible to use a negative increment in order to
* right-trim the string.
*
* Usage example:
*
* Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the
* following schema, to cat bytes coming from the kernel to the end of an
* sds string without copying into an intermediate buffer:
*
* oldlen = sdslen(s);
* s = sdsMakeRoomFor(s, BUFFER_SIZE);
* nread = read(fd, s+oldlen, BUFFER_SIZE);
* ... check for nread <= 0 and handle it ...
* sdsIncrLen(s, nread);
*/
void sdsIncrLen(sds s, int incr) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
assert(sh->free >= incr);
sh->len += incr;
sh->free -= incr;
assert(sh->free >= 0);
s[sh->len] = '\0';
}
/* Grow the sds to have the specified length. Bytes that were not part of
* the original length of the sds will be set to zero. */
* the original length of the sds will be set to zero.
*
* if the specified length is smaller than the current length, no operation
* is performed. */
sds sdsgrowzero(sds s, size_t len) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
size_t totlen, curlen = sh->len;
@@ -124,6 +229,11 @@ sds sdsgrowzero(sds s, size_t len) {
return s;
}
/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
* end of the specified sds string 's'.
*
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatlen(sds s, const void *t, size_t len) {
struct sdshdr *sh;
size_t curlen = sdslen(s);
@@ -138,11 +248,25 @@ sds sdscatlen(sds s, const void *t, size_t len) {
return s;
}
/* Append the specified null termianted C string to the sds string 's'.
*
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
sds sdscpylen(sds s, char *t, size_t len) {
/* Append the specified sds 't' to the existing sds 's'.
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatsds(sds s, const sds t) {
return sdscatlen(s, t, sdslen(t));
}
/* Destructively modify the sds string 's' to hold the specified binary
* safe string pointed by 't' of length 'len' bytes. */
sds sdscpylen(sds s, const char *t, size_t len) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t totlen = sh->free+sh->len;
@@ -159,37 +283,52 @@ sds sdscpylen(sds s, char *t, size_t len) {
return s;
}
sds sdscpy(sds s, char *t) {
/* Like sdscpylen() but 't' must be a null-termined string so that the length
* of the string is obtained with strlen(). */
sds sdscpy(sds s, const char *t) {
return sdscpylen(s, t, strlen(t));
}
/* Like sdscatpritf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char *buf, *t;
size_t buflen = 16;
while(1) {
buf = malloc(buflen);
#ifdef SDS_ABORT_ON_OOM
if (buf == NULL) sdsOomAbort();
#else
buf = zmalloc(buflen);
if (buf == NULL) return NULL;
#endif
buf[buflen-2] = '\0';
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
if (buf[buflen-2] != '\0') {
free(buf);
zfree(buf);
buflen *= 2;
continue;
}
break;
}
t = sdscat(s, buf);
free(buf);
zfree(buf);
return t;
}
/* Append to the sds string 's' a string obtained using printf-alike format
* specifier.
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call.
*
* Example:
*
* s = sdsempty("Sum is: ");
* s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
*
* Often you need to create a string from scratch with the printf-alike
* format. When this is the need, just use sdsempty() as the target string:
*
* s = sdscatprintf(sdsempty(), "... your format ...", args);
*/
sds sdscatprintf(sds s, const char *fmt, ...) {
va_list ap;
char *t;
@@ -199,6 +338,20 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
return t;
}
/* Remove the part of the string from left and from right composed just of
* contiguous characters found in 'cset', that is a null terminted C string.
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call.
*
* Example:
*
* s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
* s = sdstrim(s,"A. :");
* printf("%s\n", s);
*
* Output will be just "Hello World".
*/
sds sdstrim(sds s, const char *cset) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep;
@@ -216,11 +369,27 @@ sds sdstrim(sds s, const char *cset) {
return s;
}
sds sdsrange(sds s, int start, int end) {
/* Turn the string into a smaller (or equal) string containing only the
* substring specified by the 'start' and 'end' indexes.
*
* start and end can be negative, where -1 means the last character of the
* string, -2 the penultimate character, and so forth.
*
* The interval is inclusive, so the start and end characters will be part
* of the resulting string.
*
* The string is modified in-place.
*
* Example:
*
* s = sdsnew("Hello World");
* sdstrim(s,1,-1); => "ello Worl"
*/
void sdsrange(sds s, int start, int end) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t newlen, len = sdslen(s);
if (len == 0) return s;
if (len == 0) return;
if (start < 0) {
start = len+start;
if (start < 0) start = 0;
@@ -244,22 +413,34 @@ sds sdsrange(sds s, int start, int end) {
sh->buf[newlen] = 0;
sh->free = sh->free+(sh->len-newlen);
sh->len = newlen;
return s;
}
/* Apply tolower() to every character of the sds string 's'. */
void sdstolower(sds s) {
int len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = tolower(s[j]);
}
/* Apply toupper() to every character of the sds string 's'. */
void sdstoupper(sds s) {
int len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = toupper(s[j]);
}
int sdscmp(sds s1, sds s2) {
/* Compare two sds strings s1 and s2 with memcmp().
*
* Return value:
*
* 1 if s1 > s2.
* -1 if s1 < s2.
* 0 if s1 and s2 are exactly the same binary string.
*
* If two strings share exactly the same prefix, but one of the two has
* additional characters, the longer string is considered to be greater than
* the smaller one. */
int sdscmp(const sds s1, const sds s2) {
size_t l1, l2, minlen;
int cmp;
@@ -287,14 +468,15 @@ int sdscmp(sds s1, sds s2) {
* requires length arguments. sdssplit() is just the
* same function but for zero-terminated strings.
*/
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) {
int elements = 0, slots = 5, start = 0, j;
sds *tokens;
if (seplen < 1 || len < 0) return NULL;
tokens = zmalloc(sizeof(sds)*slots);
if (tokens == NULL) return NULL;
sds *tokens = malloc(sizeof(sds)*slots);
#ifdef SDS_ABORT_ON_OOM
if (tokens == NULL) sdsOomAbort();
#endif
if (seplen < 1 || len < 0 || tokens == NULL) return NULL;
if (len == 0) {
*count = 0;
return tokens;
@@ -305,26 +487,14 @@ sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
sds *newtokens;
slots *= 2;
newtokens = realloc(tokens,sizeof(sds)*slots);
if (newtokens == NULL) {
#ifdef SDS_ABORT_ON_OOM
sdsOomAbort();
#else
goto cleanup;
#endif
}
newtokens = zrealloc(tokens,sizeof(sds)*slots);
if (newtokens == NULL) goto cleanup;
tokens = newtokens;
}
/* search the separator */
if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
tokens[elements] = sdsnewlen(s+start,j-start);
if (tokens[elements] == NULL) {
#ifdef SDS_ABORT_ON_OOM
sdsOomAbort();
#else
goto cleanup;
#endif
}
if (tokens[elements] == NULL) goto cleanup;
elements++;
start = j+seplen;
j = j+seplen-1; /* skip the separator */
@@ -332,35 +502,33 @@ sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
}
/* Add the final element. We are sure there is room in the tokens array. */
tokens[elements] = sdsnewlen(s+start,len-start);
if (tokens[elements] == NULL) {
#ifdef SDS_ABORT_ON_OOM
sdsOomAbort();
#else
goto cleanup;
#endif
}
if (tokens[elements] == NULL) goto cleanup;
elements++;
*count = elements;
return tokens;
#ifndef SDS_ABORT_ON_OOM
cleanup:
{
int i;
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
free(tokens);
zfree(tokens);
*count = 0;
return NULL;
}
#endif
}
/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
void sdsfreesplitres(sds *tokens, int count) {
if (!tokens) return;
while(count--)
sdsfree(tokens[count]);
free(tokens);
zfree(tokens);
}
/* Create an sds string from a long long value. It is much faster than:
*
* sdscatprintf(sdsempty(),"%lld\n", value);
*/
sds sdsfromlonglong(long long value) {
char buf[32], *p;
unsigned long long v;
@@ -376,10 +544,14 @@ sds sdsfromlonglong(long long value) {
return sdsnewlen(p,32-(p-buf));
}
sds sdscatrepr(sds s, char *p, size_t len) {
/* Append to the sds string "s" an escaped string representation where
* all the non-printable characters (tested with isprint()) are turned into
* escapes in the form "\n\r\a...." or "\x<hex-number>".
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatrepr(sds s, const char *p, size_t len) {
s = sdscatlen(s,"\"",1);
if (s == NULL) return NULL;
while(len--) {
switch(*p) {
case '\\':
@@ -399,27 +571,64 @@ sds sdscatrepr(sds s, char *p, size_t len) {
break;
}
p++;
if (s == NULL) return NULL;
}
return sdscatlen(s,"\"",1);
}
/* Helper function for sdssplitargs() that returns non zero if 'c'
* is a valid hex digit. */
int is_hex_digit(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');
}
/* Helper function for sdssplitargs() that converts an hex digit into an
* integer from 0 to 15 */
int hex_digit_to_int(char c) {
switch(c) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
default: return 0;
}
}
/* Split a line into arguments, where every argument can be in the
* following programming-language REPL-alike form:
*
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
*
* The number of arguments is stored into *argc, and an array
* of sds is returned. The caller should sdsfree() all the returned
* strings and finally free() the array itself.
* of sds is returned.
*
* The caller should free the resulting array of sds strings with
* sdsfreesplitres().
*
* Note that sdscatrepr() is able to convert back a string into
* a quoted string in the same format sdssplitargs() is able to parse.
*
* The function returns the allocated tokens on success, even when the
* input string is empty, or NULL if the input contains unbalanced
* quotes or closed quotes followed by non space characters
* as in: "foo"bar or "foo'
*/
sds *sdssplitargs(char *line, int *argc) {
char *p = line;
sds *sdssplitargs(const char *line, int *argc) {
const char *p = line;
char *current = NULL;
char **vector = NULL, **_vector = NULL;
char **vector = NULL;
*argc = 0;
while(1) {
@@ -427,17 +636,24 @@ sds *sdssplitargs(char *line, int *argc) {
while(*p && isspace(*p)) p++;
if (*p) {
/* get a token */
int inq=0; /* set to 1 if we are in "quotes" */
int inq=0; /* set to 1 if we are in "quotes" */
int insq=0; /* set to 1 if we are in 'single quotes' */
int done=0;
if (current == NULL) {
current = sdsempty();
if (current == NULL) goto err;
}
if (current == NULL) current = sdsempty();
while(!done) {
if (inq) {
if (*p == '\\' && *(p+1)) {
if (*p == '\\' && *(p+1) == 'x' &&
is_hex_digit(*(p+2)) &&
is_hex_digit(*(p+3)))
{
unsigned char byte;
byte = (hex_digit_to_int(*(p+2))*16)+
hex_digit_to_int(*(p+3));
current = sdscatlen(current,(char*)&byte,1);
p += 3;
} else if (*p == '\\' && *(p+1)) {
char c;
p++;
@@ -451,7 +667,23 @@ sds *sdssplitargs(char *line, int *argc) {
}
current = sdscatlen(current,&c,1);
} else if (*p == '"') {
/* closing quote must be followed by a space */
/* closing quote must be followed by a space or
* nothing at all. */
if (*(p+1) && !isspace(*(p+1))) goto err;
done=1;
} else if (!*p) {
/* unterminated quotes */
goto err;
} else {
current = sdscatlen(current,p,1);
}
} else if (insq) {
if (*p == '\\' && *(p+1) == '\'') {
p++;
current = sdscatlen(current,"'",1);
} else if (*p == '\'') {
/* closing quote must be followed by a space or
* nothing at all. */
if (*(p+1) && !isspace(*(p+1))) goto err;
done=1;
} else if (!*p) {
@@ -472,23 +704,24 @@ sds *sdssplitargs(char *line, int *argc) {
case '"':
inq=1;
break;
case '\'':
insq=1;
break;
default:
current = sdscatlen(current,p,1);
break;
}
}
if (*p) p++;
if (current == NULL) goto err;
}
/* add the token to the vector */
_vector = realloc(vector,((*argc)+1)*sizeof(char*));
if (_vector == NULL) goto err;
vector = _vector;
vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
vector[*argc] = current;
(*argc)++;
current = NULL;
} else {
/* Even on empty input string return something not NULL. */
if (vector == NULL) vector = zmalloc(sizeof(void*));
return vector;
}
}
@@ -496,30 +729,55 @@ sds *sdssplitargs(char *line, int *argc) {
err:
while((*argc)--)
sdsfree(vector[*argc]);
if (vector != NULL) free(vector);
if (current != NULL) sdsfree(current);
zfree(vector);
if (current) sdsfree(current);
*argc = 0;
return NULL;
}
/* Modify the string substituting all the occurrences of the set of
* characters specified in the 'from' string to the corresponding character
* in the 'to' array.
*
* For instance: sdsmapchars(mystring, "ho", "01", 2)
* will have the effect of turning the string "hello" into "0ell1".
*
* The function returns the sds string pointer, that is always the same
* as the input pointer since no resize is needed. */
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
size_t j, i, l = sdslen(s);
for (j = 0; j < l; j++) {
for (i = 0; i < setlen; i++) {
if (s[j] == from[i]) {
s[j] = to[i];
break;
}
}
}
return s;
}
/* Join an array of C strings using the specified separator (also a C string).
* Returns the result as an sds string. */
sds sdsjoin(char **argv, int argc, char *sep) {
sds join = sdsempty();
int j;
for (j = 0; j < argc; j++) {
join = sdscat(join, argv[j]);
if (j != argc-1) join = sdscat(join,sep);
}
return join;
}
#ifdef SDS_TEST_MAIN
#include <stdio.h>
int __failed_tests = 0;
int __test_num = 0;
#define test_cond(descr,_c) do { \
__test_num++; printf("%d - %s: ", __test_num, descr); \
if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
} while(0);
#define test_report() do { \
printf("%d tests, %d passed, %d failed\n", __test_num, \
__test_num-__failed_tests, __failed_tests); \
if (__failed_tests) { \
printf("=== WARNING === We have failed tests here...\n"); \
} \
} while(0);
#include "testhelp.h"
int main(void) {
{
struct sdshdr *sh;
sds x = sdsnew("foo"), y;
test_cond("Create a string and obtain the length",
@@ -599,7 +857,26 @@ int main(void) {
x = sdsnew("aar");
y = sdsnew("bar");
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
{
int oldfree;
sdsfree(x);
x = sdsnew("0");
sh = (void*) (x-(sizeof(struct sdshdr)));
test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0);
x = sdsMakeRoomFor(x,1);
sh = (void*) (x-(sizeof(struct sdshdr)));
test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0);
oldfree = sh->free;
x[1] = '1';
sdsIncrLen(x,1);
test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1');
test_cond("sdsIncrLen() -- len", sh->len == 2);
test_cond("sdsIncrLen() -- free", sh->free == oldfree-1);
}
}
test_report()
return 0;
}
#endif
+20 -8
View File
@@ -31,6 +31,8 @@
#ifndef __SDS_H
#define __SDS_H
#define SDS_MAX_PREALLOC (1024*1024)
#include <sys/types.h>
#include <stdarg.h>
@@ -58,12 +60,13 @@ sds sdsempty(void);
size_t sdslen(const sds s);
sds sdsdup(const sds s);
void sdsfree(sds s);
size_t sdsavail(sds s);
size_t sdsavail(const sds s);
sds sdsgrowzero(sds s, size_t len);
sds sdscatlen(sds s, const void *t, size_t len);
sds sdscat(sds s, const char *t);
sds sdscpylen(sds s, char *t, size_t len);
sds sdscpy(sds s, char *t);
sds sdscatsds(sds s, const sds t);
sds sdscpylen(sds s, const char *t, size_t len);
sds sdscpy(sds s, const char *t);
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
#ifdef __GNUC__
@@ -74,15 +77,24 @@ sds sdscatprintf(sds s, const char *fmt, ...);
#endif
sds sdstrim(sds s, const char *cset);
sds sdsrange(sds s, int start, int end);
void sdsrange(sds s, int start, int end);
void sdsupdatelen(sds s);
int sdscmp(sds s1, sds s2);
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
void sdsclear(sds s);
int sdscmp(const sds s1, const sds s2);
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
void sdsfreesplitres(sds *tokens, int count);
void sdstolower(sds s);
void sdstoupper(sds s);
sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, char *p, size_t len);
sds *sdssplitargs(char *line, int *argc);
sds sdscatrepr(sds s, const char *p, size_t len);
sds *sdssplitargs(const char *line, int *argc);
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
sds sdsjoin(char **argv, int argc, char *sep);
/* Low level functions exposed to the user API */
sds sdsMakeRoomFor(sds s, size_t addlen);
void sdsIncrLen(sds s, int incr);
sds sdsRemoveFreeSpace(sds s);
size_t sdsAllocSize(sds s);
#endif
+13
View File
@@ -0,0 +1,13 @@
/* Drop in replacement for zmalloc.h in order to just use libc malloc without
* any wrappering. */
#ifndef ZMALLOC_H
#define ZMALLOC_H
#define zmalloc malloc
#define zrealloc realloc
#define zcalloc(x) calloc(x,1)
#define zfree free
#define zstrdup strdup
#endif
+693 -229
View File
File diff suppressed because it is too large Load Diff
+34 -23
View File
@@ -3,39 +3,44 @@
*
* See linenoise.c for more information.
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __LINENOISE_H
#define __LINENOISE_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct linenoiseCompletions {
size_t len;
char **cvec;
@@ -43,13 +48,19 @@ typedef struct linenoiseCompletions {
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, char *);
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
char *linenoise(const char *prompt);
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(char *filename);
int linenoiseHistoryLoad(char *filename);
int linenoiseHistorySave(const char *filename);
int linenoiseHistoryLoad(const char *filename);
void linenoiseClearScreen(void);
void linenoiseSetMultiLine(int ml);
void linenoisePrintKeyCodes(void);
#ifdef __cplusplus
}
#endif
#endif /* __LINENOISE_H */
+1 -1
View File
@@ -7,7 +7,7 @@
# Your platform. See PLATS for possible values.
PLAT= none
CC= gcc
CC?= gcc
CFLAGS= -O2 -Wall $(MYCFLAGS)
AR= ar rcu
RANLIB= ranlib
+1 -1
View File
@@ -374,7 +374,7 @@ static int table_is_an_array(lua_State *L) {
while(lua_next(L,-2)) {
/* Stack: ... key value */
lua_pop(L,1); /* Stack: ... key */
if (!lua_isnumber(L,-1)) goto not_array;
if (lua_type(L,-1) != LUA_TNUMBER) goto not_array;
n = lua_tonumber(L,-1);
idx = n;
if (idx != n || idx < 1) goto not_array;
+163 -29
View File
@@ -12,6 +12,26 @@
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.
################################## 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.
#
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf
################################ GENERAL #####################################
# 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
@@ -24,12 +44,26 @@ pidfile /var/run/redis.pid
# 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.
# TCP listen() backlog.
#
# In high requests-per-second environments you need an high backlog in order
# to avoid slow clients connections issues. Note that the Linux kernel
# will silently truncate it to the value of /proc/sys/net/core/somaxconn so
# make sure to raise both the value of somaxconn and tcp_max_syn_backlog
# in order to get the desired effect.
tcp-backlog 511
# By default Redis listens for connections from all the network interfaces
# available on the server. It is possible to listen to just one or multiple
# interfaces using the "bind" configuration directive, followed by one or
# more IP addresses.
#
# Examples:
#
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1
# Specify the path for the unix socket that will be used to listen for
# 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.
#
@@ -63,10 +97,10 @@ tcp-keepalive 0
# warning (only very important / critical messages are logged)
loglevel notice
# Specify the log file name. Also 'stdout' can be used to force
# Specify the log file name. Also the empty string 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
logfile ""
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
# and optionally update the other syslog parameters to suit your needs.
@@ -83,7 +117,7 @@ logfile stdout
# dbid is a number between 0 and 'databases'-1
databases 16
################################ SNAPSHOTTING #################################
################################ SNAPSHOTTING ################################
#
# Save the DB on disk:
#
@@ -111,16 +145,16 @@ 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
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# distater will happen.
# disaster 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,
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes
@@ -192,7 +226,7 @@ slave-serve-stale-data yes
# 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
# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
# security of read only slaves using 'rename-command' to shadow all the
# administrative / dangerous commands.
slave-read-only yes
@@ -203,8 +237,11 @@ slave-read-only yes
#
# 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.
# The following option sets the replication timeout for:
#
# 1) Bulk transfer I/O during SYNC, from the point of view of slave.
# 2) Master timeout from the point of view of slaves (data, pings).
# 3) Slave timeout from the point of view of masters (REPLCONF ACK pings).
#
# 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
@@ -227,13 +264,35 @@ slave-read-only yes
# be a good idea.
repl-disable-tcp-nodelay no
# Set the replication backlog size. The backlog is a buffer that accumulates
# slave data when slaves are disconnected for some time, so that when a slave
# wants to reconnect again, often a full resync is not needed, but a partial
# resync is enough, just passing the portion of data the slave missed while
# disconnected.
#
# The biggest the replication backlog, the longer the time the slave can be
# disconnected and later be able to perform a partial resynchronization.
#
# The backlog is only allocated once there is at least a slave connected.
#
# repl-backlog-size 1mb
# After a master has no longer connected slaves for some time, the backlog
# will be freed. The following option configures the amount of seconds that
# need to elapse, starting from the time the last slave disconnected, for
# the backlog buffer to be freed.
#
# A value of 0 means to never release the backlog.
#
# repl-backlog-ttl 3600
# 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.
# pick the one with 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
@@ -242,6 +301,28 @@ repl-disable-tcp-nodelay no
# By default the priority is 100.
slave-priority 100
# It is possible for a master to stop accepting writes if there are less than
# N slaves connected, having a lag less or equal than M seconds.
#
# The N slaves need to be in "online" state.
#
# The lag in seconds, that must be <= the specified value, is calculated from
# the last ping received from the slave, that is usually sent every second.
#
# This option does not GUARANTEES that N replicas will accept the write, but
# will limit the window of exposure for lost writes in case not enough slaves
# are available, to the specified number of seconds.
#
# For example to require at least 3 slaves with a lag <= 10 seconds use:
#
# min-slaves-to-write 3
# min-slaves-max-lag 10
#
# Setting one or the other to 0 disables the feature.
#
# By default min-slaves-to-write is set to 0 (feature disabled) and
# min-slaves-max-lag is set to 10.
################################## SECURITY ###################################
# Require clients to issue AUTH <PASSWORD> before processing any other
@@ -291,7 +372,7 @@ slave-priority 100
# 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).
# according to the eviction policy selected (see maxmemory-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
@@ -299,7 +380,7 @@ slave-priority 100
# 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).
# a 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
@@ -368,7 +449,8 @@ slave-priority 100
appendonly no
# The name of the append only file (default: "appendonly.aof")
# appendfilename 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
@@ -415,6 +497,7 @@ appendfsync everysec
#
# 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.
@@ -479,6 +562,52 @@ slowlog-log-slower-than 10000
# You can reclaim memory used by the slow log with SLOWLOG RESET.
slowlog-max-len 128
############################# Event notification ##############################
# Redis can notify Pub/Sub clients about events happening in the key space.
# This feature is documented at http://redis.io/topics/keyspace-events
#
# For instance if keyspace events notification is enabled, and a client
# performs a DEL operation on key "foo" stored in the Database 0, two
# messages will be published via Pub/Sub:
#
# PUBLISH __keyspace@0__:foo del
# PUBLISH __keyevent@0__:del foo
#
# It is possible to select the events that Redis will notify among a set
# of classes. Every class is identified by a single character:
#
# K Keyspace events, published with __keyspace@<db>__ prefix.
# E Keyevent events, published with __keyevent@<db>__ prefix.
# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
# $ String commands
# l List commands
# s Set commands
# h Hash commands
# z Sorted set commands
# x Expired events (events generated every time a key expires)
# e Evicted events (events generated when a key is evicted for maxmemory)
# A Alias for g$lshzxe, so that the "AKE" string means all the events.
#
# The "notify-keyspace-events" takes as argument a string that is composed
# by zero or multiple characters. The empty string means that notifications
# are disabled at all.
#
# Example: to enable list and generic events, from the point of view of the
# event name, use:
#
# notify-keyspace-events Elg
#
# Example 2: to get the stream of the expired keys subscribing to channel
# name __keyevent@0__:expired use:
#
# notify-keyspace-events Ex
#
# By default all notifications are disabled because most users don't need
# this feature and the feature has some overhead. Note that if you don't
# specify at least one of K or E, no events will be delivered.
notify-keyspace-events ""
############################### ADVANCED CONFIG ###############################
# Hashes are encoded using a memory efficient data structure when they have a
@@ -506,10 +635,24 @@ set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
# HyperLogLog sparse representation bytes limit. The limit includes the
# 16 bytes header. When an HyperLogLog using the sparse representation crosses
# this limit, it is converted into the dense representation.
#
# A value greater than 16000 is totally useless, since at that point the
# dense representation is more memory efficient.
#
# The suggested value is ~ 3000 in order to have the benefits of
# the space efficient encoding without slowing down too much PFADD,
# which is O(N) with the sparse encoding. The value can be raised to
# ~ 10000 when CPU is not a concern, but space is, and the data set is
# composed of many HyperLogLogs with cardinality in the 0 - 15000 range.
hll-sparse-max-bytes 3000
# 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
# performs a lazy rehashing: the more operation you run into a 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.
@@ -535,7 +678,7 @@ activerehashing yes
#
# normal -> normal clients
# slave -> slave clients and MONITOR clients
# pubsub -> clients subcribed to at least one pubsub channel or pattern
# pubsub -> clients subscribed to at least one pubsub channel or pattern
#
# The syntax of every client-output-buffer-limit directive is the following:
#
@@ -564,10 +707,10 @@ 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
# closing connections of clients in timeout, purging expired keys that are
# never requested, and so forth.
#
# Not all tasks are perforemd with the same frequency, but Redis checks for
# Not all tasks are performed 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
@@ -586,12 +729,3 @@ hz 10
# 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
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
TCL_VERSIONS="8.5 8.6"
TCLSH=""
for VERSION in $TCL_VERSIONS; do
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
done
if [ -z $TCLSH ]
then
echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test"
exit 1
fi
$TCLSH tests/sentinel.tcl $*
+44 -35
View File
@@ -4,11 +4,28 @@
# The port that this sentinel instance will run on
port 26379
# dir <working-directory>
# Every long running process should have a well-defined working directory.
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
# for the process to don't interferer with administrative tasks such as
# unmounting filesystems.
dir /tmp
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
#
# Tells Sentinel to monitor this slave, and to consider it in O_DOWN
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
# (Objectively Down) state only if at least <quorum> sentinels agree.
#
# Note that whatever is the ODOWN quorum, a Sentinel will require to
# be elected by the majority of the known Sentinels in order to
# start a failover, so no failover can be performed in minority.
#
# Slaves are auto-discovered, so you don't need to specify slaves in
# any way. Sentinel itself will rewrite this configuration file adding
# the slaves using additional configuration options.
# Also note that the configuration file is rewritten when a
# slave is promoted to master.
#
# Note: master name should not include special characters or spaces.
# The valid charset is A-z 0-9 and the three characters ".-_".
sentinel monitor mymaster 127.0.0.1 6379 2
@@ -42,11 +59,6 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# Default is 30 seconds.
sentinel down-after-milliseconds mymaster 30000
# sentinel can-failover <master-name> <yes|no>
#
# Specify if this Sentinel can start the failover for this master.
sentinel can-failover mymaster yes
# sentinel parallel-syncs <master-name> <numslaves>
#
# How many slaves we can reconfigure to point to the new slave simultaneously
@@ -57,19 +69,28 @@ sentinel parallel-syncs mymaster 1
# sentinel failover-timeout <master-name> <milliseconds>
#
# Specifies the failover timeout in milliseconds. When this time has elapsed
# without any progress in the failover process, it is considered concluded by
# the sentinel even if not all the attached slaves were correctly configured
# to replicate with the new master (however a "best effort" SLAVEOF command
# is sent to all the slaves before).
# Specifies the failover timeout in milliseconds. It is used in many ways:
#
# Also when 25% of this time has elapsed without any advancement, and there
# is a leader switch (the sentinel did not started the failover but is now
# elected as leader), the sentinel will continue the failover doing a
# "takeover".
# - The time needed to re-start a failover after a previous failover was
# already tried against the same master by a given Sentinel, is two
# times the failover timeout.
#
# Default is 15 minutes.
sentinel failover-timeout mymaster 900000
# - The time needed for a slave replicating to a wrong master according
# to a Sentinel current configuration, to be forced to replicate
# with the right master, is exactly the failover timeout (counting since
# the moment a Sentinel detected the misconfiguration).
#
# - The time needed to cancel a failover that is already in progress but
# did not produced any configuration change (SLAVEOF NO ONE yet not
# acknowledged by the promoted slave).
#
# - The maximum time a failover in progress waits for all the slaves to be
# reconfigured as slaves of the new master. However even after this time
# the slaves will be reconfigured by the Sentinels anyway, but not with
# the exact parallel-syncs progression as specified.
#
# Default is 3 minutes.
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#
@@ -78,10 +99,10 @@ sentinel failover-timeout mymaster 900000
# or to reconfigure clients after a failover. The scripts are executed
# with the following rules for error handling:
#
# If script exists with "1" the execution is retried later (up to a maximum
# If script exits with "1" the execution is retried later (up to a maximum
# number of times currently set to 10).
#
# If script exists with "2" (or an higher value) the script execution is
# If script exits with "2" (or an higher value) the script execution is
# not retried.
#
# If script terminates because it receives a signal the behavior is the same
@@ -94,7 +115,7 @@ sentinel failover-timeout mymaster 900000
#
# sentinel notification-script <master-name> <script-path>
#
# Call the specified notification script for any sentienl event that is
# Call the specified notification script for any sentinel event that is
# generated in the WARNING level (for instance -sdown, -odown, and so forth).
# This script should notify the system administrator via email, SMS, or any
# other messaging system, that there is something wrong with the monitored
@@ -114,32 +135,20 @@ sentinel failover-timeout mymaster 900000
#
# sentinel client-reconfig-script <master-name> <script-path>
#
# When the failover starts, ends, or is aborted, a script can be called in
# When the master changed because of a failover a script can be called in
# order to perform application-specific tasks to notify the clients that the
# configuration has changed and the master is at a different address.
#
# The script is called in the following cases:
#
# Failover started (a slave is already promoted)
# Failover finished (all the additional slaves already reconfigured)
# Failover aborted (in that case the script was previously called when the
# failover started, and now gets called again with swapped
# addresses).
#
# The following arguments are passed to the script:
#
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
#
# <state> is "start", "end" or "abort"
# <state> is currently always "failover"
# <role> is either "leader" or "observer"
#
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
# the old address of the master and the new address of the elected slave
# (now a master) in the case state is "start" or "end".
#
# For abort instead the "from" is the address of the promoted slave and
# "to" is the address of the original master address, since the failover
# was aborted.
# (now a master).
#
# This script should be resistant to multiple invocations.
#
+52 -39
View File
@@ -18,72 +18,80 @@ OPTIMIZATION?=-O2
DEPENDENCY_TARGETS=hiredis linenoise lua
# Default settings
STD= -std=c99 -pedantic
WARN= -Wall
OPT= $(OPTIMIZATION)
STD=-std=c99 -pedantic
WARN=-Wall
OPT=$(OPTIMIZATION)
PREFIX?=/usr/local
INSTALL_BIN=$(PREFIX)/bin
INSTALL=install
# Default allocator
ifeq ($(uname_S),Linux)
MALLOC=jemalloc
MALLOC=jemalloc
else
MALLOC=libc
MALLOC=libc
endif
# Backwards compatibility for selecting an allocator
ifeq ($(USE_TCMALLOC),yes)
MALLOC=tcmalloc
MALLOC=tcmalloc
endif
ifeq ($(USE_TCMALLOC_MINIMAL),yes)
MALLOC=tcmalloc_minimal
MALLOC=tcmalloc_minimal
endif
ifeq ($(USE_JEMALLOC),yes)
MALLOC=jemalloc
MALLOC=jemalloc
endif
# Override default settings if possible
-include .make-settings
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
FINAL_LIBS=-lm
DEBUG=-g -ggdb
ifeq ($(uname_S),SunOS)
FINAL_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS) -D__EXTENSIONS__ -D_XPG6
FINAL_LDFLAGS= $(LDFLAGS) $(REDIS_LDFLAGS) -g -ggdb
FINAL_LIBS= -ldl -lnsl -lsocket -lm -lpthread
DEBUG= -g -ggdb
# SunOS
INSTALL=cp -pf
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
FINAL_LIBS+= -ldl -lnsl -lsocket -lpthread
else
FINAL_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
FINAL_LDFLAGS= $(LDFLAGS) $(REDIS_LDFLAGS) -g -rdynamic -ggdb
FINAL_LIBS= -lm -pthread
DEBUG= -g -rdynamic -ggdb
ifeq ($(uname_S),Darwin)
# Darwin (nothing to do)
else
# All the other OSes (notably Linux)
FINAL_LDFLAGS+= -rdynamic
FINAL_LIBS+= -pthread
endif
endif
# Include paths to dependencies
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
ifeq ($(MALLOC),tcmalloc)
FINAL_CFLAGS+= -DUSE_TCMALLOC
FINAL_LIBS+= -ltcmalloc
FINAL_CFLAGS+= -DUSE_TCMALLOC
FINAL_LIBS+= -ltcmalloc
endif
ifeq ($(MALLOC),tcmalloc_minimal)
FINAL_CFLAGS+= -DUSE_TCMALLOC
FINAL_LIBS+= -ltcmalloc_minimal
FINAL_CFLAGS+= -DUSE_TCMALLOC
FINAL_LIBS+= -ltcmalloc_minimal
endif
ifeq ($(MALLOC),jemalloc)
DEPENDENCY_TARGETS+= jemalloc
FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include
FINAL_LIBS+= ../deps/jemalloc/lib/libjemalloc.a -ldl
DEPENDENCY_TARGETS+= jemalloc
FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include
FINAL_LIBS+= ../deps/jemalloc/lib/libjemalloc.a -ldl
endif
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
PREFIX?=/usr/local
INSTALL_BIN= $(PREFIX)/bin
INSTALL= cp -pf
CCCOLOR="\033[34m"
LINKCOLOR="\033[34;1m"
SRCCOLOR="\033[33m"
@@ -97,17 +105,17 @@ QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(EN
QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
endif
REDIS_SERVER_NAME= redis-server
REDIS_SENTINEL_NAME= redis-sentinel
REDIS_SERVER_OBJ= adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o migrate.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o
REDIS_CLI_NAME= redis-cli
REDIS_CLI_OBJ= anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o
REDIS_BENCHMARK_NAME= redis-benchmark
REDIS_BENCHMARK_OBJ= ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o redis-benchmark.o
REDIS_CHECK_DUMP_NAME= redis-check-dump
REDIS_CHECK_DUMP_OBJ= redis-check-dump.o lzf_c.o lzf_d.o crc64.o
REDIS_CHECK_AOF_NAME= redis-check-aof
REDIS_CHECK_AOF_OBJ= redis-check-aof.o
REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o migrate.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o hyperloglog.o
REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
REDIS_BENCHMARK_NAME=redis-benchmark
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o redis-benchmark.o
REDIS_CHECK_DUMP_NAME=redis-check-dump
REDIS_CHECK_DUMP_OBJ=redis-check-dump.o lzf_c.o lzf_d.o crc64.o
REDIS_CHECK_AOF_NAME=redis-check-aof
REDIS_CHECK_AOF_OBJ=redis-check-aof.o
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME)
@echo ""
@@ -196,6 +204,11 @@ distclean: clean
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)
@(cd ..; ./runtest)
test-sentinel: $(REDIS_SENTINEL_NAME)
@(cd ..; ./runtest-sentinel)
check: test
lcov:
$(MAKE) gcov
@(set -e; cd ..; ./runtest --clients 1)
@@ -226,7 +239,7 @@ src/help.h:
@../utils/generate-command-help.rb > help.h
install: all
mkdir -p $(INSTALL_BIN)
@mkdir -p $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
+22 -13
View File
@@ -1,5 +1,5 @@
adlist.o: adlist.c adlist.h zmalloc.h
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c ae_select.c ae_evport.c ae_epoll.c
ae_epoll.o: ae_epoll.c
ae_evport.o: ae_evport.c
ae_kqueue.o: ae_kqueue.c
@@ -23,13 +23,17 @@ db.o: db.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
ziplist.h intset.h version.h util.h rdb.h rio.h
debug.o: debug.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h
dict.o: dict.c fmacros.h dict.h zmalloc.h
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h crc64.h bio.h
dict.o: dict.c fmacros.h dict.h zmalloc.h redisassert.h
endianconv.o: endianconv.c
hyperloglog.o: hyperloglog.c redis.h fmacros.h config.h \
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
rio.h
intset.o: intset.c intset.h zmalloc.h endianconv.h config.h
lzf_c.o: lzf_c.c lzfP.h
lzf_d.o: lzf_d.c lzfP.h
memtest.o: memtest.c
memtest.o: memtest.c config.h
migrate.o: migrate.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
ziplist.h intset.h version.h util.h rdb.h rio.h endianconv.h
@@ -40,6 +44,9 @@ networking.o: networking.c redis.h fmacros.h config.h \
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
rio.h
notify.o: notify.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
ziplist.h intset.h version.h util.h rdb.h rio.h
object.o: object.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
ziplist.h intset.h version.h util.h rdb.h rio.h
@@ -55,30 +62,31 @@ rdb.o: rdb.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \
../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
redis-check-dump.o: redis-check-dump.c lzf.h
redis-check-dump.o: redis-check-dump.c lzf.h crc64.h
redis-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \
sds.h zmalloc.h ../deps/linenoise/linenoise.h help.h anet.h ae.h
redis.o: redis.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
ziplist.h intset.h version.h util.h rdb.h rio.h slowlog.h bio.h \
asciilogo.h
release.o: release.c release.h
release.o: release.c release.h version.h crc64.h
replication.o: replication.c redis.h fmacros.h config.h \
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
rio.h
rio.o: rio.c fmacros.h rio.h sds.h util.h
rio.o: rio.c fmacros.h rio.h sds.h util.h crc64.h config.h redis.h \
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h dict.h adlist.h \
zmalloc.h anet.h ziplist.h intset.h version.h rdb.h
scripting.o: scripting.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h rand.h \
../deps/lua/src/lauxlib.h ../deps/lua/src/lua.h \
../deps/lua/src/lualib.h
../deps/lua/src/lauxlib.h ../deps/lua/src/lualib.h
sds.o: sds.c sds.h zmalloc.h
sentinel.o: sentinel.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
ziplist.h intset.h version.h util.h rdb.h rio.h \
../deps/hiredis/hiredis.h ../deps/hiredis/async.h \
../deps/hiredis/hiredis.h
../deps/hiredis/hiredis.h ../deps/hiredis/async.h
setproctitle.o: setproctitle.c
sha1.o: sha1.c sha1.h config.h
slowlog.o: slowlog.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
@@ -104,7 +112,8 @@ t_string.o: t_string.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
t_zset.o: t_zset.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
ziplist.h intset.h version.h util.h rdb.h rio.h
util.o: util.c fmacros.h util.h
ziplist.o: ziplist.c zmalloc.h util.h ziplist.h endianconv.h config.h
util.o: util.c fmacros.h util.h sds.h
ziplist.o: ziplist.c zmalloc.h util.h sds.h ziplist.h endianconv.h \
config.h redisassert.h
zipmap.o: zipmap.c zmalloc.h endianconv.h config.h
zmalloc.o: zmalloc.c config.h zmalloc.h
+30
View File
@@ -91,6 +91,36 @@ err:
return NULL;
}
/* Return the current set size. */
int aeGetSetSize(aeEventLoop *eventLoop) {
return eventLoop->setsize;
}
/* Resize the maximum set size of the event loop.
* If the requested set size is smaller than the current set size, but
* there is already a file descriptor in use that is >= the requested
* set size minus one, AE_ERR is returned and the operation is not
* performed at all.
*
* Otherwise AE_OK is returned and the operation is successful. */
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize) {
int i;
if (setsize == eventLoop->setsize) return AE_OK;
if (eventLoop->maxfd >= setsize) return AE_ERR;
if (aeApiResize(eventLoop,setsize) == -1) return AE_ERR;
eventLoop->events = zrealloc(eventLoop->events,sizeof(aeFileEvent)*setsize);
eventLoop->fired = zrealloc(eventLoop->fired,sizeof(aeFiredEvent)*setsize);
eventLoop->setsize = setsize;
/* Make sure that if we created new slots, they are initialized with
* an AE_NONE mask. */
for (i = eventLoop->maxfd+1; i < setsize; i++)
eventLoop->events[i].mask = AE_NONE;
return AE_OK;
}
void aeDeleteEventLoop(aeEventLoop *eventLoop) {
aeApiFree(eventLoop);
zfree(eventLoop->events);
+2
View File
@@ -114,5 +114,7 @@ int aeWait(int fd, int mask, long long milliseconds);
void aeMain(aeEventLoop *eventLoop);
char *aeGetApiName(void);
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep);
int aeGetSetSize(aeEventLoop *eventLoop);
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
#endif
+8 -1
View File
@@ -45,7 +45,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
zfree(state);
return -1;
}
state->epfd = epoll_create(1024); /* 1024 is just an hint for the kernel */
state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
if (state->epfd == -1) {
zfree(state->events);
zfree(state);
@@ -55,6 +55,13 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
return 0;
}
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
aeApiState *state = eventLoop->apidata;
state->events = zrealloc(state->events, sizeof(struct epoll_event)*setsize);
return 0;
}
static void aeApiFree(aeEventLoop *eventLoop) {
aeApiState *state = eventLoop->apidata;
+5
View File
@@ -94,6 +94,11 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
return 0;
}
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
/* Nothing to resize here. */
return 0;
}
static void aeApiFree(aeEventLoop *eventLoop) {
aeApiState *state = eventLoop->apidata;
+7 -1
View File
@@ -54,10 +54,16 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
return -1;
}
eventLoop->apidata = state;
return 0;
}
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
aeApiState *state = eventLoop->apidata;
state->events = zrealloc(state->events, sizeof(struct kevent)*setsize);
return 0;
}
static void aeApiFree(aeEventLoop *eventLoop) {
aeApiState *state = eventLoop->apidata;
+6
View File
@@ -48,6 +48,12 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
return 0;
}
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
/* Just ensure we have enough room in the fd_set type. */
if (setsize >= FD_SETSIZE) return -1;
return 0;
}
static void aeApiFree(aeEventLoop *eventLoop) {
zfree(eventLoop->apidata);
}
+180 -79
View File
@@ -163,27 +163,61 @@ int anetTcpKeepAlive(char *err, int fd)
return ANET_OK;
}
int anetResolve(char *err, char *host, char *ipbuf)
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
* do the actual work. It resolves the hostname "host" and set the string
* representation of the IP address into the buffer pointed by "ipbuf".
*
* If flags is set to ANET_IP_ONLY the function only resolves hostnames
* that are actually already IPv4 or IPv6 addresses. This turns the function
* into a validating / normalizing function. */
int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
int flags)
{
struct sockaddr_in sa;
struct addrinfo hints, *info;
int rv;
sa.sin_family = AF_INET;
if (inet_aton(host, &sa.sin_addr) == 0) {
struct hostent *he;
memset(&hints,0,sizeof(hints));
if (flags & ANET_IP_ONLY) hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM; /* specify socktype to avoid dups */
he = gethostbyname(host);
if (he == NULL) {
anetSetError(err, "can't resolve: %s", host);
return ANET_ERR;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
if ((rv = getaddrinfo(host, NULL, &hints, &info)) != 0) {
anetSetError(err, "%s", gai_strerror(rv));
return ANET_ERR;
}
if (info->ai_family == AF_INET) {
struct sockaddr_in *sa = (struct sockaddr_in *)info->ai_addr;
inet_ntop(AF_INET, &(sa->sin_addr), ipbuf, ipbuf_len);
} else {
struct sockaddr_in6 *sa = (struct sockaddr_in6 *)info->ai_addr;
inet_ntop(AF_INET6, &(sa->sin6_addr), ipbuf, ipbuf_len);
}
freeaddrinfo(info);
return ANET_OK;
}
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_NONE);
}
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_IP_ONLY);
}
static int anetSetReuseAddr(char *err, int fd) {
int yes = 1;
/* Make sure connection-intensive things like the redis benckmark
* will be able to close/open sockets a zillion of times */
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno));
return ANET_ERR;
}
strcpy(ipbuf,inet_ntoa(sa.sin_addr));
return ANET_OK;
}
static int anetCreateSocket(char *err, int domain) {
int s, on = 1;
int s;
if ((s = socket(domain, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "creating socket: %s", strerror(errno));
return ANET_ERR;
@@ -191,8 +225,8 @@ static int anetCreateSocket(char *err, int domain) {
/* Make sure connection-intensive things like the redis benchmark
* will be able to close/open sockets a zillion of times */
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno));
if (anetSetReuseAddr(err,s) == ANET_ERR) {
close(s);
return ANET_ERR;
}
return s;
@@ -202,38 +236,52 @@ static int anetCreateSocket(char *err, int domain) {
#define ANET_CONNECT_NONBLOCK 1
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
{
int s;
struct sockaddr_in sa;
int s = ANET_ERR, rv;
char portstr[6]; /* strlen("65535") + 1; */
struct addrinfo hints, *servinfo, *p;
if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
snprintf(portstr,sizeof(portstr),"%d",port);
memset(&hints,0,sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((rv = getaddrinfo(addr,portstr,&hints,&servinfo)) != 0) {
anetSetError(err, "%s", gai_strerror(rv));
return ANET_ERR;
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
if (inet_aton(addr, &sa.sin_addr) == 0) {
struct hostent *he;
he = gethostbyname(addr);
if (he == NULL) {
anetSetError(err, "can't resolve: %s", addr);
}
for (p = servinfo; p != NULL; p = p->ai_next) {
/* Try to create the socket and to connect it.
* If we fail in the socket() call, or on connect(), we retry with
* the next entry in servinfo. */
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
continue;
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
if (flags & ANET_CONNECT_NONBLOCK && anetNonBlock(err,s) != ANET_OK)
goto error;
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
/* If the socket is non-blocking, it is ok for connect() to
* return an EINPROGRESS error here. */
if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK)
goto end;
close(s);
return ANET_ERR;
s = ANET_ERR;
continue;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
}
if (flags & ANET_CONNECT_NONBLOCK) {
if (anetNonBlock(err,s) != ANET_OK)
return ANET_ERR;
}
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS &&
flags & ANET_CONNECT_NONBLOCK)
return s;
anetSetError(err, "connect: %s", strerror(errno));
close(s);
return ANET_ERR;
/* If we ended an iteration of the for loop without errors, we
* have a connected socket. Let's return to the caller. */
goto end;
}
if (p == NULL)
anetSetError(err, "creating socket: %s", strerror(errno));
error:
if (s != ANET_ERR) {
close(s);
s = ANET_ERR;
}
end:
freeaddrinfo(servinfo);
return s;
}
@@ -313,17 +361,14 @@ int anetWrite(int fd, char *buf, int count)
return totlen;
}
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
if (bind(s,sa,len) == -1) {
anetSetError(err, "bind: %s", strerror(errno));
close(s);
return ANET_ERR;
}
/* Use a backlog of 512 entries. We pass 511 to the listen() call because
* the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1);
* which will thus give us a backlog of 512 entries */
if (listen(s, 511) == -1) {
if (listen(s, backlog) == -1) {
anetSetError(err, "listen: %s", strerror(errno));
close(s);
return ANET_ERR;
@@ -331,29 +376,64 @@ static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {
return ANET_OK;
}
int anetTcpServer(char *err, int port, char *bindaddr)
{
int s;
struct sockaddr_in sa;
if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
return ANET_ERR;
memset(&sa,0,sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bindaddr && inet_aton(bindaddr, &sa.sin_addr) == 0) {
anetSetError(err, "invalid bind address");
static int anetV6Only(char *err, int s) {
int yes = 1;
if (setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,&yes,sizeof(yes)) == -1) {
anetSetError(err, "setsockopt: %s", strerror(errno));
close(s);
return ANET_ERR;
}
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
return ANET_OK;
}
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog)
{
int s, rv;
char _port[6]; /* strlen("65535") */
struct addrinfo hints, *servinfo, *p;
snprintf(_port,6,"%d",port);
memset(&hints,0,sizeof(hints));
hints.ai_family = af;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; /* No effect if bindaddr != NULL */
if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) {
anetSetError(err, "%s", gai_strerror(rv));
return ANET_ERR;
}
for (p = servinfo; p != NULL; p = p->ai_next) {
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
continue;
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) goto error;
goto end;
}
if (p == NULL) {
anetSetError(err, "unable to bind socket");
goto error;
}
error:
s = ANET_ERR;
end:
freeaddrinfo(servinfo);
return s;
}
int anetUnixServer(char *err, char *path, mode_t perm)
int anetTcpServer(char *err, int port, char *bindaddr, int backlog)
{
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog);
}
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog)
{
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog);
}
int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
{
int s;
struct sockaddr_un sa;
@@ -364,7 +444,7 @@ int anetUnixServer(char *err, char *path, mode_t perm)
memset(&sa,0,sizeof(sa));
sa.sun_family = AF_LOCAL;
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa),backlog) == ANET_ERR)
return ANET_ERR;
if (perm)
chmod(sa.sun_path, perm);
@@ -388,15 +468,22 @@ static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *l
return fd;
}
int anetTcpAccept(char *err, int s, char *ip, int *port) {
int anetTcpAccept(char *err, int s, char *ip, size_t ip_len, int *port) {
int fd;
struct sockaddr_in sa;
struct sockaddr_storage sa;
socklen_t salen = sizeof(sa);
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == -1)
return ANET_ERR;
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
if (port) *port = ntohs(sa.sin_port);
if (sa.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&sa;
if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
if (port) *port = ntohs(s->sin_port);
} else {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
if (port) *port = ntohs(s->sin6_port);
}
return fd;
}
@@ -404,38 +491,52 @@ int anetUnixAccept(char *err, int s) {
int fd;
struct sockaddr_un sa;
socklen_t salen = sizeof(sa);
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == -1)
return ANET_ERR;
return fd;
}
int anetPeerToString(int fd, char *ip, int *port) {
struct sockaddr_in sa;
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
struct sockaddr_storage sa;
socklen_t salen = sizeof(sa);
if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) {
*port = 0;
if (port) *port = 0;
ip[0] = '?';
ip[1] = '\0';
return -1;
}
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
if (port) *port = ntohs(sa.sin_port);
if (sa.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&sa;
if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
if (port) *port = ntohs(s->sin_port);
} else {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
if (port) *port = ntohs(s->sin6_port);
}
return 0;
}
int anetSockName(int fd, char *ip, int *port) {
struct sockaddr_in sa;
int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
struct sockaddr_storage sa;
socklen_t salen = sizeof(sa);
if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
*port = 0;
if (port) *port = 0;
ip[0] = '?';
ip[1] = '\0';
return -1;
}
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
if (port) *port = ntohs(sa.sin_port);
if (sa.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&sa;
if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
if (port) *port = ntohs(s->sin_port);
} else {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
if (port) *port = ntohs(s->sin6_port);
}
return 0;
}
+12 -5
View File
@@ -35,6 +35,10 @@
#define ANET_ERR -1
#define ANET_ERR_LEN 256
/* Flags used with certain functions. */
#define ANET_NONE 0
#define ANET_IP_ONLY (1<<0)
#if defined(__sun)
#define AF_LOCAL AF_UNIX
#endif
@@ -44,17 +48,20 @@ int anetTcpNonBlockConnect(char *err, char *addr, int port);
int anetUnixConnect(char *err, char *path);
int anetUnixNonBlockConnect(char *err, char *path);
int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf);
int anetTcpServer(char *err, int port, char *bindaddr);
int anetUnixServer(char *err, char *path, mode_t perm);
int anetTcpAccept(char *err, int serversock, char *ip, int *port);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
int anetTcpServer(char *err, int port, char *bindaddr, int backlog);
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog);
int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
int anetUnixAccept(char *err, int serversock);
int anetWrite(int fd, char *buf, int count);
int anetNonBlock(char *err, int fd);
int anetEnableTcpNoDelay(char *err, int fd);
int anetDisableTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd);
int anetPeerToString(int fd, char *ip, int *port);
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
int anetKeepAlive(char *err, int fd, int interval);
int anetSockName(int fd, char *ip, size_t ip_len, int *port);
#endif
+80 -28
View File
@@ -126,7 +126,7 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
}
/* Write the buffer (possibly composed of multiple blocks) into the specified
* fd. If no short write or any other error happens -1 is returned,
* fd. If a short write or any other error happens -1 is returned,
* otherwise the number of bytes written is returned. */
ssize_t aofRewriteBufferWrite(int fd) {
listNode *ln;
@@ -226,6 +226,7 @@ int startAppendOnly(void) {
*
* However if force is set to 1 we'll write regardless of the background
* fsync. */
#define AOF_WRITE_LOG_ERROR_RATE 30 /* Seconds between errors logging. */
void flushAppendOnlyFile(int force) {
ssize_t nwritten;
int sync_in_progress = 0;
@@ -267,27 +268,76 @@ void flushAppendOnlyFile(int force) {
* or alike */
nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
if (nwritten != (signed)sdslen(server.aof_buf)) {
/* Ooops, we are in troubles. The best thing to do for now is
* aborting instead of giving the illusion that everything is
* working as expected. */
static time_t last_write_error_log = 0;
int can_log = 0;
/* Limit logging rate to 1 line per AOF_WRITE_LOG_ERROR_RATE seconds. */
if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
can_log = 1;
last_write_error_log = server.unixtime;
}
/* Lof the AOF write error and record the error code. */
if (nwritten == -1) {
redisLog(REDIS_WARNING,"Exiting on error writing to the append-only file: %s",strerror(errno));
if (can_log) {
redisLog(REDIS_WARNING,"Error writing to the AOF file: %s",
strerror(errno));
server.aof_last_write_errno = errno;
}
} else {
redisLog(REDIS_WARNING,"Exiting on short write while writing to "
"the append-only file: %s (nwritten=%ld, "
"expected=%ld)",
strerror(errno),
(long)nwritten,
(long)sdslen(server.aof_buf));
if (can_log) {
redisLog(REDIS_WARNING,"Short write while writing to "
"the AOF file: (nwritten=%lld, "
"expected=%lld)",
(long long)nwritten,
(long long)sdslen(server.aof_buf));
}
if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {
redisLog(REDIS_WARNING, "Could not remove short write "
"from the append-only file. Redis may refuse "
"to load the AOF the next time it starts. "
"ftruncate: %s", strerror(errno));
if (can_log) {
redisLog(REDIS_WARNING, "Could not remove short write "
"from the append-only file. Redis may refuse "
"to load the AOF the next time it starts. "
"ftruncate: %s", strerror(errno));
}
} else {
/* If the ftrunacate() succeeded we can set nwritten to
* -1 since there is no longer partial data into the AOF. */
nwritten = -1;
}
server.aof_last_write_errno = ENOSPC;
}
/* Handle the AOF write error. */
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
/* We can't recover when the fsync policy is ALWAYS since the
* reply for the client is already in the output buffers, and we
* have the contract with the user that on acknowledged write data
* is synched on disk. */
redisLog(REDIS_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
exit(1);
} else {
/* Recover from failed write leaving data into the buffer. However
* set an error to stop accepting writes as long as the error
* condition is not cleared. */
server.aof_last_write_status = REDIS_ERR;
/* Trim the sds buffer if there was a partial write, and there
* was no way to undo it with ftruncate(2). */
if (nwritten > 0) {
server.aof_current_size += nwritten;
sdsrange(server.aof_buf,nwritten,-1);
}
return; /* We'll try again on the next call... */
}
} else {
/* Successful write(2). If AOF was in error state, restore the
* OK state and log the event. */
if (server.aof_last_write_status == REDIS_ERR) {
redisLog(REDIS_WARNING,
"AOF write error looks solved, Redis can write again.");
server.aof_last_write_status = REDIS_OK;
}
exit(1);
}
server.aof_current_size += nwritten;
@@ -456,7 +506,7 @@ struct redisClient *createFakeClient(void) {
c->reply_bytes = 0;
c->obuf_soft_limit_reached_time = 0;
c->watched_keys = listCreate();
listSetFreeMethod(c->reply,decrRefCount);
listSetFreeMethod(c->reply,decrRefCountVoid);
listSetDupMethod(c->reply,dupClientReplyValue);
initClientMultiState(c);
return c;
@@ -509,7 +559,7 @@ int loadAppendOnlyFile(char *filename) {
/* Serve the clients from time to time */
if (!(loops++ % 1000)) {
loadingProgress(ftello(fp));
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
processEventsWhileBlocked();
}
if (fgets(buf,sizeof(buf),fp) == NULL) {
@@ -536,7 +586,7 @@ int loadAppendOnlyFile(char *filename) {
/* Command lookup */
cmd = lookupCommand(argv[0]->ptr);
if (!cmd) {
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", argv[0]->ptr);
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
exit(1);
}
/* Run the command in the context of a fake client */
@@ -771,7 +821,7 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
return 1;
}
/* Write either the key or the value of the currently selected item of an hash.
/* Write either the key or the value of the currently selected item of a hash.
* The 'hi' argument passes a valid Redis hash iterator.
* The 'what' filed specifies if to write a key or a value and can be
* either REDIS_HASH_KEY or REDIS_HASH_VALUE.
@@ -884,6 +934,9 @@ int rewriteAppendOnlyFile(char *filename) {
expiretime = getExpire(db,&key);
/* If this key is already expired skip it */
if (expiretime != -1 && expiretime < now) continue;
/* Save the key and associated value */
if (o->type == REDIS_STRING) {
/* Emit a SET command */
@@ -906,8 +959,6 @@ int rewriteAppendOnlyFile(char *filename) {
/* Save the expire time */
if (expiretime != -1) {
char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
/* If this key is already expired skip it */
if (expiretime < now) continue;
if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
if (rioWriteBulkLongLong(&aof,expiretime) == 0) goto werr;
@@ -917,9 +968,9 @@ int rewriteAppendOnlyFile(char *filename) {
}
/* Make sure data will not remain on the OS's output buffers */
fflush(fp);
aof_fsync(fileno(fp));
fclose(fp);
if (fflush(fp) == EOF) goto werr;
if (aof_fsync(fileno(fp)) == -1) goto werr;
if (fclose(fp) == EOF) goto werr;
/* Use RENAME to make sure the DB file is changed atomically only
* if the generate DB file is ok. */
@@ -961,15 +1012,15 @@ int rewriteAppendOnlyFileBackground(void) {
char tmpfile[256];
/* Child */
if (server.ipfd > 0) close(server.ipfd);
if (server.sofd > 0) close(server.sofd);
closeListeningSockets(0);
redisSetProcTitle("redis-aof-rewrite");
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) {
size_t private_dirty = zmalloc_get_private_dirty();
if (private_dirty) {
redisLog(REDIS_NOTICE,
"AOF rewrite: %lu MB of memory used by copy-on-write",
"AOF rewrite: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
exitFromChild(0);
@@ -996,6 +1047,7 @@ int rewriteAppendOnlyFileBackground(void) {
* accumulated by the parent into server.aof_rewrite_buf will start
* with a SELECT statement and it will be safe to merge. */
server.aof_selected_db = -1;
replicationScriptCacheFlush();
return REDIS_OK;
}
return REDIS_OK; /* unreached */
+23 -50
View File
@@ -61,6 +61,7 @@
#include "redis.h"
#include "bio.h"
static pthread_t bio_threads[REDIS_BIO_NUM_OPS];
static pthread_mutex_t bio_mutex[REDIS_BIO_NUM_OPS];
static pthread_cond_t bio_condvar[REDIS_BIO_NUM_OPS];
static list *bio_jobs[REDIS_BIO_NUM_OPS];
@@ -118,6 +119,7 @@ void bioInit(void) {
redisLog(REDIS_WARNING,"Fatal: Can't initialize Background Jobs.");
exit(1);
}
bio_threads[j] = thread;
}
}
@@ -140,7 +142,11 @@ void *bioProcessBackgroundJobs(void *arg) {
unsigned long type = (unsigned long) arg;
sigset_t sigset;
pthread_detach(pthread_self());
/* Make the thread killable at any time, so that bioKillThreads()
* can work reliably. */
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
pthread_mutex_lock(&bio_mutex[type]);
/* Block SIGALRM so we are sure that only the main thread will
* receive the watchdog signal. */
@@ -192,56 +198,23 @@ unsigned long long bioPendingJobsOfType(int type) {
return val;
}
#if 0 /* We don't use the following code for now, and bioWaitPendingJobsLE
probably needs a rewrite using conditional variables instead of the
current implementation. */
/* Kill the running bio threads in an unclean way. This function should be
* used only when it's critical to stop the threads for some reason.
* Currently Redis does this only on crash (for instance on SIGSEGV) in order
* to perform a fast memory check without other threads messing with memory. */
void bioKillThreads(void) {
int err, j;
/* Wait until the number of pending jobs of the specified type are
* less or equal to the specified number.
*
* This function may block for long time, it should only be used to perform
* the following tasks:
*
* 1) To avoid that the main thread is pushing jobs of a given time so fast
* that the background thread can't process them at the same speed.
* So before creating a new job of a given type the main thread should
* call something like: bioWaitPendingJobsLE(job_type,10000);
* 2) In order to perform special operations that make it necessary to be sure
* no one is touching shared resourced in the background.
*/
void bioWaitPendingJobsLE(int type, unsigned long long num) {
unsigned long long iteration = 0;
/* We poll the jobs queue aggressively to start, and gradually relax
* the polling speed if it is going to take too much time. */
while(1) {
iteration++;
if (iteration > 1000 && iteration <= 10000) {
usleep(100);
} else if (iteration > 10000) {
usleep(1000);
for (j = 0; j < REDIS_BIO_NUM_OPS; j++) {
if (pthread_cancel(bio_threads[j]) == 0) {
if ((err = pthread_join(bio_threads[j],NULL)) != 0) {
redisLog(REDIS_WARNING,
"Bio thread for job type #%d can be joined: %s",
j, strerror(err));
} else {
redisLog(REDIS_WARNING,
"Bio thread for job type #%d terminated",j);
}
}
if (bioPendingJobsOfType(type) <= num) break;
}
}
/* Return the older job of the specified type. */
time_t bioOlderJobOfType(int type) {
time_t time;
listNode *ln;
struct bio_job *job;
pthread_mutex_lock(&bio_mutex[type]);
ln = listFirst(bio_jobs[type]);
if (ln == NULL) {
pthread_mutex_unlock(&bio_mutex[type]);
return 0;
}
job = ln->value;
time = job->time;
pthread_mutex_unlock(&bio_mutex[type]);
return time;
}
#endif
+1
View File
@@ -33,6 +33,7 @@ void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3);
unsigned long long bioPendingJobsOfType(int type);
void bioWaitPendingJobsLE(int type, unsigned long long num);
time_t bioOlderJobOfType(int type);
void bioKillThreads(void);
/* Background job opcodes */
#define REDIS_BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */
+191 -14
View File
@@ -58,13 +58,20 @@ static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
/* Count number of bits set in the binary array pointed by 's' and long
* 'count' bytes. The implementation of this function is required to
* work with a input string length up to 512 MB. */
long popcount(void *s, long count) {
long bits = 0;
unsigned char *p;
uint32_t *p4 = s;
size_t redisPopcount(void *s, long count) {
size_t bits = 0;
unsigned char *p = s;
uint32_t *p4;
static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
/* Count initial bytes not aligned to 32 bit. */
while((unsigned long)p & 3 && count) {
bits += bitsinbyte[*p++];
count--;
}
/* Count bits 16 bytes at a time */
p4 = (uint32_t*)p;
while(count>=16) {
uint32_t aux1, aux2, aux3, aux4;
@@ -87,12 +94,99 @@ long popcount(void *s, long count) {
((((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +
((((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24);
}
/* Count the remaining bytes */
/* Count the remaining bytes. */
p = (unsigned char*)p4;
while(count--) bits += bitsinbyte[*p++];
return bits;
}
/* Return the position of the first bit set to one (if 'bit' is 1) or
* zero (if 'bit' is 0) in the bitmap starting at 's' and long 'count' bytes.
*
* The function is guaranteed to return a value >= 0 if 'bit' is 0 since if
* no zero bit is found, it returns count*8 assuming the string is zero
* padded on the right. However if 'bit' is 1 it is possible that there is
* not a single set bit in the bitmap. In this special case -1 is returned. */
long redisBitpos(void *s, long count, int bit) {
unsigned long *l;
unsigned char *c;
unsigned long skipval, word = 0, one;
long pos = 0; /* Position of bit, to return to the caller. */
int j;
/* Process whole words first, seeking for first word that is not
* all ones or all zeros respectively if we are lookig for zeros
* or ones. This is much faster with large strings having contiguous
* blocks of 1 or 0 bits compared to the vanilla bit per bit processing.
*
* Note that if we start from an address that is not aligned
* to sizeof(unsigned long) we consume it byte by byte until it is
* aligned. */
/* Skip initial bits not aligned to sizeof(unsigned long) byte by byte. */
skipval = bit ? 0 : UCHAR_MAX;
c = (unsigned char*) s;
while((unsigned long)c & (sizeof(*l)-1) && count) {
if (*c != skipval) break;
c++;
count--;
pos += 8;
}
/* Skip bits with full word step. */
skipval = bit ? 0 : ULONG_MAX;
l = (unsigned long*) c;
while (count >= sizeof(*l)) {
if (*l != skipval) break;
l++;
count -= sizeof(*l);
pos += sizeof(*l)*8;
}
/* Load bytes into "word" considering the first byte as the most significant
* (we basically consider it as written in big endian, since we consider the
* string as a set of bits from left to right, with the first bit at position
* zero.
*
* Note that the loading is designed to work even when the bytes left
* (count) are less than a full word. We pad it with zero on the right. */
c = (unsigned char*)l;
for (j = 0; j < sizeof(*l); j++) {
word <<= 8;
if (count) {
word |= *c;
c++;
count--;
}
}
/* Special case:
* If bits in the string are all zero and we are looking for one,
* return -1 to signal that there is not a single "1" in the whole
* string. This can't happen when we are looking for "0" as we assume
* that the right of the string is zero padded. */
if (bit == 1 && word == 0) return -1;
/* Last word left, scan bit by bit. The first thing we need is to
* have a single "1" set in the most significant position in an
* unsigned long. We don't know the size of the long so we use a
* simple trick. */
one = ULONG_MAX; /* All bits set to 1.*/
one >>= 1; /* All bits set to 1 but the MSB. */
one = ~one; /* All bits set to 0 but the MSB. */
while(one) {
if (((one & word) != 0) == bit) return pos;
pos++;
one >>= 1;
}
/* If we reached this point, there is a bug in the algorithm, since
* the case of no match is handled as a special case before. */
redisPanic("End of redisBitpos() reached.");
return 0; /* Just to avoid warnings. */
}
/* -----------------------------------------------------------------------------
* Bits related string commands: GETBIT, SETBIT, BITCOUNT, BITOP.
* -------------------------------------------------------------------------- */
@@ -129,14 +223,7 @@ void setbitCommand(redisClient *c) {
dbAdd(c->db,c->argv[1],o);
} else {
if (checkType(c,o,REDIS_STRING)) return;
/* Create a copy when the object is shared or encoded. */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbOverwrite(c->db,c->argv[1],o);
}
o = dbUnshareStringValue(c->db,c->argv[1],o);
}
/* Grow sds value to the right length if necessary */
@@ -153,6 +240,7 @@ void setbitCommand(redisClient *c) {
byteval |= ((on & 0x1) << bit);
((uint8_t*)o->ptr)[byte] = byteval;
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
server.dirty++;
addReply(c, bitval ? shared.cone : shared.czero);
}
@@ -346,9 +434,11 @@ void bitopCommand(redisClient *c) {
if (maxlen) {
o = createObject(REDIS_STRING,res);
setKey(c->db,targetkey,o);
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",targetkey,c->db->id);
decrRefCount(o);
} else if (dbDelete(c->db,targetkey)) {
signalModifiedKey(c->db,targetkey);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",targetkey,c->db->id);
}
server.dirty++;
addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */
@@ -404,6 +494,93 @@ void bitcountCommand(redisClient *c) {
} else {
long bytes = end-start+1;
addReplyLongLong(c,popcount(p+start,bytes));
addReplyLongLong(c,redisPopcount(p+start,bytes));
}
}
/* BITPOS key bit [start [end]] */
void bitposCommand(redisClient *c) {
robj *o;
long bit, start, end, strlen;
unsigned char *p;
char llbuf[32];
int end_given = 0;
/* Parse the bit argument to understand what we are looking for, set
* or clear bits. */
if (getLongFromObjectOrReply(c,c->argv[2],&bit,NULL) != REDIS_OK)
return;
if (bit != 0 && bit != 1) {
addReplyError(c, "The bit argument must be 1 or 0.");
return;
}
/* If the key does not exist, from our point of view it is an infinite
* array of 0 bits. If the user is looking for the fist clear bit return 0,
* If the user is looking for the first set bit, return -1. */
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
addReplyLongLong(c, bit ? -1 : 0);
return;
}
if (checkType(c,o,REDIS_STRING)) return;
/* Set the 'p' pointer to the string, that can be just a stack allocated
* array if our string was integer encoded. */
if (o->encoding == REDIS_ENCODING_INT) {
p = (unsigned char*) llbuf;
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
} else {
p = (unsigned char*) o->ptr;
strlen = sdslen(o->ptr);
}
/* Parse start/end range if any. */
if (c->argc == 4 || c->argc == 5) {
if (getLongFromObjectOrReply(c,c->argv[3],&start,NULL) != REDIS_OK)
return;
if (c->argc == 5) {
if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != REDIS_OK)
return;
end_given = 1;
} else {
end = strlen-1;
}
/* Convert negative indexes */
if (start < 0) start = strlen+start;
if (end < 0) end = strlen+end;
if (start < 0) start = 0;
if (end < 0) end = 0;
if (end >= strlen) end = strlen-1;
} else if (c->argc == 3) {
/* The whole string. */
start = 0;
end = strlen-1;
} else {
/* Syntax error. */
addReply(c,shared.syntaxerr);
return;
}
/* For empty ranges (start > end) we return -1 as an empty range does
* not contain a 0 nor a 1. */
if (start > end) {
addReplyLongLong(c, -1);
} else {
long bytes = end-start+1;
long pos = redisBitpos(p+start,bytes,bit);
/* If we are looking for clear bits, and the user specified an exact
* range with start-end, we can't consider the right of the range as
* zero padded (as we do when no explicit end is given).
*
* So if redisBitpos() returns the first bit outside the range,
* we return -1 to the caller, to mean, in the specified range there
* is not a single "0" bit. */
if (end_given && bit == 0 && pos == bytes*8) {
addReplyLongLong(c,-1);
return;
}
if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */
addReplyLongLong(c,pos);
}
}
+834 -46
View File
File diff suppressed because it is too large Load Diff
+18 -2
View File
@@ -45,7 +45,9 @@
/* Test for proc filesystem */
#ifdef __linux__
#define HAVE_PROCFS 1
#define HAVE_PROC_STAT 1
#define HAVE_PROC_MAPS 1
#define HAVE_PROC_SMAPS 1
#endif
/* Test for task_info() */
@@ -103,6 +105,20 @@
#define rdb_fsync_range(fd,off,size) fsync(fd)
#endif
/* Check if we can use setproctitle().
* BSD systems have support for it, we provide an implementation for
* Linux and osx. */
#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
#define USE_SETPROCTITLE
#endif
#if (defined __linux || defined __APPLE__)
#define USE_SETPROCTITLE
#define INIT_SETPROCTITLE_REPLACEMENT
void spt_init(int argc, char *argv[]);
void setproctitle(const char *fmt, ...);
#endif
/* Byte ordering detection */
#include <sys/types.h> /* This will likely define BYTE_ORDER */
@@ -171,7 +187,7 @@
#if (__i386 || __amd64) && __GNUC__
#define GNUC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
#if GNUC_VERSION >= 40100
#if (GNUC_VERSION >= 40100) || defined(__clang__)
#define HAVE_ATOMIC
#endif
#endif
+8
View File
@@ -0,0 +1,8 @@
#ifndef CRC64_H
#define CRC64_H
#include <stdint.h>
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
#endif
+328 -15
View File
@@ -93,6 +93,7 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
int retval = dictAdd(db->dict, copy, val);
redisAssertWithInfo(NULL,key,retval == REDIS_OK);
if (val->type == REDIS_LIST) signalListAsReady(db, key);
}
/* Overwrite an existing key with a new value. Incrementing the reference
@@ -166,14 +167,52 @@ int dbDelete(redisDb *db, robj *key) {
}
}
long long emptyDb() {
/* Prepare the string object stored at 'key' to be modified destructively
* to implement commands like SETBIT or APPEND.
*
* An object is usually ready to be modified unless one of the two conditions
* are true:
*
* 1) The object 'o' is shared (refcount > 1), we don't want to affect
* other users.
* 2) The object encoding is not "RAW".
*
* If the object is found in one of the above conditions (or both) by the
* function, an unshared / not-encoded copy of the string object is stored
* at 'key' in the specified 'db'. Otherwise the object 'o' itself is
* returned.
*
* USAGE:
*
* The object 'o' is what the caller already obtained by looking up 'key'
* in 'db', the usage pattern looks like this:
*
* o = lookupKeyWrite(db,key);
* if (checkType(c,o,REDIS_STRING)) return;
* o = dbUnshareStringValue(db,key,o);
*
* At this point the caller is ready to modify the object, for example
* using an sdscat() call to append some data, or anything else.
*/
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
redisAssert(o->type == REDIS_STRING);
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbOverwrite(db,key,o);
}
return o;
}
long long emptyDb(void(callback)(void*)) {
int j;
long long removed = 0;
for (j = 0; j < server.dbnum; j++) {
removed += dictSize(server.db[j].dict);
dictEmpty(server.db[j].dict);
dictEmpty(server.db[j].expires);
dictEmpty(server.db[j].dict,callback);
dictEmpty(server.db[j].expires,callback);
}
return removed;
}
@@ -209,14 +248,14 @@ void signalFlushedDb(int dbid) {
void flushdbCommand(redisClient *c) {
server.dirty += dictSize(c->db->dict);
signalFlushedDb(c->db->id);
dictEmpty(c->db->dict);
dictEmpty(c->db->expires);
dictEmpty(c->db->dict,NULL);
dictEmpty(c->db->expires,NULL);
addReply(c,shared.ok);
}
void flushallCommand(redisClient *c) {
signalFlushedDb(-1);
server.dirty += emptyDb();
server.dirty += emptyDb(NULL);
addReply(c,shared.ok);
if (server.rdb_child_pid != -1) {
kill(server.rdb_child_pid,SIGUSR1);
@@ -236,8 +275,11 @@ void delCommand(redisClient *c) {
int deleted = 0, j;
for (j = 1; j < c->argc; j++) {
expireIfNeeded(c->db,c->argv[j]);
if (dbDelete(c->db,c->argv[j])) {
signalModifiedKey(c->db,c->argv[j]);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
"del",c->argv[j],c->db->id);
server.dirty++;
deleted++;
}
@@ -307,6 +349,248 @@ void keysCommand(redisClient *c) {
setDeferredMultiBulkLength(c,replylen,numkeys);
}
/* This callback is used by scanGenericCommand in order to collect elements
* returned by the dictionary iterator into a list. */
void scanCallback(void *privdata, const dictEntry *de) {
void **pd = (void**) privdata;
list *keys = pd[0];
robj *o = pd[1];
robj *key, *val = NULL;
if (o == NULL) {
sds sdskey = dictGetKey(de);
key = createStringObject(sdskey, sdslen(sdskey));
} else if (o->type == REDIS_SET) {
key = dictGetKey(de);
incrRefCount(key);
} else if (o->type == REDIS_HASH) {
key = dictGetKey(de);
incrRefCount(key);
val = dictGetVal(de);
incrRefCount(val);
} else if (o->type == REDIS_ZSET) {
key = dictGetKey(de);
incrRefCount(key);
val = createStringObjectFromLongDouble(*(double*)dictGetVal(de));
} else {
redisPanic("Type not handled in SCAN callback.");
}
listAddNodeTail(keys, key);
if (val) listAddNodeTail(keys, val);
}
/* Try to parse a SCAN cursor stored at object 'o':
* if the cursor is valid, store it as unsigned integer into *cursor and
* returns REDIS_OK. Otherwise return REDIS_ERR and send an error to the
* client. */
int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor) {
char *eptr;
/* Use strtoul() because we need an *unsigned* long, so
* getLongLongFromObject() does not cover the whole cursor space. */
errno = 0;
*cursor = strtoul(o->ptr, &eptr, 10);
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' || errno == ERANGE)
{
addReplyError(c, "invalid cursor");
return REDIS_ERR;
}
return REDIS_OK;
}
/* This command implements SCAN, HSCAN and SSCAN commands.
* If object 'o' is passed, then it must be a Hash or Set object, otherwise
* if 'o' is NULL the command will operate on the dictionary associated with
* the current database.
*
* When 'o' is not NULL the function assumes that the first argument in
* the client arguments vector is a key so it skips it before iterating
* in order to parse options.
*
* In the case of a Hash object the function returns both the field and value
* of every element on the Hash. */
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
int rv;
int i, j;
char buf[REDIS_LONGSTR_SIZE];
list *keys = listCreate();
listNode *node, *nextnode;
long count = 10;
sds pat;
int patlen, use_pattern = 0;
dict *ht;
/* Object must be NULL (to iterate keys names), or the type of the object
* must be Set, Sorted Set, or Hash. */
redisAssert(o == NULL || o->type == REDIS_SET || o->type == REDIS_HASH ||
o->type == REDIS_ZSET);
/* Set i to the first option argument. The previous one is the cursor. */
i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */
/* Step 1: Parse options. */
while (i < c->argc) {
j = c->argc - i;
if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {
if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL)
!= REDIS_OK)
{
goto cleanup;
}
if (count < 1) {
addReply(c,shared.syntaxerr);
goto cleanup;
}
i += 2;
} else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {
pat = c->argv[i+1]->ptr;
patlen = sdslen(pat);
/* The pattern always matches if it is exactly "*", so it is
* equivalent to disabling it. */
use_pattern = !(pat[0] == '*' && patlen == 1);
i += 2;
} else {
addReply(c,shared.syntaxerr);
goto cleanup;
}
}
/* Step 2: Iterate the collection.
*
* Note that if the object is encoded with a ziplist, intset, or any other
* representation that is not a hash table, we are sure that it is also
* composed of a small number of elements. So to avoid taking state we
* just return everything inside the object in a single call, setting the
* cursor to zero to signal the end of the iteration. */
/* Handle the case of a hash table. */
ht = NULL;
if (o == NULL) {
ht = c->db->dict;
} else if (o->type == REDIS_SET && o->encoding == REDIS_ENCODING_HT) {
ht = o->ptr;
} else if (o->type == REDIS_HASH && o->encoding == REDIS_ENCODING_HT) {
ht = o->ptr;
count *= 2; /* We return key / value for this type. */
} else if (o->type == REDIS_ZSET && o->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = o->ptr;
ht = zs->dict;
count *= 2; /* We return key / value for this type. */
}
if (ht) {
void *privdata[2];
/* We pass two pointers to the callback: the list to which it will
* add new elements, and the object containing the dictionary so that
* it is possible to fetch more data in a type-dependent way. */
privdata[0] = keys;
privdata[1] = o;
do {
cursor = dictScan(ht, cursor, scanCallback, privdata);
} while (cursor && listLength(keys) < count);
} else if (o->type == REDIS_SET) {
int pos = 0;
int64_t ll;
while(intsetGet(o->ptr,pos++,&ll))
listAddNodeTail(keys,createStringObjectFromLongLong(ll));
cursor = 0;
} else if (o->type == REDIS_HASH || o->type == REDIS_ZSET) {
unsigned char *p = ziplistIndex(o->ptr,0);
unsigned char *vstr;
unsigned int vlen;
long long vll;
while(p) {
ziplistGet(p,&vstr,&vlen,&vll);
listAddNodeTail(keys,
(vstr != NULL) ? createStringObject((char*)vstr,vlen) :
createStringObjectFromLongLong(vll));
p = ziplistNext(o->ptr,p);
}
cursor = 0;
} else {
redisPanic("Not handled encoding in SCAN.");
}
/* Step 3: Filter elements. */
node = listFirst(keys);
while (node) {
robj *kobj = listNodeValue(node);
nextnode = listNextNode(node);
int filter = 0;
/* Filter element if it does not match the pattern. */
if (!filter && use_pattern) {
if (kobj->encoding == REDIS_ENCODING_INT) {
char buf[REDIS_LONGSTR_SIZE];
int len;
redisAssert(kobj->encoding == REDIS_ENCODING_INT);
len = ll2string(buf,sizeof(buf),(long)kobj->ptr);
if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;
} else {
if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
filter = 1;
}
}
/* Filter element if it is an expired key. */
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
/* Remove the element and its associted value if needed. */
if (filter) {
decrRefCount(kobj);
listDelNode(keys, node);
}
/* If this is a hash or a sorted set, we have a flat list of
* key-value elements, so if this element was filtered, remove the
* value, or skip it if it was not filtered: we only match keys. */
if (o && (o->type == REDIS_ZSET || o->type == REDIS_HASH)) {
node = nextnode;
nextnode = listNextNode(node);
if (filter) {
kobj = listNodeValue(node);
decrRefCount(kobj);
listDelNode(keys, node);
}
}
node = nextnode;
}
/* Step 4: Reply to the client. */
addReplyMultiBulkLen(c, 2);
rv = snprintf(buf, sizeof(buf), "%lu", cursor);
redisAssert(rv < sizeof(buf));
addReplyBulkCBuffer(c, buf, rv);
addReplyMultiBulkLen(c, listLength(keys));
while ((node = listFirst(keys)) != NULL) {
robj *kobj = listNodeValue(node);
addReplyBulk(c, kobj);
decrRefCount(kobj);
listDelNode(keys, node);
}
cleanup:
listSetFreeMethod(keys,decrRefCountVoid);
listRelease(keys);
}
/* The SCAN command completely relies on scanGenericCommand. */
void scanCommand(redisClient *c) {
unsigned long cursor;
if (parseScanCursorOrReply(c,c->argv[1],&cursor) == REDIS_ERR) return;
scanGenericCommand(c,NULL,cursor);
}
void dbsizeCommand(redisClient *c) {
addReplyLongLong(c,dictSize(c->db->dict));
}
@@ -351,6 +635,14 @@ void shutdownCommand(redisClient *c) {
return;
}
}
/* When SHUTDOWN is called while the server is loading a dataset in
* memory we need to make sure no attempt is performed to save
* the dataset on shutdown (otherwise it could overwrite the current DB
* with half-read data).
*
* Also when in Sentinel mode clear the SAVE flag and force NOSAVE. */
if (server.loading || server.sentinel_mode)
flags = (flags & ~REDIS_SHUTDOWN_SAVE) | REDIS_SHUTDOWN_NOSAVE;
if (prepareForShutdown(flags) == REDIS_OK) exit(0);
addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
}
@@ -376,7 +668,8 @@ void renameGenericCommand(redisClient *c, int nx) {
addReply(c,shared.czero);
return;
}
/* Overwrite: delete the old key before creating the new one with the same name. */
/* Overwrite: delete the old key before creating the new one
* with the same name. */
dbDelete(c->db,c->argv[2]);
}
dbAdd(c->db,c->argv[2],o);
@@ -384,6 +677,10 @@ void renameGenericCommand(redisClient *c, int nx) {
dbDelete(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[2]);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"rename_from",
c->argv[1],c->db->id);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"rename_to",
c->argv[2],c->db->id);
server.dirty++;
addReply(c,nx ? shared.cone : shared.ok);
}
@@ -493,21 +790,28 @@ void propagateExpire(redisDb *db, robj *key) {
if (server.aof_state != REDIS_AOF_OFF)
feedAppendOnlyFile(server.delCommand,db->id,argv,2);
if (listLength(server.slaves))
replicationFeedSlaves(server.slaves,db->id,argv,2);
replicationFeedSlaves(server.slaves,db->id,argv,2);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
}
int expireIfNeeded(redisDb *db, robj *key) {
long long when = getExpire(db,key);
mstime_t when = getExpire(db,key);
mstime_t now;
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0;
/* If we are in the context of a Lua script, we claim that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */
now = server.lua_caller ? server.lua_time_start : mstime();
/* If we are running in the context of a slave, return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
@@ -515,16 +819,16 @@ int expireIfNeeded(redisDb *db, robj *key) {
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
if (server.masterhost != NULL) {
return mstime() > when;
}
if (server.masterhost != NULL) return now > when;
/* Return when this key has not expired */
if (mstime() <= when) return 0;
if (now <= when) return 0;
/* Delete the key */
server.stat_expiredkeys++;
propagateExpire(db,key);
notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
"expired",key,db->id);
return dbDelete(db,key);
}
@@ -572,12 +876,14 @@ void expireGenericCommand(redisClient *c, long long basetime, int unit) {
rewriteClientCommandVector(c,2,aux,key);
decrRefCount(aux);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id);
addReply(c, shared.cone);
return;
} else {
setExpire(c->db,key,when);
addReply(c,shared.cone);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"expire",key,c->db->id);
server.dirty++;
return;
}
@@ -602,10 +908,17 @@ void pexpireatCommand(redisClient *c) {
void ttlGenericCommand(redisClient *c, int output_ms) {
long long expire, ttl = -1;
/* If the key does not exist at all, return -2 */
if (lookupKeyRead(c->db,c->argv[1]) == NULL) {
addReplyLongLong(c,-2);
return;
}
/* The key exists. Return -1 if it has no expire, or the actual
* TTL value otherwise. */
expire = getExpire(c->db,c->argv[1]);
if (expire != -1) {
ttl = expire-mstime();
if (ttl < 0) ttl = -1;
if (ttl < 0) ttl = 0;
}
if (ttl == -1) {
addReplyLongLong(c,-1);
+233 -88
View File
@@ -29,6 +29,7 @@
#include "redis.h"
#include "sha1.h" /* SHA1 is used for DEBUG DIGEST */
#include "crc64.h"
#include <arpa/inet.h>
#include <signal.h>
@@ -37,8 +38,15 @@
#include <execinfo.h>
#include <ucontext.h>
#include <fcntl.h>
#include "bio.h"
#endif /* HAVE_BACKTRACE */
#ifdef __CYGWIN__
#ifndef SA_ONSTACK
#define SA_ONSTACK 0x08000000
#endif
#endif
/* ================================= Debugging ============================== */
/* Compute the sha1 of string at 's' with 'len' bytes long.
@@ -259,7 +267,7 @@ void debugCommand(redisClient *c) {
addReply(c,shared.err);
return;
}
emptyDb();
emptyDb(NULL);
if (rdbLoad(server.rdb_filename) != REDIS_OK) {
addReplyError(c,"Error trying to load the RDB dump");
return;
@@ -267,7 +275,7 @@ void debugCommand(redisClient *c) {
redisLog(REDIS_WARNING,"DB reloaded by DEBUG RELOAD");
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
emptyDb();
emptyDb(NULL);
if (loadAppendOnlyFile(server.aof_filename) != REDIS_OK) {
addReply(c,shared.err);
return;
@@ -294,6 +302,29 @@ void debugCommand(redisClient *c) {
(void*)val, val->refcount,
strenc, (long long) rdbSavedObjectLen(val),
val->lru, estimateObjectIdleTime(val));
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
dictEntry *de;
robj *val;
sds key;
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
addReply(c,shared.nokeyerr);
return;
}
val = dictGetVal(de);
key = dictGetKey(de);
if (val->type != REDIS_STRING || val->encoding != REDIS_ENCODING_RAW) {
addReplyError(c,"Not an sds encoded string.");
} else {
addReplyStatusFormat(c,
"key_sds_len:%lld, key_sds_avail:%lld, "
"val_sds_len:%lld, val_sds_avail:%lld",
(long long) sdslen(key),
(long long) sdsavail(key),
(long long) sdslen(val->ptr),
(long long) sdsavail(val->ptr));
}
} else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) {
long keys, j;
robj *key, *val;
@@ -301,6 +332,7 @@ void debugCommand(redisClient *c) {
if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK)
return;
dictExpand(c->db->dict,keys);
for (j = 0; j < keys; j++) {
snprintf(buf,sizeof(buf),"key:%lu",j);
key = createStringObject(buf,strlen(buf));
@@ -327,14 +359,24 @@ void debugCommand(redisClient *c) {
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
double dtime = strtod(c->argv[2]->ptr,NULL);
long long utime = dtime*1000000;
struct timespec tv;
usleep(utime);
tv.tv_sec = utime / 1000000;
tv.tv_nsec = (utime % 1000000) * 1000;
nanosleep(&tv, NULL);
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"set-active-expire") &&
c->argc == 3)
{
server.active_expire_enabled = atoi(c->argv[2]->ptr);
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"error") && c->argc == 3) {
sds errstr = sdsnewlen("-",1);
errstr = sdscatsds(errstr,c->argv[2]->ptr);
errstr = sdsmapchars(errstr,"\n\r"," ",2); /* no newlines in errors. */
errstr = sdscatlen(errstr,"\r\n",2);
addReplySds(c,errstr);
} else {
addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'",
(char*)c->argv[1]->ptr);
@@ -387,9 +429,12 @@ void redisLogObjectDebugInfo(robj *o) {
redisLog(REDIS_WARNING,"Object encoding: %d", o->encoding);
redisLog(REDIS_WARNING,"Object refcount: %d", o->refcount);
if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_RAW) {
redisLog(REDIS_WARNING,"Object raw string len: %d", sdslen(o->ptr));
if (sdslen(o->ptr) < 4096)
redisLog(REDIS_WARNING,"Object raw string content: \"%s\"", (char*)o->ptr);
redisLog(REDIS_WARNING,"Object raw string len: %zu", sdslen(o->ptr));
if (sdslen(o->ptr) < 4096) {
sds repr = sdscatrepr(sdsempty(),o->ptr,sdslen(o->ptr));
redisLog(REDIS_WARNING,"Object raw string content: %s", repr);
sdsfree(repr);
}
} else if (o->type == REDIS_LIST) {
redisLog(REDIS_WARNING,"List length: %d", (int) listTypeLength(o));
} else if (o->type == REDIS_SET) {
@@ -470,10 +515,13 @@ static void *getMcontextEip(ucontext_t *uc) {
void logStackContent(void **sp) {
int i;
for (i = 15; i >= 0; i--) {
unsigned long addr = (unsigned long) sp+i;
unsigned long val = (unsigned long) sp[i];
if (sizeof(long) == 4)
redisLog(REDIS_WARNING, "(%08lx) -> %08lx", sp+i, sp[i]);
redisLog(REDIS_WARNING, "(%08lx) -> %08lx", addr, val);
else
redisLog(REDIS_WARNING, "(%016lx) -> %016lx", sp+i, sp[i]);
redisLog(REDIS_WARNING, "(%016lx) -> %016lx", addr, val);
}
}
@@ -491,27 +539,27 @@ void logRegisters(ucontext_t *uc) {
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
"RIP:%016lx EFL:%016lx\nCS :%016lx FS:%016lx GS:%016lx",
uc->uc_mcontext->__ss.__rax,
uc->uc_mcontext->__ss.__rbx,
uc->uc_mcontext->__ss.__rcx,
uc->uc_mcontext->__ss.__rdx,
uc->uc_mcontext->__ss.__rdi,
uc->uc_mcontext->__ss.__rsi,
uc->uc_mcontext->__ss.__rbp,
uc->uc_mcontext->__ss.__rsp,
uc->uc_mcontext->__ss.__r8,
uc->uc_mcontext->__ss.__r9,
uc->uc_mcontext->__ss.__r10,
uc->uc_mcontext->__ss.__r11,
uc->uc_mcontext->__ss.__r12,
uc->uc_mcontext->__ss.__r13,
uc->uc_mcontext->__ss.__r14,
uc->uc_mcontext->__ss.__r15,
uc->uc_mcontext->__ss.__rip,
uc->uc_mcontext->__ss.__rflags,
uc->uc_mcontext->__ss.__cs,
uc->uc_mcontext->__ss.__fs,
uc->uc_mcontext->__ss.__gs
(unsigned long) uc->uc_mcontext->__ss.__rax,
(unsigned long) uc->uc_mcontext->__ss.__rbx,
(unsigned long) uc->uc_mcontext->__ss.__rcx,
(unsigned long) uc->uc_mcontext->__ss.__rdx,
(unsigned long) uc->uc_mcontext->__ss.__rdi,
(unsigned long) uc->uc_mcontext->__ss.__rsi,
(unsigned long) uc->uc_mcontext->__ss.__rbp,
(unsigned long) uc->uc_mcontext->__ss.__rsp,
(unsigned long) uc->uc_mcontext->__ss.__r8,
(unsigned long) uc->uc_mcontext->__ss.__r9,
(unsigned long) uc->uc_mcontext->__ss.__r10,
(unsigned long) uc->uc_mcontext->__ss.__r11,
(unsigned long) uc->uc_mcontext->__ss.__r12,
(unsigned long) uc->uc_mcontext->__ss.__r13,
(unsigned long) uc->uc_mcontext->__ss.__r14,
(unsigned long) uc->uc_mcontext->__ss.__r15,
(unsigned long) uc->uc_mcontext->__ss.__rip,
(unsigned long) uc->uc_mcontext->__ss.__rflags,
(unsigned long) uc->uc_mcontext->__ss.__cs,
(unsigned long) uc->uc_mcontext->__ss.__fs,
(unsigned long) uc->uc_mcontext->__ss.__gs
);
logStackContent((void**)uc->uc_mcontext->__ss.__rsp);
#else
@@ -522,22 +570,22 @@ void logRegisters(ucontext_t *uc) {
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
"SS:%08lx EFL:%08lx EIP:%08lx CS :%08lx\n"
"DS:%08lx ES:%08lx FS :%08lx GS :%08lx",
uc->uc_mcontext->__ss.__eax,
uc->uc_mcontext->__ss.__ebx,
uc->uc_mcontext->__ss.__ecx,
uc->uc_mcontext->__ss.__edx,
uc->uc_mcontext->__ss.__edi,
uc->uc_mcontext->__ss.__esi,
uc->uc_mcontext->__ss.__ebp,
uc->uc_mcontext->__ss.__esp,
uc->uc_mcontext->__ss.__ss,
uc->uc_mcontext->__ss.__eflags,
uc->uc_mcontext->__ss.__eip,
uc->uc_mcontext->__ss.__cs,
uc->uc_mcontext->__ss.__ds,
uc->uc_mcontext->__ss.__es,
uc->uc_mcontext->__ss.__fs,
uc->uc_mcontext->__ss.__gs
(unsigned long) uc->uc_mcontext->__ss.__eax,
(unsigned long) uc->uc_mcontext->__ss.__ebx,
(unsigned long) uc->uc_mcontext->__ss.__ecx,
(unsigned long) uc->uc_mcontext->__ss.__edx,
(unsigned long) uc->uc_mcontext->__ss.__edi,
(unsigned long) uc->uc_mcontext->__ss.__esi,
(unsigned long) uc->uc_mcontext->__ss.__ebp,
(unsigned long) uc->uc_mcontext->__ss.__esp,
(unsigned long) uc->uc_mcontext->__ss.__ss,
(unsigned long) uc->uc_mcontext->__ss.__eflags,
(unsigned long) uc->uc_mcontext->__ss.__eip,
(unsigned long) uc->uc_mcontext->__ss.__cs,
(unsigned long) uc->uc_mcontext->__ss.__ds,
(unsigned long) uc->uc_mcontext->__ss.__es,
(unsigned long) uc->uc_mcontext->__ss.__fs,
(unsigned long) uc->uc_mcontext->__ss.__gs
);
logStackContent((void**)uc->uc_mcontext->__ss.__esp);
#endif
@@ -551,22 +599,22 @@ void logRegisters(ucontext_t *uc) {
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
uc->uc_mcontext.gregs[11],
uc->uc_mcontext.gregs[8],
uc->uc_mcontext.gregs[10],
uc->uc_mcontext.gregs[9],
uc->uc_mcontext.gregs[4],
uc->uc_mcontext.gregs[5],
uc->uc_mcontext.gregs[6],
uc->uc_mcontext.gregs[7],
uc->uc_mcontext.gregs[18],
uc->uc_mcontext.gregs[17],
uc->uc_mcontext.gregs[14],
uc->uc_mcontext.gregs[15],
uc->uc_mcontext.gregs[3],
uc->uc_mcontext.gregs[2],
uc->uc_mcontext.gregs[1],
uc->uc_mcontext.gregs[0]
(unsigned long) uc->uc_mcontext.gregs[11],
(unsigned long) uc->uc_mcontext.gregs[8],
(unsigned long) uc->uc_mcontext.gregs[10],
(unsigned long) uc->uc_mcontext.gregs[9],
(unsigned long) uc->uc_mcontext.gregs[4],
(unsigned long) uc->uc_mcontext.gregs[5],
(unsigned long) uc->uc_mcontext.gregs[6],
(unsigned long) uc->uc_mcontext.gregs[7],
(unsigned long) uc->uc_mcontext.gregs[18],
(unsigned long) uc->uc_mcontext.gregs[17],
(unsigned long) uc->uc_mcontext.gregs[14],
(unsigned long) uc->uc_mcontext.gregs[15],
(unsigned long) uc->uc_mcontext.gregs[3],
(unsigned long) uc->uc_mcontext.gregs[2],
(unsigned long) uc->uc_mcontext.gregs[1],
(unsigned long) uc->uc_mcontext.gregs[0]
);
logStackContent((void**)uc->uc_mcontext.gregs[7]);
#elif defined(__X86_64__) || defined(__x86_64__)
@@ -578,25 +626,25 @@ void logRegisters(ucontext_t *uc) {
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
uc->uc_mcontext.gregs[13],
uc->uc_mcontext.gregs[11],
uc->uc_mcontext.gregs[14],
uc->uc_mcontext.gregs[12],
uc->uc_mcontext.gregs[8],
uc->uc_mcontext.gregs[9],
uc->uc_mcontext.gregs[10],
uc->uc_mcontext.gregs[15],
uc->uc_mcontext.gregs[0],
uc->uc_mcontext.gregs[1],
uc->uc_mcontext.gregs[2],
uc->uc_mcontext.gregs[3],
uc->uc_mcontext.gregs[4],
uc->uc_mcontext.gregs[5],
uc->uc_mcontext.gregs[6],
uc->uc_mcontext.gregs[7],
uc->uc_mcontext.gregs[16],
uc->uc_mcontext.gregs[17],
uc->uc_mcontext.gregs[18]
(unsigned long) uc->uc_mcontext.gregs[13],
(unsigned long) uc->uc_mcontext.gregs[11],
(unsigned long) uc->uc_mcontext.gregs[14],
(unsigned long) uc->uc_mcontext.gregs[12],
(unsigned long) uc->uc_mcontext.gregs[8],
(unsigned long) uc->uc_mcontext.gregs[9],
(unsigned long) uc->uc_mcontext.gregs[10],
(unsigned long) uc->uc_mcontext.gregs[15],
(unsigned long) uc->uc_mcontext.gregs[0],
(unsigned long) uc->uc_mcontext.gregs[1],
(unsigned long) uc->uc_mcontext.gregs[2],
(unsigned long) uc->uc_mcontext.gregs[3],
(unsigned long) uc->uc_mcontext.gregs[4],
(unsigned long) uc->uc_mcontext.gregs[5],
(unsigned long) uc->uc_mcontext.gregs[6],
(unsigned long) uc->uc_mcontext.gregs[7],
(unsigned long) uc->uc_mcontext.gregs[16],
(unsigned long) uc->uc_mcontext.gregs[17],
(unsigned long) uc->uc_mcontext.gregs[18]
);
logStackContent((void**)uc->uc_mcontext.gregs[15]);
#endif
@@ -611,11 +659,12 @@ void logRegisters(ucontext_t *uc) {
void logStackTrace(ucontext_t *uc) {
void *trace[100];
int trace_size = 0, fd;
int log_to_stdout = server.logfile[0] == '\0';
/* Open the log file in append mode. */
fd = server.logfile ?
open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644) :
STDOUT_FILENO;
fd = log_to_stdout ?
STDOUT_FILENO :
open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644);
if (fd == -1) return;
/* Generate the stack trace */
@@ -629,7 +678,7 @@ void logStackTrace(ucontext_t *uc) {
backtrace_symbols_fd(trace, trace_size, fd);
/* Cleanup */
if (server.logfile) close(fd);
if (!log_to_stdout) close(fd);
}
/* Log information about the "current" client, that is, the client that is
@@ -663,13 +712,96 @@ void logCurrentClient(void) {
de = dictFind(cc->db->dict, key->ptr);
if (de) {
val = dictGetVal(de);
redisLog(REDIS_WARNING,"key '%s' found in DB containing the following object:", key->ptr);
redisLog(REDIS_WARNING,"key '%s' found in DB containing the following object:", (char*)key->ptr);
redisLogObjectDebugInfo(val);
}
decrRefCount(key);
}
}
#if defined(HAVE_PROC_MAPS)
void memtest_non_destructive_invert(void *addr, size_t size);
void memtest_non_destructive_swap(void *addr, size_t size);
#define MEMTEST_MAX_REGIONS 128
int memtest_test_linux_anonymous_maps(void) {
FILE *fp = fopen("/proc/self/maps","r");
char line[1024];
size_t start_addr, end_addr, size;
size_t start_vect[MEMTEST_MAX_REGIONS];
size_t size_vect[MEMTEST_MAX_REGIONS];
int regions = 0, j;
uint64_t crc1 = 0, crc2 = 0, crc3 = 0;
while(fgets(line,sizeof(line),fp) != NULL) {
char *start, *end, *p = line;
start = p;
p = strchr(p,'-');
if (!p) continue;
*p++ = '\0';
end = p;
p = strchr(p,' ');
if (!p) continue;
*p++ = '\0';
if (strstr(p,"stack") ||
strstr(p,"vdso") ||
strstr(p,"vsyscall")) continue;
if (!strstr(p,"00:00")) continue;
if (!strstr(p,"rw")) continue;
start_addr = strtoul(start,NULL,16);
end_addr = strtoul(end,NULL,16);
size = end_addr-start_addr;
start_vect[regions] = start_addr;
size_vect[regions] = size;
printf("Testing %lx %lu\n", (unsigned long) start_vect[regions],
(unsigned long) size_vect[regions]);
regions++;
}
/* Test all the regions as an unique sequential region.
* 1) Take the CRC64 of the memory region. */
for (j = 0; j < regions; j++) {
crc1 = crc64(crc1,(void*)start_vect[j],size_vect[j]);
}
/* 2) Invert bits, swap adjacent words, swap again, invert bits.
* This is the error amplification step. */
for (j = 0; j < regions; j++)
memtest_non_destructive_invert((void*)start_vect[j],size_vect[j]);
for (j = 0; j < regions; j++)
memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]);
for (j = 0; j < regions; j++)
memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]);
for (j = 0; j < regions; j++)
memtest_non_destructive_invert((void*)start_vect[j],size_vect[j]);
/* 3) Take the CRC64 sum again. */
for (j = 0; j < regions; j++)
crc2 = crc64(crc2,(void*)start_vect[j],size_vect[j]);
/* 4) Swap + Swap again */
for (j = 0; j < regions; j++)
memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]);
for (j = 0; j < regions; j++)
memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]);
/* 5) Take the CRC64 sum again. */
for (j = 0; j < regions; j++)
crc3 = crc64(crc3,(void*)start_vect[j],size_vect[j]);
/* NOTE: It is very important to close the file descriptor only now
* because closing it before may result into unmapping of some memory
* region that we are testing. */
fclose(fp);
/* If the two CRC are not the same, we trapped a memory error. */
return crc1 != crc2 || crc2 != crc3;
}
#endif
void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
ucontext_t *uc = (ucontext_t*) secret;
sds infostring, clients;
@@ -705,11 +837,24 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
/* Log dump of processor registers */
logRegisters(uc);
#if defined(HAVE_PROC_MAPS)
/* Test memory */
redisLog(REDIS_WARNING, "--- FAST MEMORY TEST");
bioKillThreads();
if (memtest_test_linux_anonymous_maps()) {
redisLog(REDIS_WARNING,
"!!! MEMORY ERROR DETECTED! Check your memory ASAP !!!");
} else {
redisLog(REDIS_WARNING,
"Fast memory test PASSED, however your memory can still be broken. Please run a memory test for several hours if possible.");
}
#endif
redisLog(REDIS_WARNING,
"\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n"
" Please report the crash opening an issue on github:\n\n"
" http://github.com/antirez/redis/issues\n\n"
" Suspect RAM error? Use redis-server --test-memory to veryfy it.\n\n"
" Suspect RAM error? Use redis-server --test-memory to verify it.\n\n"
);
/* free(messages); Don't call free() with possibly corrupted memory. */
if (server.daemonize) unlink(server.pidfile);
@@ -782,7 +927,7 @@ void watchdogScheduleSignal(int period) {
setitimer(ITIMER_REAL, &it, NULL);
}
/* Enable the software watchdong with the specified period in milliseconds. */
/* Enable the software watchdog with the specified period in milliseconds. */
void enableWatchdog(int period) {
int min_period;
@@ -792,7 +937,7 @@ void enableWatchdog(int period) {
/* Watchdog was actually disabled, so we have to setup the signal
* handler. */
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_SIGINFO;
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
act.sa_sigaction = watchdogSignalHandler;
sigaction(SIGALRM, &act, NULL);
}
+230 -17
View File
@@ -39,13 +39,13 @@
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include <limits.h>
#include <sys/time.h>
#include <ctype.h>
#include "dict.h"
#include "zmalloc.h"
#include "redisassert.h"
/* Using dictEnableResize() / dictDisableResize() we make possible to
* enable/disable resizing of the hash table as needed. This is very important
@@ -53,7 +53,7 @@
* around when there is a child performing saving operations.
*
* Note that even when dict_can_resize is set to 0, not all resizes are
* prevented: an hash table is still allowed to grow if the ratio between
* prevented: a hash table is still allowed to grow if the ratio between
* the number of elements and the buckets > dict_force_resize_ratio. */
static int dict_can_resize = 1;
static unsigned int dict_force_resize_ratio = 5;
@@ -239,7 +239,7 @@ int dictExpand(dict *d, unsigned long size)
/* Performs N steps of incremental rehashing. Returns 1 if there are still
* keys to move from the old to the new hash table, otherwise 0 is returned.
* Note that a rehashing step consists in moving a bucket (that may have more
* thank one key as we use chaining) from the old to the new hash table. */
* than one key as we use chaining) from the old to the new hash table. */
int dictRehash(dict *d, int n) {
if (!dictIsRehashing(d)) return 0;
@@ -444,14 +444,15 @@ int dictDeleteNoFree(dict *ht, const void *key) {
}
/* Destroy an entire dictionary */
int _dictClear(dict *d, dictht *ht)
{
int _dictClear(dict *d, dictht *ht, void(callback)(void *)) {
unsigned long i;
/* Free all the elements */
for (i = 0; i < ht->size && ht->used > 0; i++) {
dictEntry *he, *nextHe;
if (callback && (i & 65535) == 0) callback(d->privdata);
if ((he = ht->table[i]) == NULL) continue;
while(he) {
nextHe = he->next;
@@ -472,8 +473,8 @@ int _dictClear(dict *d, dictht *ht)
/* Clear & Release the hash table */
void dictRelease(dict *d)
{
_dictClear(d,&d->ht[0]);
_dictClear(d,&d->ht[1]);
_dictClear(d,&d->ht[0],NULL);
_dictClear(d,&d->ht[1],NULL);
zfree(d);
}
@@ -505,6 +506,44 @@ void *dictFetchValue(dict *d, const void *key) {
return he ? dictGetVal(he) : NULL;
}
/* A fingerprint is a 64 bit number that represents the state of the dictionary
* at a given time, it's just a few dict properties xored together.
* When an unsafe iterator is initialized, we get the dict fingerprint, and check
* the fingerprint again when the iterator is released.
* If the two fingerprints are different it means that the user of the iterator
* performed forbidden operations against the dictionary while iterating. */
long long dictFingerprint(dict *d) {
long long integers[6], hash = 0;
int j;
integers[0] = (long) d->ht[0].table;
integers[1] = d->ht[0].size;
integers[2] = d->ht[0].used;
integers[3] = (long) d->ht[1].table;
integers[4] = d->ht[1].size;
integers[5] = d->ht[1].used;
/* We hash N integers by summing every successive integer with the integer
* hashing of the previous sum. Basically:
*
* Result = hash(hash(hash(int1)+int2)+int3) ...
*
* This way the same set of integers in a different order will (likely) hash
* to a different number. */
for (j = 0; j < 6; j++) {
hash += integers[j];
/* For the hashing step we use Tomas Wang's 64 bit integer hash. */
hash = (~hash) + (hash << 21); // hash = (hash << 21) - hash - 1;
hash = hash ^ (hash >> 24);
hash = (hash + (hash << 3)) + (hash << 8); // hash * 265
hash = hash ^ (hash >> 14);
hash = (hash + (hash << 2)) + (hash << 4); // hash * 21
hash = hash ^ (hash >> 28);
hash = hash + (hash << 31);
}
return hash;
}
dictIterator *dictGetIterator(dict *d)
{
dictIterator *iter = zmalloc(sizeof(*iter));
@@ -530,8 +569,12 @@ dictEntry *dictNext(dictIterator *iter)
while (1) {
if (iter->entry == NULL) {
dictht *ht = &iter->d->ht[iter->table];
if (iter->safe && iter->index == -1 && iter->table == 0)
iter->d->iterators++;
if (iter->index == -1 && iter->table == 0) {
if (iter->safe)
iter->d->iterators++;
else
iter->fingerprint = dictFingerprint(iter->d);
}
iter->index++;
if (iter->index >= (signed) ht->size) {
if (dictIsRehashing(iter->d) && iter->table == 0) {
@@ -558,8 +601,12 @@ dictEntry *dictNext(dictIterator *iter)
void dictReleaseIterator(dictIterator *iter)
{
if (iter->safe && !(iter->index == -1 && iter->table == 0))
iter->d->iterators--;
if (!(iter->index == -1 && iter->table == 0)) {
if (iter->safe)
iter->d->iterators--;
else
assert(iter->fingerprint == dictFingerprint(iter->d));
}
zfree(iter);
}
@@ -602,6 +649,173 @@ dictEntry *dictGetRandomKey(dict *d)
return he;
}
/* Function to reverse bits. Algorithm from:
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
static unsigned long rev(unsigned long v) {
unsigned long s = 8 * sizeof(v); // bit size; must be power of 2
unsigned long mask = ~0;
while ((s >>= 1) > 0) {
mask ^= (mask << s);
v = ((v >> s) & mask) | ((v << s) & ~mask);
}
return v;
}
/* dictScan() is used to iterate over the elements of a dictionary.
*
* Iterating works in the following way:
*
* 1) Initially you call the function using a cursor (v) value of 0.
* 2) The function performs one step of the iteration, and returns the
* new cursor value that you must use in the next call.
* 3) When the returned cursor is 0, the iteration is complete.
*
* The function guarantees that all the elements that are present in the
* dictionary from the start to the end of the iteration are returned.
* However it is possible that some element is returned multiple time.
*
* For every element returned, the callback 'fn' passed as argument is
* called, with 'privdata' as first argument and the dictionar entry
* 'de' as second argument.
*
* HOW IT WORKS.
*
* The algorithm used in the iteration was designed by Pieter Noordhuis.
* The main idea is to increment a cursor starting from the higher order
* bits, that is, instead of incrementing the cursor normally, the bits
* of the cursor are reversed, then the cursor is incremented, and finally
* the bits are reversed again.
*
* This strategy is needed because the hash table may be resized from one
* call to the other call of the same iteration.
*
* dict.c hash tables are always power of two in size, and they
* use chaining, so the position of an element in a given table is given
* always by computing the bitwise AND between Hash(key) and SIZE-1
* (where SIZE-1 is always the mask that is equivalent to taking the rest
* of the division between the Hash of the key and SIZE).
*
* For example if the current hash table size is 16, the mask is
* (in binary) 1111. The position of a key in the hash table will be always
* the last four bits of the hash output, and so forth.
*
* WHAT HAPPENS IF THE TABLE CHANGES IN SIZE?
*
* If the hash table grows, elements can go anyway in one multiple of
* the old bucket: for example let's say that we already iterated with
* a 4 bit cursor 1100, since the mask is 1111 (hash table size = 16).
*
* If the hash table will be resized to 64 elements, and the new mask will
* be 111111, the new buckets that you obtain substituting in ??1100
* either 0 or 1, can be targeted only by keys that we already visited
* when scanning the bucket 1100 in the smaller hash table.
*
* By iterating the higher bits first, because of the inverted counter, the
* cursor does not need to restart if the table size gets bigger, and will
* just continue iterating with cursors that don't have '1100' at the end,
* nor any other combination of final 4 bits already explored.
*
* Similarly when the table size shrinks over time, for example going from
* 16 to 8, If a combination of the lower three bits (the mask for size 8
* is 111) was already completely explored, it will not be visited again
* as we are sure that, we tried for example, both 0111 and 1111 (all the
* variations of the higher bit) so we don't need to test it again.
*
* WAIT... YOU HAVE *TWO* TABLES DURING REHASHING!
*
* Yes, this is true, but we always iterate the smaller one of the tables,
* testing also all the expansions of the current cursor into the larger
* table. So for example if the current cursor is 101 and we also have a
* larger table of size 16, we also test (0)101 and (1)101 inside the larger
* table. This reduces the problem back to having only one table, where
* the larger one, if exists, is just an expansion of the smaller one.
*
* LIMITATIONS
*
* This iterator is completely stateless, and this is a huge advantage,
* including no additional memory used.
*
* The disadvantages resulting from this design are:
*
* 1) It is possible that we return duplicated elements. However this is usually
* easy to deal with in the application level.
* 2) The iterator must return multiple elements per call, as it needs to always
* return all the keys chained in a given bucket, and all the expansions, so
* we are sure we don't miss keys moving.
* 3) The reverse cursor is somewhat hard to understand at first, but this
* comment is supposed to help.
*/
unsigned long dictScan(dict *d,
unsigned long v,
dictScanFunction *fn,
void *privdata)
{
dictht *t0, *t1;
const dictEntry *de;
unsigned long m0, m1;
if (dictSize(d) == 0) return 0;
if (!dictIsRehashing(d)) {
t0 = &(d->ht[0]);
m0 = t0->sizemask;
/* Emit entries at cursor */
de = t0->table[v & m0];
while (de) {
fn(privdata, de);
de = de->next;
}
} else {
t0 = &d->ht[0];
t1 = &d->ht[1];
/* Make sure t0 is the smaller and t1 is the bigger table */
if (t0->size > t1->size) {
t0 = &d->ht[1];
t1 = &d->ht[0];
}
m0 = t0->sizemask;
m1 = t1->sizemask;
/* Emit entries at cursor */
de = t0->table[v & m0];
while (de) {
fn(privdata, de);
de = de->next;
}
/* Iterate over indices in larger table that are the expansion
* of the index pointed to by the cursor in the smaller table */
do {
/* Emit entries at cursor */
de = t1->table[v & m1];
while (de) {
fn(privdata, de);
de = de->next;
}
/* Increment bits not covered by the smaller mask */
v = (((v | m0) + 1) & ~m0) | (v & m0);
/* Continue while bits covered by mask difference is non-zero */
} while (v & (m0 ^ m1));
}
/* Set unmasked bits so incrementing the reversed cursor
* operates on the masked bits of the smaller table */
v |= ~m0;
/* Increment the reverse cursor */
v = rev(v);
v++;
v = rev(v);
return v;
}
/* ------------------------- private functions ------------------------------ */
/* Expand the hash table if needed */
@@ -621,8 +835,7 @@ static int _dictExpandIfNeeded(dict *d)
(dict_can_resize ||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
{
return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?
d->ht[0].size : d->ht[0].used)*2);
return dictExpand(d, d->ht[0].used*2);
}
return DICT_OK;
}
@@ -641,7 +854,7 @@ static unsigned long _dictNextPower(unsigned long size)
}
/* Returns the index of a free slot that can be populated with
* an hash entry for the given 'key'.
* a hash entry for the given 'key'.
* If the key already exists, -1 is returned.
*
* Note that if we are in the process of rehashing the hash table, the
@@ -670,9 +883,9 @@ static int _dictKeyIndex(dict *d, const void *key)
return idx;
}
void dictEmpty(dict *d) {
_dictClear(d,&d->ht[0]);
_dictClear(d,&d->ht[1]);
void dictEmpty(dict *d, void(callback)(void*)) {
_dictClear(d,&d->ht[0],callback);
_dictClear(d,&d->ht[1],callback);
d->rehashidx = -1;
d->iterators = 0;
}
+5 -1
View File
@@ -88,8 +88,11 @@ typedef struct dictIterator {
dict *d;
int table, index, safe;
dictEntry *entry, *nextEntry;
long long fingerprint; /* unsafe iterator fingerprint for misuse detection */
} dictIterator;
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
/* This is the initial size of every hash table */
#define DICT_HT_INITIAL_SIZE 4
@@ -157,13 +160,14 @@ dictEntry *dictGetRandomKey(dict *d);
void dictPrintStats(dict *d);
unsigned int dictGenHashFunction(const void *key, int len);
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
void dictEmpty(dict *d);
void dictEmpty(dict *d, void(callback)(void*));
void dictEnableResize(void);
void dictDisableResize(void);
int dictRehash(dict *d, int n);
int dictRehashMilliseconds(dict *d, int ms);
void dictSetHashFunctionSeed(unsigned int initval);
unsigned int dictGetHashFunctionSeed(void);
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, void *privdata);
/* Hash table types */
extern dictType dictTypeHeapStringCopyKey;
+6 -2
View File
@@ -36,9 +36,13 @@
#define _GNU_SOURCE
#endif
#if defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
#if defined(__linux__) || defined(__OpenBSD__)
#define _XOPEN_SOURCE 700
#else
/*
* On NetBSD, _XOPEN_SOURCE undefines _NETBSD_SOURCE and
* thus hides inet_aton etc.
*/
#elif !defined(__NetBSD__)
#define _XOPEN_SOURCE
#endif
+86 -5
View File
@@ -14,7 +14,8 @@ static char *commandGroups[] = {
"transactions",
"connection",
"server",
"scripting"
"scripting",
"hyperloglog"
};
struct commandHelp {
@@ -54,6 +55,11 @@ struct commandHelp {
"Perform bitwise operations between strings",
1,
"2.6.0" },
{ "BITPOS",
"key bit [start] [end]",
"Find first bit set or clear in a string",
1,
"2.8.7" },
{ "BLPOP",
"key [key ...] timeout",
"Remove and get the first element in a list, or block until one is available",
@@ -69,6 +75,11 @@ struct commandHelp {
"Pop a value from a list, push it to another list and return it; or block until one is available",
2,
"2.2.0" },
{ "CLIENT GETNAME",
"-",
"Get the current connection name",
9,
"2.6.9" },
{ "CLIENT KILL",
"ip:port",
"Kill the connection of a client",
@@ -79,6 +90,16 @@ struct commandHelp {
"Get the list of client connections",
9,
"2.4.0" },
{ "CLIENT PAUSE",
"timeout",
"Stop processing commands from clients for some time",
9,
"2.9.50" },
{ "CLIENT SETNAME",
"connection-name",
"Set the current connection name",
9,
"2.6.9" },
{ "CONFIG GET",
"parameter",
"Get the value of a configuration parameter",
@@ -89,6 +110,11 @@ struct commandHelp {
"Reset the stats returned by INFO",
9,
"2.0.0" },
{ "CONFIG REWRITE",
"-",
"Rewrite the configuration file with the in memory configuration",
9,
"2.8.0" },
{ "CONFIG SET",
"parameter value",
"Set a configuration parameter to the given value",
@@ -249,6 +275,11 @@ struct commandHelp {
"Set multiple hash fields to multiple values",
5,
"2.0.0" },
{ "HSCAN",
"key cursor [MATCH pattern] [COUNT count]",
"Incrementally iterate hash fields and associated values",
5,
"2.8.0" },
{ "HSET",
"key field value",
"Set the string value of a hash field",
@@ -280,7 +311,7 @@ struct commandHelp {
1,
"2.6.0" },
{ "INFO",
"-",
"[section]",
"Get information and statistics about the server",
9,
"1.0.0" },
@@ -350,7 +381,7 @@ struct commandHelp {
1,
"1.0.0" },
{ "MIGRATE",
"host port key destination-db timeout",
"host port key destination-db timeout [COPY] [REPLACE]",
"Atomically transfer a key from a Redis instance to another one.",
0,
"2.6.0" },
@@ -399,6 +430,21 @@ struct commandHelp {
"Set the expiration for a key as a UNIX timestamp specified in milliseconds",
0,
"2.6.0" },
{ "PFADD",
"key element [element ...]",
"Adds the specified elements to the specified HyperLogLog.",
11,
"2.8.9" },
{ "PFCOUNT",
"key [key ...]",
"Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).",
11,
"2.8.9" },
{ "PFMERGE",
"destkey sourcekey [sourcekey ...]",
"Merge N different HyperLogLogs into a single one.",
11,
"2.8.9" },
{ "PING",
"-",
"Ping the server",
@@ -424,6 +470,11 @@ struct commandHelp {
"Post a message to a channel",
6,
"2.0.0" },
{ "PUBSUB",
"subcommand [argument [argument ...]]",
"Inspect the state of the Pub/Sub subsystem",
6,
"2.8.0" },
{ "PUNSUBSCRIBE",
"[pattern [pattern ...]]",
"Stop listening for messages posted to channels matching the given patterns",
@@ -484,6 +535,11 @@ struct commandHelp {
"Synchronously save the dataset to disk",
9,
"1.0.0" },
{ "SCAN",
"cursor [MATCH pattern] [COUNT count]",
"Incrementally iterate the keys space",
0,
"2.8.0" },
{ "SCARD",
"key",
"Get the number of members in a set",
@@ -525,7 +581,7 @@ struct commandHelp {
8,
"1.0.0" },
{ "SET",
"key value",
"key value [EX seconds] [PX milliseconds] [NX|XX]",
"Set the string value of a key",
1,
"1.0.0" },
@@ -609,6 +665,11 @@ struct commandHelp {
"Remove one or more members from a set",
3,
"1.0.0" },
{ "SSCAN",
"key cursor [MATCH pattern] [COUNT count]",
"Incrementally iterate Set elements",
3,
"2.8.0" },
{ "STRLEN",
"key",
"Get the length of the value stored in a key",
@@ -665,7 +726,7 @@ struct commandHelp {
7,
"2.2.0" },
{ "ZADD",
"key score member [score] [member]",
"key score member [score member ...]",
"Add one or more members to a sorted set, or update its score if it already exists",
4,
"1.2.0" },
@@ -689,11 +750,21 @@ struct commandHelp {
"Intersect multiple sorted sets and store the resulting sorted set in a new key",
4,
"2.0.0" },
{ "ZLEXCOUNT",
"key min max",
"Count the number of members in a sorted set between a given lexicographical range",
4,
"2.8.9" },
{ "ZRANGE",
"key start stop [WITHSCORES]",
"Return a range of members in a sorted set, by index",
4,
"1.2.0" },
{ "ZRANGEBYLEX",
"key min max [LIMIT offset count]",
"Return a range of members in a sorted set, by lexicographical range",
4,
"2.8.9" },
{ "ZRANGEBYSCORE",
"key min max [WITHSCORES] [LIMIT offset count]",
"Return a range of members in a sorted set, by score",
@@ -709,6 +780,11 @@ struct commandHelp {
"Remove one or more members from a sorted set",
4,
"1.2.0" },
{ "ZREMRANGEBYLEX",
"key min max",
"Remove all members in a sorted set between the given lexicographical range",
4,
"2.8.9" },
{ "ZREMRANGEBYRANK",
"key start stop",
"Remove all members in a sorted set within the given indexes",
@@ -734,6 +810,11 @@ struct commandHelp {
"Determine the index of a member in a sorted set, with scores ordered from high to low",
4,
"2.0.0" },
{ "ZSCAN",
"key cursor [MATCH pattern] [COUNT count]",
"Incrementally iterate sorted sets elements and associated scores",
4,
"2.8.0" },
{ "ZSCORE",
"key member",
"Get the score associated with the given member in a sorted set",
+1548
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -35,6 +35,7 @@
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>
#include "config.h"
#if (ULONG_MAX == 4294967295UL)
#define MEMTEST_32BIT
@@ -238,6 +239,32 @@ void memtest_test(size_t megabytes, int passes) {
}
}
void memtest_non_destructive_invert(void *addr, size_t size) {
volatile unsigned long *p = addr;
size_t words = size / sizeof(unsigned long);
size_t j;
/* Invert */
for (j = 0; j < words; j++)
p[j] = ~p[j];
}
void memtest_non_destructive_swap(void *addr, size_t size) {
volatile unsigned long *p = addr;
size_t words = size / sizeof(unsigned long);
size_t j;
/* Swap */
for (j = 0; j < words; j += 2) {
unsigned long a, b;
a = p[j];
b = p[j+1];
p[j] = b;
p[j+1] = a;
}
}
void memtest(size_t megabytes, int passes) {
if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
ws.ws_col = 80;
+6 -6
View File
@@ -84,7 +84,7 @@ void dumpCommand(redisClient *c) {
/* RESTORE key ttl serialized-value */
void restoreCommand(redisClient *c) {
long ttl;
long long ttl;
rio payload;
int type;
robj *obj;
@@ -96,7 +96,7 @@ void restoreCommand(redisClient *c) {
}
/* Check if the TTL value makes sense */
if (getLongFromObjectOrReply(c,c->argv[2],&ttl,NULL) != REDIS_OK) {
if (getLongLongFromObjectOrReply(c,c->argv[2],&ttl,NULL) != REDIS_OK) {
return;
} else if (ttl < 0) {
addReplyError(c,"Invalid TTL value, must be >= 0");
@@ -104,7 +104,8 @@ void restoreCommand(redisClient *c) {
}
/* Verify RDB version and data checksum. */
if (verifyDumpPayload(c->argv[3]->ptr,sdslen(c->argv[3]->ptr)) == REDIS_ERR) {
if (verifyDumpPayload(c->argv[3]->ptr,sdslen(c->argv[3]->ptr)) == REDIS_ERR)
{
addReplyError(c,"DUMP payload version or checksum are wrong");
return;
}
@@ -139,7 +140,7 @@ void migrateCommand(redisClient *c) {
return;
if (getLongFromObjectOrReply(c,c->argv[4],&dbid,NULL) != REDIS_OK)
return;
if (timeout <= 0) timeout = 1000;
if (timeout <= 0) timeout = 1;
/* Check if the key is here. If not we reply with success as there is
* nothing to migrate (for instance the key expired in the meantime), but
@@ -157,8 +158,7 @@ void migrateCommand(redisClient *c) {
server.neterr);
return;
}
if ((aeWait(fd,AE_WRITABLE,timeout) & AE_WRITABLE) == 0) {
close(fd);
if ((aeWait(fd,AE_WRITABLE,timeout*1000) & AE_WRITABLE) == 0) {
addReplySds(c,sdsnew("-IOERR error or timeout connecting to the client\r\n"));
return;
}
+2
View File
@@ -1,9 +1,11 @@
#!/bin/sh
GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n1`
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
BUILD_ID=`uname -n`"-"`date +%s`
test -f release.h || touch release.h
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
(cat release.h | grep DIRTY | grep $GIT_DIRTY) && exit 0 # Already up-to-date
echo "#define REDIS_GIT_SHA1 \"$GIT_SHA1\"" > release.h
echo "#define REDIS_GIT_DIRTY \"$GIT_DIRTY\"" >> release.h
echo "#define REDIS_BUILD_ID \"$BUILD_ID\"" >> release.h
touch release.c # Force recompile of release.c
+248 -95
View File
@@ -29,6 +29,7 @@
#include "redis.h"
#include <sys/uio.h>
#include <math.h>
static void setProtocolError(redisClient *c, int pos);
@@ -87,21 +88,22 @@ redisClient *createClient(int fd) {
c->ctime = c->lastinteraction = server.unixtime;
c->authenticated = 0;
c->replstate = REDIS_REPL_NONE;
c->reploff = 0;
c->repl_ack_off = 0;
c->repl_ack_time = 0;
c->slave_listening_port = 0;
c->reply = listCreate();
c->reply_bytes = 0;
c->obuf_soft_limit_reached_time = 0;
listSetFreeMethod(c->reply,decrRefCount);
listSetFreeMethod(c->reply,decrRefCountVoid);
listSetDupMethod(c->reply,dupClientReplyValue);
c->bpop.keys = dictCreate(&setDictType,NULL);
c->bpop.timeout = 0;
c->bpop.target = NULL;
c->io_keys = listCreate();
c->watched_keys = listCreate();
listSetFreeMethod(c->io_keys,decrRefCount);
c->pubsub_channels = dictCreate(&setDictType,NULL);
c->pubsub_patterns = listCreate();
listSetFreeMethod(c->pubsub_patterns,decrRefCount);
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
if (fd != -1) listAddNodeTail(server.clients,c);
initClientMultiState(c);
@@ -115,15 +117,17 @@ redisClient *createClient(int fd) {
* returns REDIS_OK, and make sure to install the write handler in our event
* loop so that when the socket is writable new data gets written.
*
* If the client should not receive new data, because it is a fake client
* or a slave, or because the setup of the write handler failed, the function
* returns REDIS_ERR.
* If the client should not receive new data, because it is a fake client,
* a master, a slave not yet online, or because the setup of the write handler
* failed, the function returns REDIS_ERR.
*
* Typically gets called every time a reply is built, before adding more
* data to the clients output buffers. If the function returns REDIS_ERR no
* data should be appended to the output buffers. */
int prepareClientToWrite(redisClient *c) {
if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK;
if ((c->flags & REDIS_MASTER) &&
!(c->flags & REDIS_MASTER_FORCE_REPLY)) return REDIS_ERR;
if (c->fd <= 0) return REDIS_ERR; /* Fake client */
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE ||
@@ -410,9 +414,15 @@ void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
void addReplyDouble(redisClient *c, double d) {
char dbuf[128], sbuf[128];
int dlen, slen;
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
addReplyString(c,sbuf,slen);
if (isinf(d)) {
/* Libc in odd systems (Hi Solaris!) will format infinite in a
* different way, so better to handle it in an explicit way. */
addReplyBulkCString(c, d > 0 ? "inf" : "-inf");
} else {
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
addReplyString(c,sbuf,slen);
}
}
/* Add a long long as integer reply or bulk len / multi bulk count.
@@ -449,7 +459,10 @@ void addReplyLongLong(redisClient *c, long long ll) {
}
void addReplyMultiBulkLen(redisClient *c, long length) {
addReplyLongLongWithPrefix(c,length,'*');
if (length < REDIS_SHARED_BULKHDR_LEN)
addReply(c,shared.mbulkhdr[length]);
else
addReplyLongLongWithPrefix(c,length,'*');
}
/* Create the length prefix of a bulk reply, example: $2234 */
@@ -471,7 +484,11 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
len++;
}
}
addReplyLongLongWithPrefix(c,len,'$');
if (len < REDIS_SHARED_BULKHDR_LEN)
addReply(c,shared.bulkhdr[len]);
else
addReplyLongLongWithPrefix(c,len,'$');
}
/* Add a Redis Object as a bulk reply */
@@ -517,6 +534,7 @@ void copyClientOutputBuffer(redisClient *dst, redisClient *src) {
dst->reply_bytes = src->reply_bytes;
}
#define MAX_ACCEPTS_PER_CALL 1000
static void acceptCommonHandler(int fd, int flags) {
redisClient *c;
if ((c = createClient(fd)) == NULL) {
@@ -546,37 +564,44 @@ static void acceptCommonHandler(int fd, int flags) {
}
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd;
char cip[128];
int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
char cip[REDIS_IP_STR_LEN];
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask);
REDIS_NOTUSED(privdata);
cfd = anetTcpAccept(server.neterr, fd, cip, &cport);
if (cfd == AE_ERR) {
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
return;
while(max--) {
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
if (cfd == ANET_ERR) {
if (errno != EWOULDBLOCK)
redisLog(REDIS_WARNING,
"Accepting client connection: %s", server.neterr);
return;
}
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
acceptCommonHandler(cfd,0);
}
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
acceptCommonHandler(cfd,0);
}
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cfd;
int cfd, max = MAX_ACCEPTS_PER_CALL;
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask);
REDIS_NOTUSED(privdata);
cfd = anetUnixAccept(server.neterr, fd);
if (cfd == AE_ERR) {
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
return;
while(max--) {
cfd = anetUnixAccept(server.neterr, fd);
if (cfd == ANET_ERR) {
if (errno != EWOULDBLOCK)
redisLog(REDIS_WARNING,
"Accepting client connection: %s", server.neterr);
return;
}
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
acceptCommonHandler(cfd,REDIS_UNIX_SOCKET);
}
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
acceptCommonHandler(cfd,REDIS_UNIX_SOCKET);
}
static void freeClientArgv(redisClient *c) {
int j;
for (j = 0; j < c->argc; j++)
@@ -595,19 +620,58 @@ void disconnectSlaves(void) {
}
}
/* This function is called when the slave lose the connection with the
* master into an unexpected way. */
void replicationHandleMasterDisconnection(void) {
server.master = NULL;
server.repl_state = REDIS_REPL_CONNECT;
server.repl_down_since = server.unixtime;
/* We lost connection with our master, force our slaves to resync
* with us as well to load the new data set.
*
* If server.masterhost is NULL the user called SLAVEOF NO ONE so
* slave resync is not needed. */
if (server.masterhost != NULL) disconnectSlaves();
}
void freeClient(redisClient *c) {
listNode *ln;
/* If this is marked as current client unset it */
if (server.current_client == c) server.current_client = NULL;
/* Note that if the client we are freeing is blocked into a blocking
* call, we have to set querybuf to NULL *before* to call
* unblockClientWaitingData() to avoid processInputBuffer() will get
* called. Also it is important to remove the file events after
* this, because this call adds the READABLE event. */
/* If it is our master that's beging disconnected we should make sure
* to cache the state to try a partial resynchronization later.
*
* Note that before doing this we make sure that the client is not in
* some unexpected state, by checking its flags. */
if (server.master && c->flags & REDIS_MASTER) {
redisLog(REDIS_WARNING,"Connection with master lost.");
if (!(c->flags & (REDIS_CLOSE_AFTER_REPLY|
REDIS_CLOSE_ASAP|
REDIS_BLOCKED|
REDIS_UNBLOCKED)))
{
replicationCacheMaster(c);
return;
}
}
/* Log link disconnection with slave */
if ((c->flags & REDIS_SLAVE) && !(c->flags & REDIS_MONITOR)) {
char ip[REDIS_IP_STR_LEN];
if (anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1) {
redisLog(REDIS_WARNING,"Connection with slave %s:%d lost.",
ip, c->slave_listening_port);
}
}
/* Free the query buffer */
sdsfree(c->querybuf);
c->querybuf = NULL;
/* Deallocate structures used to block on blocking ops. */
if (c->flags & REDIS_BLOCKED)
unblockClientWaitingData(c);
dictRelease(c->bpop.keys);
@@ -615,31 +679,40 @@ void freeClient(redisClient *c) {
/* UNWATCH all the keys */
unwatchAllKeys(c);
listRelease(c->watched_keys);
/* Unsubscribe from all the pubsub channels */
pubsubUnsubscribeAllChannels(c,0);
pubsubUnsubscribeAllPatterns(c,0);
dictRelease(c->pubsub_channels);
listRelease(c->pubsub_patterns);
/* Obvious cleanup */
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
/* Close socket, unregister events, and remove list of replies and
* accumulated arguments. */
if (c->fd != -1) {
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
close(c->fd);
}
listRelease(c->reply);
freeClientArgv(c);
close(c->fd);
/* Remove from the list of clients */
ln = listSearchKey(server.clients,c);
redisAssert(ln != NULL);
listDelNode(server.clients,ln);
if (c->fd != -1) {
ln = listSearchKey(server.clients,c);
redisAssert(ln != NULL);
listDelNode(server.clients,ln);
}
/* When client was just unblocked because of a blocking operation,
* remove it from the list with unblocked clients. */
* remove it from the list of unblocked clients. */
if (c->flags & REDIS_UNBLOCKED) {
ln = listSearchKey(server.unblocked_clients,c);
redisAssert(ln != NULL);
listDelNode(server.unblocked_clients,ln);
}
listRelease(c->io_keys);
/* Master/slave cleanup.
* Case 1: we lost the connection with a slave. */
/* Master/slave cleanup Case 1:
* we lost the connection with a slave. */
if (c->flags & REDIS_SLAVE) {
if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1)
close(c->repldbfd);
@@ -647,20 +720,17 @@ void freeClient(redisClient *c) {
ln = listSearchKey(l,c);
redisAssert(ln != NULL);
listDelNode(l,ln);
/* We need to remember the time when we started to have zero
* attached slaves, as after some time we'll free the replication
* backlog. */
if (c->flags & REDIS_SLAVE && listLength(server.slaves) == 0)
server.repl_no_slaves_since = server.unixtime;
refreshGoodSlavesCount();
}
/* Case 2: we lost the connection with the master. */
if (c->flags & REDIS_MASTER) {
server.master = NULL;
server.repl_state = REDIS_REPL_CONNECT;
server.repl_down_since = server.unixtime;
/* We lost connection with our master, force our slaves to resync
* with us as well to load the new data set.
*
* If server.masterhost is NULL the user called SLAVEOF NO ONE so
* slave resync is not needed. */
if (server.masterhost != NULL) disconnectSlaves();
}
/* Master/slave cleanup Case 2:
* we lost the connection with the master. */
if (c->flags & REDIS_MASTER) replicationHandleMasterDisconnection();
/* If this client was scheduled for async freeing we need to remove it
* from the queue. */
@@ -670,7 +740,8 @@ void freeClient(redisClient *c) {
listDelNode(server.clients_to_close,ln);
}
/* Release memory */
/* Release other dynamically allocated client structure fields,
* and finally release the client structure itself. */
if (c->name) decrRefCount(c->name);
zfree(c->argv);
freeClientMultiState(c);
@@ -708,13 +779,8 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
while(c->bufpos > 0 || listLength(c->reply)) {
if (c->bufpos > 0) {
if (c->flags & REDIS_MASTER) {
/* Don't reply to a master */
nwritten = c->bufpos - c->sentlen;
} else {
nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
if (nwritten <= 0) break;
}
nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
if (nwritten <= 0) break;
c->sentlen += nwritten;
totwritten += nwritten;
@@ -734,13 +800,8 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
continue;
}
if (c->flags & REDIS_MASTER) {
/* Don't reply to a master */
nwritten = objlen - c->sentlen;
} else {
nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
if (nwritten <= 0) break;
}
nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
if (nwritten <= 0) break;
c->sentlen += nwritten;
totwritten += nwritten;
@@ -773,7 +834,13 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
}
if (totwritten > 0) c->lastinteraction = server.unixtime;
if (totwritten > 0) {
/* For clients representing masters we don't count sending data
* as an interaction, since we always send REPLCONF ACK commands
* that take some time to just fill the socket output buffer.
* We just rely on data / pings received for timeout detection. */
if (!(c->flags & REDIS_MASTER)) c->lastinteraction = server.unixtime;
}
if (c->bufpos == 0 && listLength(c->reply) == 0) {
c->sentlen = 0;
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
@@ -794,11 +861,14 @@ void resetClient(redisClient *c) {
}
int processInlineBuffer(redisClient *c) {
char *newline = strstr(c->querybuf,"\r\n");
char *newline;
int argc, j;
sds *argv;
sds *argv, aux;
size_t querylen;
/* Search for end of line */
newline = strchr(c->querybuf,'\n');
/* Nothing to do without a \r\n */
if (newline == NULL) {
if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {
@@ -808,12 +878,29 @@ int processInlineBuffer(redisClient *c) {
return REDIS_ERR;
}
/* Handle the \r\n case. */
if (newline && newline != c->querybuf && *(newline-1) == '\r')
newline--;
/* Split the input buffer up to the \r\n */
querylen = newline-(c->querybuf);
argv = sdssplitlen(c->querybuf,querylen," ",1,&argc);
aux = sdsnewlen(c->querybuf,querylen);
argv = sdssplitargs(aux,&argc);
sdsfree(aux);
if (argv == NULL) {
addReplyError(c,"Protocol error: unbalanced quotes in request");
setProtocolError(c,0);
return REDIS_ERR;
}
/* Newline from slaves can be used to refresh the last ACK time.
* This is useful for a slave to ping back while loading a big
* RDB file. */
if (querylen == 0 && c->flags & REDIS_SLAVE)
c->repl_ack_time = server.unixtime;
/* Leave data after the first line of the query in the buffer */
c->querybuf = sdsrange(c->querybuf,querylen+2,-1);
sdsrange(c->querybuf,querylen+2,-1);
/* Setup argv array on client structure */
if (c->argv) zfree(c->argv);
@@ -842,7 +929,7 @@ static void setProtocolError(redisClient *c, int pos) {
sdsfree(client);
}
c->flags |= REDIS_CLOSE_AFTER_REPLY;
c->querybuf = sdsrange(c->querybuf,pos,-1);
sdsrange(c->querybuf,pos,-1);
}
int processMultibulkBuffer(redisClient *c) {
@@ -880,7 +967,7 @@ int processMultibulkBuffer(redisClient *c) {
pos = (newline-c->querybuf)+2;
if (ll <= 0) {
c->querybuf = sdsrange(c->querybuf,pos,-1);
sdsrange(c->querybuf,pos,-1);
return REDIS_OK;
}
@@ -898,8 +985,10 @@ int processMultibulkBuffer(redisClient *c) {
newline = strchr(c->querybuf+pos,'\r');
if (newline == NULL) {
if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {
addReplyError(c,"Protocol error: too big bulk count string");
addReplyError(c,
"Protocol error: too big bulk count string");
setProtocolError(c,0);
return REDIS_ERR;
}
break;
}
@@ -925,15 +1014,19 @@ int processMultibulkBuffer(redisClient *c) {
pos += newline-(c->querybuf+pos)+2;
if (ll >= REDIS_MBULK_BIG_ARG) {
size_t qblen;
/* If we are going to read a large object from network
* try to make it likely that it will start at c->querybuf
* boundary so that we can optimized object creation
* boundary so that we can optimize object creation
* avoiding a large copy of data. */
c->querybuf = sdsrange(c->querybuf,pos,-1);
sdsrange(c->querybuf,pos,-1);
pos = 0;
qblen = sdslen(c->querybuf);
/* Hint the sds library about the amount of bytes this string is
* going to contain. */
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2);
if (qblen < ll+2)
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-qblen);
}
c->bulklen = ll;
}
@@ -968,7 +1061,7 @@ int processMultibulkBuffer(redisClient *c) {
}
/* Trim to pos */
if (pos) c->querybuf = sdsrange(c->querybuf,pos,-1);
if (pos) sdsrange(c->querybuf,pos,-1);
/* We're done when c->multibulk == 0 */
if (c->multibulklen == 0) return REDIS_OK;
@@ -1059,6 +1152,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
if (nread) {
sdsIncrLen(c->querybuf,nread);
c->lastinteraction = server.unixtime;
if (c->flags & REDIS_MASTER) c->reploff += nread;
} else {
server.current_client = NULL;
return;
@@ -1095,14 +1189,52 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
*biggest_input_buffer = bib;
}
/* This is a helper function for getClientPeerId().
* It writes the specified ip/port to "peerid" as a null termiated string
* in the form ip:port if ip does not contain ":" itself, otherwise
* [ip]:port format is used (for IPv6 addresses basically). */
void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port) {
if (strchr(ip,':'))
snprintf(peerid,peerid_len,"[%s]:%d",ip,port);
else
snprintf(peerid,peerid_len,"%s:%d",ip,port);
}
/* A Redis "Peer ID" is a colon separated ip:port pair.
* For IPv4 it's in the form x.y.z.k:pork, example: "127.0.0.1:1234".
* For IPv6 addresses we use [] around the IP part, like in "[::1]:1234".
* For Unix socekts we use path:0, like in "/tmp/redis:0".
*
* A Peer ID always fits inside a buffer of REDIS_PEER_ID_LEN bytes, including
* the null term.
*
* The function returns REDIS_OK on succcess, and REDIS_ERR on failure.
*
* On failure the function still populates 'peerid' with the "?:0" string
* in case you want to relax error checking or need to display something
* anyway (see anetPeerToString implementation for more info). */
int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
char ip[REDIS_IP_STR_LEN];
int port;
if (client->flags & REDIS_UNIX_SOCKET) {
/* Unix socket client. */
snprintf(peerid,peerid_len,"%s:0",server.unixsocket);
return REDIS_OK;
} else {
/* TCP client. */
int retval = anetPeerToString(client->fd,ip,sizeof(ip),&port);
formatPeerId(peerid,peerid_len,ip,port);
return (retval == -1) ? REDIS_ERR : REDIS_OK;
}
}
/* Turn a Redis client into an sds string representing its state. */
sds getClientInfoString(redisClient *client) {
char ip[32], flags[16], events[3], *p;
int port = 0; /* initialized to zero for the unix socket case. */
char peerid[REDIS_PEER_ID_LEN], flags[16], events[3], *p;
int emask;
if (!(client->flags & REDIS_UNIX_SOCKET))
anetPeerToString(client->fd,ip,&port);
getClientPeerId(client,peerid,sizeof(peerid));
p = flags;
if (client->flags & REDIS_SLAVE) {
if (client->flags & REDIS_MONITOR)
@@ -1127,9 +1259,8 @@ sds getClientInfoString(redisClient *client) {
if (emask & AE_WRITABLE) *p++ = 'w';
*p = '\0';
return sdscatprintf(sdsempty(),
"addr=%s:%d fd=%d name=%s age=%ld idle=%ld flags=%s db=%d sub=%d psub=%d multi=%d qbuf=%lu qbuf-free=%lu obl=%lu oll=%lu omem=%lu events=%s cmd=%s",
(client->flags & REDIS_UNIX_SOCKET) ? server.unixsocket : ip,
port,
"addr=%s fd=%d name=%s age=%ld idle=%ld flags=%s db=%d sub=%d psub=%d multi=%d qbuf=%lu qbuf-free=%lu obl=%lu oll=%lu omem=%lu events=%s cmd=%s",
peerid,
client->fd,
client->name ? (char*)client->name->ptr : "",
(long)(server.unixtime - client->ctime),
@@ -1179,13 +1310,12 @@ void clientCommand(redisClient *c) {
} else if (!strcasecmp(c->argv[1]->ptr,"kill") && c->argc == 3) {
listRewind(server.clients,&li);
while ((ln = listNext(&li)) != NULL) {
char ip[32], addr[64];
int port;
char peerid[REDIS_PEER_ID_LEN];
client = listNodeValue(ln);
if (anetPeerToString(client->fd,ip,&port) == -1) continue;
snprintf(addr,sizeof(addr),"%s:%d",ip,port);
if (strcmp(addr,c->argv[2]->ptr) == 0) {
if (getClientPeerId(client,peerid,sizeof(peerid)) == REDIS_ERR)
continue;
if (strcmp(peerid,c->argv[2]->ptr) == 0) {
addReply(c,shared.ok);
if (c == client) {
client->flags |= REDIS_CLOSE_AFTER_REPLY;
@@ -1411,3 +1541,26 @@ void flushSlavesOutputBuffers(void) {
}
}
}
/* This function is called by Redis in order to process a few events from
* time to time while blocked into some not interruptible operation.
* This allows to reply to clients with the -LOADING error while loading the
* data set at startup or after a full resynchronization with the master
* and so forth.
*
* It calls the event loop in order to process a few events. Specifically we
* try to call the event loop for times as long as we receive acknowledge that
* some event was processed, in order to go forward with the accept, read,
* write, close sequence needed to serve a client.
*
* The function returns the total number of events processed. */
int processEventsWhileBlocked(void) {
int iterations = 4; /* See the function top-comment. */
int count = 0;
while (iterations--) {
int events = aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
if (!events) break;
count += events;
}
return count;
}
+130
View File
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2013, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "redis.h"
/* This file implements keyspace events notification via Pub/Sub ad
* described at http://redis.io/topics/keyspace-events. */
/* Turn a string representing notification classes into an integer
* representing notification classes flags xored.
*
* The function returns -1 if the input contains characters not mapping to
* any class. */
int keyspaceEventsStringToFlags(char *classes) {
char *p = classes;
int c, flags = 0;
while((c = *p++) != '\0') {
switch(c) {
case 'A': flags |= REDIS_NOTIFY_ALL; break;
case 'g': flags |= REDIS_NOTIFY_GENERIC; break;
case '$': flags |= REDIS_NOTIFY_STRING; break;
case 'l': flags |= REDIS_NOTIFY_LIST; break;
case 's': flags |= REDIS_NOTIFY_SET; break;
case 'h': flags |= REDIS_NOTIFY_HASH; break;
case 'z': flags |= REDIS_NOTIFY_ZSET; break;
case 'x': flags |= REDIS_NOTIFY_EXPIRED; break;
case 'e': flags |= REDIS_NOTIFY_EVICTED; break;
case 'K': flags |= REDIS_NOTIFY_KEYSPACE; break;
case 'E': flags |= REDIS_NOTIFY_KEYEVENT; break;
default: return -1;
}
}
return flags;
}
/* This function does exactly the revese of the function above: it gets
* as input an integer with the xored flags and returns a string representing
* the selected classes. The string returned is an sds string that needs to
* be released with sdsfree(). */
sds keyspaceEventsFlagsToString(int flags) {
sds res;
res = sdsempty();
if ((flags & REDIS_NOTIFY_ALL) == REDIS_NOTIFY_ALL) {
res = sdscatlen(res,"A",1);
} else {
if (flags & REDIS_NOTIFY_GENERIC) res = sdscatlen(res,"g",1);
if (flags & REDIS_NOTIFY_STRING) res = sdscatlen(res,"$",1);
if (flags & REDIS_NOTIFY_LIST) res = sdscatlen(res,"l",1);
if (flags & REDIS_NOTIFY_SET) res = sdscatlen(res,"s",1);
if (flags & REDIS_NOTIFY_HASH) res = sdscatlen(res,"h",1);
if (flags & REDIS_NOTIFY_ZSET) res = sdscatlen(res,"z",1);
if (flags & REDIS_NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
if (flags & REDIS_NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
}
if (flags & REDIS_NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
if (flags & REDIS_NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
return res;
}
/* The API provided to the rest of the Redis core is a simple function:
*
* notifyKeyspaceEvent(char *event, robj *key, int dbid);
*
* 'event' is a C string representing the event name.
* 'key' is a Redis object representing the key name.
* 'dbid' is the database ID where the key lives. */
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
sds chan;
robj *chanobj, *eventobj;
int len = -1;
char buf[24];
/* If notifications for this class of events are off, return ASAP. */
if (!(server.notify_keyspace_events & type)) return;
eventobj = createStringObject(event,strlen(event));
/* __keyspace@<db>__:<key> <event> notifications. */
if (server.notify_keyspace_events & REDIS_NOTIFY_KEYSPACE) {
chan = sdsnewlen("__keyspace@",11);
len = ll2string(buf,sizeof(buf),dbid);
chan = sdscatlen(chan, buf, len);
chan = sdscatlen(chan, "__:", 3);
chan = sdscatsds(chan, key->ptr);
chanobj = createObject(REDIS_STRING, chan);
pubsubPublishMessage(chanobj, eventobj);
decrRefCount(chanobj);
}
/* __keyevente@<db>__:<event> <key> notifications. */
if (server.notify_keyspace_events & REDIS_NOTIFY_KEYEVENT) {
chan = sdsnewlen("__keyevent@",11);
if (len == -1) len = ll2string(buf,sizeof(buf),dbid);
chan = sdscatlen(chan, buf, len);
chan = sdscatlen(chan, "__:", 3);
chan = sdscatsds(chan, eventobj->ptr);
chanobj = createObject(REDIS_STRING, chan);
pubsubPublishMessage(chanobj, key);
decrRefCount(chanobj);
}
decrRefCount(eventobj);
}
+75 -19
View File
@@ -32,6 +32,10 @@
#include <math.h>
#include <ctype.h>
#ifdef __CYGWIN__
#define strtold(a,b) ((long double)strtod((a),(b)))
#endif
robj *createObject(int type, void *ptr) {
robj *o = zmalloc(sizeof(*o));
o->type = type;
@@ -97,7 +101,7 @@ robj *dupStringObject(robj *o) {
robj *createListObject(void) {
list *l = listCreate();
robj *o = createObject(REDIS_LIST,l);
listSetFreeMethod(l,decrRefCount);
listSetFreeMethod(l,decrRefCountVoid);
o->encoding = REDIS_ENCODING_LINKEDLIST;
return o;
}
@@ -215,9 +219,7 @@ void incrRefCount(robj *o) {
o->refcount++;
}
void decrRefCount(void *obj) {
robj *o = obj;
void decrRefCount(robj *o) {
if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
if (o->refcount == 1) {
switch(o->type) {
@@ -234,6 +236,13 @@ void decrRefCount(void *obj) {
}
}
/* This variant of decrRefCount() gets its argument as void, and is useful
* as free method in data structures that expect a 'void free_object(void*)'
* prototype for the free method. */
void decrRefCountVoid(void *o) {
decrRefCount(o);
}
/* This function set the ref count to zero without freeing the object.
* It is useful in order to pass a new object to functions incrementing
* the ref count of the received object. Example:
@@ -273,6 +282,7 @@ int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
robj *tryObjectEncoding(robj *o) {
long value;
sds s = o->ptr;
size_t len;
if (o->encoding != REDIS_ENCODING_RAW)
return o; /* Already encoded */
@@ -286,7 +296,27 @@ robj *tryObjectEncoding(robj *o) {
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
/* Check if we can represent this string as a long integer */
if (!string2l(s,sdslen(s),&value)) return o;
len = sdslen(s);
if (len > 21 || !string2l(s,len,&value)) {
/* We can't encode the object...
*
* Do the last try, and at least optimize the SDS string inside
* the string object to require little space, in case there
* is more than 10% of free space at the end of the SDS string.
*
* We do that for larger strings, using the arbitrary value
* of 32 bytes. This code was backported from the unstable branch
* where this is performed when the object is too large to be
* encoded as EMBSTR. */
if (len > 32 &&
o->encoding == REDIS_ENCODING_RAW &&
sdsavail(s) > len/10)
{
o->ptr = sdsRemoveFreeSpace(o->ptr);
}
/* Return the original object. */
return o;
}
/* Ok, this object can be encoded...
*
@@ -327,35 +357,57 @@ robj *getDecodedObject(robj *o) {
}
}
/* Compare two string objects via strcmp() or alike.
/* Compare two string objects via strcmp() or strcoll() depending on flags.
* Note that the objects may be integer-encoded. In such a case we
* use ll2string() to get a string representation of the numbers on the stack
* and compare the strings, it's much faster than calling getDecodedObject().
*
* Important note: if objects are not integer encoded, but binary-safe strings,
* sdscmp() from sds.c will apply memcmp() so this function ca be considered
* binary safe. */
int compareStringObjects(robj *a, robj *b) {
* Important note: when REDIS_COMPARE_BINARY is used a binary-safe comparison
* is used. */
#define REDIS_COMPARE_BINARY (1<<0)
#define REDIS_COMPARE_COLL (1<<1)
int compareStringObjectsWithFlags(robj *a, robj *b, int flags) {
redisAssertWithInfo(NULL,a,a->type == REDIS_STRING && b->type == REDIS_STRING);
char bufa[128], bufb[128], *astr, *bstr;
int bothsds = 1;
size_t alen, blen, minlen;
if (a == b) return 0;
if (a->encoding != REDIS_ENCODING_RAW) {
ll2string(bufa,sizeof(bufa),(long) a->ptr);
alen = ll2string(bufa,sizeof(bufa),(long) a->ptr);
astr = bufa;
bothsds = 0;
} else {
astr = a->ptr;
alen = sdslen(astr);
}
if (b->encoding != REDIS_ENCODING_RAW) {
ll2string(bufb,sizeof(bufb),(long) b->ptr);
blen = ll2string(bufb,sizeof(bufb),(long) b->ptr);
bstr = bufb;
bothsds = 0;
} else {
bstr = b->ptr;
blen = sdslen(bstr);
}
return bothsds ? sdscmp(astr,bstr) : strcmp(astr,bstr);
if (flags & REDIS_COMPARE_COLL) {
return strcoll(astr,bstr);
} else {
int cmp;
minlen = (alen < blen) ? alen : blen;
cmp = memcmp(astr,bstr,minlen);
if (cmp == 0) return alen-blen;
return cmp;
}
}
/* Wrapper for compareStringObjectsWithFlags() using binary comparison. */
int compareStringObjects(robj *a, robj *b) {
return compareStringObjectsWithFlags(a,b,REDIS_COMPARE_BINARY);
}
/* Wrapper for compareStringObjectsWithFlags() using collation. */
int collateStringObjects(robj *a, robj *b) {
return compareStringObjectsWithFlags(a,b,REDIS_COMPARE_COLL);
}
/* Equal string objects return 1 if the two objects are the same from the
@@ -392,8 +444,12 @@ int getDoubleFromObject(robj *o, double *target) {
if (o->encoding == REDIS_ENCODING_RAW) {
errno = 0;
value = strtod(o->ptr, &eptr);
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
errno == ERANGE || isnan(value))
if (isspace(((char*)o->ptr)[0]) ||
eptr[0] != '\0' ||
(errno == ERANGE &&
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
errno == EINVAL ||
isnan(value))
return REDIS_ERR;
} else if (o->encoding == REDIS_ENCODING_INT) {
value = (long)o->ptr;
@@ -535,7 +591,7 @@ unsigned long estimateObjectIdleTime(robj *o) {
}
}
/* This is an helper function for the DEBUG command. We need to lookup keys
/* This is a helper function for the OBJECT command. We need to lookup keys
* without any modification of LRU or other parameters. */
robj *objectCommandLookup(redisClient *c, robj *key) {
dictEntry *de;
+1 -13
View File
@@ -39,7 +39,6 @@
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
@@ -102,10 +101,9 @@ _pqsort(void *a, size_t n, size_t es,
{
char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
size_t d, r;
int swaptype, swap_cnt, cmp_result;
int swaptype, cmp_result;
loop: SWAPINIT(a, es);
swap_cnt = 0;
if (n < 7) {
for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
@@ -132,7 +130,6 @@ loop: SWAPINIT(a, es);
for (;;) {
while (pb <= pc && (cmp_result = cmp(pb, a)) <= 0) {
if (cmp_result == 0) {
swap_cnt = 1;
swap(pa, pb);
pa += es;
}
@@ -140,7 +137,6 @@ loop: SWAPINIT(a, es);
}
while (pb <= pc && (cmp_result = cmp(pc, a)) >= 0) {
if (cmp_result == 0) {
swap_cnt = 1;
swap(pc, pd);
pd -= es;
}
@@ -149,17 +145,9 @@ loop: SWAPINIT(a, es);
if (pb > pc)
break;
swap(pb, pc);
swap_cnt = 1;
pb += es;
pc -= es;
}
if (swap_cnt == 0) { /* Switch to insertion sort */
for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
pl -= es)
swap(pl, pl - es);
return;
}
pn = (char *) a + n * es;
r = min(pa - (char *) a, pb - pa);
+48
View File
@@ -306,5 +306,53 @@ void punsubscribeCommand(redisClient *c) {
void publishCommand(redisClient *c) {
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
forceCommandPropagation(c,REDIS_PROPAGATE_REPL);
addReplyLongLong(c,receivers);
}
/* PUBSUB command for Pub/Sub introspection. */
void pubsubCommand(redisClient *c) {
if (!strcasecmp(c->argv[1]->ptr,"channels") &&
(c->argc == 2 || c->argc ==3))
{
/* PUBSUB CHANNELS [<pattern>] */
sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr;
dictIterator *di = dictGetIterator(server.pubsub_channels);
dictEntry *de;
long mblen = 0;
void *replylen;
replylen = addDeferredMultiBulkLength(c);
while((de = dictNext(di)) != NULL) {
robj *cobj = dictGetKey(de);
sds channel = cobj->ptr;
if (!pat || stringmatchlen(pat, sdslen(pat),
channel, sdslen(channel),0))
{
addReplyBulk(c,cobj);
mblen++;
}
}
dictReleaseIterator(di);
setDeferredMultiBulkLength(c,replylen,mblen);
} else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) {
/* PUBSUB NUMSUB [Channel_1 ... Channel_N] */
int j;
addReplyMultiBulkLen(c,(c->argc-2)*2);
for (j = 2; j < c->argc; j++) {
list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);
addReplyBulk(c,c->argv[j]);
addReplyBulkLongLong(c,l ? listLength(l) : 0);
}
} else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) {
/* PUBSUB NUMPAT */
addReplyLongLong(c,listLength(server.pubsub_patterns));
} else {
addReplyErrorFormat(c,
"Unknown PUBSUB subcommand or wrong number of arguments for '%s'",
(char*)c->argv[1]->ptr);
}
}
+32 -20
View File
@@ -99,7 +99,7 @@ int rdbSaveLen(rio *rdb, uint32_t len) {
buf[0] = (REDIS_RDB_32BITLEN<<6);
if (rdbWriteRaw(rdb,buf,1) == -1) return -1;
len = htonl(len);
if (rdbWriteRaw(rdb,&len,4) == -4) return -1;
if (rdbWriteRaw(rdb,&len,4) == -1) return -1;
nwritten = 1+4;
}
return nwritten;
@@ -398,7 +398,7 @@ int rdbSaveDoubleValue(rio *rdb, double val) {
double min = -4503599627370495; /* (2^52)-1 */
double max = 4503599627370496; /* -(2^52) */
if (val > min && val < max && val == ((double)((long long)val)))
ll2string((char*)buf+1,sizeof(buf),(long long)val);
ll2string((char*)buf+1,sizeof(buf)-1,(long long)val);
else
#endif
snprintf((char*)buf+1,sizeof(buf)-1,"%.17g",val);
@@ -410,7 +410,7 @@ int rdbSaveDoubleValue(rio *rdb, double val) {
/* For information about double serialization check rdbSaveDoubleValue() */
int rdbLoadDoubleValue(rio *rdb, double *val) {
char buf[128];
char buf[256];
unsigned char len;
if (rioRead(rdb,&len,1) == 0) return -1;
@@ -690,9 +690,9 @@ int rdbSave(char *filename) {
rioWrite(&rdb,&cksum,8);
/* Make sure data will not remain on the OS's output buffers */
fflush(fp);
fsync(fileno(fp));
fclose(fp);
if (fflush(fp) == EOF) goto werr;
if (fsync(fileno(fp)) == -1) goto werr;
if (fclose(fp) == EOF) goto werr;
/* Use RENAME to make sure the DB file is changed atomically only
* if the generate DB file is ok. */
@@ -729,15 +729,15 @@ int rdbSaveBackground(char *filename) {
int retval;
/* Child */
if (server.ipfd > 0) close(server.ipfd);
if (server.sofd > 0) close(server.sofd);
closeListeningSockets(0);
redisSetProcTitle("redis-rdb-bgsave");
retval = rdbSave(filename);
if (retval == REDIS_OK) {
size_t private_dirty = zmalloc_get_private_dirty();
if (private_dirty) {
redisLog(REDIS_NOTICE,
"RDB: %lu MB of memory used by copy-on-write",
"RDB: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
}
@@ -746,6 +746,7 @@ int rdbSaveBackground(char *filename) {
/* Parent */
server.stat_fork_time = ustime()-start;
if (childpid == -1) {
server.lastbgsave_status = REDIS_ERR;
redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
strerror(errno));
return REDIS_ERR;
@@ -773,7 +774,6 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
size_t len;
unsigned int i;
redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",rdbtype,rioTell(rdb));
if (rdbtype == REDIS_RDB_TYPE_STRING) {
/* Read string value */
if ((o = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
@@ -892,7 +892,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
o = createHashObject();
/* Too many entries? Use an hash table. */
/* Too many entries? Use a hash table. */
if (len > server.hash_max_ziplist_entries)
hashTypeConvert(o, REDIS_ENCODING_HT);
@@ -1058,21 +1058,39 @@ void stopLoading(void) {
server.loading = 0;
}
/* Track loading progress in order to serve client's from time to time
and if needed calculate rdb checksum */
void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
if (server.rdb_checksum)
rioGenericUpdateChecksum(r, buf, len);
if (server.loading_process_events_interval_bytes &&
(r->processed_bytes + len)/server.loading_process_events_interval_bytes > r->processed_bytes/server.loading_process_events_interval_bytes)
{
/* The DB can take some non trivial amount of time to load. Update
* our cached time since it is used to create and update the last
* interaction time with clients and for other important things. */
updateCachedTime();
if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER)
replicationSendNewlineToMaster();
loadingProgress(r->processed_bytes);
processEventsWhileBlocked();
}
}
int rdbLoad(char *filename) {
uint32_t dbid;
int type, rdbver;
redisDb *db = server.db+0;
char buf[1024];
long long expiretime, now = mstime();
long loops = 0;
FILE *fp;
rio rdb;
if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;
rioInitWithFile(&rdb,fp);
if (server.rdb_checksum)
rdb.update_cksum = rioGenericUpdateChecksum;
rdb.update_cksum = rdbLoadProgressCallback;
rdb.max_processing_chunk = server.loading_process_events_interval_bytes;
if (rioRead(&rdb,buf,9) == 0) goto eoferr;
buf[9] = '\0';
if (memcmp(buf,"REDIS",5) != 0) {
@@ -1094,12 +1112,6 @@ int rdbLoad(char *filename) {
robj *key, *val;
expiretime = -1;
/* Serve the clients from time to time */
if (!(loops++ % 1000)) {
loadingProgress(rioTell(&rdb));
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
}
/* Read type. */
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
if (type == REDIS_RDB_OPCODE_EXPIRETIME) {
+151 -38
View File
@@ -35,6 +35,7 @@
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <signal.h>
#include <assert.h>
@@ -46,6 +47,7 @@
#include "zmalloc.h"
#define REDIS_NOTUSED(V) ((void) V)
#define RANDPTR_INITIAL_SIZE 8
static struct config {
aeEventLoop *el;
@@ -72,18 +74,24 @@ static struct config {
int csv;
int loop;
int idlemode;
int dbnum;
sds dbnumstr;
char *tests;
} config;
typedef struct _client {
redisContext *context;
sds obuf;
char *randptr[32]; /* needed for MSET against 10 keys */
size_t randlen;
unsigned int written; /* bytes of 'obuf' already written */
long long start; /* start time of a request */
long long latency; /* request latency */
int pending; /* Number of pending requests (sent but no reply received) */
char **randptr; /* Pointers to :rand: strings inside the command buf */
size_t randlen; /* Number of pointers in client->randptr */
size_t randfree; /* Number of unused pointers in client->randptr */
unsigned int written; /* Bytes of 'obuf' already written */
long long start; /* Start time of a request */
long long latency; /* Request latency */
int pending; /* Number of pending requests (replies to consume) */
int selectlen; /* If non-zero, a SELECT of 'selectlen' bytes is currently
used as a prefix of the pipline of commands. This gets
discarded the first time it's sent. */
} *client;
/* Prototypes */
@@ -117,6 +125,7 @@ static void freeClient(client c) {
aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
redisFree(c->context);
sdsfree(c->obuf);
zfree(c->randptr);
zfree(c);
config.liveclients--;
ln = listSearchKey(config.clients,c);
@@ -143,13 +152,18 @@ static void resetClient(client c) {
}
static void randomizeClientKey(client c) {
char buf[32];
size_t i, r;
size_t i;
for (i = 0; i < c->randlen; i++) {
r = random() % config.randomkeys_keyspacelen;
snprintf(buf,sizeof(buf),"%012zu",r);
memcpy(c->randptr[i],buf,12);
char *p = c->randptr[i]+11;
size_t r = random() % config.randomkeys_keyspacelen;
size_t j;
for (j = 0; j < 12; j++) {
*p = '0'+r%10;
r/=10;
p--;
}
}
}
@@ -198,6 +212,21 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
freeReplyObject(reply);
if (c->selectlen) {
int j;
/* This is the OK from SELECT. Just discard the SELECT
* from the buffer. */
c->pending--;
sdsrange(c->obuf,c->selectlen,-1);
/* We also need to fix the pointers to the strings
* we need to randomize. */
for (j = 0; j < c->randlen; j++)
c->randptr[j] -= c->selectlen;
c->selectlen = 0;
continue;
}
if (config.requests_finished < config.requests)
config.latency[config.requests_finished++] = c->latency;
c->pending--;
@@ -249,7 +278,29 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
}
}
static client createClient(char *cmd, size_t len) {
/* Create a benchmark client, configured to send the command passed as 'cmd' of
* 'len' bytes.
*
* The command is copied N times in the client output buffer (that is reused
* again and again to send the request to the server) accordingly to the configured
* pipeline size.
*
* Also an initial SELECT command is prepended in order to make sure the right
* database is selected, if needed. The initial SELECT will be discarded as soon
* as the first reply is received.
*
* To create a client from scratch, the 'from' pointer is set to NULL. If instead
* we want to create a client using another client as reference, the 'from' pointer
* points to the client to use as reference. In such a case the following
* information is take from the 'from' client:
*
* 1) The command line to use.
* 2) The offsets of the __rand_int__ elements inside the command line, used
* for arguments randomization.
*
* Even when cloning another client, the SELECT command is automatically prefixed
* if needed. */
static client createClient(char *cmd, size_t len, client from) {
int j;
client c = zmalloc(sizeof(struct _client));
@@ -268,25 +319,68 @@ static client createClient(char *cmd, size_t len) {
}
/* Suppress hiredis cleanup of unused buffers for max speed. */
c->context->reader->maxbuf = 0;
/* Queue N requests accordingly to the pipeline size. */
/* Build the request buffer:
* Queue N requests accordingly to the pipeline size, or simply clone
* the example client buffer. */
c->obuf = sdsempty();
for (j = 0; j < config.pipeline; j++)
c->obuf = sdscatlen(c->obuf,cmd,len);
c->randlen = 0;
/* If a DB number different than zero is selected, prefix our request
* buffer with the SELECT command, that will be discarded the first
* time the replies are received, so if the client is reused the
* SELECT command will not be used again. */
if (config.dbnum != 0) {
c->obuf = sdscatprintf(c->obuf,"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",
(int)sdslen(config.dbnumstr),config.dbnumstr);
c->selectlen = sdslen(c->obuf);
} else {
c->selectlen = 0;
}
/* Append the request itself. */
if (from) {
c->obuf = sdscatlen(c->obuf,
from->obuf+from->selectlen,
sdslen(from->obuf)-from->selectlen);
} else {
for (j = 0; j < config.pipeline; j++)
c->obuf = sdscatlen(c->obuf,cmd,len);
}
c->written = 0;
c->pending = config.pipeline;
c->randptr = NULL;
c->randlen = 0;
if (c->selectlen) c->pending++;
/* Find substrings in the output buffer that need to be randomized. */
if (config.randomkeys) {
char *p = c->obuf;
while ((p = strstr(p,":rand:")) != NULL) {
assert(c->randlen < (signed)(sizeof(c->randptr)/sizeof(char*)));
c->randptr[c->randlen++] = p+6;
p += 6;
if (from) {
c->randlen = from->randlen;
c->randfree = 0;
c->randptr = zmalloc(sizeof(char*)*c->randlen);
/* copy the offsets. */
for (j = 0; j < c->randlen; j++) {
c->randptr[j] = c->obuf + (from->randptr[j]-from->obuf);
/* Adjust for the different select prefix length. */
c->randptr[j] += c->selectlen - from->selectlen;
}
} else {
char *p = c->obuf;
c->randlen = 0;
c->randfree = RANDPTR_INITIAL_SIZE;
c->randptr = zmalloc(sizeof(char*)*c->randfree);
while ((p = strstr(p,"__rand_int__")) != NULL) {
if (c->randfree == 0) {
c->randptr = zrealloc(c->randptr,sizeof(char*)*c->randlen*2);
c->randfree += c->randlen;
}
c->randptr[c->randlen++] = p;
c->randfree--;
p += 12; /* 12 is strlen("__rand_int__). */
}
}
}
/* redisSetReplyObjectFunctions(c->context,NULL); */
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
listAddNodeTail(config.clients,c);
config.liveclients++;
@@ -295,9 +389,18 @@ static client createClient(char *cmd, size_t len) {
static void createMissingClients(client c) {
int n = 0;
char *buf = c->obuf;
size_t buflen = sdslen(c->obuf);
/* If we are cloning from a client with a SELECT prefix, skip it since the
* client will be created with the prefixed SELECT if needed. */
if (c->selectlen) {
buf += c->selectlen;
buflen -= c->selectlen;
}
while(config.liveclients < config.numclients) {
createClient(c->obuf,sdslen(c->obuf)/config.pipeline);
createClient(NULL,0,c);
/* Listen backlog is quite limited on most systems */
if (++n > 64) {
@@ -348,7 +451,7 @@ static void benchmark(char *title, char *cmd, int len) {
config.requests_issued = 0;
config.requests_finished = 0;
c = createClient(cmd,len);
c = createClient(cmd,len,NULL);
createMissingClients(c);
config.start = mstime();
@@ -420,6 +523,10 @@ int parseOptions(int argc, const char **argv) {
config.tests = sdscat(config.tests,(char*)argv[++i]);
config.tests = sdscat(config.tests,",");
sdstolower(config.tests);
} else if (!strcmp(argv[i],"--dbnum")) {
if (lastarg) goto invalid;
config.dbnum = atoi(argv[++i]);
config.dbnumstr = sdsfromlonglong(config.dbnum);
} else if (!strcmp(argv[i],"--help")) {
exit_status = 0;
goto usage;
@@ -446,14 +553,14 @@ usage:
" -c <clients> Number of parallel connections (default 50)\n"
" -n <requests> Total number of requests (default 10000)\n"
" -d <size> Data size of SET/GET value in bytes (default 2)\n"
" -dbnum <db> SELECT the specified db number (default 0)\n"
" -k <boolean> 1=keep alive 0=reconnect (default 1)\n"
" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n"
" Using this option the benchmark will get/set keys\n"
" in the form mykey_rand:000000012456 instead of constant\n"
" keys, the <keyspacelen> argument determines the max\n"
" number of values for the random number. For instance\n"
" if set to 10 only rand:000000000000 - rand:000000000009\n"
" range will be allowed.\n"
" Using this option the benchmark will expand the string __rand_int__\n"
" inside an argument with a 12 digits number in the specified range\n"
" from 0 to keyspacelen-1. The substitution changes every time a command\n"
" is executed. Default tests use this to hit random keys in the\n"
" specified range.\n"
" -P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline).\n"
" -q Quiet. Just show query/sec values\n"
" --csv Output in CSV format\n"
@@ -470,8 +577,12 @@ usage:
" $ redis-benchmark -t set -n 1000000 -r 100000000\n\n"
" Benchmark 127.0.0.1:6379 for a few commands producing CSV output:\n"
" $ redis-benchmark -t ping,set,get -n 100000 --csv\n\n"
" Benchmark a specific command line:\n"
" $ redis-benchmark -r 10000 -n 10000 eval 'return redis.call(\"ping\")' 0\n\n"
" Fill a list with 10000 random elements:\n"
" $ redis-benchmark -r 10000 -n 10000 lpush mylist ele:rand:000000000000\n\n"
" $ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__\n\n"
" On user specified command lines __rand_int__ is replaced with a random integer\n"
" with a range of values selected by the -r option.\n"
);
exit(exit_status);
}
@@ -510,6 +621,7 @@ int main(int argc, const char **argv) {
client c;
srandom(time(NULL));
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
@@ -533,6 +645,7 @@ int main(int argc, const char **argv) {
config.hostport = 6379;
config.hostsocket = NULL;
config.tests = NULL;
config.dbnum = 0;
i = parseOptions(argc,argv);
argc -= i;
@@ -546,7 +659,7 @@ int main(int argc, const char **argv) {
if (config.idlemode) {
printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
c = createClient("",0); /* will never receive a reply */
c = createClient("",0,NULL); /* will never receive a reply */
createMissingClients(c);
aeMain(config.el);
/* and will wait for every */
@@ -585,19 +698,19 @@ int main(int argc, const char **argv) {
}
if (test_is_selected("set")) {
len = redisFormatCommand(&cmd,"SET foo:rand:000000000000 %s",data);
len = redisFormatCommand(&cmd,"SET key:__rand_int__ %s",data);
benchmark("SET",cmd,len);
free(cmd);
}
if (test_is_selected("get")) {
len = redisFormatCommand(&cmd,"GET foo:rand:000000000000");
len = redisFormatCommand(&cmd,"GET key:__rand_int__");
benchmark("GET",cmd,len);
free(cmd);
}
if (test_is_selected("incr")) {
len = redisFormatCommand(&cmd,"INCR counter:rand:000000000000");
len = redisFormatCommand(&cmd,"INCR counter:__rand_int__");
benchmark("INCR",cmd,len);
free(cmd);
}
@@ -616,7 +729,7 @@ int main(int argc, const char **argv) {
if (test_is_selected("sadd")) {
len = redisFormatCommand(&cmd,
"SADD myset counter:rand:000000000000");
"SADD myset element:__rand_int__");
benchmark("SADD",cmd,len);
free(cmd);
}
@@ -666,7 +779,7 @@ int main(int argc, const char **argv) {
const char *argv[21];
argv[0] = "MSET";
for (i = 1; i < 21; i += 2) {
argv[i] = "foo:rand:000000000000";
argv[i] = "key:__rand_int__";
argv[i+1] = data;
}
len = redisFormatCommandArgv(&cmd,21,argv,NULL);
+2 -4
View File
@@ -40,6 +40,7 @@
#include <stdint.h>
#include <limits.h>
#include "lzf.h"
#include "crc64.h"
/* Object types */
#define REDIS_STRING 0
@@ -59,7 +60,7 @@
#define REDIS_ENCODING_RAW 0 /* Raw representation */
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
#define REDIS_ENCODING_ZIPMAP 2 /* Encoded as zipmap */
#define REDIS_ENCODING_HT 3 /* Encoded as an hash table */
#define REDIS_ENCODING_HT 3 /* Encoded as a hash table */
/* Object types only used for dumping to disk */
#define REDIS_EXPIRETIME_MS 252
@@ -140,9 +141,6 @@ static double R_Zero, R_PosInf, R_NegInf, R_Nan;
/* store string types for output */
static char types[256][16];
/* Prototypes */
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
/* Return true if 't' is a valid object type. */
int checkType(unsigned char t) {
/* In case a new object type is added, update the following
+488 -97
View File
@@ -58,6 +58,7 @@
#define OUTPUT_RAW 1
#define OUTPUT_CSV 2
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
static redisContext *context;
static struct config {
@@ -77,8 +78,13 @@ static struct config {
int cluster_reissue_command;
int slave_mode;
int pipe_mode;
int pipe_timeout;
int getrdb_mode;
int stat_mode;
int scan_mode;
int intrinsic_latency_mode;
int intrinsic_latency_duration;
char *pattern;
char *rdb_filename;
int bigkeys;
int stdinarg; /* get last arg from stdin. (-x option) */
@@ -90,6 +96,7 @@ static struct config {
} config;
static void usage();
static void slaveMode(void);
char *redisGitSHA1(void);
char *redisGitDirty(void);
@@ -97,14 +104,18 @@ char *redisGitDirty(void);
* Utility functions
*--------------------------------------------------------------------------- */
static long long mstime(void) {
static long long ustime(void) {
struct timeval tv;
long long mst;
long long ust;
gettimeofday(&tv, NULL);
mst = ((long long)tv.tv_sec)*1000;
mst += tv.tv_usec/1000;
return mst;
ust = ((long long)tv.tv_sec)*1000000;
ust += tv.tv_usec;
return ust;
}
static long long mstime(void) {
return ustime()/1000;
}
static void cliRefreshPrompt(void) {
@@ -114,7 +125,8 @@ static void cliRefreshPrompt(void) {
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
config.hostsocket);
else
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d",
len = snprintf(config.prompt,sizeof(config.prompt),
strchr(config.hostip,':') ? "[%s]:%d" : "%s:%d",
config.hostip, config.hostport);
/* Add [dbnum] if needed */
if (config.dbnum != 0)
@@ -594,6 +606,8 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
if (!strcasecmp(command,"subscribe") ||
!strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
if (!strcasecmp(command,"sync") ||
!strcasecmp(command,"psync")) config.slave_mode = 1;
/* Setup argument length */
argvlen = malloc(argc*sizeof(size_t));
@@ -615,6 +629,13 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
}
}
if (config.slave_mode) {
printf("Entering slave output mode... (press Ctrl-C to quit)\n");
slaveMode();
config.slave_mode = 0;
return REDIS_ERR; /* Error = slaveMode lost connection to master */
}
if (cliReadReply(output_raw) != REDIS_OK) {
free(argvlen);
return REDIS_ERR;
@@ -708,11 +729,20 @@ static int parseOptions(int argc, char **argv) {
config.slave_mode = 1;
} else if (!strcmp(argv[i],"--stat")) {
config.stat_mode = 1;
} else if (!strcmp(argv[i],"--scan")) {
config.scan_mode = 1;
} else if (!strcmp(argv[i],"--pattern") && !lastarg) {
config.pattern = argv[++i];
} else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
config.intrinsic_latency_mode = 1;
config.intrinsic_latency_duration = atoi(argv[++i]);
} else if (!strcmp(argv[i],"--rdb") && !lastarg) {
config.getrdb_mode = 1;
config.rdb_filename = argv[++i];
} else if (!strcmp(argv[i],"--pipe")) {
config.pipe_mode = 1;
} else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) {
config.pipe_timeout = atoi(argv[++i]);
} else if (!strcmp(argv[i],"--bigkeys")) {
config.bigkeys = 1;
} else if (!strcmp(argv[i],"--eval") && !lastarg) {
@@ -765,29 +795,37 @@ static void usage() {
"redis-cli %s\n"
"\n"
"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
" -h <hostname> Server hostname (default: 127.0.0.1)\n"
" -p <port> Server port (default: 6379)\n"
" -s <socket> Server socket (overrides hostname and port)\n"
" -a <password> Password to use when connecting to the server\n"
" -r <repeat> Execute specified command N times\n"
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
" It is possible to specify sub-second times like -i 0.1\n"
" -n <db> Database number\n"
" -x Read last argument from STDIN\n"
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n"
" -c Enable cluster mode (follow -ASK and -MOVED redirections)\n"
" --raw Use raw formatting for replies (default when STDOUT is\n"
" not a tty)\n"
" --latency Enter a special mode continuously sampling latency\n"
" --latency-history Like --latency but tracking latency changes over time.\n"
" Default time interval is 15 sec. Change it using -i.\n"
" --slave Simulate a slave showing commands received from the master\n"
" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
" --pipe Transfer raw Redis protocol from stdin to server\n"
" --bigkeys Sample Redis keys looking for big keys\n"
" --eval <file> Send an EVAL command using the Lua script at <file>\n"
" --help Output this help and exit\n"
" --version Output version and exit\n"
" -h <hostname> Server hostname (default: 127.0.0.1).\n"
" -p <port> Server port (default: 6379).\n"
" -s <socket> Server socket (overrides hostname and port).\n"
" -a <password> Password to use when connecting to the server.\n"
" -r <repeat> Execute specified command N times.\n"
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
" It is possible to specify sub-second times like -i 0.1.\n"
" -n <db> Database number.\n"
" -x Read last argument from STDIN.\n"
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n"
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
" --raw Use raw formatting for replies (default when STDOUT is\n"
" not a tty).\n"
" --csv Output in CSV format.\n"
" --latency Enter a special mode continuously sampling latency.\n"
" --latency-history Like --latency but tracking latency changes over time.\n"
" Default time interval is 15 sec. Change it using -i.\n"
" --slave Simulate a slave showing commands received from the master.\n"
" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
" --pipe Transfer raw Redis protocol from stdin to server.\n"
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
" no reply is received within <n> seconds.\n"
" Default timeout: %d. Use 0 to wait forever.\n"
" --bigkeys Sample Redis keys looking for big keys.\n"
" --scan List all keys using the SCAN command.\n"
" --pattern <pat> Useful with --scan to specify a SCAN pattern.\n"
" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
" The test will run for the specified amount of seconds.\n"
" --eval <file> Send an EVAL command using the Lua script at <file>.\n"
" --help Output this help and exit.\n"
" --version Output version and exit.\n"
"\n"
"Examples:\n"
" cat /etc/passwd | redis-cli -x set mypasswd\n"
@@ -795,12 +833,14 @@ static void usage() {
" redis-cli -r 100 lpush mylist x\n"
" redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
" redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
" redis-cli --scan --pattern '*:12345*'\n"
"\n"
" (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
"\n"
"When no command is given, redis-cli starts in interactive mode.\n"
"Type \"help\" in interactive mode for information on available commands.\n"
"\n",
version);
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
sdsfree(version);
exit(1);
}
@@ -825,6 +865,7 @@ static void repl() {
sds *argv;
config.interactive = 1;
linenoiseSetMultiLine(1);
linenoiseSetCompletionCallback(completionCallback);
/* Only use history when stdin is a tty. */
@@ -920,6 +961,10 @@ static int noninteractive(int argc, char **argv) {
return retval;
}
/*------------------------------------------------------------------------------
* Eval mode
*--------------------------------------------------------------------------- */
static int evalMode(int argc, char **argv) {
sds script = sdsempty();
FILE *fp;
@@ -958,6 +1003,10 @@ static int evalMode(int argc, char **argv) {
return cliSendCommand(argc+3-got_comma, argv2, config.repeat);
}
/*------------------------------------------------------------------------------
* Latency and latency history modes
*--------------------------------------------------------------------------- */
#define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
#define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
static void latencyMode(void) {
@@ -1002,6 +1051,10 @@ static void latencyMode(void) {
}
}
/*------------------------------------------------------------------------------
* Slave mode
*--------------------------------------------------------------------------- */
/* Sends SYNC and reads the number of bytes in the payload. Used both by
* slaveMode() and getRDB(). */
unsigned long long sendSync(int fd) {
@@ -1041,6 +1094,7 @@ static void slaveMode(void) {
int fd = context->fd;
unsigned long long payload = sendSync(fd);
char buf[1024];
int original_output = config.output;
fprintf(stderr,"SYNC with master, discarding %llu "
"bytes of bulk transfer...\n", payload);
@@ -1061,8 +1115,13 @@ static void slaveMode(void) {
/* Now we can use hiredis to read the incoming protocol. */
config.output = OUTPUT_CSV;
while (cliReadReply(0) == REDIS_OK);
config.output = original_output;
}
/*------------------------------------------------------------------------------
* RDB transfer mode
*--------------------------------------------------------------------------- */
/* This function implements --rdb, so it uses the replication protocol in order
* to fetch the RDB file from a remote server. */
static void getRDB(void) {
@@ -1108,6 +1167,10 @@ static void getRDB(void) {
exit(0);
}
/*------------------------------------------------------------------------------
* Bulk import (pipe) mode
*--------------------------------------------------------------------------- */
static void pipeMode(void) {
int fd = context->fd;
long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
@@ -1118,6 +1181,7 @@ static void pipeMode(void) {
int eof = 0; /* True once we consumed all the standard input. */
int done = 0;
char magic[20]; /* Special reply we recognize. */
time_t last_read_time = time(NULL);
srand(time(NULL));
@@ -1148,7 +1212,10 @@ static void pipeMode(void) {
strerror(errno));
exit(1);
}
if (nread > 0) redisReaderFeed(reader,ibuf,nread);
if (nread > 0) {
redisReaderFeed(reader,ibuf,nread);
last_read_time = time(NULL);
}
} while(nread > 0);
/* Consume replies. */
@@ -1203,8 +1270,12 @@ static void pipeMode(void) {
ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf));
if (nread == 0) {
/* The ECHO sequence starts with a "\r\n" so that if there
* is garbage in the protocol we read from stdin, the ECHO
* will likely still be properly formatted.
* CRLF is ignored by Redis, so it has no effects. */
char echo[] =
"*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
"\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
int j;
eof = 1;
@@ -1213,7 +1284,7 @@ static void pipeMode(void) {
* to make sure everything was read from the server. */
for (j = 0; j < 20; j++)
magic[j] = rand() & 0xff;
memcpy(echo+19,magic,20);
memcpy(echo+21,magic,20);
memcpy(obuf,echo,sizeof(echo)-1);
obuf_len = sizeof(echo)-1;
obuf_pos = 0;
@@ -1230,6 +1301,18 @@ static void pipeMode(void) {
if (obuf_len == 0 && eof) break;
}
}
/* Handle timeout, that is, we reached EOF, and we are not getting
* replies from the server for a few seconds, nor the final ECHO is
* received. */
if (eof && config.pipe_timeout > 0 &&
time(NULL)-last_read_time > config.pipe_timeout)
{
fprintf(stderr,"No replies for %d seconds: exiting.\n",
config.pipe_timeout);
errors++;
break;
}
}
redisReaderFree(reader);
printf("errors: %lld, replies: %lld\n", errors, replies);
@@ -1239,93 +1322,287 @@ static void pipeMode(void) {
exit(0);
}
/*------------------------------------------------------------------------------
* Find big keys
*--------------------------------------------------------------------------- */
#define TYPE_STRING 0
#define TYPE_LIST 1
#define TYPE_SET 2
#define TYPE_HASH 3
#define TYPE_ZSET 4
#define TYPE_NONE 5
static redisReply *sendScan(unsigned long long *it) {
redisReply *reply = redisCommand(context, "SCAN %llu", *it);
/* Handle any error conditions */
if(reply == NULL) {
fprintf(stderr, "\nI/O error\n");
exit(1);
} else if(reply->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "SCAN error: %s\n", reply->str);
exit(1);
} else if(reply->type != REDIS_REPLY_ARRAY) {
fprintf(stderr, "Non ARRAY response from SCAN!\n");
exit(1);
} else if(reply->elements != 2) {
fprintf(stderr, "Invalid element count from SCAN!\n");
exit(1);
}
/* Validate our types are correct */
assert(reply->element[0]->type == REDIS_REPLY_STRING);
assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
/* Update iterator */
*it = atoi(reply->element[0]->str);
return reply;
}
static int getDbSize(void) {
redisReply *reply;
int size;
reply = redisCommand(context, "DBSIZE");
if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
fprintf(stderr, "Couldn't determine DBSIZE!\n");
exit(1);
}
/* Grab the number of keys and free our reply */
size = reply->integer;
freeReplyObject(reply);
return size;
}
static int toIntType(char *key, char *type) {
if(!strcmp(type, "string")) {
return TYPE_STRING;
} else if(!strcmp(type, "list")) {
return TYPE_LIST;
} else if(!strcmp(type, "set")) {
return TYPE_SET;
} else if(!strcmp(type, "hash")) {
return TYPE_HASH;
} else if(!strcmp(type, "zset")) {
return TYPE_ZSET;
} else if(!strcmp(type, "none")) {
return TYPE_NONE;
} else {
fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key);
exit(1);
}
}
static void getKeyTypes(redisReply *keys, int *types) {
redisReply *reply;
int i;
/* Pipeline TYPE commands */
for(i=0;i<keys->elements;i++) {
redisAppendCommand(context, "TYPE %s", keys->element[i]->str);
}
/* Retrieve types */
for(i=0;i<keys->elements;i++) {
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n",
keys->element[i]->str, context->err, context->errstr);
exit(1);
} else if(reply->type != REDIS_REPLY_STATUS) {
fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n",
reply->type, keys->element[i]->str);
exit(1);
}
types[i] = toIntType(keys->element[i]->str, reply->str);
freeReplyObject(reply);
}
}
static void getKeySizes(redisReply *keys, int *types,
unsigned long long *sizes)
{
redisReply *reply;
char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
int i;
/* Pipeline size commands */
for(i=0;i<keys->elements;i++) {
/* Skip keys that were deleted */
if(types[i]==TYPE_NONE)
continue;
redisAppendCommand(context, "%s %s", sizecmds[types[i]],
keys->element[i]->str);
}
/* Retreive sizes */
for(i=0;i<keys->elements;i++) {
/* Skip keys that dissapeared between SCAN and TYPE */
if(types[i] == TYPE_NONE) {
sizes[i] = 0;
continue;
}
/* Retreive size */
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
keys->element[i]->str, context->err, context->errstr);
exit(1);
} else if(reply->type != REDIS_REPLY_INTEGER) {
/* Theoretically the key could have been removed and
* added as a different type between TYPE and SIZE */
fprintf(stderr,
"Warning: %s on '%s' failed (may have changed type)\n",
sizecmds[types[i]], keys->element[i]->str);
sizes[i] = 0;
} else {
sizes[i] = reply->integer;
}
freeReplyObject(reply);
}
}
static void findBigKeys(void) {
unsigned long long biggest[5] = {0,0,0,0,0};
unsigned long long samples = 0;
redisReply *reply1, *reply2, *reply3 = NULL;
char *sizecmd, *typename[] = {"string","list","set","hash","zset"};
unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0};
unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
sds maxkeys[5] = {0};
char *typename[] = {"string","list","set","hash","zset"};
char *typeunit[] = {"bytes","items","members","fields","members"};
int type;
redisReply *reply, *keys;
int type, *types=NULL, arrsize=0, i;
double pct;
printf("\n# Press ctrl+c when you have had enough of it... :)\n");
printf("# You can use -i 0.1 to sleep 0.1 sec every 100 sampled keys\n");
printf("# in order to reduce server load (usually not needed).\n\n");
while(1) {
/* Sample with RANDOMKEY */
reply1 = redisCommand(context,"RANDOMKEY");
if (reply1 == NULL) {
fprintf(stderr,"\nI/O error\n");
exit(1);
} else if (reply1->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "RANDOMKEY error: %s\n",
reply1->str);
exit(1);
} else if (reply1->type == REDIS_REPLY_NIL) {
fprintf(stderr, "It looks like the database is empty!\n");
/* Total keys pre scanning */
total_keys = getDbSize();
/* Status message */
printf("\n# Scanning the entire keyspace to find biggest keys as well as\n");
printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
printf("# per 100 SCAN commands (not usually needed).\n\n");
/* New up sds strings to keep track of overall biggest per type */
for(i=0;i<TYPE_NONE; i++) {
maxkeys[i] = sdsempty();
if(!maxkeys[i]) {
fprintf(stderr, "Failed to allocate memory for largest key names!");
exit(1);
}
}
/* Get the key type */
reply2 = redisCommand(context,"TYPE %s",reply1->str);
assert(reply2 && reply2->type == REDIS_REPLY_STATUS);
samples++;
/* SCAN loop */
do {
/* Calculate approximate percentage completion */
pct = 100 * (double)sampled/total_keys;
/* Get the key "size" */
if (!strcmp(reply2->str,"string")) {
sizecmd = "STRLEN";
type = TYPE_STRING;
} else if (!strcmp(reply2->str,"list")) {
sizecmd = "LLEN";
type = TYPE_LIST;
} else if (!strcmp(reply2->str,"set")) {
sizecmd = "SCARD";
type = TYPE_SET;
} else if (!strcmp(reply2->str,"hash")) {
sizecmd = "HLEN";
type = TYPE_HASH;
} else if (!strcmp(reply2->str,"zset")) {
sizecmd = "ZCARD";
type = TYPE_ZSET;
} else if (!strcmp(reply2->str,"none")) {
freeReplyObject(reply1);
freeReplyObject(reply2);
freeReplyObject(reply3);
continue;
} else {
fprintf(stderr, "Unknown key type '%s' for key '%s'\n",
reply2->str, reply1->str);
exit(1);
/* Grab some keys and point to the keys array */
reply = sendScan(&it);
keys = reply->element[1];
/* Reallocate our type and size array if we need to */
if(keys->elements > arrsize) {
types = zrealloc(types, sizeof(int)*keys->elements);
sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
if(!types || !sizes) {
fprintf(stderr, "Failed to allocate storage for keys!\n");
exit(1);
}
arrsize = keys->elements;
}
reply3 = redisCommand(context,"%s %s", sizecmd, reply1->str);
if (reply3 && reply3->type == REDIS_REPLY_INTEGER) {
if (biggest[type] < reply3->integer) {
printf("Biggest %-6s found so far '%s' with %llu %s.\n",
typename[type], reply1->str,
(unsigned long long) reply3->integer,
typeunit[type]);
biggest[type] = reply3->integer;
/* Retreive types and then sizes */
getKeyTypes(keys, types);
getKeySizes(keys, types, sizes);
/* Now update our stats */
for(i=0;i<keys->elements;i++) {
if((type = types[i]) == TYPE_NONE)
continue;
totalsize[type] += sizes[i];
counts[type]++;
totlen += keys->element[i]->len;
sampled++;
if(biggest[type]<sizes[i]) {
printf(
"[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
pct, typename[type], keys->element[i]->str, sizes[i],
typeunit[type]);
/* Keep track of biggest key name for this type */
maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str);
if(!maxkeys[type]) {
fprintf(stderr, "Failed to allocate memory for key!\n");
exit(1);
}
/* Keep track of the biggest size for this type */
biggest[type] = sizes[i];
}
/* Update overall progress */
if(sampled % 1000000 == 0) {
printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
}
}
if ((samples % 1000000) == 0)
printf("(%llu keys sampled)\n", samples);
if ((samples % 100) == 0 && config.interval)
/* Sleep if we've been directed to do so */
if(sampled && (sampled %100) == 0 && config.interval) {
usleep(config.interval);
}
freeReplyObject(reply);
} while(it != 0);
freeReplyObject(reply1);
freeReplyObject(reply2);
if (reply3) freeReplyObject(reply3);
if(types) zfree(types);
if(sizes) zfree(sizes);
/* We're done */
printf("\n-------- summary -------\n\n");
printf("Sampled %llu keys in the keyspace!\n", sampled);
printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
totlen, totlen ? (double)totlen/sampled : 0);
/* Output the biggest keys we found, for types we did find */
for(i=0;i<TYPE_NONE;i++) {
if(sdslen(maxkeys[i])>0) {
printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i],
biggest[i], typeunit[i]);
}
}
printf("\n");
for(i=0;i<TYPE_NONE;i++) {
printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
counts[i], typename[i], totalsize[i], typeunit[i],
sampled ? 100 * (double)counts[i]/sampled : 0,
counts[i] ? (double)totalsize[i]/counts[i] : 0);
}
/* Free sds strings containing max keys */
for(i=0;i<TYPE_NONE;i++) {
sdsfree(maxkeys[i]);
}
/* Success! */
exit(0);
}
/*------------------------------------------------------------------------------
* Stats mode
*--------------------------------------------------------------------------- */
/* Return the specified INFO field from the INFO command output "info".
* A new buffer is allocated for the result, that needs to be free'd.
* If the field is not found NULL is returned. */
@@ -1465,6 +1742,106 @@ static void statMode() {
}
}
/*------------------------------------------------------------------------------
* Scan mode
*--------------------------------------------------------------------------- */
static void scanMode() {
redisReply *reply;
unsigned long long cur = 0;
do {
if (config.pattern)
reply = redisCommand(context,"SCAN %llu MATCH %s",
cur,config.pattern);
else
reply = redisCommand(context,"SCAN %llu",cur);
if (reply == NULL) {
printf("I/O error\n");
exit(1);
} else if (reply->type == REDIS_REPLY_ERROR) {
printf("ERROR: %s\n", reply->str);
exit(1);
} else {
int j;
cur = strtoull(reply->element[0]->str,NULL,10);
for (j = 0; j < reply->element[1]->elements; j++)
printf("%s\n", reply->element[1]->element[j]->str);
}
freeReplyObject(reply);
} while(cur != 0);
exit(0);
}
/*------------------------------------------------------------------------------
* Intrisic latency mode.
*
* Measure max latency of a running process that does not result from
* syscalls. Basically this software should provide an hint about how much
* time the kernel leaves the process without a chance to run.
*--------------------------------------------------------------------------- */
/* This is just some computation the compiler can't optimize out.
* Should run in less than 100-200 microseconds even using very
* slow hardware. Runs in less than 10 microseconds in modern HW. */
unsigned long compute_something_fast(void) {
unsigned char s[256], i, j, t;
int count = 1000, k;
unsigned long output = 0;
for (k = 0; k < 256; k++) s[k] = k;
i = 0;
j = 0;
while(count--) {
i++;
j = j + s[i];
t = s[i];
s[i] = s[j];
s[j] = t;
output += s[(s[i]+s[j])&255];
}
return output;
}
static void intrinsicLatencyMode(void) {
long long test_end, run_time, max_latency = 0, runs = 0;
run_time = config.intrinsic_latency_duration*1000000;
test_end = ustime() + run_time;
while(1) {
long long start, end, latency;
start = ustime();
compute_something_fast();
end = ustime();
latency = end-start;
runs++;
if (latency <= 0) continue;
/* Reporting */
if (latency > max_latency) {
max_latency = latency;
printf("Max latency so far: %lld microseconds.\n", max_latency);
}
if (end > test_end) {
printf("\n%lld total runs (avg %lld microseconds per run).\n",
runs, run_time/runs);
printf("Worst run took %.02fx times the average.\n",
(double) max_latency / (run_time/runs));
exit(0);
}
}
}
/*------------------------------------------------------------------------------
* Program main()
*--------------------------------------------------------------------------- */
int main(int argc, char **argv) {
int firstarg;
@@ -1483,8 +1860,13 @@ int main(int argc, char **argv) {
config.cluster_mode = 0;
config.slave_mode = 0;
config.getrdb_mode = 0;
config.stat_mode = 0;
config.scan_mode = 0;
config.intrinsic_latency_mode = 0;
config.pattern = NULL;
config.rdb_filename = NULL;
config.pipe_mode = 0;
config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
config.bigkeys = 0;
config.stdinarg = 0;
config.auth = NULL;
@@ -1537,6 +1919,15 @@ int main(int argc, char **argv) {
statMode();
}
/* Scan mode */
if (config.scan_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
scanMode();
}
/* Intrinsic latency mode */
if (config.intrinsic_latency_mode) intrinsicLatencyMode();
/* Start interactive mode when no command is provided */
if (argc == 0 && !config.eval) {
/* Note that in repl mode we don't abort on connection error.
+625 -190
View File
File diff suppressed because it is too large Load Diff
+216 -40
View File
@@ -71,11 +71,10 @@
#define REDIS_MIN_HZ 1
#define REDIS_MAX_HZ 500
#define REDIS_SERVERPORT 6379 /* TCP port */
#define REDIS_TCP_BACKLOG 511 /* TCP listen backlog */
#define REDIS_MAXIDLETIME 0 /* default client timeout: infinite */
#define REDIS_DEFAULT_DBNUM 16
#define REDIS_CONFIGLINE_MAX 1024
#define REDIS_EXPIRELOOKUPS_PER_CRON 10 /* lookup 10 expires per loop */
#define REDIS_EXPIRELOOKUPS_TIME_PERC 25 /* CPU max % for keys collection */
#define REDIS_DBCRON_DBS_PER_CALL 16
#define REDIS_MAX_WRITE_PER_EVENT (1024*64)
#define REDIS_SHARED_SELECT_CMDS 10
@@ -83,7 +82,7 @@
#define REDIS_SHARED_BULKHDR_LEN 32
#define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */
#define REDIS_AOF_REWRITE_PERC 100
#define REDIS_AOF_REWRITE_MIN_SIZE (1024*1024)
#define REDIS_AOF_REWRITE_MIN_SIZE (64*1024*1024)
#define REDIS_AOF_REWRITE_ITEMS_PER_CMD 64
#define REDIS_SLOWLOG_LOG_SLOWER_THAN 10000
#define REDIS_SLOWLOG_MAX_LEN 128
@@ -94,7 +93,43 @@
#define REDIS_REPL_PING_SLAVE_PERIOD 10
#define REDIS_RUN_ID_SIZE 40
#define REDIS_OPS_SEC_SAMPLES 16
#define REDIS_DEFAULT_REPL_BACKLOG_SIZE (1024*1024) /* 1mb */
#define REDIS_DEFAULT_REPL_BACKLOG_TIME_LIMIT (60*60) /* 1 hour */
#define REDIS_REPL_BACKLOG_MIN_SIZE (1024*16) /* 16k */
#define REDIS_BGSAVE_RETRY_DELAY 5 /* Wait a few secs before trying again. */
#define REDIS_DEFAULT_PID_FILE "/var/run/redis.pid"
#define REDIS_DEFAULT_SYSLOG_IDENT "redis"
#define REDIS_DEFAULT_CLUSTER_CONFIG_FILE "nodes.conf"
#define REDIS_DEFAULT_DAEMONIZE 0
#define REDIS_DEFAULT_UNIX_SOCKET_PERM 0
#define REDIS_DEFAULT_TCP_KEEPALIVE 0
#define REDIS_DEFAULT_LOGFILE ""
#define REDIS_DEFAULT_SYSLOG_ENABLED 0
#define REDIS_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
#define REDIS_DEFAULT_RDB_COMPRESSION 1
#define REDIS_DEFAULT_RDB_CHECKSUM 1
#define REDIS_DEFAULT_RDB_FILENAME "dump.rdb"
#define REDIS_DEFAULT_SLAVE_SERVE_STALE_DATA 1
#define REDIS_DEFAULT_SLAVE_READ_ONLY 1
#define REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
#define REDIS_DEFAULT_MAXMEMORY 0
#define REDIS_DEFAULT_MAXMEMORY_SAMPLES 3
#define REDIS_DEFAULT_AOF_FILENAME "appendonly.aof"
#define REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
#define REDIS_DEFAULT_ACTIVE_REHASHING 1
#define REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1
#define REDIS_DEFAULT_MIN_SLAVES_TO_WRITE 0
#define REDIS_DEFAULT_MIN_SLAVES_MAX_LAG 10
#define REDIS_IP_STR_LEN INET6_ADDRSTRLEN
#define REDIS_PEER_ID_LEN (REDIS_IP_STR_LEN+32) /* Must be enough for ip:port */
#define REDIS_BINDADDR_MAX 16
#define REDIS_MIN_RESERVED_FDS 32
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* CPU max % for keys collection */
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
#define ACTIVE_EXPIRE_CYCLE_FAST 1
/* Protocol and I/O related defines */
#define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */
@@ -102,7 +137,12 @@
#define REDIS_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */
#define REDIS_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */
#define REDIS_MBULK_BIG_ARG (1024*32)
#define REDIS_LONGSTR_SIZE 21 /* Bytes needed for long -> str */
#define REDIS_AOF_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */
/* When configuring the Redis eventloop, we setup it so that the total number
* of file descriptors we can handle are server.maxclients + RESERVED_FDS + FDSET_INCR
* that is our safety margin. */
#define REDIS_EVENTLOOP_FDSET_INCR (REDIS_MIN_RESERVED_FDS+96)
/* Hash table parameters */
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
@@ -112,7 +152,7 @@
#define REDIS_CMD_WRITE 1 /* "w" flag */
#define REDIS_CMD_READONLY 2 /* "r" flag */
#define REDIS_CMD_DENYOOM 4 /* "m" flag */
#define REDIS_CMD_FORCE_REPLICATION 8 /* "f" flag */
#define REDIS_CMD_NOT_USED_1 8 /* no longer used flag */
#define REDIS_CMD_ADMIN 16 /* "a" flag */
#define REDIS_CMD_PUBSUB 32 /* "p" flag */
#define REDIS_CMD_NOSCRIPT 64 /* "s" flag */
@@ -188,6 +228,10 @@
#define REDIS_CLOSE_ASAP (1<<10)/* Close this client ASAP */
#define REDIS_UNIX_SOCKET (1<<11) /* Client connected via Unix domain socket */
#define REDIS_DIRTY_EXEC (1<<12) /* EXEC will fail for errors while queueing */
#define REDIS_MASTER_FORCE_REPLY (1<<13) /* Queue replies even if is master */
#define REDIS_FORCE_AOF (1<<14) /* Force AOF propagation of current cmd. */
#define REDIS_FORCE_REPL (1<<15) /* Force replication of current cmd. */
#define REDIS_PRE_PSYNC (1<<16) /* Instance don't understand PSYNC. */
/* Client request types */
#define REDIS_REQ_INLINE 1
@@ -200,7 +244,7 @@
#define REDIS_CLIENT_LIMIT_CLASS_PUBSUB 2
#define REDIS_CLIENT_LIMIT_NUM_CLASSES 3
/* Slave replication state - slave side */
/* Slave replication state - from the point of view of the slave. */
#define REDIS_REPL_NONE 0 /* No active replication */
#define REDIS_REPL_CONNECT 1 /* Must connect to master */
#define REDIS_REPL_CONNECTING 2 /* Connecting to master */
@@ -208,17 +252,17 @@
#define REDIS_REPL_TRANSFER 4 /* Receiving .rdb from master */
#define REDIS_REPL_CONNECTED 5 /* Connected to master */
/* Synchronous read timeout - slave side */
#define REDIS_REPL_SYNCIO_TIMEOUT 5
/* Slave replication state - from the point of view of master
* Note that in SEND_BULK and ONLINE state the slave receives new updates
/* Slave replication state - from the point of view of the master.
* In SEND_BULK and ONLINE state the slave receives new updates
* in its output queue. In the WAIT_BGSAVE state instead the server is waiting
* to start the next background saving in order to send updates to it. */
#define REDIS_REPL_WAIT_BGSAVE_START 3 /* master waits bgsave to start feeding it */
#define REDIS_REPL_WAIT_BGSAVE_END 4 /* master waits bgsave to start bulk DB transmission */
#define REDIS_REPL_SEND_BULK 5 /* master is sending the bulk DB */
#define REDIS_REPL_ONLINE 6 /* bulk DB already transmitted, receive updates */
#define REDIS_REPL_WAIT_BGSAVE_START 6 /* We need to produce a new RDB file. */
#define REDIS_REPL_WAIT_BGSAVE_END 7 /* Waiting RDB file creation to finish. */
#define REDIS_REPL_SEND_BULK 8 /* Sending RDB file to slave. */
#define REDIS_REPL_ONLINE 9 /* RDB file transmitted, sending just updates. */
/* Synchronous read timeout - slave side */
#define REDIS_REPL_SYNCIO_TIMEOUT 5
/* List related stuff */
#define REDIS_HEAD 0
@@ -236,6 +280,7 @@
#define REDIS_NOTICE 2
#define REDIS_WARNING 3
#define REDIS_LOG_RAW (1<<10) /* Modifier to log without timestamp */
#define REDIS_DEFAULT_VERBOSITY REDIS_NOTICE
/* Anti-warning macro... */
#define REDIS_NOTUSED(V) ((void) V)
@@ -247,6 +292,7 @@
#define AOF_FSYNC_NO 0
#define AOF_FSYNC_ALWAYS 1
#define AOF_FSYNC_EVERYSEC 2
#define REDIS_DEFAULT_AOF_FSYNC AOF_FSYNC_EVERYSEC
/* Zip structure related defaults */
#define REDIS_HASH_MAX_ZIPLIST_ENTRIES 512
@@ -257,6 +303,9 @@
#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128
#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64
/* HyperLogLog defines */
#define REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES 3000
/* Sets operations codes */
#define REDIS_OP_UNION 0
#define REDIS_OP_DIFF 1
@@ -269,6 +318,7 @@
#define REDIS_MAXMEMORY_ALLKEYS_LRU 3
#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4
#define REDIS_MAXMEMORY_NO_EVICTION 5
#define REDIS_DEFAULT_MAXMEMORY_POLICY REDIS_MAXMEMORY_VOLATILE_LRU
/* Scripting */
#define REDIS_LUA_TIME_LIMIT 5000 /* milliseconds */
@@ -294,6 +344,20 @@
#define REDIS_PROPAGATE_AOF 1
#define REDIS_PROPAGATE_REPL 2
/* Keyspace changes notification classes. Every class is associated with a
* character for configuration purposes. */
#define REDIS_NOTIFY_KEYSPACE (1<<0) /* K */
#define REDIS_NOTIFY_KEYEVENT (1<<1) /* E */
#define REDIS_NOTIFY_GENERIC (1<<2) /* g */
#define REDIS_NOTIFY_STRING (1<<3) /* $ */
#define REDIS_NOTIFY_LIST (1<<4) /* l */
#define REDIS_NOTIFY_SET (1<<5) /* s */
#define REDIS_NOTIFY_HASH (1<<6) /* h */
#define REDIS_NOTIFY_ZSET (1<<7) /* z */
#define REDIS_NOTIFY_EXPIRED (1<<8) /* x */
#define REDIS_NOTIFY_EVICTED (1<<9) /* e */
#define REDIS_NOTIFY_ALL (REDIS_NOTIFY_GENERIC | REDIS_NOTIFY_STRING | REDIS_NOTIFY_LIST | REDIS_NOTIFY_SET | REDIS_NOTIFY_HASH | REDIS_NOTIFY_ZSET | REDIS_NOTIFY_EXPIRED | REDIS_NOTIFY_EVICTED) /* A */
/* Using the following macro you can run code inside serverCron() with the
* specified period, specified in milliseconds.
* The actual resolution depends on server.hz. */
@@ -308,16 +372,18 @@
* Data types
*----------------------------------------------------------------------------*/
typedef long long mstime_t; /* millisecond time type. */
/* A redis object, that is a type able to hold a string / list / set */
/* The actual Redis Object */
#define REDIS_LRU_CLOCK_MAX ((1<<21)-1) /* Max value of obj->lru */
#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */
#define REDIS_LRU_BITS 24
#define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1) /* Max value of obj->lru */
#define REDIS_LRU_CLOCK_RESOLUTION 1 /* LRU clock resolution in seconds */
typedef struct redisObject {
unsigned type:4;
unsigned notused:2; /* Not used */
unsigned encoding:4;
unsigned lru:22; /* lru time (relative to server.lruclock) */
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr;
} robj;
@@ -340,6 +406,7 @@ typedef struct redisDb {
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id;
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
/* Client MULTI/EXEC state */
@@ -352,13 +419,15 @@ typedef struct multiCmd {
typedef struct multiState {
multiCmd *commands; /* Array of MULTI commands */
int count; /* Total number of MULTI commands */
int minreplicas; /* MINREPLICAS for synchronous replication */
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
} multiState;
typedef struct blockingState {
dict *keys; /* The keys we are waiting to terminate a blocking
* operation such as BLPOP. Otherwise NULL. */
time_t timeout; /* Blocking operation timeout. If UNIX current time
* is >= timeout then the operation timed out. */
* is > timeout then the operation timed out. */
robj *target; /* The key that should receive the element,
* for BRPOPLPUSH. */
} blockingState;
@@ -396,22 +465,24 @@ typedef struct redisClient {
long bulklen; /* length of bulk argument in multi bulk request */
list *reply;
unsigned long reply_bytes; /* Tot bytes of objects in reply list */
int sentlen;
int sentlen; /* Amount of bytes already sent in the current
buffer or object being sent. */
time_t ctime; /* Client creation time */
time_t lastinteraction; /* time of the last interaction, used for timeout */
time_t obuf_soft_limit_reached_time;
int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
int slaveseldb; /* slave selected db, if this client is a slave */
int authenticated; /* when requirepass is non-NULL */
int replstate; /* replication state if this is a slave */
int repldbfd; /* replication DB file descriptor */
long repldboff; /* replication DB file offset */
off_t repldboff; /* replication DB file offset */
off_t repldbsize; /* replication DB file size */
long long reploff; /* replication offset if this is our master */
long long repl_ack_off; /* replication ack offset, if this is a slave */
long long repl_ack_time;/* replication ack time, if this is a slave */
char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
multiState mstate; /* MULTI/EXEC state */
blockingState bpop; /* blocking state */
list *io_keys; /* Keys this client is waiting to be loaded from the
* swap file in order to continue. */
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
@@ -431,10 +502,10 @@ struct sharedObjectsStruct {
*colon, *nullbulk, *nullmultibulk, *queued,
*emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
*masterdownerr, *roslaveerr, *execaborterr,
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
*oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop,
*lpush,
*lpush, *emptyscan, *minstring, *maxstring,
*select[REDIS_SHARED_SELECT_CMDS],
*integers[REDIS_SHARED_INTEGERS],
*mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
@@ -469,6 +540,8 @@ typedef struct clientBufferLimitsConfig {
time_t soft_limit_seconds;
} clientBufferLimitsConfig;
extern clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_LIMIT_NUM_CLASSES];
/* The redisOp structure defines a Redis Operation, that is an instance of
* a command with an argument vector, database ID, propagation target
* (REDIS_PROPAGATE_*), and command pointer.
@@ -499,13 +572,13 @@ typedef struct redisOpArray {
struct redisServer {
/* General */
char *configfile; /* Absolute config file path, or NULL */
int hz; /* serverCron() calls frequency in hertz */
redisDb *db;
dict *commands; /* Command table */
dict *orig_commands; /* Command table before command renaming. */
aeEventLoop *el;
unsigned lruclock:22; /* Clock incrementing every minute, for LRU */
unsigned lruclock_padding:10;
unsigned lruclock:REDIS_LRU_BITS; /* Clock for LRU eviction */
int shutdown_asap; /* SHUTDOWN needed ASAP */
int activerehashing; /* Incremental rehash in serverCron() */
char *requirepass; /* Pass for AUTH command, or NULL */
@@ -516,10 +589,13 @@ struct redisServer {
int sentinel_mode; /* True if this instance is a Sentinel. */
/* Networking */
int port; /* TCP listening port */
char *bindaddr; /* Bind address or NULL */
int tcp_backlog; /* TCP listen() backlog */
char *bindaddr[REDIS_BINDADDR_MAX]; /* Addresses we should bind to */
int bindaddr_count; /* Number of addresses in server.bindaddr[] */
char *unixsocket; /* UNIX socket path */
mode_t unixsocketperm; /* UNIX socket permission */
int ipfd; /* TCP socket file descriptor */
int ipfd[REDIS_BINDADDR_MAX]; /* TCP socket file descriptors */
int ipfd_count; /* Used slots in ipfd[] */
int sofd; /* Unix socket file descriptor */
list *clients; /* List of active clients */
list *clients_to_close; /* Clients to close asynchronously */
@@ -531,6 +607,7 @@ struct redisServer {
off_t loading_total_bytes;
off_t loading_loaded_bytes;
time_t loading_start_time;
off_t loading_process_events_interval_bytes;
/* Fast pointers to often looked up command */
struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand,
*rpopCommand;
@@ -545,10 +622,14 @@ struct redisServer {
size_t stat_peak_memory; /* Max used memory record */
long long stat_fork_time; /* Time needed to perform latest fork() */
long long stat_rejected_conn; /* Clients rejected because of maxclients */
long long stat_sync_full; /* Number of full resyncs with slaves. */
long long stat_sync_partial_ok; /* Number of accepted PSYNC requests. */
long long stat_sync_partial_err;/* Number of unaccepted PSYNC requests. */
list *slowlog; /* SLOWLOG list of commands */
long long slowlog_entry_id; /* SLOWLOG current entry ID */
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
size_t resident_set_size; /* RSS sampled in serverCron(). */
/* The following two are used to track instantaneous "load" in terms
* of operations per second. */
long long ops_sec_last_sample_time; /* Timestamp of last sample (in ms) */
@@ -586,6 +667,8 @@ struct redisServer {
int aof_lastbgrewrite_status; /* REDIS_OK or REDIS_ERR */
unsigned long aof_delayed_fsync; /* delayed AOF fsync() counter */
int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
int aof_last_write_status; /* REDIS_OK or REDIS_ERR */
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
/* RDB persistence */
long long dirty; /* Changes to DB from the last save */
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
@@ -608,13 +691,30 @@ struct redisServer {
int syslog_enabled; /* Is syslog enabled? */
char *syslog_ident; /* Syslog ident */
int syslog_facility; /* Syslog facility */
/* Slave specific fields */
/* Replication (master) */
int slaveseldb; /* Last SELECTed DB in replication output */
long long master_repl_offset; /* Global replication offset */
int repl_ping_slave_period; /* Master pings the slave every N seconds */
char *repl_backlog; /* Replication backlog for partial syncs */
long long repl_backlog_size; /* Backlog circular buffer size */
long long repl_backlog_histlen; /* Backlog actual data length */
long long repl_backlog_idx; /* Backlog circular buffer current offset */
long long repl_backlog_off; /* Replication offset of first byte in the
backlog buffer. */
time_t repl_backlog_time_limit; /* Time without slaves after the backlog
gets released. */
time_t repl_no_slaves_since; /* We have no slaves since that time.
Only valid if server.slaves len is 0. */
int repl_min_slaves_to_write; /* Min number of slaves to write. */
int repl_min_slaves_max_lag; /* Max lag of <count> slaves to write. */
int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */
/* Replication (slave) */
char *masterauth; /* AUTH with this password with master */
char *masterhost; /* Hostname of master */
int masterport; /* Port of master */
int repl_ping_slave_period; /* Master pings the slave every N seconds */
int repl_timeout; /* Timeout after N seconds of master idle */
redisClient *master; /* Client that is master for this slave */
redisClient *cached_master; /* Cached master to be reused for PSYNC. */
int repl_syncio_timeout; /* Timeout for synchronous I/O calls */
int repl_state; /* Replication status if the instance is a slave */
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
@@ -629,8 +729,14 @@ struct redisServer {
time_t repl_down_since; /* Unix time at which link with master went down */
int repl_disable_tcp_nodelay; /* Disable TCP_NODELAY after SYNC? */
int slave_priority; /* Reported in INFO and used by Sentinel. */
char repl_master_runid[REDIS_RUN_ID_SIZE+1]; /* Master run id for PSYNC. */
long long repl_master_initial_offset; /* Master PSYNC offset. */
/* Replication script cache. */
dict *repl_scriptcache_dict; /* SHA1 all slaves are aware of. */
list *repl_scriptcache_fifo; /* First in, first out LRU eviction. */
int repl_scriptcache_size; /* Max number of elements. */
/* Limits */
unsigned int maxclients; /* Max number of simultaneous clients */
int maxclients; /* Max number of simultaneous clients */
unsigned long long maxmemory; /* Max number of memory bytes to use */
int maxmemory_policy; /* Policy for key eviction */
int maxmemory_samples; /* Pricision of random sampling */
@@ -643,6 +749,7 @@ struct redisServer {
int sort_desc;
int sort_alpha;
int sort_bypattern;
int sort_store;
/* Zip structure config, see redis.conf for more information */
size_t hash_max_ziplist_entries;
size_t hash_max_ziplist_value;
@@ -651,17 +758,21 @@ struct redisServer {
size_t set_max_intset_entries;
size_t zset_max_ziplist_entries;
size_t zset_max_ziplist_value;
time_t unixtime; /* Unix time sampled every second. */
size_t hll_sparse_max_bytes;
time_t unixtime; /* Unix time sampled every cron cycle. */
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
/* Pubsub */
dict *pubsub_channels; /* Map channels to list of subscribed clients */
list *pubsub_patterns; /* A list of pubsub_patterns */
int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
xor of REDIS_NOTIFY... flags. */
/* Scripting */
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
redisClient *lua_client; /* The "fake client" to query Redis from Lua */
redisClient *lua_caller; /* The client running EVAL right now, or NULL */
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
long long lua_time_limit; /* Script timeout in seconds */
long long lua_time_start; /* Start time of script */
mstime_t lua_time_limit; /* Script timeout in milliseconds */
mstime_t lua_time_start; /* Start time of script, milliseconds time */
int lua_write_dirty; /* True if a write command was called during the
execution of the current script. */
int lua_random_dirty; /* True if a random command was called during the
@@ -770,6 +881,7 @@ extern dictType dbDictType;
extern dictType shaScriptObjectDictType;
extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
extern dictType hashDictType;
extern dictType replScriptCacheDictType;
/*-----------------------------------------------------------------------------
* Functions prototypes
@@ -781,11 +893,14 @@ long long mstime(void);
void getRandomHexChars(char *p, unsigned int len);
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
void exitFromChild(int retcode);
size_t redisPopcount(void *s, long count);
void redisSetProcTitle(char *title);
/* networking.c -- Networking and Client related operations */
redisClient *createClient(int fd);
void closeTimedoutClients(void);
void freeClient(redisClient *c);
void freeClientAsync(redisClient *c);
void resetClient(redisClient *c);
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
void addReply(redisClient *c, robj *obj);
@@ -812,6 +927,8 @@ void copyClientOutputBuffer(redisClient *dst, redisClient *src);
void *dupClientReplyValue(void *o);
void getClientsMaxBuffers(unsigned long *longest_output_list,
unsigned long *biggest_input_buffer);
void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port);
int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len);
sds getClientInfoString(redisClient *client);
sds getAllClientsInfoString(void);
void rewriteClientCommandVector(redisClient *c, int argc, ...);
@@ -823,6 +940,7 @@ int getClientLimitClassByName(char *name);
char *getClientLimitClassName(int class);
void flushSlavesOutputBuffers(void);
void disconnectSlaves(void);
int processEventsWhileBlocked(void);
#ifdef __GNUC__
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
@@ -850,6 +968,7 @@ void listTypeConvert(robj *subject, int enc);
void unblockClientWaitingData(redisClient *c);
void handleClientsBlockedOnLists(void);
void popGenericCommand(redisClient *c, int where);
void signalListAsReady(redisDb *db, robj *key);
/* MULTI/EXEC/WATCH... */
void unwatchAllKeys(redisClient *c);
@@ -862,7 +981,8 @@ void discardTransaction(redisClient *c);
void flagTransaction(redisClient *c);
/* Redis object implementation */
void decrRefCount(void *o);
void decrRefCount(robj *o);
void decrRefCountVoid(void *o);
void incrRefCount(robj *o);
robj *resetRefCount(robj *obj);
void freeStringObject(robj *o);
@@ -895,6 +1015,7 @@ int getLongDoubleFromObject(robj *o, long double *target);
int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg);
char *strEncoding(int encoding);
int compareStringObjects(robj *a, robj *b);
int collateStringObjects(robj *a, robj *b);
int equalStringObjects(robj *a, robj *b);
unsigned long estimateObjectIdleTime(robj *o);
@@ -908,6 +1029,17 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc);
void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **argv, int argc);
void updateSlavesWaitingBgsave(int bgsaveerr);
void replicationCron(void);
void replicationHandleMasterDisconnection(void);
void replicationCacheMaster(redisClient *c);
void resizeReplicationBacklog(long long newsize);
void refreshGoodSlavesCount(void);
void replicationScriptCacheInit(void);
void replicationScriptCacheFlush(void);
void replicationScriptCacheAdd(sds sha1);
int replicationScriptCacheExists(sds sha1);
void replicationSetMaster(char *ip, int port);
void replicationUnsetMaster(void);
void replicationSendNewlineToMaster(void);
/* Generic persistence functions */
void startLoading(FILE *fp);
@@ -931,18 +1063,24 @@ unsigned long aofRewriteBufferSize(void);
/* Sorted sets data type */
/* Struct to hold a inclusive/exclusive range spec. */
/* Struct to hold a inclusive/exclusive range spec by score comparison. */
typedef struct {
double min, max;
int minex, maxex; /* are min or max exclusive? */
} zrangespec;
/* Struct to hold an inclusive/exclusive range spec by lexicographic comparison. */
typedef struct {
robj *min, *max; /* May be set to shared.(minstring|maxstring) */
int minex, maxex; /* are min or max exclusive? */
} zlexrangespec;
zskiplist *zslCreate(void);
void zslFree(zskiplist *zsl);
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
unsigned char *zzlInsert(unsigned char *zl, robj *ele, double score);
int zslDelete(zskiplist *zsl, double score, robj *obj);
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec range);
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range);
double zzlGetScore(unsigned char *sptr);
void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
@@ -959,8 +1097,14 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
void call(redisClient *c, int flags);
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
void forceCommandPropagation(redisClient *c, int flags);
int prepareForShutdown();
#ifdef __GNUC__
void redisLog(int level, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
#else
void redisLog(int level, const char *fmt, ...);
#endif
void redisLogRaw(int level, const char *msg);
void redisLogFromHandler(int level, const char *msg);
void usage();
@@ -969,6 +1113,10 @@ int htNeedsResize(dict *dict);
void oom(const char *msg);
void populateCommandTable(void);
void resetCommandTableStats(void);
void adjustOpenFilesLimit(void);
void closeListeningSockets(int unlink_unix_socket);
void updateCachedTime(void);
void resetServerStats(void);
/* Set data type */
robj *setTypeCreate(robj *value);
@@ -1010,10 +1158,18 @@ void freePubsubPattern(void *p);
int listMatchPubsubPattern(void *a, void *b);
int pubsubPublishMessage(robj *channel, robj *message);
/* Keyspace events notification */
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid);
int keyspaceEventsStringToFlags(char *classes);
sds keyspaceEventsFlagsToString(int flags);
/* Configuration */
void loadServerConfig(char *filename, char *options);
void appendServerSaveParams(time_t seconds, int changes);
void resetServerSaveParams();
struct rewriteConfigState; /* Forward declaration to export API. */
void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force);
int rewriteConfig(char *path);
/* db.c -- Keyspace access API */
int removeExpire(redisDb *db, robj *key);
@@ -1032,11 +1188,14 @@ void setKey(redisDb *db, robj *key, robj *val);
int dbExists(redisDb *db, robj *key);
robj *dbRandomKey(redisDb *db);
int dbDelete(redisDb *db, robj *key);
long long emptyDb();
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
long long emptyDb(void(callback)(void*));
int selectDb(redisClient *c, int id);
void signalModifiedKey(redisDb *db, robj *key);
void signalFlushedDb(int dbid);
unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor);
int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor);
/* API to get key arguments from commands */
#define REDIS_GETKEYS_ALL 0
@@ -1052,6 +1211,7 @@ void initSentinelConfig(void);
void initSentinel(void);
void sentinelTimer(void);
char *sentinelHandleConfiguration(char **argv, int argc);
void sentinelIsRunning(void);
/* Scripting */
void scriptingInit(void);
@@ -1059,6 +1219,7 @@ void scriptingInit(void);
/* Git SHA1 */
char *redisGitSHA1(void);
char *redisGitDirty(void);
uint64_t redisBuildId(void);
/* Commands prototypes */
void authCommand(redisClient *c);
@@ -1083,6 +1244,7 @@ void incrbyfloatCommand(redisClient *c);
void selectCommand(redisClient *c);
void randomkeyCommand(redisClient *c);
void keysCommand(redisClient *c);
void scanCommand(redisClient *c);
void dbsizeCommand(redisClient *c);
void lastsaveCommand(redisClient *c);
void saveCommand(redisClient *c);
@@ -1118,6 +1280,7 @@ void sunionCommand(redisClient *c);
void sunionstoreCommand(redisClient *c);
void sdiffCommand(redisClient *c);
void sdiffstoreCommand(redisClient *c);
void sscanCommand(redisClient *c);
void syncCommand(redisClient *c);
void flushdbCommand(redisClient *c);
void flushallCommand(redisClient *c);
@@ -1144,12 +1307,16 @@ void zincrbyCommand(redisClient *c);
void zrangeCommand(redisClient *c);
void zrangebyscoreCommand(redisClient *c);
void zrevrangebyscoreCommand(redisClient *c);
void zrangebylexCommand(redisClient *c);
void zrevrangebylexCommand(redisClient *c);
void zcountCommand(redisClient *c);
void zlexcountCommand(redisClient *c);
void zrevrangeCommand(redisClient *c);
void zcardCommand(redisClient *c);
void zremCommand(redisClient *c);
void zscoreCommand(redisClient *c);
void zremrangebyscoreCommand(redisClient *c);
void zremrangebylexCommand(redisClient *c);
void multiCommand(redisClient *c);
void execCommand(redisClient *c);
void discardCommand(redisClient *c);
@@ -1170,10 +1337,12 @@ void hlenCommand(redisClient *c);
void zremrangebyrankCommand(redisClient *c);
void zunionstoreCommand(redisClient *c);
void zinterstoreCommand(redisClient *c);
void zscanCommand(redisClient *c);
void hkeysCommand(redisClient *c);
void hvalsCommand(redisClient *c);
void hgetallCommand(redisClient *c);
void hexistsCommand(redisClient *c);
void hscanCommand(redisClient *c);
void configCommand(redisClient *c);
void hincrbyCommand(redisClient *c);
void hincrbyfloatCommand(redisClient *c);
@@ -1182,6 +1351,7 @@ void unsubscribeCommand(redisClient *c);
void psubscribeCommand(redisClient *c);
void punsubscribeCommand(redisClient *c);
void publishCommand(redisClient *c);
void pubsubCommand(redisClient *c);
void watchCommand(redisClient *c);
void unwatchCommand(redisClient *c);
void restoreCommand(redisClient *c);
@@ -1195,7 +1365,13 @@ void scriptCommand(redisClient *c);
void timeCommand(redisClient *c);
void bitopCommand(redisClient *c);
void bitcountCommand(redisClient *c);
void bitposCommand(redisClient *c);
void replconfCommand(redisClient *c);
void pfselftestCommand(redisClient *c);
void pfaddCommand(redisClient *c);
void pfcountCommand(redisClient *c);
void pfmergeCommand(redisClient *c);
void pfdebugCommand(redisClient *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
+47
View File
@@ -0,0 +1,47 @@
/* redisassert.h -- Drop in replacemnet assert.h that prints the stack trace
* in the Redis logs.
*
* This file should be included instead of "assert.h" inside libraries used by
* Redis that are using assertions, so instead of Redis disappearing with
* SIGABORT, we get the details and stack trace inside the log file.
*
* ----------------------------------------------------------------------------
*
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __REDIS_ASSERT_H__
#define __REDIS_ASSERT_H__
#include <unistd.h> /* for _exit() */
#define assert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
void _redisAssert(char *estr, char *file, int line);
#endif
+10
View File
@@ -31,7 +31,11 @@
* file is recompiled, as we access this information in all the other
* files using this functions. */
#include <string.h>
#include "release.h"
#include "version.h"
#include "crc64.h"
char *redisGitSHA1(void) {
return REDIS_GIT_SHA1;
@@ -40,3 +44,9 @@ char *redisGitSHA1(void) {
char *redisGitDirty(void) {
return REDIS_GIT_DIRTY;
}
uint64_t redisBuildId(void) {
char *buildid = REDIS_VERSION REDIS_BUILD_ID REDIS_GIT_DIRTY REDIS_GIT_SHA1;
return crc64(0,(unsigned char*)buildid,strlen(buildid));
}
+898 -76
View File
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -51,11 +51,10 @@
#include <unistd.h>
#include "rio.h"
#include "util.h"
#include "crc64.h"
#include "config.h"
#include "redis.h"
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
/* Returns 1 or 0 for success/failure. */
static size_t rioBufferWrite(rio *r, const void *buf, size_t len) {
r->io.buffer.ptr = sdscatlen(r->io.buffer.ptr,(char*)buf,len);
@@ -87,6 +86,7 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
if (r->io.file.autosync &&
r->io.file.buffered >= r->io.file.autosync)
{
fflush(r->io.file.fp);
aof_fsync(fileno(r->io.file.fp));
r->io.file.buffered = 0;
}
@@ -109,6 +109,8 @@ static const rio rioBufferIO = {
rioBufferTell,
NULL, /* update_checksum */
0, /* current checksum */
0, /* bytes read or written */
0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */
};
@@ -118,6 +120,8 @@ static const rio rioFileIO = {
rioFileTell,
NULL, /* update_checksum */
0, /* current checksum */
0, /* bytes read or written */
0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */
};
+25 -6
View File
@@ -53,6 +53,12 @@ struct _rio {
/* The current checksum */
uint64_t cksum;
/* number of bytes read or written */
size_t processed_bytes;
/* maximum single read or write chunk size */
size_t max_processing_chunk;
/* Backend-specific vars. */
union {
struct {
@@ -74,16 +80,29 @@ typedef struct _rio rio;
* if needed. */
static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
if (r->update_cksum) r->update_cksum(r,buf,len);
return r->write(r,buf,len);
while (len) {
size_t bytes_to_write = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_write);
if (r->write(r,buf,bytes_to_write) == 0)
return 0;
buf = (char*)buf + bytes_to_write;
len -= bytes_to_write;
r->processed_bytes += bytes_to_write;
}
return 1;
}
static inline size_t rioRead(rio *r, void *buf, size_t len) {
if (r->read(r,buf,len) == 1) {
if (r->update_cksum) r->update_cksum(r,buf,len);
return 1;
while (len) {
size_t bytes_to_read = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
if (r->read(r,buf,bytes_to_read) == 0)
return 0;
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_read);
buf = (char*)buf + bytes_to_read;
len -= bytes_to_read;
r->processed_bytes += bytes_to_read;
}
return 0;
return 1;
}
static inline off_t rioTell(rio *r) {
+191 -53
View File
@@ -152,9 +152,20 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply) {
}
void luaPushError(lua_State *lua, char *error) {
lua_Debug dbg;
lua_newtable(lua);
lua_pushstring(lua,"err");
lua_pushstring(lua, error);
/* Attempt to figure out where this function was called, if possible */
if(lua_getstack(lua, 1, &dbg) && lua_getinfo(lua, "nSl", &dbg)) {
sds msg = sdscatprintf(sdsempty(), "%s: %d: %s",
dbg.source, dbg.currentline, error);
lua_pushstring(lua, msg);
sdsfree(msg);
} else {
lua_pushstring(lua, error);
}
lua_settable(lua,-3);
}
@@ -189,13 +200,20 @@ void luaSortArray(lua_State *lua) {
lua_pop(lua,1); /* Stack: array (sorted) */
}
#define LUA_CMD_OBJCACHE_SIZE 32
#define LUA_CMD_OBJCACHE_MAX_LEN 64
int luaRedisGenericCommand(lua_State *lua, int raise_error) {
int j, argc = lua_gettop(lua);
struct redisCommand *cmd;
robj **argv;
redisClient *c = server.lua_client;
sds reply;
/* Cached across calls. */
static robj **argv = NULL;
static int argv_size = 0;
static robj *cached_objects[LUA_CMD_OBJCACHE_SIZE];
static int cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
/* Require at least one argument */
if (argc == 0) {
luaPushError(lua,
@@ -204,13 +222,47 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
}
/* Build the arguments vector */
argv = zmalloc(sizeof(robj*)*argc);
for (j = 0; j < argc; j++) {
if (!lua_isstring(lua,j+1)) break;
argv[j] = createStringObject((char*)lua_tostring(lua,j+1),
lua_strlen(lua,j+1));
if (!argv) {
argv = zmalloc(sizeof(robj*)*argc);
} else if (argv_size < argc) {
argv = zrealloc(argv,sizeof(robj*)*argc);
argv_size = argc;
}
for (j = 0; j < argc; j++) {
char *obj_s;
size_t obj_len;
char dbuf[64];
if (lua_type(lua,j+1) == LUA_TNUMBER) {
/* We can't use lua_tolstring() for number -> string conversion
* since Lua uses a format specifier that loses precision. */
lua_Number num = lua_tonumber(lua,j+1);
obj_len = snprintf(dbuf,sizeof(dbuf),"%.17g",(double)num);
obj_s = dbuf;
} else {
obj_s = (char*)lua_tolstring(lua,j+1,&obj_len);
if (obj_s == NULL) break; /* Not a string. */
}
/* Try to use a cached object. */
if (j < LUA_CMD_OBJCACHE_SIZE && cached_objects[j] &&
cached_objects_len[j] >= obj_len)
{
char *s = cached_objects[j]->ptr;
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
argv[j] = cached_objects[j];
cached_objects[j] = NULL;
memcpy(s,obj_s,obj_len+1);
sh->free += sh->len - obj_len;
sh->len = obj_len;
} else {
argv[j] = createStringObject(obj_s, obj_len);
}
}
/* Check if one of the arguments passed by the Lua script
* is not a string or an integer (lua_isstring() return true for
* integers as well). */
@@ -220,7 +272,6 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
decrRefCount(argv[j]);
j--;
}
zfree(argv);
luaPushError(lua,
"Lua redis() command arguments must be strings or integers");
return 1;
@@ -258,6 +309,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
"Write commands not allowed after non deterministic commands");
goto cleanup;
} else if (server.masterhost && server.repl_slave_ro &&
!server.loading &&
!(server.lua_caller->flags & REDIS_MASTER))
{
luaPushError(lua, shared.roslaveerr->ptr);
@@ -294,16 +346,22 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
/* Convert the result of the Redis command into a suitable Lua type.
* The first thing we need is to create a single string from the client
* output buffers. */
reply = sdsempty();
if (c->bufpos) {
reply = sdscatlen(reply,c->buf,c->bufpos);
if (listLength(c->reply) == 0 && c->bufpos < REDIS_REPLY_CHUNK_BYTES) {
/* This is a fast path for the common case of a reply inside the
* client static buffer. Don't create an SDS string but just use
* the client buffer directly. */
c->buf[c->bufpos] = '\0';
reply = c->buf;
c->bufpos = 0;
}
while(listLength(c->reply)) {
robj *o = listNodeValue(listFirst(c->reply));
} else {
reply = sdsnewlen(c->buf,c->bufpos);
c->bufpos = 0;
while(listLength(c->reply)) {
robj *o = listNodeValue(listFirst(c->reply));
reply = sdscatlen(reply,o->ptr,sdslen(o->ptr));
listDelNode(c->reply,listFirst(c->reply));
reply = sdscatlen(reply,o->ptr,sdslen(o->ptr));
listDelNode(c->reply,listFirst(c->reply));
}
}
if (raise_error && reply[0] != '-') raise_error = 0;
redisProtocolToLuaType(lua,reply);
@@ -313,15 +371,37 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
(reply[0] == '*' && reply[1] != '-')) {
luaSortArray(lua);
}
sdsfree(reply);
if (reply != c->buf) sdsfree(reply);
c->reply_bytes = 0;
cleanup:
/* Clean up. Command code may have changed argv/argc so we use the
* argv/argc of the client instead of the local variables. */
for (j = 0; j < c->argc; j++)
decrRefCount(c->argv[j]);
zfree(c->argv);
for (j = 0; j < c->argc; j++) {
robj *o = c->argv[j];
/* Try to cache the object in the cached_objects array.
* The object must be small, SDS-encoded, and with refcount = 1
* (we must be the only owner) for us to cache it. */
if (j < LUA_CMD_OBJCACHE_SIZE &&
o->refcount == 1 &&
o->encoding == REDIS_ENCODING_RAW &&
sdslen(o->ptr) <= LUA_CMD_OBJCACHE_MAX_LEN)
{
struct sdshdr *sh = (void*)(((char*)(o->ptr))-(sizeof(struct sdshdr)));
if (cached_objects[j]) decrRefCount(cached_objects[j]);
cached_objects[j] = o;
cached_objects_len[j] = sh->free + sh->len;
} else {
decrRefCount(o);
}
}
if (c->argv != argv) {
zfree(c->argv);
argv = NULL;
}
if (raise_error) {
/* If we are here we should have an error in the stack, in the
@@ -429,7 +509,7 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
REDIS_NOTUSED(ar);
REDIS_NOTUSED(lua);
elapsed = (ustime()/1000) - server.lua_time_start;
elapsed = mstime() - server.lua_time_start;
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
redisLog(REDIS_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
server.lua_timedout = 1;
@@ -440,8 +520,7 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
* here when the EVAL command will return. */
aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
}
if (server.lua_timedout)
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
if (server.lua_timedout) processEventsWhileBlocked();
if (server.lua_kill) {
redisLog(REDIS_WARNING,"Lua script killed by user with SCRIPT KILL.");
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
@@ -464,7 +543,7 @@ void luaLoadLibraries(lua_State *lua) {
luaLoadLib(lua, LUA_TABLIBNAME, luaopen_table);
luaLoadLib(lua, LUA_STRLIBNAME, luaopen_string);
luaLoadLib(lua, LUA_MATHLIBNAME, luaopen_math);
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
luaLoadLib(lua, "cjson", luaopen_cjson);
luaLoadLib(lua, "struct", luaopen_struct);
luaLoadLib(lua, "cmsgpack", luaopen_cmsgpack);
@@ -609,6 +688,26 @@ void scriptingInit(void) {
lua_pcall(lua,0,0,0);
}
/* Add a helper function we use for pcall error reporting.
* Note that when the error is in the C function we want to report the
* information about the caller, that's what makes sense from the point
* of view of the user debugging a script. */
{
char *errh_func = "function __redis__err__handler(err)\n"
" local i = debug.getinfo(2,'nSl')\n"
" if i and i.what == 'C' then\n"
" i = debug.getinfo(3,'nSl')\n"
" end\n"
" if i then\n"
" return i.source .. ':' .. i.currentline .. ': ' .. err\n"
" else\n"
" return err\n"
" end\n"
"end\n";
luaL_loadbuffer(lua,errh_func,strlen(errh_func),"@err_handler_def");
lua_pcall(lua,0,0,0);
}
/* Create the (non connected) client that we use to execute Redis commands
* inside the Lua interpreter.
* Note: there is no need to create it again when this function is called
@@ -824,26 +923,39 @@ void evalGenericCommand(redisClient *c, int evalsha) {
int j;
char *sha = c->argv[1]->ptr;
/* Convert to lowercase. We don't use tolower since the function
* managed to always show up in the profiler output consuming
* a non trivial amount of time. */
for (j = 0; j < 40; j++)
funcname[j+2] = tolower(sha[j]);
funcname[j+2] = (sha[j] >= 'A' && sha[j] <= 'Z') ?
sha[j]+('a'-'A') : sha[j];
funcname[42] = '\0';
}
/* Push the pcall error handler function on the stack. */
lua_getglobal(lua, "__redis__err__handler");
/* Try to lookup the Lua function */
lua_getglobal(lua, funcname);
if (lua_isnil(lua,1)) {
if (lua_isnil(lua,-1)) {
lua_pop(lua,1); /* remove the nil from the stack */
/* Function not defined... let's define it if we have the
* body of the function. If this is an EVALSHA call we can just
* return an error. */
if (evalsha) {
lua_pop(lua,1); /* remove the error handler from the stack. */
addReply(c, shared.noscripterr);
return;
}
if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) return;
if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) {
lua_pop(lua,1); /* remove the error handler from the stack. */
/* The error is sent to the client by luaCreateFunction()
* itself when it returns REDIS_ERR. */
return;
}
/* Now the following is guaranteed to return non nil */
lua_getglobal(lua, funcname);
redisAssert(!lua_isnil(lua,1));
redisAssert(!lua_isnil(lua,-1));
}
/* Populate the argv and keys table accordingly to the arguments that
@@ -853,23 +965,23 @@ void evalGenericCommand(redisClient *c, int evalsha) {
/* Select the right DB in the context of the Lua client */
selectDb(server.lua_client,c->db->id);
/* Set an hook in order to be able to stop the script execution if it
/* Set a hook in order to be able to stop the script execution if it
* is running for too much time.
* We set the hook only if the time limit is enabled as the hook will
* make the Lua script execution slower. */
server.lua_caller = c;
server.lua_time_start = ustime()/1000;
server.lua_time_start = mstime();
server.lua_kill = 0;
if (server.lua_time_limit > 0 && server.masterhost == NULL) {
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
delhook = 1;
}
/* At this point whatever this script was never seen before or if it was
/* At this point whether this script was never seen before or if it was
* already defined, we can call it. We have zero arguments and expect
* a single return value. */
err = lua_pcall(lua,0,1,0);
err = lua_pcall(lua,0,1,-2);
/* Perform some cleanup that we need to do both on error and success. */
if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */
@@ -882,35 +994,59 @@ void evalGenericCommand(redisClient *c, int evalsha) {
}
server.lua_caller = NULL;
selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */
lua_gc(lua,LUA_GCSTEP,1);
/* Call the Lua garbage collector from time to time to avoid a
* full cycle performed by Lua, which adds too latency.
*
* The call is performed every LUA_GC_CYCLE_PERIOD executed commands
* (and for LUA_GC_CYCLE_PERIOD collection steps) because calling it
* for every command uses too much CPU. */
#define LUA_GC_CYCLE_PERIOD 50
{
static long gc_count = 0;
gc_count++;
if (gc_count == LUA_GC_CYCLE_PERIOD) {
lua_gc(lua,LUA_GCSTEP,LUA_GC_CYCLE_PERIOD);
gc_count = 0;
}
}
if (err) {
addReplyErrorFormat(c,"Error running script (call to %s): %s\n",
funcname, lua_tostring(lua,-1));
lua_pop(lua,1); /* Consume the Lua reply. */
lua_pop(lua,2); /* Consume the Lua reply and remove error handler. */
} else {
/* On success convert the Lua return value into Redis protocol, and
* send it to * the client. */
luaReplyToRedisReply(c,lua);
luaReplyToRedisReply(c,lua); /* Convert and consume the reply. */
lua_pop(lua,1); /* Remove the error handler. */
}
/* If we have slaves attached we want to replicate this command as
* EVAL instead of EVALSHA. We do this also in the AOF as currently there
* is no easy way to propagate a command in a different way in the AOF
* and in the replication link.
/* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless
* we are sure that the script was already in the context of all the
* attached slaves *and* the current AOF file if enabled.
*
* IMPROVEMENT POSSIBLE:
* 1) Replicate this command as EVALSHA in the AOF.
* 2) Remember what slave already received a given script, and replicate
* the EVALSHA against this slaves when possible.
*/
* To do so we use a cache of SHA1s of scripts that we already propagated
* as full EVAL, that's called the Replication Script Cache.
*
* For repliation, everytime a new slave attaches to the master, we need to
* flush our cache of scripts that can be replicated as EVALSHA, while
* for AOF we need to do so every time we rewrite the AOF file. */
if (evalsha) {
robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr);
if (!replicationScriptCacheExists(c->argv[1]->ptr)) {
/* This script is not in our script cache, replicate it as
* EVAL, then add it into the script cache, as from now on
* slaves and AOF know about it. */
robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr);
redisAssertWithInfo(c,NULL,script != NULL);
rewriteClientCommandArgument(c,0,
resetRefCount(createStringObject("EVAL",4)));
rewriteClientCommandArgument(c,1,script);
replicationScriptCacheAdd(c->argv[1]->ptr);
redisAssertWithInfo(c,NULL,script != NULL);
rewriteClientCommandArgument(c,0,
resetRefCount(createStringObject("EVAL",4)));
rewriteClientCommandArgument(c,1,script);
forceCommandPropagation(c,REDIS_PROPAGATE_REPL|REDIS_PROPAGATE_AOF);
}
}
}
@@ -977,7 +1113,8 @@ void scriptCommand(redisClient *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"flush")) {
scriptingReset();
addReply(c,shared.ok);
server.dirty++; /* Replicating this command is a good idea. */
replicationScriptCacheFlush();
server.dirty++; /* Propagating this command is a good idea. */
} else if (c->argc >= 2 && !strcasecmp(c->argv[1]->ptr,"exists")) {
int j;
@@ -1005,11 +1142,12 @@ void scriptCommand(redisClient *c) {
}
addReplyBulkCBuffer(c,funcname+2,40);
sdsfree(sha);
forceCommandPropagation(c,REDIS_PROPAGATE_REPL|REDIS_PROPAGATE_AOF);
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
if (server.lua_caller == NULL) {
addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n"));
} else if (server.lua_write_dirty) {
addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in an hard way using the SHUTDOWN NOSAVE command.\r\n"));
addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.\r\n"));
} else {
server.lua_kill = 1;
addReply(c,shared.ok);
+179 -16
View File
@@ -36,6 +36,18 @@
#include "sds.h"
#include "zmalloc.h"
/* Create a new sds string with the content specified by the 'init' pointer
* and 'initlen'.
* If NULL is used for 'init' the string is initialized with zero bytes.
*
* The string is always null-termined (all the sds strings are, always) so
* even if you create an sds string with:
*
* mystring = sdsnewlen("abc",3");
*
* You can print the string with printf() as there is an implicit \0 at the
* end of the string. However the string is binary safe and can contain
* \0 characters in the middle, as the length is stored in the sds header. */
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
@@ -53,24 +65,43 @@ sds sdsnewlen(const void *init, size_t initlen) {
return (char*)sh->buf;
}
/* Create an empty (zero length) sds string. Even in this case the string
* always has an implicit null term. */
sds sdsempty(void) {
return sdsnewlen("",0);
}
/* Create a new sds string starting from a null termined C string. */
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
/* Duplicate an sds string. */
sds sdsdup(const sds s) {
return sdsnewlen(s, sdslen(s));
}
/* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) {
if (s == NULL) return;
zfree(s-sizeof(struct sdshdr));
}
/* Set the sds string length to the length as obtained with strlen(), so
* considering as content only up to the first null term character.
*
* This function is useful when the sds string is hacked manually in some
* way, like in the following example:
*
* s = sdsnew("foobar");
* s[2] = '\0';
* sdsupdatelen(s);
* printf("%d\n", sdslen(s));
*
* The output will be "2", but if we comment out the call to sdsupdatelen()
* the output will be "6" as the string was modified but the logical length
* remains 6 bytes. */
void sdsupdatelen(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
int reallen = strlen(s);
@@ -78,6 +109,10 @@ void sdsupdatelen(sds s) {
sh->len = reallen;
}
/* Modify an sds string on-place to make it empty (zero length).
* However all the existing buffer is not discarded but set as free space
* so that next append operations will not require allocations up to the
* number of bytes previously available. */
void sdsclear(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
sh->free += sh->len;
@@ -89,7 +124,7 @@ void sdsclear(sds s) {
* is sure that after calling this function can overwrite up to addlen
* bytes after the end of the string, plus one more byte for nul term.
*
* Note: this does not change the *size* of the sds string as returned
* Note: this does not change the *length* of the sds string as returned
* by sdslen(), but only the free buffer space we have. */
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
@@ -113,7 +148,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
/* Reallocate the sds string so that it has no free space at the end. The
* contained string remains not altered, but next concatenation operations
* will require a reallocation. */
* will require a reallocation.
*
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdsRemoveFreeSpace(sds s) {
struct sdshdr *sh;
@@ -123,6 +161,13 @@ sds sdsRemoveFreeSpace(sds s) {
return sh->buf;
}
/* Return the total size of the allocation of the specifed sds string,
* including:
* 1) The sds header before the pointer.
* 2) The string.
* 3) The free buffer at the end if any.
* 4) The implicit null term.
*/
size_t sdsAllocSize(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
@@ -130,7 +175,7 @@ size_t sdsAllocSize(sds s) {
}
/* Increment the sds length and decrements the left free space at the
* end of the string accordingly to 'incr'. Also set the null term
* end of the string according to 'incr'. Also set the null term
* in the new end of the string.
*
* This function is used in order to fix the string length after the
@@ -140,15 +185,17 @@ size_t sdsAllocSize(sds s) {
* Note: it is possible to use a negative increment in order to
* right-trim the string.
*
* Usage example:
*
* Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the
* following schema to cat bytes coming from the kernel to the end of an
* sds string new things without copying into an intermediate buffer:
* following schema, to cat bytes coming from the kernel to the end of an
* sds string without copying into an intermediate buffer:
*
* oldlen = sdslen(s);
* s = sdsMakeRoomFor(s, BUFFER_SIZE);
* nread = read(fd, s+oldlen, BUFFER_SIZE);
* ... check for nread <= 0 and handle it ...
* sdsIncrLen(s, nhread);
* sdsIncrLen(s, nread);
*/
void sdsIncrLen(sds s, int incr) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
@@ -161,7 +208,10 @@ void sdsIncrLen(sds s, int incr) {
}
/* Grow the sds to have the specified length. Bytes that were not part of
* the original length of the sds will be set to zero. */
* the original length of the sds will be set to zero.
*
* if the specified length is smaller than the current length, no operation
* is performed. */
sds sdsgrowzero(sds s, size_t len) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
size_t totlen, curlen = sh->len;
@@ -179,6 +229,11 @@ sds sdsgrowzero(sds s, size_t len) {
return s;
}
/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
* end of the specified sds string 's'.
*
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatlen(sds s, const void *t, size_t len) {
struct sdshdr *sh;
size_t curlen = sdslen(s);
@@ -193,14 +248,24 @@ sds sdscatlen(sds s, const void *t, size_t len) {
return s;
}
/* Append the specified null termianted C string to the sds string 's'.
*
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
/* Append the specified sds 't' to the existing sds 's'.
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatsds(sds s, const sds t) {
return sdscatlen(s, t, sdslen(t));
}
/* Destructively modify the sds string 's' to hold the specified binary
* safe string pointed by 't' of length 'len' bytes. */
sds sdscpylen(sds s, const char *t, size_t len) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t totlen = sh->free+sh->len;
@@ -218,33 +283,65 @@ sds sdscpylen(sds s, const char *t, size_t len) {
return s;
}
/* Like sdscpylen() but 't' must be a null-termined string so that the length
* of the string is obtained with strlen(). */
sds sdscpy(sds s, const char *t) {
return sdscpylen(s, t, strlen(t));
}
/* Like sdscatpritf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char *buf, *t;
size_t buflen = 16;
char staticbuf[1024], *buf = staticbuf, *t;
size_t buflen = strlen(fmt)*2;
while(1) {
/* We try to start using a static buffer for speed.
* If not possible we revert to heap allocation. */
if (buflen > sizeof(staticbuf)) {
buf = zmalloc(buflen);
if (buf == NULL) return NULL;
} else {
buflen = sizeof(staticbuf);
}
/* Try with buffers two times bigger every time we fail to
* fit the string in the current buffer size. */
while(1) {
buf[buflen-2] = '\0';
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
if (buf[buflen-2] != '\0') {
zfree(buf);
if (buf != staticbuf) zfree(buf);
buflen *= 2;
buf = zmalloc(buflen);
if (buf == NULL) return NULL;
continue;
}
break;
}
/* Finally concat the obtained string to the SDS string and return it. */
t = sdscat(s, buf);
zfree(buf);
if (buf != staticbuf) zfree(buf);
return t;
}
/* Append to the sds string 's' a string obtained using printf-alike format
* specifier.
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call.
*
* Example:
*
* s = sdsempty("Sum is: ");
* s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
*
* Often you need to create a string from scratch with the printf-alike
* format. When this is the need, just use sdsempty() as the target string:
*
* s = sdscatprintf(sdsempty(), "... your format ...", args);
*/
sds sdscatprintf(sds s, const char *fmt, ...) {
va_list ap;
char *t;
@@ -254,6 +351,20 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
return t;
}
/* Remove the part of the string from left and from right composed just of
* contiguous characters found in 'cset', that is a null terminted C string.
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call.
*
* Example:
*
* s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
* s = sdstrim(s,"A. :");
* printf("%s\n", s);
*
* Output will be just "Hello World".
*/
sds sdstrim(sds s, const char *cset) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep;
@@ -271,11 +382,27 @@ sds sdstrim(sds s, const char *cset) {
return s;
}
sds sdsrange(sds s, int start, int end) {
/* Turn the string into a smaller (or equal) string containing only the
* substring specified by the 'start' and 'end' indexes.
*
* start and end can be negative, where -1 means the last character of the
* string, -2 the penultimate character, and so forth.
*
* The interval is inclusive, so the start and end characters will be part
* of the resulting string.
*
* The string is modified in-place.
*
* Example:
*
* s = sdsnew("Hello World");
* sdsrange(s,1,-1); => "ello World"
*/
void sdsrange(sds s, int start, int end) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t newlen, len = sdslen(s);
if (len == 0) return s;
if (len == 0) return;
if (start < 0) {
start = len+start;
if (start < 0) start = 0;
@@ -299,21 +426,33 @@ sds sdsrange(sds s, int start, int end) {
sh->buf[newlen] = 0;
sh->free = sh->free+(sh->len-newlen);
sh->len = newlen;
return s;
}
/* Apply tolower() to every character of the sds string 's'. */
void sdstolower(sds s) {
int len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = tolower(s[j]);
}
/* Apply toupper() to every character of the sds string 's'. */
void sdstoupper(sds s) {
int len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = toupper(s[j]);
}
/* Compare two sds strings s1 and s2 with memcmp().
*
* Return value:
*
* 1 if s1 > s2.
* -1 if s1 < s2.
* 0 if s1 and s2 are exactly the same binary string.
*
* If two strings share exactly the same prefix, but one of the two has
* additional characters, the longer string is considered to be greater than
* the smaller one. */
int sdscmp(const sds s1, const sds s2) {
size_t l1, l2, minlen;
int cmp;
@@ -391,6 +530,7 @@ cleanup:
}
}
/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
void sdsfreesplitres(sds *tokens, int count) {
if (!tokens) return;
while(count--)
@@ -398,6 +538,10 @@ void sdsfreesplitres(sds *tokens, int count) {
zfree(tokens);
}
/* Create an sds string from a long long value. It is much faster than:
*
* sdscatprintf(sdsempty(),"%lld\n", value);
*/
sds sdsfromlonglong(long long value) {
char buf[32], *p;
unsigned long long v;
@@ -413,6 +557,12 @@ sds sdsfromlonglong(long long value) {
return sdsnewlen(p,32-(p-buf));
}
/* Append to the sds string "s" an escaped string representation where
* all the non-printable characters (tested with isprint()) are turned into
* escapes in the form "\n\r\a...." or "\x<hex-number>".
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatrepr(sds s, const char *p, size_t len) {
s = sdscatlen(s,"\"",1);
while(len--) {
@@ -445,7 +595,7 @@ int is_hex_digit(char c) {
(c >= 'A' && c <= 'F');
}
/* Helper function for sdssplitargs() that converts an hex digit into an
/* Helper function for sdssplitargs() that converts a hex digit into an
* integer from 0 to 15 */
int hex_digit_to_int(char c) {
switch(c) {
@@ -621,6 +771,19 @@ sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
return s;
}
/* Join an array of C strings using the specified separator (also a C string).
* Returns the result as an sds string. */
sds sdsjoin(char **argv, int argc, char *sep) {
sds join = sdsempty();
int j;
for (j = 0; j < argc; j++) {
join = sdscat(join, argv[j]);
if (j != argc-1) join = sdscat(join,sep);
}
return join;
}
#ifdef SDS_TEST_MAIN
#include <stdio.h>
#include "testhelp.h"
+3 -2
View File
@@ -56,7 +56,7 @@ static inline size_t sdsavail(const sds s) {
sds sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init);
sds sdsempty();
sds sdsempty(void);
size_t sdslen(const sds s);
sds sdsdup(const sds s);
void sdsfree(sds s);
@@ -77,7 +77,7 @@ sds sdscatprintf(sds s, const char *fmt, ...);
#endif
sds sdstrim(sds s, const char *cset);
sds sdsrange(sds s, int start, int end);
void sdsrange(sds s, int start, int end);
void sdsupdatelen(sds s);
void sdsclear(sds s);
int sdscmp(const sds s1, const sds s2);
@@ -89,6 +89,7 @@ sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, const char *p, size_t len);
sds *sdssplitargs(const char *line, int *argc);
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
sds sdsjoin(char **argv, int argc, char *sep);
/* Low level functions exposed to the user API */
sds sdsMakeRoomFor(sds s, size_t addlen);
+1333 -718
View File
File diff suppressed because it is too large Load Diff
+261
View File
@@ -0,0 +1,261 @@
/* ==========================================================================
* setproctitle.c - Linux/Darwin setproctitle.
* --------------------------------------------------------------------------
* Copyright (C) 2010 William Ahern
* Copyright (C) 2013 Salvatore Sanfilippo
* Copyright (C) 2013 Stam He
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* ==========================================================================
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stddef.h> /* NULL size_t */
#include <stdarg.h> /* va_list va_start va_end */
#include <stdlib.h> /* malloc(3) setenv(3) clearenv(3) setproctitle(3) getprogname(3) */
#include <stdio.h> /* vsnprintf(3) snprintf(3) */
#include <string.h> /* strlen(3) strchr(3) strdup(3) memset(3) memcpy(3) */
#include <errno.h> /* errno program_invocation_name program_invocation_short_name */
#if !defined(HAVE_SETPROCTITLE)
#define HAVE_SETPROCTITLE (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
#endif
#if !HAVE_SETPROCTITLE
#if (defined __linux || defined __APPLE__)
extern char **environ;
static struct {
/* original value */
const char *arg0;
/* title space available */
char *base, *end;
/* pointer to original nul character within base */
char *nul;
_Bool reset;
int error;
} SPT;
#ifndef SPT_MIN
#define SPT_MIN(a, b) (((a) < (b))? (a) : (b))
#endif
static inline size_t spt_min(size_t a, size_t b) {
return SPT_MIN(a, b);
} /* spt_min() */
/*
* For discussion on the portability of the various methods, see
* http://lists.freebsd.org/pipermail/freebsd-stable/2008-June/043136.html
*/
static int spt_clearenv(void) {
#if __GLIBC__
clearenv();
return 0;
#else
extern char **environ;
static char **tmp;
if (!(tmp = malloc(sizeof *tmp)))
return errno;
tmp[0] = NULL;
environ = tmp;
return 0;
#endif
} /* spt_clearenv() */
static int spt_copyenv(char *oldenv[]) {
extern char **environ;
char *eq;
int i, error;
if (environ != oldenv)
return 0;
if ((error = spt_clearenv()))
goto error;
for (i = 0; oldenv[i]; i++) {
if (!(eq = strchr(oldenv[i], '=')))
continue;
*eq = '\0';
error = (0 != setenv(oldenv[i], eq + 1, 1))? errno : 0;
*eq = '=';
if (error)
goto error;
}
return 0;
error:
environ = oldenv;
return error;
} /* spt_copyenv() */
static int spt_copyargs(int argc, char *argv[]) {
char *tmp;
int i;
for (i = 1; i < argc || (i >= argc && argv[i]); i++) {
if (!argv[i])
continue;
if (!(tmp = strdup(argv[i])))
return errno;
argv[i] = tmp;
}
return 0;
} /* spt_copyargs() */
void spt_init(int argc, char *argv[]) {
char **envp = environ;
char *base, *end, *nul, *tmp;
int i, error;
if (!(base = argv[0]))
return;
nul = &base[strlen(base)];
end = nul + 1;
for (i = 0; i < argc || (i >= argc && argv[i]); i++) {
if (!argv[i] || argv[i] < end)
continue;
end = argv[i] + strlen(argv[i]) + 1;
}
for (i = 0; envp[i]; i++) {
if (envp[i] < end)
continue;
end = envp[i] + strlen(envp[i]) + 1;
}
if (!(SPT.arg0 = strdup(argv[0])))
goto syerr;
#if __GLIBC__
if (!(tmp = strdup(program_invocation_name)))
goto syerr;
program_invocation_name = tmp;
if (!(tmp = strdup(program_invocation_short_name)))
goto syerr;
program_invocation_short_name = tmp;
#elif __APPLE__
if (!(tmp = strdup(getprogname())))
goto syerr;
setprogname(tmp);
#endif
if ((error = spt_copyenv(envp)))
goto error;
if ((error = spt_copyargs(argc, argv)))
goto error;
SPT.nul = nul;
SPT.base = base;
SPT.end = end;
return;
syerr:
error = errno;
error:
SPT.error = error;
} /* spt_init() */
#ifndef SPT_MAXTITLE
#define SPT_MAXTITLE 255
#endif
void setproctitle(const char *fmt, ...) {
char buf[SPT_MAXTITLE + 1]; /* use buffer in case argv[0] is passed */
va_list ap;
char *nul;
int len, error;
if (!SPT.base)
return;
if (fmt) {
va_start(ap, fmt);
len = vsnprintf(buf, sizeof buf, fmt, ap);
va_end(ap);
} else {
len = snprintf(buf, sizeof buf, "%s", SPT.arg0);
}
if (len <= 0)
{ error = errno; goto error; }
if (!SPT.reset) {
memset(SPT.base, 0, SPT.end - SPT.base);
SPT.reset = 1;
} else {
memset(SPT.base, 0, spt_min(sizeof buf, SPT.end - SPT.base));
}
len = spt_min(len, spt_min(sizeof buf, SPT.end - SPT.base) - 1);
memcpy(SPT.base, buf, len);
nul = &SPT.base[len];
if (nul < SPT.nul) {
*SPT.nul = '.';
} else if (nul == SPT.nul && &nul[1] < SPT.end) {
*SPT.nul = ' ';
*++nul = '\0';
}
return;
error:
SPT.error = error;
} /* setproctitle() */
#endif /* __linux || __APPLE__ */
#endif /* !HAVE_SETPROCTITLE */
+28 -9
View File
@@ -48,8 +48,8 @@ redisSortOperation *createSortOperation(int type, robj *pattern) {
* 1) The first occurrence of '*' in 'pattern' is substituted with 'subst'.
*
* 2) If 'pattern' matches the "->" string, everything on the left of
* the arrow is treated as the name of an hash field, and the part on the
* left as the key name containing an hash. The value of the specified
* the arrow is treated as the name of a hash field, and the part on the
* left as the key name containing a hash. The value of the specified
* field is returned.
*
* 3) If 'pattern' equals "#", the function simply returns 'subst' itself so
@@ -163,12 +163,22 @@ int sortCompare(const void *s1, const void *s2) {
else
cmp = 1;
} else {
/* We have both the objects, use strcoll */
cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr);
/* We have both the objects, compare them. */
if (server.sort_store) {
cmp = compareStringObjects(so1->u.cmpobj,so2->u.cmpobj);
} else {
/* Here we can use strcoll() directly as we are sure that
* the objects are decoded string objects. */
cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr);
}
}
} else {
/* Compare elements directly. */
cmp = compareStringObjects(so1->obj,so2->obj);
if (server.sort_store) {
cmp = compareStringObjects(so1->obj,so2->obj);
} else {
cmp = collateStringObjects(so1->obj,so2->obj);
}
}
}
return server.sort_desc ? -cmp : cmp;
@@ -221,8 +231,15 @@ void sortCommand(redisClient *c) {
} else if (!strcasecmp(c->argv[j]->ptr,"alpha")) {
alpha = 1;
} else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) ||
(getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return;
if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL)
!= REDIS_OK) ||
(getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL)
!= REDIS_OK))
{
decrRefCount(sortval);
listRelease(operations);
return;
}
j+=2;
} else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) {
storekey = c->argv[j+1];
@@ -432,6 +449,7 @@ void sortCommand(redisClient *c) {
server.sort_desc = desc;
server.sort_alpha = alpha;
server.sort_bypattern = sortby ? 1 : 0;
server.sort_store = storekey ? 1 : 0;
if (sortby && (start != 0 || end != vectorlen-1))
pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end);
else
@@ -504,9 +522,12 @@ void sortCommand(redisClient *c) {
}
if (outputlen) {
setKey(c->db,storekey,sobj);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"sortstore",storekey,
c->db->id);
server.dirty += outputlen;
} else if (dbDelete(c->db,storekey)) {
signalModifiedKey(c->db,storekey);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",storekey,c->db->id);
server.dirty++;
}
decrRefCount(sobj);
@@ -525,5 +546,3 @@ void sortCommand(redisClient *c) {
}
zfree(vector);
}
+21 -1
View File
@@ -474,6 +474,7 @@ void hsetCommand(redisClient *c) {
update = hashTypeSet(o,c->argv[2],c->argv[3]);
addReply(c, update ? shared.czero : shared.cone);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty++;
}
@@ -489,6 +490,7 @@ void hsetnxCommand(redisClient *c) {
hashTypeSet(o,c->argv[2],c->argv[3]);
addReply(c, shared.cone);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty++;
}
}
@@ -510,6 +512,7 @@ void hmsetCommand(redisClient *c) {
}
addReply(c, shared.ok);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty++;
}
@@ -543,6 +546,7 @@ void hincrbyCommand(redisClient *c) {
decrRefCount(new);
addReplyLongLong(c,value);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
server.dirty++;
}
@@ -569,6 +573,7 @@ void hincrbyfloatCommand(redisClient *c) {
hashTypeSet(o,c->argv[2],new);
addReplyBulk(c,new);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id);
server.dirty++;
/* Always replicate HINCRBYFLOAT as an HSET command with the final value
@@ -649,7 +654,7 @@ void hmgetCommand(redisClient *c) {
void hdelCommand(redisClient *c) {
robj *o;
int j, deleted = 0;
int j, deleted = 0, keyremoved = 0;
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
@@ -659,12 +664,17 @@ void hdelCommand(redisClient *c) {
deleted++;
if (hashTypeLength(o) == 0) {
dbDelete(c->db,c->argv[1]);
keyremoved = 1;
break;
}
}
}
if (deleted) {
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
if (keyremoved)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
c->db->id);
server.dirty += deleted;
}
addReplyLongLong(c,deleted);
@@ -752,3 +762,13 @@ void hexistsCommand(redisClient *c) {
addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
}
void hscanCommand(redisClient *c) {
robj *o;
unsigned long cursor;
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == REDIS_ERR) return;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
scanGenericCommand(c,o,cursor);
}
+51 -24
View File
@@ -29,8 +29,6 @@
#include "redis.h"
void signalListAsReady(redisClient *c, robj *key);
/*-----------------------------------------------------------------------------
* List API
*----------------------------------------------------------------------------*/
@@ -275,7 +273,7 @@ void listTypeConvert(robj *subject, int enc) {
if (enc == REDIS_ENCODING_LINKEDLIST) {
list *l = listCreate();
listSetFreeMethod(l,decrRefCount);
listSetFreeMethod(l,decrRefCountVoid);
/* listTypeGet returns a robj with incremented refcount */
li = listTypeInitIterator(subject,0,REDIS_TAIL);
@@ -297,15 +295,12 @@ void listTypeConvert(robj *subject, int enc) {
void pushGenericCommand(redisClient *c, int where) {
int j, waiting = 0, pushed = 0;
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
int may_have_waiting_clients = (lobj == NULL);
if (lobj && lobj->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr);
return;
}
if (may_have_waiting_clients) signalListAsReady(c,c->argv[1]);
for (j = 2; j < c->argc; j++) {
c->argv[j] = tryObjectEncoding(c->argv[j]);
if (!lobj) {
@@ -316,7 +311,12 @@ void pushGenericCommand(redisClient *c, int where) {
pushed++;
}
addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));
if (pushed) signalModifiedKey(c->db,c->argv[1]);
if (pushed) {
char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
}
server.dirty += pushed;
}
@@ -338,10 +338,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
checkType(c,subject,REDIS_LIST)) return;
if (refval != NULL) {
/* Note: we expect refval to be string-encoded because it is *not* the
* last argument of the multi-bulk LINSERT. */
redisAssertWithInfo(c,refval,refval->encoding == REDIS_ENCODING_RAW);
/* We're not sure if this value can be inserted yet, but we cannot
* convert the list inside the iterator. We don't want to loop over
* the list twice (once to see if the value can be inserted and once
@@ -366,6 +362,8 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert",
c->argv[1],c->db->id);
server.dirty++;
} else {
/* Notify client of a failed insert */
@@ -373,8 +371,11 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
return;
}
} else {
char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";
listTypePush(subject,val,where);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
server.dirty++;
}
@@ -469,6 +470,7 @@ void lsetCommand(redisClient *c) {
decrRefCount(value);
addReply(c,shared.ok);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
server.dirty++;
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
@@ -481,6 +483,7 @@ void lsetCommand(redisClient *c) {
incrRefCount(value);
addReply(c,shared.ok);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
server.dirty++;
}
} else {
@@ -496,9 +499,16 @@ void popGenericCommand(redisClient *c, int where) {
if (value == NULL) {
addReply(c,shared.nullbulk);
} else {
char *event = (where == REDIS_HEAD) ? "lpop" : "rpop";
addReplyBulk(c,value);
decrRefCount(value);
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
if (listTypeLength(o) == 0) {
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
c->argv[1],c->db->id);
dbDelete(c->db,c->argv[1]);
}
signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
}
@@ -618,7 +628,12 @@ void ltrimCommand(redisClient *c) {
} else {
redisPanic("Unknown list encoding");
}
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"ltrim",c->argv[1],c->db->id);
if (listTypeLength(o) == 0) {
dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
}
signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.ok);
@@ -689,10 +704,10 @@ void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value
if (!dstobj) {
dstobj = createZiplistObject();
dbAdd(c->db,dstkey,dstobj);
signalListAsReady(c,dstkey);
}
signalModifiedKey(c->db,dstkey);
listTypePush(dstobj,value,REDIS_HEAD);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lpush",dstkey,c->db->id);
/* Always send the pushed value to the client. */
addReplyBulk(c,value);
}
@@ -722,7 +737,12 @@ void rpoplpushCommand(redisClient *c) {
decrRefCount(value);
/* Delete the source list when it is empty */
if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"rpop",touchedkey,c->db->id);
if (listTypeLength(sobj) == 0) {
dbDelete(c->db,touchedkey);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
touchedkey,c->db->id);
}
signalModifiedKey(c->db,touchedkey);
decrRefCount(touchedkey);
server.dirty++;
@@ -811,7 +831,7 @@ void unblockClientWaitingData(redisClient *c) {
dictReleaseIterator(di);
/* Cleanup the client structure */
dictEmpty(c->bpop.keys);
dictEmpty(c->bpop.keys,NULL);
if (c->bpop.target) {
decrRefCount(c->bpop.target);
c->bpop.target = NULL;
@@ -824,24 +844,24 @@ void unblockClientWaitingData(redisClient *c) {
/* If the specified key has clients blocked waiting for list pushes, this
* function will put the key reference into the server.ready_keys list.
* Note that db->ready_keys is an hash table that allows us to avoid putting
* Note that db->ready_keys is a hash table that allows us to avoid putting
* the same key again and again in the list in case of multiple pushes
* made by a script or in the context of MULTI/EXEC.
*
* The list will be finally processed by handleClientsBlockedOnLists() */
void signalListAsReady(redisClient *c, robj *key) {
void signalListAsReady(redisDb *db, robj *key) {
readyList *rl;
/* No clients blocking for this key? No need to queue it. */
if (dictFind(c->db->blocking_keys,key) == NULL) return;
if (dictFind(db->blocking_keys,key) == NULL) return;
/* Key was already signaled? No need to queue it again. */
if (dictFind(c->db->ready_keys,key) != NULL) return;
if (dictFind(db->ready_keys,key) != NULL) return;
/* Ok, we need to queue this key into server.ready_keys. */
rl = zmalloc(sizeof(*rl));
rl->key = key;
rl->db = c->db;
rl->db = db;
incrRefCount(key);
listAddNodeTail(server.ready_keys,rl);
@@ -849,10 +869,10 @@ void signalListAsReady(redisClient *c, robj *key) {
* to avoid adding it multiple times into a list with a simple O(1)
* check. */
incrRefCount(key);
redisAssert(dictAdd(c->db->ready_keys,key,NULL) == DICT_OK);
redisAssert(dictAdd(db->ready_keys,key,NULL) == DICT_OK);
}
/* This is an helper function for handleClientsBlockedOnLists(). It's work
/* This is a helper function for handleClientsBlockedOnLists(). It's work
* is to serve a specific client (receiver) that is blocked on 'key'
* in the context of the specified 'db', doing the following:
*
@@ -1046,6 +1066,7 @@ void blockingPopGenericCommand(redisClient *c, int where) {
} else {
if (listTypeLength(o) != 0) {
/* Non empty list, this is like a non normal [LR]POP. */
char *event = (where == REDIS_HEAD) ? "lpop" : "rpop";
robj *value = listTypePop(o,where);
redisAssert(value != NULL);
@@ -1053,7 +1074,13 @@ void blockingPopGenericCommand(redisClient *c, int where) {
addReplyBulk(c,c->argv[j]);
addReplyBulk(c,value);
decrRefCount(value);
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[j]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,
c->argv[j],c->db->id);
if (listTypeLength(o) == 0) {
dbDelete(c->db,c->argv[j]);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
c->argv[j],c->db->id);
}
signalModifiedKey(c->db,c->argv[j]);
server.dirty++;
+145 -23
View File
@@ -266,14 +266,17 @@ void saddCommand(redisClient *c) {
c->argv[j] = tryObjectEncoding(c->argv[j]);
if (setTypeAdd(set,c->argv[j])) added++;
}
if (added) signalModifiedKey(c->db,c->argv[1]);
if (added) {
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[1],c->db->id);
}
server.dirty += added;
addReplyLongLong(c,added);
}
void sremCommand(redisClient *c) {
robj *set;
int j, deleted = 0;
int j, deleted = 0, keyremoved = 0;
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return;
@@ -283,12 +286,17 @@ void sremCommand(redisClient *c) {
deleted++;
if (setTypeSize(set) == 0) {
dbDelete(c->db,c->argv[1]);
keyremoved = 1;
break;
}
}
}
if (deleted) {
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);
if (keyremoved)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
c->db->id);
server.dirty += deleted;
}
addReplyLongLong(c,deleted);
@@ -322,9 +330,13 @@ void smoveCommand(redisClient *c) {
addReply(c,shared.czero);
return;
}
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);
/* Remove the src set from the database when empty */
if (setTypeSize(srcset) == 0) dbDelete(c->db,c->argv[1]);
if (setTypeSize(srcset) == 0) {
dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
}
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[2]);
server.dirty++;
@@ -336,7 +348,10 @@ void smoveCommand(redisClient *c) {
}
/* An extra key has changed when ele was successfully added to dstset */
if (setTypeAdd(dstset,ele)) server.dirty++;
if (setTypeAdd(dstset,ele)) {
server.dirty++;
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[2],c->db->id);
}
addReply(c,shared.cone);
}
@@ -378,6 +393,7 @@ void spopCommand(redisClient *c) {
incrRefCount(ele);
setTypeRemove(set,ele);
}
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id);
/* Replicate/AOF this command as an SREM operation */
aux = createStringObject("SREM",4);
@@ -386,7 +402,10 @@ void spopCommand(redisClient *c) {
decrRefCount(aux);
addReplyBulk(c,ele);
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
if (setTypeSize(set) == 0) {
dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
}
signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
}
@@ -565,6 +584,14 @@ int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
return setTypeSize(*(robj**)s1)-setTypeSize(*(robj**)s2);
}
/* This is used by SDIFF and in this case we can receive NULL that should
* be handled as empty sets. */
int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
robj *o1 = *(robj**)s1, *o2 = *(robj**)s2;
return (o2 ? setTypeSize(o2) : 0) - (o1 ? setTypeSize(o1) : 0);
}
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
robj **sets = zmalloc(sizeof(robj*)*setnum);
setTypeIterator *si;
@@ -679,13 +706,18 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
if (dstkey) {
/* Store the resulting set into the target, if the intersection
* is not an empty set. */
dbDelete(c->db,dstkey);
int deleted = dbDelete(c->db,dstkey);
if (setTypeSize(dstset) > 0) {
dbAdd(c->db,dstkey,dstset);
addReplyLongLong(c,setTypeSize(dstset));
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sinterstore",
dstkey,c->db->id);
} else {
decrRefCount(dstset);
addReply(c,shared.czero);
if (deleted)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
dstkey,c->db->id);
}
signalModifiedKey(c->db,dstkey);
server.dirty++;
@@ -712,6 +744,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
setTypeIterator *si;
robj *ele, *dstset = NULL;
int j, cardinality = 0;
int diff_algo = 1;
for (j = 0; j < setnum; j++) {
robj *setobj = dstkey ?
@@ -728,34 +761,107 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
sets[j] = setobj;
}
/* Select what DIFF algorithm to use.
*
* Algorithm 1 is O(N*M) where N is the size of the element first set
* and M the total number of sets.
*
* Algorithm 2 is O(N) where N is the total number of elements in all
* the sets.
*
* We compute what is the best bet with the current input here. */
if (op == REDIS_OP_DIFF && sets[0]) {
long long algo_one_work = 0, algo_two_work = 0;
for (j = 0; j < setnum; j++) {
if (sets[j] == NULL) continue;
algo_one_work += setTypeSize(sets[0]);
algo_two_work += setTypeSize(sets[j]);
}
/* Algorithm 1 has better constant times and performs less operations
* if there are elements in common. Give it some advantage. */
algo_one_work /= 2;
diff_algo = (algo_one_work <= algo_two_work) ? 1 : 2;
if (diff_algo == 1 && setnum > 1) {
/* With algorithm 1 it is better to order the sets to subtract
* by decreasing size, so that we are more likely to find
* duplicated elements ASAP. */
qsort(sets+1,setnum-1,sizeof(robj*),
qsortCompareSetsByRevCardinality);
}
}
/* We need a temp set object to store our union. If the dstkey
* is not NULL (that is, we are inside an SUNIONSTORE operation) then
* this set object will be the resulting object to set into the target key*/
dstset = createIntsetObject();
/* Iterate all the elements of all the sets, add every element a single
* time to the result set */
for (j = 0; j < setnum; j++) {
if (op == REDIS_OP_DIFF && j == 0 && !sets[j]) break; /* result set is empty */
if (!sets[j]) continue; /* non existing keys are like empty sets */
if (op == REDIS_OP_UNION) {
/* Union is trivial, just add every element of every set to the
* temporary set. */
for (j = 0; j < setnum; j++) {
if (!sets[j]) continue; /* non existing keys are like empty sets */
si = setTypeInitIterator(sets[j]);
si = setTypeInitIterator(sets[j]);
while((ele = setTypeNextObject(si)) != NULL) {
if (setTypeAdd(dstset,ele)) cardinality++;
decrRefCount(ele);
}
setTypeReleaseIterator(si);
}
} else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 1) {
/* DIFF Algorithm 1:
*
* We perform the diff by iterating all the elements of the first set,
* and only adding it to the target set if the element does not exist
* into all the other sets.
*
* This way we perform at max N*M operations, where N is the size of
* the first set, and M the number of sets. */
si = setTypeInitIterator(sets[0]);
while((ele = setTypeNextObject(si)) != NULL) {
if (op == REDIS_OP_UNION || j == 0) {
if (setTypeAdd(dstset,ele)) {
cardinality++;
}
} else if (op == REDIS_OP_DIFF) {
if (setTypeRemove(dstset,ele)) {
cardinality--;
}
for (j = 1; j < setnum; j++) {
if (!sets[j]) continue; /* no key is an empty set. */
if (sets[j] == sets[0]) break; /* same set! */
if (setTypeIsMember(sets[j],ele)) break;
}
if (j == setnum) {
/* There is no other set with this element. Add it. */
setTypeAdd(dstset,ele);
cardinality++;
}
decrRefCount(ele);
}
setTypeReleaseIterator(si);
} else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 2) {
/* DIFF Algorithm 2:
*
* Add all the elements of the first set to the auxiliary set.
* Then remove all the elements of all the next sets from it.
*
* This is O(N) where N is the sum of all the elements in every
* set. */
for (j = 0; j < setnum; j++) {
if (!sets[j]) continue; /* non existing keys are like empty sets */
/* Exit when result set is empty. */
if (op == REDIS_OP_DIFF && cardinality == 0) break;
si = setTypeInitIterator(sets[j]);
while((ele = setTypeNextObject(si)) != NULL) {
if (j == 0) {
if (setTypeAdd(dstset,ele)) cardinality++;
} else {
if (setTypeRemove(dstset,ele)) cardinality--;
}
decrRefCount(ele);
}
setTypeReleaseIterator(si);
/* Exit if result set is empty as any additional removal
* of elements will have no effect. */
if (cardinality == 0) break;
}
}
/* Output the content of the resulting set, if not in STORE mode */
@@ -771,13 +877,19 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
} else {
/* If we have a target key where to store the resulting set
* create this key with the result set inside */
dbDelete(c->db,dstkey);
int deleted = dbDelete(c->db,dstkey);
if (setTypeSize(dstset) > 0) {
dbAdd(c->db,dstkey,dstset);
addReplyLongLong(c,setTypeSize(dstset));
notifyKeyspaceEvent(REDIS_NOTIFY_SET,
op == REDIS_OP_UNION ? "sunionstore" : "sdiffstore",
dstkey,c->db->id);
} else {
decrRefCount(dstset);
addReply(c,shared.czero);
if (deleted)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
dstkey,c->db->id);
}
signalModifiedKey(c->db,dstkey);
server.dirty++;
@@ -800,3 +912,13 @@ void sdiffCommand(redisClient *c) {
void sdiffstoreCommand(redisClient *c) {
sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],REDIS_OP_DIFF);
}
void sscanCommand(redisClient *c) {
robj *set;
unsigned long cursor;
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == REDIS_ERR) return;
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
checkType(c,set,REDIS_SET)) return;
scanGenericCommand(c,set,cursor);
}
+12 -14
View File
@@ -84,6 +84,9 @@ void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *ex
setKey(c->db,key,val);
server.dirty++;
if (expire) setExpire(c->db,key,mstime()+milliseconds);
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",key,c->db->id);
if (expire) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
"expire",key,c->db->id);
addReply(c, ok_reply ? ok_reply : shared.ok);
}
@@ -162,6 +165,7 @@ void getsetCommand(redisClient *c) {
if (getGenericCommand(c) == REDIS_ERR) return;
c->argv[2] = tryObjectEncoding(c->argv[2]);
setKey(c->db,c->argv[1],c->argv[2]);
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[1],c->db->id);
server.dirty++;
}
@@ -211,18 +215,15 @@ void setrangeCommand(redisClient *c) {
return;
/* Create a copy when the object is shared or encoded. */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbOverwrite(c->db,c->argv[1],o);
}
o = dbUnshareStringValue(c->db,c->argv[1],o);
}
if (sdslen(value) > 0) {
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
memcpy((char*)o->ptr+offset,value,sdslen(value));
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,
"setrange",c->argv[1],c->db->id);
server.dirty++;
}
addReplyLongLong(c,sdslen(o->ptr));
@@ -307,6 +308,7 @@ void msetGenericCommand(redisClient *c, int nx) {
for (j = 1; j < c->argc; j += 2) {
c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
setKey(c->db,c->argv[j],c->argv[j+1]);
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[j],c->db->id);
}
server.dirty += (c->argc-1)/2;
addReply(c, nx ? shared.cone : shared.ok);
@@ -341,6 +343,7 @@ void incrDecrCommand(redisClient *c, long long incr) {
else
dbAdd(c->db,c->argv[1],new);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
server.dirty++;
addReply(c,shared.colon);
addReply(c,new);
@@ -390,6 +393,7 @@ void incrbyfloatCommand(redisClient *c) {
else
dbAdd(c->db,c->argv[1],new);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
server.dirty++;
addReplyBulk(c,new);
@@ -424,19 +428,13 @@ void appendCommand(redisClient *c) {
if (checkStringLength(c,totlen) != REDIS_OK)
return;
/* If the object is shared or encoded, we have to make a copy */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbOverwrite(c->db,c->argv[1],o);
}
/* Append the value */
o = dbUnshareStringValue(c->db,c->argv[1],o);
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
totlen = sdslen(o->ptr);
}
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"append",c->argv[1],c->db->id);
server.dirty++;
addReplyLongLong(c,totlen);
}
+783 -135
View File
File diff suppressed because it is too large Load Diff
+61 -1
View File
@@ -402,7 +402,67 @@ void getRandomHexChars(char *p, unsigned int len) {
/* Turn it into hex digits taking just 4 bits out of 8 for every byte. */
for (j = 0; j < len; j++)
p[j] = charset[p[j] & 0x0F];
fclose(fp);
if (fp) fclose(fp);
}
/* Given the filename, return the absolute path as an SDS string, or NULL
* if it fails for some reason. Note that "filename" may be an absolute path
* already, this will be detected and handled correctly.
*
* The function does not try to normalize everything, but only the obvious
* case of one or more "../" appearning at the start of "filename"
* relative path. */
sds getAbsolutePath(char *filename) {
char cwd[1024];
sds abspath;
sds relpath = sdsnew(filename);
relpath = sdstrim(relpath," \r\n\t");
if (relpath[0] == '/') return relpath; /* Path is already absolute. */
/* If path is relative, join cwd and relative path. */
if (getcwd(cwd,sizeof(cwd)) == NULL) {
sdsfree(relpath);
return NULL;
}
abspath = sdsnew(cwd);
if (sdslen(abspath) && abspath[sdslen(abspath)-1] != '/')
abspath = sdscat(abspath,"/");
/* At this point we have the current path always ending with "/", and
* the trimmed relative path. Try to normalize the obvious case of
* trailing ../ elements at the start of the path.
*
* For every "../" we find in the filename, we remove it and also remove
* the last element of the cwd, unless the current cwd is "/". */
while (sdslen(relpath) >= 3 &&
relpath[0] == '.' && relpath[1] == '.' && relpath[2] == '/')
{
sdsrange(relpath,3,-1);
if (sdslen(abspath) > 1) {
char *p = abspath + sdslen(abspath)-2;
int trimlen = 1;
while(*p != '/') {
p--;
trimlen++;
}
sdsrange(abspath,0,-(trimlen+1));
}
}
/* Finally glue the two parts together. */
abspath = sdscatsds(abspath,relpath);
sdsfree(relpath);
return abspath;
}
/* Return true if the specified path is just a file basename without any
* relative or absolute path. This function just checks that no / or \
* character exists inside the specified path, that's enough in the
* environments where Redis runs. */
int pathIsBaseName(char *path) {
return strchr(path,'/') == NULL && strchr(path,'\\') == NULL;
}
#ifdef UTIL_TEST_MAIN
+4
View File
@@ -30,6 +30,8 @@
#ifndef __REDIS_UTIL_H
#define __REDIS_UTIL_H
#include "sds.h"
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
int stringmatch(const char *p, const char *s, int nocase);
long long memtoll(const char *p, int *err);
@@ -37,5 +39,7 @@ int ll2string(char *s, size_t len, long long value);
int string2ll(const char *s, size_t slen, long long *value);
int string2l(const char *s, size_t slen, long *value);
int d2string(char *buf, size_t len, double value);
sds getAbsolutePath(char *filename);
int pathIsBaseName(char *path);
#endif
+1 -1
View File
@@ -1 +1 @@
#define REDIS_VERSION "2.6.13"
#define REDIS_VERSION "2.8.11"
+3 -2
View File
@@ -105,12 +105,12 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <limits.h>
#include "zmalloc.h"
#include "util.h"
#include "ziplist.h"
#include "endianconv.h"
#include "redisassert.h"
#define ZIP_END 255
#define ZIP_BIGLEN 254
@@ -1041,7 +1041,8 @@ void pop(unsigned char *zl, int where) {
}
int randstring(char *target, unsigned int min, unsigned int max) {
int p, len = min+rand()%(max-min+1);
int p = 0;
int len = min+rand()%(max-min+1);
int minval, maxval;
switch(rand() % 3) {
case 0:
+1 -2
View File
@@ -4,7 +4,7 @@
* efficient.
*
* The Redis Hash type uses this data structure for hashes composed of a small
* number of elements, to switch to an hash table once a given number of
* number of elements, to switch to a hash table once a given number of
* elements is reached.
*
* Given that many times Redis Hashes are used to represent objects composed
@@ -78,7 +78,6 @@
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "zmalloc.h"
#include "endianconv.h"
+1 -1
View File
@@ -32,7 +32,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _ZIMMAP_H
#ifndef _ZIPMAP_H
#define _ZIPMAP_H
unsigned char *zipmapNew(void);
+5 -5
View File
@@ -176,7 +176,7 @@ void *zrealloc(void *ptr, size_t size) {
}
/* Provide zmalloc_size() for systems where this function is not provided by
* malloc itself, given that in that case we store an header with this
* malloc itself, given that in that case we store a header with this
* information as the first bytes of every allocation. */
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr) {
@@ -252,7 +252,7 @@ void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
* function RedisEstimateRSS() that is a much faster (and less precise)
* version of the function. */
#if defined(HAVE_PROCFS)
#if defined(HAVE_PROC_STAT)
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -321,11 +321,11 @@ size_t zmalloc_get_rss(void) {
#endif
/* Fragmentation = RSS / allocated-bytes */
float zmalloc_get_fragmentation_ratio(void) {
return (float)zmalloc_get_rss()/zmalloc_used_memory();
float zmalloc_get_fragmentation_ratio(size_t rss) {
return (float)rss/zmalloc_used_memory();
}
#if defined(HAVE_PROCFS)
#if defined(HAVE_PROC_SMAPS)
size_t zmalloc_get_private_dirty(void) {
char line[1024];
size_t pd = 0;
+1 -1
View File
@@ -73,7 +73,7 @@ char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
float zmalloc_get_fragmentation_ratio(void);
float zmalloc_get_fragmentation_ratio(size_t rss);
size_t zmalloc_get_rss(void);
size_t zmalloc_get_private_dirty(void);
void zlibc_free(void *ptr);
+3 -1
View File
@@ -1,11 +1,13 @@
# Redis configuration for testing.
notify-keyspace-events KEA
daemonize no
pidfile /var/run/redis.pid
port 6379
timeout 0
bind 127.0.0.1
loglevel verbose
logfile stdout
logfile ''
databases 16
save 900 1
+1 -1
View File
@@ -98,7 +98,7 @@ tags {"aof"} {
}
}
## Test that SPOP (that modifies the client its argc/argv) is correctly free'd
## Test that SPOP (that modifies the client's argc/argv) is correctly free'd
create_aof {
append_to_aof [formatCommand sadd set foo]
append_to_aof [formatCommand sadd set bar]
+60
View File
@@ -6,6 +6,66 @@ start_server {tags {"repl"}} {
s -1 role
} {slave}
test {If min-slaves-to-write is honored, write is accepted} {
r config set min-slaves-to-write 1
r config set min-slaves-max-lag 10
r set foo 12345
wait_for_condition 50 100 {
[r -1 get foo] eq {12345}
} else {
fail "Write did not reached slave"
}
}
test {No write if min-slaves-to-write is < attached slaves} {
r config set min-slaves-to-write 2
r config set min-slaves-max-lag 10
catch {r set foo 12345} err
set err
} {NOREPLICAS*}
test {If min-slaves-to-write is honored, write is accepted (again)} {
r config set min-slaves-to-write 1
r config set min-slaves-max-lag 10
r set foo 12345
wait_for_condition 50 100 {
[r -1 get foo] eq {12345}
} else {
fail "Write did not reached slave"
}
}
test {No write if min-slaves-max-lag is > of the slave lag} {
r -1 deferred 1
r config set min-slaves-to-write 1
r config set min-slaves-max-lag 2
r -1 debug sleep 6
assert {[r set foo 12345] eq {OK}}
after 4000
catch {r set foo 12345} err
assert {[r -1 read] eq {OK}}
r -1 deferred 0
set err
} {NOREPLICAS*}
test {min-slaves-to-write is ignored by slaves} {
r config set min-slaves-to-write 1
r config set min-slaves-max-lag 10
r -1 config set min-slaves-to-write 1
r -1 config set min-slaves-max-lag 10
r set foo aaabbb
wait_for_condition 50 100 {
[r -1 get foo] eq {aaabbb}
} else {
fail "Write did not reached slave"
}
}
# Fix parameters for the next test to work
r config set min-slaves-to-write 0
r -1 config set min-slaves-to-write 0
r flushall
test {MASTER and SLAVE dataset should be identical after complex ops} {
createComplexDataset r 10000
after 500
+73 -3
View File
@@ -2,9 +2,12 @@ start_server {tags {"repl"}} {
start_server {} {
test {First server should have role slave after SLAVEOF} {
r -1 slaveof [srv 0 host] [srv 0 port]
after 1000
s -1 role
} {slave}
wait_for_condition 50 100 {
[s -1 master_link_status] eq {up}
} else {
fail "Replication not started."
}
}
if {$::accurate} {set numops 50000} else {set numops 5000}
@@ -29,3 +32,70 @@ start_server {tags {"repl"}} {
}
}
}
start_server {tags {"repl"}} {
start_server {} {
test {First server should have role slave after SLAVEOF} {
r -1 slaveof [srv 0 host] [srv 0 port]
wait_for_condition 50 100 {
[s -1 master_link_status] eq {up}
} else {
fail "Replication not started."
}
}
set numops 20000 ;# Enough to trigger the Script Cache LRU eviction.
# While we are at it, enable AOF to test it will be consistent as well
# after the test.
r config set appendonly yes
test {MASTER and SLAVE consistency with EVALSHA replication} {
array set oldsha {}
for {set j 0} {$j < $numops} {incr j} {
set key "key:$j"
# Make sure to create scripts that have different SHA1s
set script "return redis.call('incr','$key')"
set sha1 [r eval "return redis.sha1hex(\"$script\")" 0]
set oldsha($j) $sha1
r eval $script 0
set res [r evalsha $sha1 0]
assert {$res == 2}
# Additionally call one of the old scripts as well, at random.
set res [r evalsha $oldsha([randomInt $j]) 0]
assert {$res > 2}
# Trigger an AOF rewrite while we are half-way, this also
# forces the flush of the script cache, and we will cover
# more code as a result.
if {$j == $numops / 2} {
catch {r bgrewriteaof}
}
}
wait_for_condition 50 100 {
[r dbsize] == $numops &&
[r -1 dbsize] == $numops &&
[r debug digest] eq [r -1 debug digest]
} else {
set csv1 [csvdump r]
set csv2 [csvdump {r -1}]
set fd [open /tmp/repldump1.txt w]
puts -nonewline $fd $csv1
close $fd
set fd [open /tmp/repldump2.txt w]
puts -nonewline $fd $csv2
close $fd
puts "Master - Slave inconsistency"
puts "Run diff -u against /tmp/repldump*.txt for more info"
}
set old_digest [r debug digest]
r config set appendonly no
r debug loadaof
set new_digest [r debug digest]
assert {$old_digest eq $new_digest}
}
}
}
+88 -7
View File
@@ -10,21 +10,22 @@ proc stop_bg_complex_data {handle} {
start_server {tags {"repl"}} {
start_server {} {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
set master [srv -1 client]
set master_host [srv -1 host]
set master_port [srv -1 port]
set slave [srv 0 client]
set load_handle0 [start_bg_complex_data $master_host $master_port 9 100000]
set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000]
set load_handle2 [start_bg_complex_data $master_host $master_port 12 100000]
test {First server should have role slave after SLAVEOF} {
r -1 slaveof [srv 0 host] [srv 0 port]
$slave slaveof $master_host $master_port
after 1000
s -1 role
s 0 role
} {slave}
test {Test replication with parallel clients writing in differnet DBs} {
lappend slave [srv 0 client]
after 5000
stop_bg_complex_data $load_handle0
stop_bg_complex_data $load_handle1
@@ -37,7 +38,7 @@ start_server {tags {"repl"}} {
}
assert {[$master dbsize] > 0}
if {[r debug digest] ne [r -1 debug digest]} {
if {[$master debug digest] ne [$slave debug digest]} {
set csv1 [csvdump r]
set csv2 [csvdump {r -1}]
set fd [open /tmp/repldump1.txt w]
@@ -53,3 +54,83 @@ start_server {tags {"repl"}} {
}
}
}
start_server {tags {"repl"}} {
start_server {} {
set master [srv -1 client]
set master_host [srv -1 host]
set master_port [srv -1 port]
set slave [srv 0 client]
test {First server should have role slave after SLAVEOF} {
$slave slaveof $master_host $master_port
wait_for_condition 50 100 {
[s 0 master_link_status] eq {up}
} else {
fail "Replication not started."
}
}
test {With min-slaves-to-write (1,3): master should be writable} {
$master config set min-slaves-max-lag 3
$master config set min-slaves-to-write 1
$master set foo bar
} {OK}
test {With min-slaves-to-write (2,3): master should not be writable} {
$master config set min-slaves-max-lag 3
$master config set min-slaves-to-write 2
catch {$master set foo bar} e
set e
} {NOREPLICAS*}
test {With min-slaves-to-write: master not writable with lagged slave} {
$master config set min-slaves-max-lag 2
$master config set min-slaves-to-write 1
assert {[$master set foo bar] eq {OK}}
$slave deferred 1
$slave debug sleep 6
after 4000
catch {$master set foo bar} e
set e
} {NOREPLICAS*}
}
}
start_server {tags {"repl"}} {
start_server {} {
set master [srv -1 client]
set master_host [srv -1 host]
set master_port [srv -1 port]
set slave [srv 0 client]
test {First server should have role slave after SLAVEOF} {
$slave slaveof $master_host $master_port
wait_for_condition 50 100 {
[s 0 role] eq {slave}
} else {
fail "Replication not started."
}
}
test {Replication: commands with many arguments (issue #1221)} {
# We now issue large MSET commands, that may trigger a specific
# class of bugs, see issue #1221.
for {set j 0} {$j < 100} {incr j} {
set cmd [list mset]
for {set x 0} {$x < 1000} {incr x} {
lappend cmd [randomKey] [randomValue]
}
$master {*}$cmd
}
set retry 10
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
{
after 1000
incr retry -1
}
assert {[$master dbsize] > 0}
}
}
}
+105
View File
@@ -0,0 +1,105 @@
proc start_bg_complex_data {host port db ops} {
set tclsh [info nameofexecutable]
exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops &
}
proc stop_bg_complex_data {handle} {
catch {exec /bin/kill -9 $handle}
}
# Creates a master-slave pair and breaks the link continuously to force
# partial resyncs attempts, all this while flooding the master with
# write queries.
#
# You can specifiy backlog size, ttl, delay before reconnection, test duration
# in seconds, and an additional condition to verify at the end.
proc test_psync {descr duration backlog_size backlog_ttl delay cond} {
start_server {tags {"repl"}} {
start_server {} {
set master [srv -1 client]
set master_host [srv -1 host]
set master_port [srv -1 port]
set slave [srv 0 client]
$master config set repl-backlog-size $backlog_size
$master config set repl-backlog-ttl $backlog_ttl
set load_handle0 [start_bg_complex_data $master_host $master_port 9 100000]
set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000]
set load_handle2 [start_bg_complex_data $master_host $master_port 12 100000]
test {First server should have role slave after SLAVEOF} {
$slave slaveof $master_host $master_port
wait_for_condition 50 100 {
[s 0 role] eq {slave}
} else {
fail "Replication not started."
}
}
test "Test replication partial resync: $descr" {
# Now while the clients are writing data, break the maste-slave
# link multiple times.
for {set j 0} {$j < $duration*10} {incr j} {
after 100
# catch {puts "MASTER [$master dbsize] keys, SLAVE [$slave dbsize] keys"}
if {($j % 20) == 0} {
catch {
if {$delay} {
$slave multi
$slave client kill $master_host:$master_port
$slave debug sleep $delay
$slave exec
} else {
$slave client kill $master_host:$master_port
}
}
}
}
stop_bg_complex_data $load_handle0
stop_bg_complex_data $load_handle1
stop_bg_complex_data $load_handle2
set retry 10
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
{
after 1000
incr retry -1
}
assert {[$master dbsize] > 0}
if {[$master debug digest] ne [$slave debug digest]} {
set csv1 [csvdump r]
set csv2 [csvdump {r -1}]
set fd [open /tmp/repldump1.txt w]
puts -nonewline $fd $csv1
close $fd
set fd [open /tmp/repldump2.txt w]
puts -nonewline $fd $csv2
close $fd
puts "Master - Slave inconsistency"
puts "Run diff -u against /tmp/repldump*.txt for more info"
}
assert_equal [r debug digest] [r -1 debug digest]
eval $cond
}
}
}
}
test_psync {ok psync} 6 1000000 3600 0 {
assert {[s -1 sync_partial_ok] > 0}
}
test_psync {no backlog} 6 100 3600 0 {
assert {[s -1 sync_partial_err] > 0}
}
test_psync {ok after delay} 3 100000000 3600 3 {
assert {[s -1 sync_partial_ok] > 0}
}
test_psync {backlog expired} 3 100000000 1 3 {
assert {[s -1 sync_partial_err] > 0}
}
+1 -1
View File
@@ -112,7 +112,7 @@ start_server {tags {"repl"}} {
set retry 500
while {$retry} {
set info [r -3 info]
if {[string match {*slave0:*,online*slave1:*,online*slave2:*,online*} $info]} {
if {[string match {*slave0:*state=online*slave1:*state=online*slave2:*state=online*} $info]} {
break
} else {
incr retry -1
+126
View File
@@ -0,0 +1,126 @@
# Check the basic monitoring and failover capabilities.
source "../sentinel-tests/includes/init-tests.tcl"
if {$::simulate_error} {
test "This test will fail" {
fail "Simulated error"
}
}
test "Basic failover works if the master is down" {
set old_port [RI $master_id tcp_port]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port}
kill_instance redis $master_id
foreach_sentinel_id id {
wait_for_condition 1000 50 {
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
fail "At least one Sentinel did not received failover info"
}
}
restart_instance redis $master_id
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
}
test "New master [join $addr {:}] role matches" {
assert {[RI $master_id role] eq {master}}
}
test "All the other slaves now point to the new master" {
foreach_redis_id id {
if {$id != $master_id && $id != 0} {
wait_for_condition 1000 50 {
[RI $id master_port] == [lindex $addr 1]
} else {
fail "Redis ID $id not configured to replicate with new master"
}
}
}
}
test "The old master eventually gets reconfigured as a slave" {
wait_for_condition 1000 50 {
[RI 0 master_port] == [lindex $addr 1]
} else {
fail "Old master not reconfigured as slave of new master"
}
}
test "ODOWN is not possible without N (quorum) Sentinels reports" {
foreach_sentinel_id id {
S $id SENTINEL SET mymaster quorum [expr $sentinels+1]
}
set old_port [RI $master_id tcp_port]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port}
kill_instance redis $master_id
# Make sure failover did not happened.
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port}
restart_instance redis $master_id
}
test "Failover is not possible without majority agreement" {
foreach_sentinel_id id {
S $id SENTINEL SET mymaster quorum $quorum
}
# Crash majority of sentinels
for {set id 0} {$id < $quorum} {incr id} {
kill_instance sentinel $id
}
# Kill the current master
kill_instance redis $master_id
# Make sure failover did not happened.
set addr [S $quorum SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port}
restart_instance redis $master_id
# Cleanup: restart Sentinels to monitor the master.
for {set id 0} {$id < $quorum} {incr id} {
restart_instance sentinel $id
}
}
test "Failover works if we configure for absolute agreement" {
foreach_sentinel_id id {
S $id SENTINEL SET mymaster quorum $sentinels
}
# Wait for Sentinels to monitor the master again
foreach_sentinel_id id {
wait_for_condition 1000 50 {
[dict get [S $id SENTINEL MASTER mymaster] info-refresh] < 100000
} else {
fail "At least one Sentinel is not monitoring the master"
}
}
kill_instance redis $master_id
foreach_sentinel_id id {
wait_for_condition 1000 50 {
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
fail "At least one Sentinel did not received failover info"
}
}
restart_instance redis $master_id
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
# Set the min ODOWN agreement back to strict majority.
foreach_sentinel_id id {
S $id SENTINEL SET mymaster quorum $quorum
}
}
test "New master [join $addr {:}] role matches" {
assert {[RI $master_id role] eq {master}}
}
+39
View File
@@ -0,0 +1,39 @@
# Test Sentinel configuration consistency after partitions heal.
source "../sentinel-tests/includes/init-tests.tcl"
test "We can failover with Sentinel 1 crashed" {
set old_port [RI $master_id tcp_port]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port}
# Crash Sentinel 1
kill_instance sentinel 1
kill_instance redis $master_id
foreach_sentinel_id id {
if {$id != 1} {
wait_for_condition 1000 50 {
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
fail "Sentinel $id did not received failover info"
}
}
}
restart_instance redis $master_id
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
}
test "After Sentinel 1 is restarted, its config gets updated" {
restart_instance sentinel 1
wait_for_condition 1000 50 {
[lindex [S 1 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
fail "Restarted Sentinel did not received failover info"
}
}
test "New master [join $addr {:}] role matches" {
assert {[RI $master_id role] eq {master}}
}
+83
View File
@@ -0,0 +1,83 @@
# Check that slaves are reconfigured at a latter time if they are partitioned.
#
# Here we should test:
# 1) That slaves point to the new master after failover.
# 2) That partitioned slaves point to new master when they are partitioned
# away during failover and return at a latter time.
source "../sentinel-tests/includes/init-tests.tcl"
proc 03_test_slaves_replication {} {
uplevel 1 {
test "Check that slaves replicate from current master" {
set master_port [RI $master_id tcp_port]
foreach_redis_id id {
if {$id == $master_id} continue
if {[instance_is_killed redis $id]} continue
wait_for_condition 1000 50 {
[RI $id master_port] == $master_port
} else {
fail "Redis slave $id is replicating from wrong master"
}
}
}
}
}
proc 03_crash_and_failover {} {
uplevel 1 {
test "Crash the master and force a failover" {
set old_port [RI $master_id tcp_port]
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
assert {[lindex $addr 1] == $old_port}
kill_instance redis $master_id
foreach_sentinel_id id {
wait_for_condition 1000 50 {
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
fail "At least one Sentinel did not received failover info"
}
}
restart_instance redis $master_id
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
}
}
}
03_test_slaves_replication
03_crash_and_failover
03_test_slaves_replication
test "Kill a slave instance" {
foreach_redis_id id {
if {$id == $master_id} continue
set killed_slave_id $id
kill_instance redis $id
break
}
}
03_crash_and_failover
03_test_slaves_replication
test "Wait for failover to end" {
set inprogress 1
while {$inprogress} {
set inprogress 0
foreach_sentinel_id id {
if {[dict exists [S $id SENTINEL MASTER mymaster] failover-state]} {
incr inprogress
}
}
if {$inprogress} {after 100}
}
}
test "Restart killed slave and test replication of slaves again..." {
restart_instance redis $killed_slave_id
}
# Now we check if the slave rejoining the partition is reconfigured even
# if the failover finished.
03_test_slaves_replication
@@ -0,0 +1 @@
# Test runtime reconfiguration command SENTINEL SET.
@@ -0,0 +1,5 @@
# Test slave selection algorithm.
#
# This unit should test:
# 1) That when there are no suitable slaves no failover is performed.
# 2) That among the available slaves, the one with better offset is picked.
@@ -0,0 +1,67 @@
# Initialization tests -- most units will start including this.
test "(init) Restart killed instances" {
foreach type {redis sentinel} {
foreach_${type}_id id {
if {[get_instance_attrib $type $id pid] == -1} {
puts -nonewline "$type/$id "
flush stdout
restart_instance $type $id
}
}
}
}
set redis_slaves 4
test "(init) Create a master-slaves cluster of [expr $redis_slaves+1] instances" {
create_redis_master_slave_cluster [expr {$redis_slaves+1}]
}
set master_id 0
test "(init) Sentinels can start monitoring a master" {
set sentinels [llength $::sentinel_instances]
set quorum [expr {$sentinels/2+1}]
foreach_sentinel_id id {
catch {S $id SENTINEL REMOVE mymaster}
S $id SENTINEL MONITOR mymaster \
[get_instance_attrib redis $master_id host] \
[get_instance_attrib redis $master_id port] $quorum
}
foreach_sentinel_id id {
assert {[S $id sentinel master mymaster] ne {}}
S $id SENTINEL SET mymaster down-after-milliseconds 2000
S $id SENTINEL SET mymaster failover-timeout 20000
S $id SENTINEL SET mymaster parallel-syncs 10
}
}
test "(init) Sentinels can talk with the master" {
foreach_sentinel_id id {
wait_for_condition 100 50 {
[catch {S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster}] == 0
} else {
fail "Sentinel $id can't talk with the master."
}
}
}
test "(init) Sentinels are able to auto-discover other sentinels" {
set sentinels [llength $::sentinel_instances]
foreach_sentinel_id id {
wait_for_condition 100 50 {
[dict get [S $id SENTINEL MASTER mymaster] num-other-sentinels] == ($sentinels-1)
} else {
fail "At least some sentinel can't detect some other sentinel"
}
}
}
test "(init) Sentinels are able to auto-discover slaves" {
foreach_sentinel_id id {
wait_for_condition 100 50 {
[dict get [S $id SENTINEL MASTER mymaster] num-slaves] == $redis_slaves
} else {
fail "At least some sentinel can't detect some slave"
}
}
}
+2
View File
@@ -0,0 +1,2 @@
redis_*
sentinel_*
+396
View File
@@ -0,0 +1,396 @@
# Sentinel test suite. Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
# This softare is released under the BSD License. See the COPYING file for
# more information.
package require Tcl 8.5
set tcl_precision 17
source tests/support/redis.tcl
source tests/support/util.tcl
source tests/support/server.tcl
source tests/support/test.tcl
set ::verbose 0
set ::pause_on_error 0
set ::simulate_error 0
set ::sentinel_instances {}
set ::redis_instances {}
set ::sentinel_base_port 20000
set ::redis_base_port 30000
set ::instances_count 5 ; # How many Sentinels / Instances we use at max
set ::pids {} ; # We kill everything at exit
set ::dirs {} ; # We remove all the temp dirs at exit
set ::run_matching {} ; # If non empty, only tests matching pattern are run.
if {[catch {cd tests/sentinel-tmp}]} {
puts "tests/sentinel-tmp directory not found."
puts "Please run this test from the Redis source root."
exit 1
}
# Spawn a redis or sentinel instance, depending on 'type'.
proc spawn_instance {type base_port count} {
for {set j 0} {$j < $count} {incr j} {
set port [find_available_port $base_port]
incr base_port
puts "Starting $type #$j at port $port"
# Create a directory for this Sentinel.
set dirname "${type}_${j}"
lappend ::dirs $dirname
catch {exec rm -rf $dirname}
file mkdir $dirname
# Write the Sentinel config file.
set cfgfile [file join $dirname $type.conf]
set cfg [open $cfgfile w]
puts $cfg "port $port"
puts $cfg "dir ./$dirname"
puts $cfg "logfile log.txt"
close $cfg
# Finally exec it and remember the pid for later cleanup.
if {$type eq "redis"} {
set prgname redis-server
} else {
set prgname redis-sentinel
}
set pid [exec ../../src/${prgname} $cfgfile &]
lappend ::pids $pid
# Check availability
if {[server_is_up 127.0.0.1 $port 100] == 0} {
abort_sentinel_test "Problems starting $type #$j: ping timeout"
}
# Push the instance into the right list
lappend ::${type}_instances [list \
pid $pid \
host 127.0.0.1 \
port $port \
link [redis 127.0.0.1 $port] \
]
}
}
proc cleanup {} {
puts "Cleaning up..."
foreach pid $::pids {
catch {exec kill -9 $pid}
}
foreach dir $::dirs {
catch {exec rm -rf $dir}
}
}
proc abort_sentinel_test msg {
puts "WARNING: Aborting the test."
puts ">>>>>>>> $msg"
cleanup
exit 1
}
proc parse_options {} {
for {set j 0} {$j < [llength $::argv]} {incr j} {
set opt [lindex $::argv $j]
set val [lindex $::argv [expr $j+1]]
if {$opt eq "--single"} {
incr j
set ::run_matching "*${val}*"
} elseif {$opt eq "--pause-on-error"} {
set ::pause_on_error 1
} elseif {$opt eq "--fail"} {
set ::simulate_error 1
} elseif {$opt eq "--help"} {
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
puts "\nOptions:"
puts "--single <pattern> Only runs tests specified by pattern."
puts "--pause-on-error Pause for manual inspection on error."
puts "--fail Simulate a test failure."
puts "--help Shows this help."
exit 0
} else {
puts "Unknown option $opt"
exit 1
}
}
}
proc main {} {
parse_options
spawn_instance sentinel $::sentinel_base_port $::instances_count
spawn_instance redis $::redis_base_port $::instances_count
run_tests
cleanup
}
# If --pause-on-error option was passed at startup this function is called
# on error in order to give the developer a chance to understand more about
# the error condition while the instances are still running.
proc pause_on_error {} {
puts ""
puts [colorstr yellow "*** Please inspect the error now ***"]
puts "\nType \"continue\" to resume the test, \"help\" for help screen.\n"
while 1 {
puts -nonewline "> "
flush stdout
set line [gets stdin]
set argv [split $line " "]
set cmd [lindex $argv 0]
if {$cmd eq {continue}} {
break
} elseif {$cmd eq {show-sentinel-logs}} {
set count 10
if {[lindex $argv 1] ne {}} {set count [lindex $argv 1]}
foreach_sentinel_id id {
puts "=== SENTINEL $id ===="
puts [exec tail -$count sentinel_$id/log.txt]
puts "---------------------\n"
}
} elseif {$cmd eq {ls}} {
foreach_redis_id id {
puts -nonewline "Redis $id"
set errcode [catch {
set str {}
append str "@[RI $id tcp_port]: "
append str "[RI $id role] "
if {[RI $id role] eq {slave}} {
append str "[RI $id master_host]:[RI $id master_port]"
}
set str
} retval]
if {$errcode} {
puts " -- $retval"
} else {
puts $retval
}
}
foreach_sentinel_id id {
puts -nonewline "Sentinel $id"
set errcode [catch {
set str {}
append str "@[SI $id tcp_port]: "
append str "[join [S $id sentinel get-master-addr-by-name mymaster]]"
set str
} retval]
if {$errcode} {
puts " -- $retval"
} else {
puts $retval
}
}
} elseif {$cmd eq {help}} {
puts "ls List Sentinel and Redis instances."
puts "show-sentinel-logs \[N\] Show latest N lines of logs."
puts "S <id> cmd ... arg Call command in Sentinel <id>."
puts "R <id> cmd ... arg Call command in Redis <id>."
puts "SI <id> <field> Show Sentinel <id> INFO <field>."
puts "RI <id> <field> Show Sentinel <id> INFO <field>."
puts "continue Resume test."
} else {
set errcode [catch {eval $line} retval]
if {$retval ne {}} {puts "$retval"}
}
}
}
# We redefine 'test' as for Sentinel we don't use the server-client
# architecture for the test, everything is sequential.
proc test {descr code} {
set ts [clock format [clock seconds] -format %H:%M:%S]
puts -nonewline "$ts> $descr: "
flush stdout
if {[catch {set retval [uplevel 1 $code]} error]} {
if {[string match "assertion:*" $error]} {
set msg [string range $error 10 end]
puts [colorstr red $msg]
if {$::pause_on_error} pause_on_error
puts "(Jumping to next unit after error)"
return -code continue
} else {
# Re-raise, let handler up the stack take care of this.
error $error $::errorInfo
}
} else {
puts [colorstr green OK]
}
}
proc run_tests {} {
set tests [lsort [glob ../sentinel-tests/*]]
foreach test $tests {
if {$::run_matching ne {} && [string match $::run_matching $test] == 0} {
continue
}
if {[file isdirectory $test]} continue
puts [colorstr yellow "Testing unit: [lindex [file split $test] end]"]
source $test
}
}
# The "S" command is used to interact with the N-th Sentinel.
# The general form is:
#
# S <sentinel-id> command arg arg arg ...
#
# Example to ping the Sentinel 0 (first instance): S 0 PING
proc S {n args} {
set s [lindex $::sentinel_instances $n]
[dict get $s link] {*}$args
}
# Like R but to chat with Redis instances.
proc R {n args} {
set r [lindex $::redis_instances $n]
[dict get $r link] {*}$args
}
proc get_info_field {info field} {
set fl [string length $field]
append field :
foreach line [split $info "\n"] {
set line [string trim $line "\r\n "]
if {[string range $line 0 $fl] eq $field} {
return [string range $line [expr {$fl+1}] end]
}
}
return {}
}
proc SI {n field} {
get_info_field [S $n info] $field
}
proc RI {n field} {
get_info_field [R $n info] $field
}
# Iterate over IDs of sentinel or redis instances.
proc foreach_instance_id {instances idvar code} {
upvar 1 $idvar id
for {set id 0} {$id < [llength $instances]} {incr id} {
set errcode [catch {uplevel 1 $code} result]
if {$errcode == 1} {
error $result $::errorInfo $::errorCode
} elseif {$errcode == 4} {
continue
} elseif {$errcode == 3} {
break
} elseif {$errcode != 0} {
return -code $errcode $result
}
}
}
proc foreach_sentinel_id {idvar code} {
set errcode [catch {uplevel 1 [list foreach_instance_id $::sentinel_instances $idvar $code]} result]
return -code $errcode $result
}
proc foreach_redis_id {idvar code} {
set errcode [catch {uplevel 1 [list foreach_instance_id $::redis_instances $idvar $code]} result]
return -code $errcode $result
}
# Get the specific attribute of the specified instance type, id.
proc get_instance_attrib {type id attrib} {
dict get [lindex [set ::${type}_instances] $id] $attrib
}
# Set the specific attribute of the specified instance type, id.
proc set_instance_attrib {type id attrib newval} {
set d [lindex [set ::${type}_instances] $id]
dict set d $attrib $newval
lset ::${type}_instances $id $d
}
# Create a master-slave cluster of the given number of total instances.
# The first instance "0" is the master, all others are configured as
# slaves.
proc create_redis_master_slave_cluster n {
foreach_redis_id id {
if {$id == 0} {
# Our master.
R $id slaveof no one
R $id flushall
} elseif {$id < $n} {
R $id slaveof [get_instance_attrib redis 0 host] \
[get_instance_attrib redis 0 port]
} else {
# Instances not part of the cluster.
R $id slaveof no one
}
}
# Wait for all the slaves to sync.
wait_for_condition 1000 50 {
[RI 0 connected_slaves] == ($n-1)
} else {
fail "Unable to create a master-slaves cluster."
}
}
proc get_instance_id_by_port {type port} {
foreach_${type}_id id {
if {[get_instance_attrib $type $id port] == $port} {
return $id
}
}
fail "Instance $type port $port not found."
}
# Kill an instance of the specified type/id with SIGKILL.
# This function will mark the instance PID as -1 to remember that this instance
# is no longer running and will remove its PID from the list of pids that
# we kill at cleanup.
#
# The instance can be restarted with restart-instance.
proc kill_instance {type id} {
set pid [get_instance_attrib $type $id pid]
if {$pid == -1} {
error "You tried to kill $type $id twice."
}
exec kill -9 $pid
set_instance_attrib $type $id pid -1
set_instance_attrib $type $id link you_tried_to_talk_with_killed_instance
# Remove the PID from the list of pids to kill at exit.
set ::pids [lsearch -all -inline -not -exact $::pids $pid]
}
# Return true of the instance of the specified type/id is killed.
proc instance_is_killed {type id} {
set pid [get_instance_attrib $type $id pid]
return $pid == -1
}
# Restart an instance previously killed by kill_instance
proc restart_instance {type id} {
set dirname "${type}_${id}"
set cfgfile [file join $dirname $type.conf]
set port [get_instance_attrib $type $id port]
# Execute the instance with its old setup and append the new pid
# file for cleanup.
if {$type eq "redis"} {
set prgname redis-server
} else {
set prgname redis-sentinel
}
set pid [exec ../../src/${prgname} $cfgfile &]
set_instance_attrib $type $id pid $pid
lappend ::pids $pid
# Check that the instance is running
if {[server_is_up 127.0.0.1 $port 100] == 0} {
abort_sentinel_test "Problems starting $type #$j: ping timeout"
}
# Connect with it with a fresh link
set_instance_attrib $type $id link [redis 127.0.0.1 $port]
}
if {[catch main e]} {
puts $::errorInfo
cleanup
}
+4
View File
@@ -115,6 +115,10 @@ proc ::redis::__method__channel {id fd} {
return $fd
}
proc ::redis::__method__deferred {id fd val} {
set ::redis::deferred($id) $val
}
proc ::redis::redis_write {fd buf} {
puts -nonewline $fd $buf
}
+25 -14
View File
@@ -40,6 +40,11 @@ proc kill_server config {
test "Check for memory leaks (pid $pid)" {
set output {0 leaks}
catch {exec leaks $pid} output
if {[string match {*process does not exist*} $output] ||
[string match {*cannot examine*} $output]} {
# In a few tests we kill the server process.
set output "0 leaks"
}
set output
} {*0 leaks*}
}
@@ -79,13 +84,13 @@ proc is_alive config {
proc ping_server {host port} {
set retval 0
if {[catch {
set fd [socket $::host $::port]
set fd [socket $host $port]
fconfigure $fd -translation binary
puts $fd "PING\r\n"
flush $fd
set reply [gets $fd]
if {[string range $reply 0 4] eq {+PONG} ||
[string range $reply 0 3] eq {-ERR}} {
if {[string range $reply 0 0] eq {+} ||
[string range $reply 0 0] eq {-}} {
set retval 1
}
close $fd
@@ -101,6 +106,22 @@ proc ping_server {host port} {
return $retval
}
# Return 1 if the server at the specified addr is reachable by PING, otherwise
# returns 0. Performs a try every 50 milliseconds for the specified number
# of retries.
proc server_is_up {host port retrynum} {
after 10 ;# Use a small delay to make likely a first-try success.
set retval 0
while {[incr retrynum -1]} {
if {[catch {ping_server $host $port} ping]} {
set ping 0
}
if {$ping} {return 1}
after 50
}
return 0
}
# doesn't really belong here, but highly coupled to code in start_server
proc tags {tags code} {
set ::tags [concat $::tags $tags]
@@ -191,23 +212,13 @@ proc start_server {options {code undefined}} {
# check that the server actually started
# ugly but tries to be as fast as possible...
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
set serverisup 0
if {$::verbose} {
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
}
after 10
if {$code ne "undefined"} {
while {[incr retrynum -1]} {
catch {
if {[ping_server $::host $::port]} {
set serverisup 1
}
}
if {$serverisup} break
after 50
}
set serverisup [server_is_up $::host $::port $retrynum]
} else {
set serverisup 1
}
+8 -32
View File
@@ -53,41 +53,17 @@ proc assert_type {type key} {
# executed.
proc wait_for_condition {maxtries delay e _else_ elsescript} {
while {[incr maxtries -1] >= 0} {
if {[uplevel 1 [list expr $e]]} break
set errcode [catch {uplevel 1 [list expr $e]} result]
if {$errcode == 0} {
if {$result} break
} else {
return -code $errcode $result
}
after $delay
}
if {$maxtries == -1} {
uplevel 1 $elsescript
}
}
# Test if TERM looks like to support colors
proc color_term {} {
expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]}
}
proc colorstr {color str} {
if {[color_term]} {
set b 0
if {[string range $color 0 4] eq {bold-}} {
set b 1
set color [string range $color 5 end]
}
switch $color {
red {set colorcode {31}}
green {set colorcode {32}}
yellow {set colorcode {33}}
blue {set colorcode {34}}
magenta {set colorcode {35}}
cyan {set colorcode {36}}
white {set colorcode {37}}
default {set colorcode {37}}
}
if {$colorcode ne {}} {
return "\033\[$b;${colorcode};40m$str\033\[0m"
}
} else {
return $str
set errcode [catch [uplevel 1 $elsescript] result]
return -code $errcode $result
}
}
+47
View File
@@ -91,10 +91,12 @@ proc wait_for_sync r {
}
}
# Random integer between 0 and max (excluded).
proc randomInt {max} {
expr {int(rand()*$max)}
}
# Random signed integer between -max and max (both extremes excluded).
proc randomSignedInt {max} {
set i [randomInt $max]
if {rand() > 0.5} {
@@ -310,3 +312,48 @@ proc csvstring s {
proc roundFloat f {
format "%.10g" $f
}
proc find_available_port start {
for {set j $start} {$j < $start+1024} {incr j} {
if {[catch {
set fd [socket 127.0.0.1 $j]
}]} {
return $j
} else {
close $fd
}
}
if {$j == $start+1024} {
error "Can't find a non busy port in the $start-[expr {$start+1023}] range."
}
}
# Test if TERM looks like to support colors
proc color_term {} {
expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]}
}
proc colorstr {color str} {
if {[color_term]} {
set b 0
if {[string range $color 0 4] eq {bold-}} {
set b 1
set color [string range $color 5 end]
}
switch $color {
red {set colorcode {31}}
green {set colorcode {32}}
yellow {set colorcode {33}}
blue {set colorcode {34}}
magenta {set colorcode {35}}
cyan {set colorcode {36}}
white {set colorcode {37}}
default {set colorcode {37}}
}
if {$colorcode ne {}} {
return "\033\[$b;${colorcode};40m$str\033\[0m"
}
} else {
return $str
}
}
+30 -25
View File
@@ -16,6 +16,7 @@ set ::all_tests {
unit/auth
unit/protocol
unit/basic
unit/scan
unit/type/list
unit/type/list-2
unit/type/list-3
@@ -32,6 +33,7 @@ set ::all_tests {
integration/replication-2
integration/replication-3
integration/replication-4
integration/replication-psync
integration/aof
integration/rdb
integration/convert-zipmap-hash-on-load
@@ -44,6 +46,8 @@ set ::all_tests {
unit/obuf-limits
unit/dump
unit/bitops
unit/memefficiency
unit/hyperloglog
}
# Index to the next test to run in the ::all_tests list.
set ::next_test 0
@@ -161,21 +165,6 @@ proc cleanup {} {
if {!$::quiet} {puts "OK"}
}
proc find_available_port start {
for {set j $start} {$j < $start+1024} {incr j} {
if {[catch {
set fd [socket 127.0.0.1 $j]
}]} {
return $j
} else {
close $fd
}
}
if {$j == $start+1024} {
error "Can't find a non busy port in the $start-[expr {$start+1023}] range."
}
}
proc test_server_main {} {
cleanup
set tclsh [info nameofexecutable]
@@ -185,7 +174,7 @@ proc test_server_main {} {
if {!$::quiet} {
puts "Starting test server at port $port"
}
socket -server accept_test_clients $port
socket -server accept_test_clients -myaddr 127.0.0.1 $port
# Start the client instances
set ::clients_pids {}
@@ -201,6 +190,7 @@ proc test_server_main {} {
# Setup global state for the test server
set ::idle_clients {}
set ::active_clients {}
array set ::active_clients_task {}
array set ::clients_start_time {}
set ::clients_time_history {}
set ::failed_tests {}
@@ -214,9 +204,12 @@ proc test_server_main {} {
# may be used in the future in order to detect test clients taking too much
# time to execute the task.
proc test_server_cron {} {
# Do some work here.
after 100 test_server_cron
}
proc accept_test_clients {fd addr port} {
fconfigure $fd -encoding binary
fileevent $fd readable [list read_from_test_client $fd]
}
@@ -250,14 +243,17 @@ proc read_from_test_client fd {
puts "\[$completed_tests_count/$all_tests_count [colorstr yellow $status]\]: $data ($elapsed seconds)"
lappend ::clients_time_history $elapsed $data
signal_idle_client $fd
set ::active_clients_task($fd) DONE
} elseif {$status eq {ok}} {
if {!$::quiet} {
puts "\[[colorstr green $status]\]: $data"
}
set ::active_clients_task($fd) "(OK) $data"
} elseif {$status eq {err}} {
set err "\[[colorstr red $status]\]: $data"
puts $err
lappend ::failed_tests $err
set ::active_clients_task($fd) "(ERR) $data"
} elseif {$status eq {exception}} {
puts "\[[colorstr red $status]\]: $data"
foreach p $::clients_pids {
@@ -265,7 +261,7 @@ proc read_from_test_client fd {
}
exit 1
} elseif {$status eq {testing}} {
# No op
set ::active_clients_task($fd) "(IN PROGRESS) $data"
} else {
if {!$::quiet} {
puts "\[$status\]: $data"
@@ -279,10 +275,24 @@ proc signal_idle_client fd {
# Remove this fd from the list of active clients.
set ::active_clients \
[lsearch -all -inline -not -exact $::active_clients $fd]
if 0 {
# The following loop is only useful for debugging tests that may
# enter an infinite loop. Commented out normally.
foreach x $::active_clients {
if {[info exist ::active_clients_task($x)]} {
puts "$x => $::active_clients_task($x)"
} else {
puts "$x => ???"
}
}
}
# New unit to process?
if {$::next_test != [llength $::all_tests]} {
if {!$::quiet} {
puts [colorstr bold-white "Testing [lindex $::all_tests $::next_test]"]
set ::active_clients_task($fd) "ASSIGNED: $fd ([lindex $::all_tests $::next_test])"
}
set ::clients_start_time($fd) [clock seconds]
send_data_packet $fd run [lindex $::all_tests $::next_test]
@@ -323,6 +333,7 @@ proc the_end {} {
# to read the command, execute, reply... all this in a loop.
proc test_client_main server_port {
set ::test_server_fd [socket localhost $server_port]
fconfigure $::test_server_fd -encoding binary
send_data_packet $::test_server_fd ready [pid]
while 1 {
set bytes [gets $::test_server_fd]
@@ -438,15 +449,9 @@ proc read_from_replication_stream {s} {
after 100
}
fconfigure $s -blocking 1
# Workaround for Redis 2.6, not always using the new protocol in the
# replication channel (this was fixed in >= 2.8).
if {[string tolower [lindex [split $count] 0]] eq {select}} {
return $count
}
set count [string range $count 1 end]
# Return a list of arguments for the command.
set count [string range $count 1 end]
set res {}
for {set j 0} {$j < $count} {incr j} {
read $s 1
@@ -470,7 +475,7 @@ proc close_replication_stream {s} {
# With the parallel test running multiple Redis instances at the same time
# we need a fast enough computer, otherwise a lot of tests may generate
# false positives.
# If the computer is too slow we revert the sequetial test without any
# If the computer is too slow we revert the sequential test without any
# parallelism, that is, clients == 1.
proc is_a_slow_computer {} {
set start [clock milliseconds]
+1 -1
View File
@@ -14,7 +14,7 @@ start_server {tags {"auth"} overrides {requirepass foobar}} {
test {Arbitrary command gives an error when AUTH is required} {
catch {r set foo bar} err
set _ $err
} {ERR*operation not permitted}
} {NOAUTH*}
test {AUTH succeeds when the right password is given} {
r auth foobar

Some files were not shown because too many files have changed in this diff Show More