Compare commits

...
102 Commits
Author SHA1 Message Date
Oran Agra a767d84a72 Redis 5.0.10. 2020-10-27 08:49:22 +02:00
Yossi Gottlieb ee91248505 Backport Lua 5.2.2 stack overflow fix. (#7733)
This fixes the issue described in CVE-2014-5461. At this time we cannot
confirm that the original issue has a real impact on Redis, but it is
included as an extra safety measure.

(cherry picked from commit d75ad774a9)
(cherry picked from commit 941174d9c9ed438f1c70dd46cddc02468614db12)
2020-10-27 08:49:22 +02:00
Yossi Gottlieb 3cf3beff2c Fix wrong zmalloc_size() assumption. (#7963)
When using a system with no malloc_usable_size(), zmalloc_size() assumed
that the heap allocator always returns blocks that are long-padded.

This may not always be the case, and will result with zmalloc_size()
returning a size that is bigger than allocated. At least in one case
this leads to out of bound write, process crash and a potential security
vulnerability.

Effectively this does not affect the vast majority of users, who use
jemalloc or glibc.

This problem along with a (different) fix was reported by Drew DeVault.

(cherry picked from commit 9824fe3e39)
(cherry picked from commit ce0d74d8fdff55d07929f562ec9acf2d00caf893)
2020-10-27 08:49:22 +02:00
WuYunlong 54eb66495f Add fsync to readSyncBulkPayload(). (#7839)
We should sync temp DB file before renaming as rdb_fsync_range does not use
flag `SYNC_FILE_RANGE_WAIT_AFTER`.

Refer to `Linux Programmer's Manual`:
SYNC_FILE_RANGE_WAIT_AFTER
    Wait upon write-out of all pages in the range after performing any write.

(cherry picked from commit 0d62caab21)
2020-10-27 08:49:22 +02:00
Ariel Shtul 77f91e09cf Fix redis-check-rdb support for modules aux data (#7826)
redis-check-rdb was unable to parse rdb files containing module aux data.

Co-authored-by: Oran Agra <oran@redislabs.com>
(cherry picked from commit 63a05dde46)
2020-10-27 08:49:22 +02:00
hwware d60953bf25 fix memory leak in sentinel connection sharing
(cherry picked from commit 1bfa2d27a6)
(cherry picked from commit d3aa3791fe)
2020-10-27 08:49:22 +02:00
Oran Agra 89c68ba3f7 Allow blocked XREAD on a cluster replica (#7881)
I suppose that it was overlooked, since till recently none of the blocked commands were readonly.

other changes:
- add test for the above.
- add better support for additional (and deferring) clients for
  cluster tests
- improve a test which left the client in MULTI state.

(cherry picked from commit 216c110609)
2020-10-27 08:49:22 +02:00
guybe7 fcda82930e Modules: Invalidate saved_oparray after use (#7688)
We wanna avoid a chance of someone using the pointer in it after it'll be freed / realloced.

(cherry picked from commit 65c24bd3d4)
2020-10-27 08:49:22 +02:00
antirez cefd33925c Modules: remove spurious call from moduleHandleBlockedClients().
Now we handle propagation when we free the context.

(cherry picked from commit 4534960b29)
2020-10-27 08:49:22 +02:00
Angus Pearson 10202ba1fd Fix broken interval and repeat bahaviour in redis-cli (incluing cluster mode)
This addresses two problems, one where infinite (negative) repeat count is broken for all types for Redis,
and another specific to cluster mode where redirection is needed.

Now allows and works correctly for negative (i.e. -1) repeat values passed with `-r` argument to redis-cli
as documented here https://redis.io/topics/rediscli#continuously-run-the-same-command which seems to have
regressed as a feature in 95b988 (though that commit removed bad integer wrap-around to `0` behaviour).

This broken behaviour exists currently (e50458), and redis-cli will just exit immediately with repeat `-r <= 0`
as opposed to send commands indefinitely as it should with `-r < 0`

Additionally prevents a repeat * interval seconds hang/time spent doing nothing at the start before issuing
commands in cluster mode (`-c`), where the command needed to redirect to a slot on another node, as commands
where failing and waiting to be reissued but this was fully repeated before being reissued. For example,

        redis-cli -c -r 10 -i 0.5 INCR test_key_not_on_6379

Would hang and show nothing for 5 seconds (10 * 0.5) before showing

        (integer) 1
        (integer) 2
        (integer) 3
        (integer) 4
        (integer) 5
        (integer) 6
        (integer) 7
        (integer) 8
        (integer) 9
        (integer) 10

at half second intervals as intended.

(cherry picked from commit 2f6ed9333f)
2020-10-27 08:49:22 +02:00
antirez 97816fd63e Cluster: introduce data_received field.
We want to send pings and pongs at specific intervals, since our packets
also contain information about the configuration of the cluster and are
used for gossip. However since our cluster bus is used in a mixed way
for data (such as Pub/Sub or modules cluster messages) and metadata,
sometimes a very busy channel may delay the reception of pong packets.
So after discussing it in #7216, this commit introduces a new field that
is not exposed in the cluster, is only an internal information about
the last time we received any data from a given node: we use this field
in order to avoid detecting failures, claiming data reception of new
data from the node is a proof of liveness.

(cherry picked from commit 960186a71f)
2020-10-27 08:49:22 +02:00
Madelyn Olson 3b792f5100 Hide AUTH from monitor
partial cherry pick from 7d21754710
2020-10-27 08:49:22 +02:00
Guy Benoish da2906e507 Support streams in general module API functions
Fixes GitHub issue #6492
Added stream support in RM_KeyType and RM_ValueLength.
Also moduleDelKeyIfEmpty was updated, even though it has
no effect now (It will be relevant when stream type direct
API will be coded - i.e. RM_StreamAdd)

cherry picked from commit 1833d008b3
* modified to avoid adding new API to 5.0 (reverting the change to
  RM_KeyType)
2020-10-27 08:49:22 +02:00
Itamar Haber 18264d641b Expands lazyfree's effort estimate to include Streams (#5794)
Otherwise, it is treated as a single allocation and freed synchronously. The following logic is used for estimating the effort in constant-ish time complexity:

1. Check the number of nodes.
1. Add an allocation for each consumer group registered inside the stream.
1. Check the number of PELs in the first CG, and then add this count times the number of CGs.
1. Check the number of consumers in the first CG, and then add this count times the number of CGs.

(cherry picked from commit 5b0a06af48)
(cherry picked from commit 5a9a653f3e)
2020-10-27 08:49:22 +02:00
huangzhw 918c9aa58c defrag.c activeDefragSdsListAndDict when defrag sdsele, We can't use (#7492)
it to calculate hash, we should use newsds.

(cherry picked from commit d6180c8c86)
(cherry picked from commit 7b21b8c3fb)
2020-10-27 08:49:22 +02:00
Oran Agra 8cc6698567 RESTORE ABSTTL skip expired keys - leak (#7511)
(cherry picked from commit 6a81450144)
(cherry picked from commit c4b428a388)
2020-10-27 08:49:22 +02:00
Oran Agra e9c9e4c2af RESTORE ABSTTL won't store expired keys into the db (#7472)
Similarly to EXPIREAT with TTL in the past, which implicitly deletes the
key and return success, RESTORE should not store key that are already
expired into the db.
When used together with REPLACE it should emit a DEL to keyspace
notification and replication stream.

(cherry picked from commit 5977a94842)
(cherry picked from commit 95ba01b538)
2020-10-27 08:49:22 +02:00
Liu Zhen ee4696b150 fix clusters mixing accidentally by gossip
`clusterStartHandshake` will start hand handshake
and eventually send CLUSTER MEET message, which is strictly prohibited
in the REDIS CLUSTER SPEC.
Only system administrator can initiate CLUSTER MEET message.
Futher, according to the SPEC, rather than IP/PORT pairs, only nodeid
can be trusted.

(cherry picked from commit 84a7a90586)
2020-10-27 08:49:22 +02:00
Guy Benoish c9e370c6b8 XPENDING should not update consumer's seen-time
Same goes for XGROUP DELCONSUMER (But in this case, it doesn't
have any visible effect)

(cherry picked from commit 3a441c7d95)
2020-10-27 08:49:22 +02:00
antirez a3ca53e4a7 Also use propagate() in streamPropagateGroupID(). 2020-04-24 10:13:36 +02:00
yanhui13 7a62eb96ef optimize the output of cluster slots 2020-04-23 16:19:56 +02:00
srzhao 0efb93d0c0 Check OOM at script start to get stable lua OOM state.
Checking OOM by `getMaxMemoryState` inside script might get different result
with `freeMemoryIfNeededAndSafe` at script start, because lua stack and
arguments also consume memory.

This leads to memory `borderline` when memory grows near server.maxmemory:

- `freeMemoryIfNeededAndSafe` at script start detects no OOM, no memory freed
- `getMaxMemoryState` inside script detects OOM, script aborted

We solve this 'borderline' issue by saving OOM state at script start to get
stable lua OOM state.

related to issue #6565 and #5250.
2020-04-23 16:06:21 +02:00
antirez ce878b6ed5 Redis 5.0.9. 2020-04-17 12:45:57 +02:00
antirez 1fc8ef81aa Fix XCLAIM propagation in AOF/replicas for blocking XREADGROUP.
See issue #7105.
2020-04-17 12:40:57 +02:00
antirez a5e24eabc3 Speedup: unblock clients on keys in O(1).
See #7071.
2020-04-08 19:22:56 +02:00
antirez 1f7d08b76d Redis 5.0.8. 2020-03-12 16:07:44 +01:00
Salvatore Sanfilippo 2bea502d25 Merge pull request #6975 from dustinmm80/add-arm-latomic-linking
Fix Pi building needing -latomic, 5.0 branch backport
2020-03-12 15:55:24 +01:00
Dustin Collins b5931405ff Fix Pi building needing -latomic, backport 2020-03-11 11:34:59 -05:00
srzhao fd4413002d fix impl of aof-child whitelist SIGUSR1 feature. 2020-03-05 16:30:22 +01:00
Ariel 77ff332b4c fix ThreadSafeContext lock/unlock function names 2020-03-05 16:30:10 +01:00
Guy Benoish 4f0f799c96 XREADGROUP should propagate XCALIM/SETID in MULTI/EXEC
Use built-in alsoPropagate mechanism that wraps commands
in MULTI/EXEC before sending them to replica/AOF
2020-03-05 16:30:04 +01:00
Oran Agra 0c1273c389 Fix client flags to be int64 in module.c
currently there's no bug since the flags these functions handle are
always lower than 32bit, but still better fix the type to prevent future
bugs.
2020-03-05 16:29:12 +01:00
Guy Benoish 708a4e8a9b Fix small bugs related to replica and monitor ambiguity
1. server.repl_no_slaves_since can be set when a MONITOR client disconnects
2. c->repl_ack_time can be set by a newline from a MONITOR client
3. Improved comments
2020-03-05 16:29:08 +01:00
WuYunlong eac4115d36 Fix lua related memory leak. 2020-03-05 16:28:44 +01:00
antirez d075df1768 Simplify #6379 changes. 2020-03-05 16:28:35 +01:00
WuYunlong 80a49c37f9 Free allocated sds in pfdebugCommand() to avoid memory leak. 2020-03-05 16:28:30 +01:00
antirez 60870d3a10 Jump to right label on AOF parsing error.
Related to #6054.
2020-03-05 16:28:24 +01:00
antirez d90f599b4d Free fakeclient argv on AOF error.
We exit later, so no bug fixed, but it is more correct.

See #6054, thanks to @ShooterIT for finding the issue.
2020-03-05 16:28:19 +01:00
WuYunlong 8ee3bddfc7 Fix potential memory leak of rioWriteBulkStreamID(). 2020-03-05 16:26:43 +01:00
WuYunlong 4780fe78ba Fix potential memory leak of clusterLoadConfig(). 2020-03-05 16:26:40 +01:00
Leo Murillo f3b77510ef Fix bug on KEYS command where pattern starts with * followed by \x00 (null char). 2020-03-05 16:26:32 +01:00
Guy Benoish 7f3fcedb8c Blocking XREAD[GROUP] should always reply with valid data (or timeout)
This commit solves the following bug:
127.0.0.1:6379> XGROUP CREATE x grp $ MKSTREAM
OK
127.0.0.1:6379> XADD x 666 f v
"666-0"
127.0.0.1:6379> XREADGROUP GROUP grp Alice BLOCK 0 STREAMS x >
1) 1) "x"
   2) 1) 1) "666-0"
         2) 1) "f"
            2) "v"
127.0.0.1:6379> XADD x 667 f v
"667-0"
127.0.0.1:6379> XDEL x 667
(integer) 1
127.0.0.1:6379> XREADGROUP GROUP grp Alice BLOCK 0 STREAMS x >
1) 1) "x"
   2) (empty array)

The root cause is that we use s->last_id in streamCompareID
while we should use the last *valid* ID
2020-03-05 16:26:27 +01:00
antirez f93b2fa524 XCLAIM: Create the consumer only on successful claims.
Fixes #6744.
2020-03-05 16:25:01 +01:00
Guy Benoish 89682d96ea Stream: Handle streamID-related edge cases
This commit solves several edge cases that are related to
exhausting the streamID limits: We should correctly calculate
the succeeding streamID instead of blindly incrementing 'seq'
This affects both XREAD and XADD.

Other (unrelated) changes:
Reply with a better error message when trying to add an entry
to a stream that has exhausted last_id
2020-03-05 16:21:21 +01:00
antirez 920e108f84 Fix ip and missing mode in RM_GetClusterNodeInfo(). 2020-03-05 16:15:36 +01:00
antirez 7569b210d6 Inline protocol: handle empty strings well.
This bug is from the first version of Redis. Probably the problem here
is that before we used an SDS split function that created empty strings
for additional spaces, like in "SET    foo          bar".
AFAIK later we replaced it with the curretn sdssplitarg() API that has
no such a problem. As a result, we introduced a bug, where it is no
longer possible to do something like:

    SET foo ""

Using the inline protocol. Now it is fixed.
2020-03-05 16:15:31 +01:00
Khem Raj 3c610b4e8d Mark extern definition of SDS_NOINIT in sds.h
This helps in avoiding multiple definition of this variable, its also
defined globally in sds.c

Signed-off-by: Khem Raj <raj.khem@gmail.com>
2020-02-12 14:06:04 +01:00
Seunghoon Woo 16b2d07f0a [FIX] revisit CVE-2015-8080 vulnerability 2020-02-10 10:49:43 +01:00
yz1509 19f3358564 avoid sentinel changes promoted_slave to be its own replica. 2020-01-08 12:31:23 +01:00
antirez 4891612bb5 Redis 5.0.7. 2019-11-19 18:05:52 +01:00
antirez 4d2a31aed8 Test: fix implementation-dependent test after code change. 2019-11-19 17:41:47 +01:00
Oran Agra 9f63fc98df RED-31295 - redis: avoid race between dlopen and thread creation
It seeems that since I added the creation of the jemalloc thread redis
sometimes fails to start with the following error:

Inconsistency detected by ld.so: dl-tls.c: 493: _dl_allocate_tls_init: Assertion `listp->slotinfo[cnt].gen <= GL(dl_tls_generation)' failed!

This seems to be due to a race bug in ld.so, in which TLS creation on the
thread, collide with dlopen.

Move the creation of BIO and jemalloc threads to after modules are loaded.

plus small bugfix when trying to disable the jemalloc thread at runtime
2019-11-19 17:37:21 +01:00
antirez 1a9e70c1d7 Cluster: fix memory leak of cached master.
This is what happened:

1. Instance starts, is a slave in the cluster configuration, but
actually server.masterhost is not set, so technically the instance
is acting like a master.

2. loadDataFromDisk() calls replicationCacheMasterUsingMyself() even if
the instance is a master, in the case it is logically a slave and the
cluster is enabled. So now we have a cached master even if the instance
is practically configured as a master (from the POV of
server.masterhost value and so forth).

3. clusterCron() sees that the instance requires to replicate from its
master, because logically it is a slave, so it calls
replicationSetMaster() that will in turn call
replicationCacheMasterUsingMyself(): before this commit, this call would
overwrite the old cached master, creating a memory leak.
2019-11-19 17:28:59 +01:00
Guy Benoish 69b1b5be6b Fix usage of server.stream_node_max_* 2019-11-19 17:27:57 +01:00
喜欢兰花山丘 1fd97ee7f1 Update mkreleasehdr.sh
fix date +%s errata
2019-11-19 17:26:14 +01:00
antirez 1a9855d7d9 Remove additional space from comment. 2019-11-19 17:25:56 +01:00
antirez 32a6e3e48e Fix stream test after addition of 0-0 ID test. 2019-11-19 17:25:42 +01:00
Yuan Zhou c9e6cda9e8 aof: fix assignment for aof_fsync_offset
Signed-off-by: Yuan Zhou <yuan.zhou@intel.com>
2019-11-19 17:25:18 +01:00
antirez d3eeacf93d Merge branch '5.0' of github.com:/antirez/redis into 5.0 2019-11-19 17:24:30 +01:00
antirez da5dc4583f Rename var to fixed_time_expire now that is more general. 2019-11-19 17:24:06 +01:00
antirez 0fefed25e4 Fix patch provided in #6554. 2019-11-19 17:22:39 +01:00
zhaozhao.zz e9fbc96033 expires & blocking: handle ready keys as call() 2019-11-19 17:22:21 +01:00
Guy Benoish 08ec8f71ca XADD with ID 0-0 stores an empty key
Calling XADD with 0-0 or 0 would result in creating an
empty key and storing it in the database.
Even worse, because XADD will reply with error the action
will not be replicated, creating a master-replica
inconsistency
2019-11-19 17:21:08 +01:00
Loris Cro 09e1fe274d fix unreported overflow in autogerenared stream IDs 2019-11-19 17:20:45 +01:00
Salvatore Sanfilippo 09f9e4b030 Merge pull request #6600 from oranagra/5_module_flags
module documentation mismatches: loading and fork child for 5.0 branch
2019-11-19 17:19:12 +01:00
Oran Agra 8d8d68ddf3 module documentation mismatches: loading and fork child for 5.0 branch
loading flag was missing from docs, and fork chidl indicator was removed from
the code but left int he doc and header, re-adding it to the doc together with
the missing function that was needed for it to work.
2019-11-19 17:59:29 +02:00
antirez 7a7fbe708a Modules: RM_GetContextFlags(): remove non Redis 5 features. 2019-11-14 17:50:39 +01:00
antirez b5830486ca Modules: fix moduleCreateArgvFromUserFormat() casting bug.
In 32 bit systems casting to "long" will cut the result to 32 bit.
2019-11-14 17:48:59 +01:00
antirez b7a2a53a8c module: fix propagation API bug. 2019-11-14 17:48:51 +01:00
antirez 278bd6e3b6 Modules: add new flags to context, replica state + more. 2019-11-14 17:47:21 +01:00
antirez 61d9a1542a Modules: RM_Call(): give pointer to documentation. 2019-11-14 17:46:31 +01:00
antirez 0e7ea0aaa3 Modules: RM_Call/Replicate() ability to exclude AOF/replicas. 2019-11-14 17:46:25 +01:00
antirez 3b38164e8f Modules: RM_Replicate() in thread safe contexts. 2019-11-14 17:44:37 +01:00
antirez ef9fe9b0cb Modules: implement RM_Replicate() from async callbacks. 2019-11-14 17:44:29 +01:00
antirez 8066d2a197 Modules: handle propagation when ctx is freed. Flag modules commands ctx. 2019-11-14 17:44:26 +01:00
antirez d3f4dec440 Update PR #6537: use a fresh time outside call().
One problem with the solution proposed so far in #6537 is that key
lookups outside a command execution via call(), still used a cached
time. The cached time needed to be refreshed in multiple places,
especially because of modules callbacks from timers, cluster bus, and
thread safe contexts, that may use RM_Open().

In order to avoid this problem, this commit introduces the ability to
detect if we are inside call(): this way we can use the reference fixed
time only when we are in the context of a command execution or Lua
script, but for the asynchronous lookups, we can still use mstime() to
get a fresh time reference.
2019-11-06 17:31:52 +01:00
antirez 33f42665a8 Update PR #6537 patch to for generality.
After the thread in #6537 and thanks to the suggestions received, this
commit updates the original patch in order to:

1. Solve the problem of updating the time in multiple places by updating
it in call().
2. Avoid introducing a new field but use our cached time.

This required some minor refactoring to the function updating the time,
and the introduction of a new cached time in microseconds in order to
use less gettimeofday() calls.
2019-11-06 17:30:57 +01:00
zhaozhao.zz 68d71d8370 expires: refactoring judgment about whether a key is expired
Calling lookupKey*() many times to search a key in one command
may get different result.

That's because lookupKey*() calls expireIfNeeded(), and delete
the key when reach the expire time. So we can get an robj before
the expire time, but a NULL after the expire time.

The worst is that may lead to Redis crash, for example
`RPOPLPUSH foo foo` the first time we get a list form `foo` and
hold the pointer, but when we get `foo` again it's expired and
deleted. Now we hold a freed memory, when execute rpoplpushHandlePush()
redis crash.

To fix it, we can refactor the judgment about whether a key is expired,
using the same basetime `server.cmd_start_mstime` instead of calling
mstime() everytime.
2019-11-06 17:27:50 +01:00
antirez 62588dbfa7 Modules: fix thread safe context creation crash.
See #6525, this likely creates a NULL deference if the client was
terminated by Redis between the creation of the blocked client and the
creation of the thread safe context.
2019-10-31 18:09:06 +01:00
antirez bb78454b0f Redis 5.0.6. 2019-09-25 12:40:18 +02:00
antirez 7a41047a61 RDB: fix MODULE_AUX loading by continuing to next opcode.
Thanks to @JohnSully for noticing this problem.
2019-09-25 12:32:49 +02:00
Oran Agra 4eb3028bc6 missing per-skiplist overheads in MEMORY USAGE
these had severe impact for small zsets, for instance ones with just one
element that is longer than 64 (causing it not to be ziplist encoded)
2019-09-25 11:52:24 +02:00
Oran Agra 5d09f9bc8e RM_Log - add support for logging without a context or context without module
for instance detached thread safe contexts, or various callbacks that don't
provide a context.
2019-09-25 11:51:08 +02:00
antirez 2810de9fc1 Cluster: abort loading nodes data if vars arguments are unbalanced.
See for reference PR #6337. Thanks to @git-hulk for spotting this.
2019-09-25 11:50:30 +02:00
antirez f5c63ce0ae More strict checks and better comments in flushSlaveOutputBuffers().
Related to #6296.
2019-09-25 11:50:17 +02:00
antirez 7f289c3b92 Improve comment in flushSlavesOutputBuffers(). 2019-09-25 11:50:07 +02:00
antirez 7ab62d4b92 Replication: clarify why repl_put_online_on_ack exists at all. 2019-09-25 11:49:28 +02:00
zhaozhao.zz 495dd0da54 networking: flushSlavesOutputBuffers bugfix 2019-09-25 11:49:12 +02:00
Salvatore Sanfilippo c1ccf0f188 Merge pull request #6366 from oranagra/5.0_rm_reply_cstring
RM_ReplyWithCString was missing registration
2019-09-05 13:31:26 +02:00
Salvatore Sanfilippo a50dad73c2 Merge pull request #6365 from oranagra/5.0_module_aux
backport module rdb aux data into 5.0
2019-09-05 13:31:10 +02:00
Oran Agra d6294d05cd RM_ReplyWithCString was missing registration
(cherry picked from commit 0a97149dec)
2019-09-05 14:26:32 +03:00
Oran Agra 8c56fc864b Fix to module aux data rdb format for backwards compatibility with old check-rdb
When implementing the code that saves and loads these aux fields we used rdb
format that was added for that in redis 5.0, but then we added the 'when' field
which meant that the old redis-check-rdb won't be able to skip these.
this fix adds an opcode as if that 'when' is part of the module data.

(cherry picked from commit 3bfcae247a)
2019-09-05 14:22:07 +03:00
Oran Agra 98b1314f07 Implement module api for aux data in rdb
Other changes:
* fix memory leak in error handling of rdb loading of type OBJ_MODULE

(cherry picked from commit 3b6aeea44c)
2019-09-05 14:20:30 +03:00
antirez 08b03e2322 redis-cli: always report server errors on read errors.
Before this commit we may have not consumer buffers when a read error is
encountered. Such buffers may contain errors that are important clues
for the user: for instance a protocol error in the payload we send in
pipe mode will cause the server to abort the connection. If the user
does not get the protocol error, debugging what is happening can be a
nightmare.

This commit fixes issue #3756.
2019-09-04 17:57:39 +02:00
wubostc 239069dec7 Reduce the calling stack 2019-07-31 18:57:42 +02:00
antirez 90bf631345 Make EMBSTR case of #6261 more obvious. 2019-07-31 18:57:35 +02:00
chendianqiang 2f8a07498e make memory usage consistent of robj with OBJ_ENCODING_INT 2019-07-31 18:57:33 +02:00
antirez 436ed56d45 HyperLogLog: fix the fix of a corruption bug. 2019-07-31 10:36:57 +02:00
John Sully 680f89fbf9 Fix HLL corruption bug 2019-07-30 10:28:12 +02:00
swilly22 388efbf8b6 Extend REDISMODULE_CTX_FLAGS to indicate if redis is currently loading from either RDB or AOF 2019-07-07 17:02:18 +02:00
Itamar Haber 0ccbdcee26 Uses addReplyBulkCString
Signed-off-by: Itamar Haber <itamar@redislabs.com>
2019-07-07 17:02:14 +02:00
Itamar Haber 707a59c643 Adds RedisModule_ReplyWithCString
Signed-off-by: Itamar Haber <itamar@redislabs.com>
2019-07-07 17:02:11 +02:00
44 changed files with 1875 additions and 275 deletions
+467
View File
@@ -11,6 +11,473 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
SECURITY: There are security fixes in the release.
--------------------------------------------------------------------------------
================================================================================
Redis 5.0.10 Released Mon Oct 26 09:21:49 IST 2020
================================================================================
Upgrade urgency: SECURITY if you use an affected platform (see below).
Otherwise the upgrade urgency is MODERATE.
This release fixes a potential heap overflow when using a heap allocator other
than jemalloc or glibc's malloc. See:
https://github.com/redis/redis/pull/7963
Other fixes in this release:
* Avoid case of Lua scripts being consistently aborted due to OOM
* XPENDING will not update consumer's seen-time
* A blocked XREADGROUP didn't propagated the XSETID to replicas / AOF
* UNLINK support for streams
* RESTORE ABSTTL won't store expired keys into the DB
* Hide AUTH from MONITOR
* Cluster: reduce spurious PFAIL/FAIL states upon delayed PONG receival
* Cluster: Fix case of clusters mixing accidentally by gossip
* Cluster: Allow blocked XREAD on a cluster replica
* Cluster: Optimize memory usage CLUSTER SLOTS command
* RedisModule_ValueLength support for stream data type
* Minor fixes in redis-check-rdb and redis-cli
* Fix redis-check-rdb support for modules aux data
* Add fsync in replica when full RDB payload was received
Full list of commits:
Yossi Gottlieb in commit ce0d74d8f:
Fix wrong zmalloc_size() assumption. (#7963)
1 file changed, 3 deletions(-)
Yossi Gottlieb in commit 066699240:
Backport Lua 5.2.2 stack overflow fix. (#7733)
1 file changed, 1 insertion(+), 1 deletion(-)
WuYunlong in commit 8a90c7ef3:
Add fsync to readSyncBulkPayload(). (#7839)
1 file changed, 11 insertions(+)
Ariel Shtul in commit f0df2bb3c:
Fix redis-check-rdb support for modules aux data (#7826)
3 files changed, 21 insertions(+), 1 deletion(-)
hwware in commit 7add2a412:
fix memory leak in sentinel connection sharing
1 file changed, 1 insertion(+)
Oran Agra in commit 315e648f8:
Allow blocked XREAD on a cluster replica (#7881)
3 files changed, 100 insertions(+), 2 deletions(-)
guybe7 in commit 4967ee94e:
Modules: Invalidate saved_oparray after use (#7688)
1 file changed, 2 insertions(+)
antirez in commit 065003e8f:
Modules: remove spurious call from moduleHandleBlockedClients().
1 file changed, 1 deletion(-)
Angus Pearson in commit 6cdf62928:
Fix broken interval and repeat bahaviour in redis-cli (incluing cluster mode)
1 file changed, 11 insertions(+), 6 deletions(-)
antirez in commit cb6a4971c:
Cluster: introduce data_received field.
2 files changed, 27 insertions(+), 10 deletions(-)
Madelyn Olson in commit 83f4de865:
Hide AUTH from monitor
1 file changed, 1 insertion(+), 1 deletion(-)
Guy Benoish in commit 3ba08d185:
Support streams in general module API functions
3 files changed, 11 insertions(+), 1 deletion(-)
Itamar Haber in commit 109c0635c:
Expands lazyfree's effort estimate to include Streams (#5794)
1 file changed, 24 insertions(+)
huangzhw in commit 235210d5b:
defrag.c activeDefragSdsListAndDict when defrag sdsele, We can't use (#7492)
1 file changed, 1 insertion(+), 1 deletion(-)
Oran Agra in commit fdd3162fe:
RESTORE ABSTTL skip expired keys - leak (#7511)
1 file changed, 1 insertion(+)
Oran Agra in commit 6139d6d18:
RESTORE ABSTTL won't store expired keys into the db (#7472)
4 files changed, 45 insertions(+), 15 deletions(-)
Liu Zhen in commit 0f502c58d:
fix clusters mixing accidentally by gossip
1 file changed, 10 insertions(+), 2 deletions(-)
Guy Benoish in commit 37fd50718:
XPENDING should not update consumer's seen-time
4 files changed, 30 insertions(+), 18 deletions(-)
antirez in commit a3ca53e4a:
Also use propagate() in streamPropagateGroupID().
1 file changed, 11 insertions(+), 1 deletion(-)
yanhui13 in commit 7a62eb96e:
optimize the output of cluster slots
1 file changed, 7 insertions(+), 4 deletions(-)
srzhao in commit 0efb93d0c:
Check OOM at script start to get stable lua OOM state.
3 files changed, 11 insertions(+), 4 deletions(-)
================================================================================
Redis 5.0.9 Released Thu Apr 17 12:41:00 CET 2020
================================================================================
Upgrade urgency:CRITICAL if you use Streams with AOF ore replicas.
Otherwise the upgrade urgency is LOW.
This release has a speed improvement and a critical fix:
* FIX: XREADGROUP when fetching data in a blocking way, would not
emit the XCLAIM in the AOF file and to replicas. This means
that the last ID is not updated, and that restarting the server
will have the effect of reprocessing some entries.
* NEW: Clients blocked on the same key are now unblocked on
O(1) time. Backported from Redis 6.
Commits:
1fc8ef81a Fix XCLAIM propagation in AOF/replicas for blocking XREADGROUP.
a5e24eabc Speedup: unblock clients on keys in O(1).
================================================================================
Redis 5.0.8 Released Thu Mar 12 16:05:41 CET 2020
================================================================================
Upgrade urgency HIGH: This release fixes security issues.
This is a list of fixes in this release:
Salvatore Sanfilippo in commit 2bea502d:
Merge pull request #6975 from dustinmm80/add-arm-latomic-linking
Dustin Collins in commit b5931405:
Fix Pi building needing -latomic, backport
1 file changed, 9 insertions(+)
srzhao in commit fd441300:
fix impl of aof-child whitelist SIGUSR1 feature.
1 file changed, 5 insertions(+), 4 deletions(-)
Ariel in commit 77ff332b:
fix ThreadSafeContext lock/unlock function names
1 file changed, 2 insertions(+), 2 deletions(-)
Guy Benoish in commit 4f0f799c:
XREADGROUP should propagate XCALIM/SETID in MULTI/EXEC
1 file changed, 2 insertions(+), 2 deletions(-)
Oran Agra in commit 0c1273c3:
Fix client flags to be int64 in module.c
1 file changed, 3 insertions(+), 3 deletions(-)
Guy Benoish in commit 708a4e8a:
Fix small bugs related to replica and monitor ambiguity
2 files changed, 8 insertions(+), 6 deletions(-)
WuYunlong in commit eac4115d:
Fix lua related memory leak.
1 file changed, 1 insertion(+)
antirez in commit d075df17:
Simplify #6379 changes.
2 files changed, 4 insertions(+), 9 deletions(-)
WuYunlong in commit 80a49c37:
Free allocated sds in pfdebugCommand() to avoid memory leak.
1 file changed, 1 insertion(+)
antirez in commit 60870d3a:
Jump to right label on AOF parsing error.
1 file changed, 6 insertions(+), 4 deletions(-)
antirez in commit d90f599b:
Free fakeclient argv on AOF error.
1 file changed, 11 insertions(+), 3 deletions(-)
WuYunlong in commit 8ee3bddf:
Fix potential memory leak of rioWriteBulkStreamID().
1 file changed, 4 insertions(+), 1 deletion(-)
WuYunlong in commit 4780fe78:
Fix potential memory leak of clusterLoadConfig().
1 file changed, 20 insertions(+), 5 deletions(-)
Leo Murillo in commit f3b77510:
Fix bug on KEYS command where pattern starts with * followed by \x00 (null char).
1 file changed, 1 insertion(+), 1 deletion(-)
Guy Benoish in commit 7f3fcedb:
Blocking XREAD[GROUP] should always reply with valid data (or timeout)
3 files changed, 44 insertions(+), 10 deletions(-)
antirez in commit f93b2fa5:
XCLAIM: Create the consumer only on successful claims.
1 file changed, 4 insertions(+), 2 deletions(-)
Guy Benoish in commit 89682d96:
Stream: Handle streamID-related edge cases
4 files changed, 54 insertions(+), 4 deletions(-)
antirez in commit 920e108f:
Fix ip and missing mode in RM_GetClusterNodeInfo().
1 file changed, 5 insertions(+), 2 deletions(-)
antirez in commit 7569b210:
Inline protocol: handle empty strings well.
1 file changed, 2 insertions(+), 6 deletions(-)
Khem Raj in commit 3c610b4e:
Mark extern definition of SDS_NOINIT in sds.h
1 file changed, 1 insertion(+), 1 deletion(-)
Seunghoon Woo in commit 16b2d07f:
[FIX] revisit CVE-2015-8080 vulnerability
1 file changed, 6 insertions(+), 4 deletions(-)
yz1509 in commit 19f33585:
avoid sentinel changes promoted_slave to be its own replica.
1 file changed, 1 insertion(+), 1 deletion(-)
================================================================================
Redis 5.0.7 Released Tue Nov 19 17:52:44 CET 2019
================================================================================
Upgrade urgency HIGH: many issues fixed, some may have an impact.
Hi all, Redis 5.0.7 fixes a number of bugs, none is very critical, however
there are a few that may have an impact. It's a good idea to upgrade.
There are fixes in the area of replication from modules commands and
callbacks, AOF fsync (non critical issue), memory leaks (very rare and small),
streams beahvior (non critical), and a potential crash in commands
processing multiple keys at the same time that is there for years, and happens
very rarely, but is not impossible to trigger.
The following is the list of commits in this release.
antirez in commit 4d2a31ae:
Test: fix implementation-dependent test after code change.
1 file changed, 2 insertions(+), 2 deletions(-)
Oran Agra in commit 9f63fc98:
RED-31295 - redis: avoid race between dlopen and thread creation
2 files changed, 17 insertions(+)
antirez in commit 1a9e70c1:
Cluster: fix memory leak of cached master.
2 files changed, 9 insertions(+), 4 deletions(-)
Guy Benoish in commit 69b1b5be:
Fix usage of server.stream_node_max_*
1 file changed, 3 insertions(+), 3 deletions(-)
喜欢兰花山丘 in commit 1fd97ee7:
Update mkreleasehdr.sh
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit 1a9855d7:
Remove additional space from comment.
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit 32a6e3e4:
Fix stream test after addition of 0-0 ID test.
1 file changed, 3 insertions(+), 3 deletions(-)
Yuan Zhou in commit c9e6cda9:
aof: fix assignment for aof_fsync_offset
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit d3eeacf9:
Merge branch '5.0' of github.com:/antirez/redis into 5.0
antirez in commit da5dc458:
Rename var to fixed_time_expire now that is more general.
4 files changed, 7 insertions(+), 8 deletions(-)
antirez in commit 0fefed25:
Fix patch provided in #6554.
1 file changed, 8 insertions(+), 1 deletion(-)
zhaozhao.zz in commit e9fbc960:
expires & blocking: handle ready keys as call()
1 file changed, 5 insertions(+)
Guy Benoish in commit 08ec8f71:
XADD with ID 0-0 stores an empty key
2 files changed, 14 insertions(+)
Loris Cro in commit 09e1fe27:
fix unreported overflow in autogerenared stream IDs
2 files changed, 19 insertions(+), 10 deletions(-)
Salvatore Sanfilippo in commit 09f9e4b0:
Merge pull request #6600 from oranagra/5_module_flags
Oran Agra in commit 8d8d68dd:
module documentation mismatches: loading and fork child for 5.0 branch
3 files changed, 11 insertions(+)
antirez in commit 7a7fbe70:
Modules: RM_GetContextFlags(): remove non Redis 5 features.
1 file changed, 3 deletions(-)
antirez in commit b5830486:
Modules: fix moduleCreateArgvFromUserFormat() casting bug.
1 file changed, 1 insertion(+), 1 deletion(-)
antirez in commit b7a2a53a:
module: fix propagation API bug.
1 file changed, 2 insertions(+), 2 deletions(-)
antirez in commit 278bd6e3:
Modules: add new flags to context, replica state + more.
2 files changed, 48 insertions(+), 1 deletion(-)
antirez in commit 61d9a154:
Modules: RM_Call(): give pointer to documentation.
1 file changed, 4 insertions(+), 1 deletion(-)
antirez in commit 0e7ea0aa:
Modules: RM_Call/Replicate() ability to exclude AOF/replicas.
1 file changed, 28 insertions(+), 9 deletions(-)
antirez in commit 3b38164e:
Modules: RM_Replicate() in thread safe contexts.
1 file changed, 26 insertions(+), 4 deletions(-)
antirez in commit ef9fe9b0:
Modules: implement RM_Replicate() from async callbacks.
2 files changed, 33 insertions(+), 2 deletions(-)
antirez in commit 8066d2a1:
Modules: handle propagation when ctx is freed. Flag modules commands ctx.
1 file changed, 23 insertions(+), 19 deletions(-)
antirez in commit d3f4dec4:
Update PR #6537: use a fresh time outside call().
3 files changed, 27 insertions(+), 7 deletions(-)
antirez in commit 33f42665:
Update PR #6537 patch to for generality.
4 files changed, 40 insertions(+), 23 deletions(-)
zhaozhao.zz in commit 68d71d83:
expires: refactoring judgment about whether a key is expired
3 files changed, 3 insertions(+), 1 deletion(-)
antirez in commit 62588dbf:
Modules: fix thread safe context creation crash.
1 file changed, 1 insertion(+), 1 deletion(-)
================================================================================
Redis 5.0.6 Released Wed Sep 25 12:33:56 CEST 2019
================================================================================
Upgrade urgency CRITICAL: Only in case of exposed instances to untrusted users.
This Redis release, 5.0.6, is a bugfix and enhancement release. The most
important bugfix is a corruption related to the HyperLogLog. A malformed
HyperLogLog string could cause an invalid access to the memory. At a first
glance the vulnerability appears to be not exploitable but just a DoS. The
way to trigger the issue is complex, we'll not provide any information about
how to do that for the users safety.
Other significant changes in this release:
* New modules APIs merged from Redis unstable to Redis 5.
* Some memory optimization related to objects creation.
* Fixes to flushSlaveOutputBuffer() that make sure that SHUTDOWN will
transfer pending buffers to replicas.
This is the full list of commits:
antirez in commit 7a41047a:
RDB: fix MODULE_AUX loading by continuing to next opcode.
1 file changed, 1 insertion(+)
Oran Agra in commit 4eb3028b:
missing per-skiplist overheads in MEMORY USAGE
1 file changed, 3 insertions(+), 1 deletion(-)
Oran Agra in commit 5d09f9bc:
RM_Log - add support for logging without a context or context without module
1 file changed, 6 insertions(+), 4 deletions(-)
antirez in commit 2810de9f:
Cluster: abort loading nodes data if vars arguments are unbalanced.
1 file changed, 1 insertion(+)
antirez in commit f5c63ce0:
More strict checks and better comments in flushSlaveOutputBuffers().
1 file changed, 18 insertions(+), 7 deletions(-)
antirez in commit 7f289c3b:
Improve comment in flushSlavesOutputBuffers().
1 file changed, 2 insertions(+), 1 deletion(-)
antirez in commit 7ab62d4b:
Replication: clarify why repl_put_online_on_ack exists at all.
2 files changed, 34 insertions(+), 10 deletions(-)
zhaozhao.zz in commit 495dd0da:
networking: flushSlavesOutputBuffers bugfix
1 file changed, 2 insertions(+), 4 deletions(-)
Salvatore Sanfilippo in commit c1ccf0f1:
Merge pull request #6366 from oranagra/5.0_rm_reply_cstring
Salvatore Sanfilippo in commit a50dad73:
Merge pull request #6365 from oranagra/5.0_module_aux
Oran Agra in commit d6294d05:
RM_ReplyWithCString was missing registration
1 file changed, 1 insertion(+)
Oran Agra in commit 8c56fc86:
Fix to module aux data rdb format for backwards compatibility with old check-rdb
1 file changed, 9 insertions(+), 1 deletion(-)
Oran Agra in commit 98b1314f:
Implement module api for aux data in rdb
9 files changed, 431 insertions(+), 18 deletions(-)
antirez in commit 08b03e23:
redis-cli: always report server errors on read errors.
1 file changed, 8 insertions(+), 1 deletion(-)
wubostc in commit 239069de:
Reduce the calling stack
1 file changed, 2 insertions(+), 3 deletions(-)
antirez in commit 90bf6313:
Make EMBSTR case of #6261 more obvious.
1 file changed, 1 insertion(+), 1 deletion(-)
chendianqiang in commit 2f8a0749:
make memory usage consistent of robj with OBJ_ENCODING_INT
1 file changed, 9 insertions(+), 4 deletions(-)
antirez in commit 436ed56d:
HyperLogLog: fix the fix of a corruption bug.
1 file changed, 1 insertion(+), 2 deletions(-)
John Sully in commit 680f89fb:
Fix HLL corruption bug
1 file changed, 1 insertion(+)
swilly22 in commit 388efbf8:
Extend REDISMODULE_CTX_FLAGS to indicate if redis is currently loading from either RDB or AOF
2 files changed, 5 insertions(+)
Itamar Haber in commit 0ccbdcee:
Uses addReplyBulkCString
1 file changed, 1 insertion(+), 1 deletion(-)
Itamar Haber in commit 707a59c6:
Adds RedisModule_ReplyWithCString
2 files changed, 13 insertions(+)
================================================================================
Redis 5.0.5 Released Wed May 15 17:57:41 CEST 2019
================================================================================
+1 -1
View File
@@ -274,7 +274,7 @@ int luaD_precall (lua_State *L, StkId func, int nresults) {
CallInfo *ci;
StkId st, base;
Proto *p = cl->p;
luaD_checkstack(L, p->maxstacksize);
luaD_checkstack(L, p->maxstacksize + p->numparams);
func = restorestack(L, funcr);
if (!p->is_vararg) { /* no varargs? */
base = func + 1;
+6 -4
View File
@@ -89,12 +89,14 @@ typedef struct Header {
} Header;
static int getnum (const char **fmt, int df) {
static int getnum (lua_State *L, const char **fmt, int df) {
if (!isdigit(**fmt)) /* no number? */
return df; /* return default value */
else {
int a = 0;
do {
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
luaL_error(L, "integral size overflow");
a = a*10 + *((*fmt)++) - '0';
} while (isdigit(**fmt));
return a;
@@ -115,9 +117,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
case 'f': return sizeof(float);
case 'd': return sizeof(double);
case 'x': return 1;
case 'c': return getnum(fmt, 1);
case 'c': return getnum(L, fmt, 1);
case 'i': case 'I': {
int sz = getnum(fmt, sizeof(int));
int sz = getnum(L, fmt, sizeof(int));
if (sz > MAXINTSIZE)
luaL_error(L, "integral size %d is larger than limit of %d",
sz, MAXINTSIZE);
@@ -150,7 +152,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
case '>': h->endian = BIG; return;
case '<': h->endian = LITTLE; return;
case '!': {
int a = getnum(fmt, MAXALIGN);
int a = getnum(L, fmt, MAXALIGN);
if (!isp2(a))
luaL_error(L, "alignment %d is not a power of 2", a);
h->align = a;
+1 -1
View File
@@ -13,4 +13,4 @@ then
fi
make -C tests/modules && \
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter "${@}"
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/testrdb "${@}"
+9
View File
@@ -77,6 +77,15 @@ FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
FINAL_LIBS=-lm
DEBUG=-g -ggdb
# Linux ARM needs -latomic at linking time
ifneq (,$(filter aarch64 armv,$(uname_M)))
FINAL_LIBS+=-latomic
else
ifneq (,$(findstring armv,$(uname_M)))
FINAL_LIBS+=-latomic
endif
endif
ifeq ($(uname_S),SunOS)
# SunOS
ifneq ($(@@),32bit)
+17 -3
View File
@@ -327,12 +327,11 @@ listNode *listIndex(list *list, long index) {
}
/* Rotate the list removing the tail node and inserting it to the head. */
void listRotate(list *list) {
listNode *tail = list->tail;
void listRotateTailToHead(list *list) {
if (listLength(list) <= 1) return;
/* Detach current tail */
listNode *tail = list->tail;
list->tail = tail->prev;
list->tail->next = NULL;
/* Move it as head */
@@ -342,6 +341,21 @@ void listRotate(list *list) {
list->head = tail;
}
/* Rotate the list removing the head node and inserting it to the tail. */
void listRotateHeadToTail(list *list) {
if (listLength(list) <= 1) return;
listNode *head = list->head;
/* Detach current head */
list->head = head->next;
list->head->prev = NULL;
/* Move it as tail */
list->tail->next = head;
head->next = NULL;
head->prev = list->tail;
list->tail = head;
}
/* Add all the elements of the list 'o' at the end of the
* list 'l'. The list 'other' remains empty but otherwise valid. */
void listJoin(list *l, list *o) {
+2 -1
View File
@@ -85,7 +85,8 @@ listNode *listSearchKey(list *list, void *key);
listNode *listIndex(list *list, long index);
void listRewind(list *list, listIter *li);
void listRewindTail(list *list, listIter *li);
void listRotate(list *list);
void listRotateTailToHead(list *list);
void listRotateHeadToTail(list *list);
void listJoin(list *l, list *o);
/* Directions for iterators */
+21 -10
View File
@@ -774,18 +774,26 @@ int loadAppendOnlyFile(char *filename) {
argc = atoi(buf+1);
if (argc < 1) goto fmterr;
/* Load the next command in the AOF as our fake client
* argv. */
argv = zmalloc(sizeof(robj*)*argc);
fakeClient->argc = argc;
fakeClient->argv = argv;
for (j = 0; j < argc; j++) {
if (fgets(buf,sizeof(buf),fp) == NULL) {
/* Parse the argument len. */
char *readres = fgets(buf,sizeof(buf),fp);
if (readres == NULL || buf[0] != '$') {
fakeClient->argc = j; /* Free up to j-1. */
freeFakeClientArgv(fakeClient);
goto readerr;
if (readres == NULL)
goto readerr;
else
goto fmterr;
}
if (buf[0] != '$') goto fmterr;
len = strtol(buf+1,NULL,10);
/* Read it into a string object. */
argsds = sdsnewlen(SDS_NOINIT,len);
if (len && fread(argsds,len,1,fp) == 0) {
sdsfree(argsds);
@@ -794,10 +802,12 @@ int loadAppendOnlyFile(char *filename) {
goto readerr;
}
argv[j] = createObject(OBJ_STRING,argsds);
/* Discard CRLF. */
if (fread(buf,2,1,fp) == 0) {
fakeClient->argc = j+1; /* Free up to j. */
freeFakeClientArgv(fakeClient);
goto readerr; /* discard CRLF */
goto readerr;
}
}
@@ -1127,7 +1137,7 @@ int rioWriteBulkStreamID(rio *r,streamID *id) {
int retval;
sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq);
if ((retval = rioWriteBulkString(r,replyid,sdslen(replyid))) == 0) return 0;
retval = rioWriteBulkString(r,replyid,sdslen(replyid));
sdsfree(replyid);
return retval;
}
@@ -1761,7 +1771,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
aofUpdateCurrentSize();
server.aof_rewrite_base_size = server.aof_current_size;
server.aof_current_size = server.aof_current_size;
server.aof_fsync_offset = server.aof_current_size;
/* Clear regular AOF buffer since its contents was just written to
* the new AOF from the background rewrite buffer. */
@@ -1782,14 +1792,15 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
serverLog(LL_VERBOSE,
"Background AOF rewrite signal handler took %lldus", ustime()-now);
} else if (!bysignal && exitcode != 0) {
server.aof_lastbgrewrite_status = C_ERR;
serverLog(LL_WARNING,
"Background AOF rewrite terminated with error");
} else {
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
* tirggering an error condition. */
if (bysignal != SIGUSR1)
server.aof_lastbgrewrite_status = C_ERR;
serverLog(LL_WARNING,
"Background AOF rewrite terminated with error");
} else {
server.aof_lastbgrewrite_status = C_ERR;
serverLog(LL_WARNING,
"Background AOF rewrite terminated by signal %d", bysignal);
+43 -18
View File
@@ -67,6 +67,21 @@
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where);
/* This structure represents the blocked key information that we store
* in the client structure. Each client blocked on keys, has a
* client->bpop.keys hash table. The keys of the hash table are Redis
* keys pointers to 'robj' structures. The value is this structure.
* The structure has two goals: firstly we store the list node that this
* client uses to be listed in the database "blocked clients for this key"
* list, so we can later unblock in O(1) without a list scan.
* Secondly for certain blocking types, we have additional info. Right now
* the only use for additional info we have is when clients are blocked
* on streams, as we have to remember the ID it blocked for. */
typedef struct bkinfo {
listNode *listnode; /* List node for db->blocking_keys[key] list. */
streamID stream_id; /* Stream ID if we blocked in a stream. */
} bkinfo;
/* Get a timeout value from an object and store it into 'timeout'.
* The final timeout is always stored as milliseconds as a time where the
* timeout will expire, however the parsing is performed according to
@@ -262,6 +277,16 @@ void handleClientsBlockedOnKeys(void) {
* we can safely call signalKeyAsReady() against this key. */
dictDelete(rl->db->ready_keys,rl->key);
/* Even if we are not inside call(), increment the call depth
* in order to make sure that keys are expired against a fixed
* reference time, and not against the wallclock time. This
* way we can lookup an object multiple times (BRPOPLPUSH does
* that) without the risk of it being freed in the second
* lookup, invalidating the first one.
* See https://github.com/antirez/redis/pull/6554. */
server.fixed_time_expire++;
updateCachedTime(0);
/* Serve clients blocked on list key. */
robj *o = lookupKeyWrite(rl->db,rl->key);
if (o != NULL && o->type == OBJ_LIST) {
@@ -281,8 +306,7 @@ void handleClientsBlockedOnKeys(void) {
if (receiver->btype != BLOCKED_LIST) {
/* Put at the tail, so that at the next call
* we'll not run into it again. */
listDelNode(clients,clientnode);
listAddNodeTail(clients,receiver);
listRotateHeadToTail(clients);
continue;
}
@@ -343,8 +367,7 @@ void handleClientsBlockedOnKeys(void) {
if (receiver->btype != BLOCKED_ZSET) {
/* Put at the tail, so that at the next call
* we'll not run into it again. */
listDelNode(clients,clientnode);
listAddNodeTail(clients,receiver);
listRotateHeadToTail(clients);
continue;
}
@@ -388,8 +411,9 @@ void handleClientsBlockedOnKeys(void) {
while((ln = listNext(&li))) {
client *receiver = listNodeValue(ln);
if (receiver->btype != BLOCKED_STREAM) continue;
streamID *gt = dictFetchValue(receiver->bpop.keys,
rl->key);
bkinfo *bki = dictFetchValue(receiver->bpop.keys,
rl->key);
streamID *gt = &bki->stream_id;
/* If we blocked in the context of a consumer
* group, we need to resolve the group and update the
@@ -419,7 +443,7 @@ void handleClientsBlockedOnKeys(void) {
if (streamCompareID(&s->last_id, gt) > 0) {
streamID start = *gt;
start.seq++; /* Can't overflow, it's an uint64_t */
streamIncrID(&start);
/* Lookup the consumer for the group, if any. */
streamConsumer *consumer = NULL;
@@ -428,7 +452,7 @@ void handleClientsBlockedOnKeys(void) {
if (group) {
consumer = streamLookupConsumer(group,
receiver->bpop.xread_consumer->ptr,
1);
SLC_NONE);
noack = receiver->bpop.xread_group_noack;
}
@@ -457,6 +481,7 @@ void handleClientsBlockedOnKeys(void) {
}
}
}
server.fixed_time_expire--;
/* Free this item. */
decrRefCount(rl->key);
@@ -505,17 +530,15 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
if (target != NULL) incrRefCount(target);
for (j = 0; j < numkeys; j++) {
/* The value associated with the key name in the bpop.keys dictionary
* is NULL for lists and sorted sets, or the stream ID for streams. */
void *key_data = NULL;
if (btype == BLOCKED_STREAM) {
key_data = zmalloc(sizeof(streamID));
memcpy(key_data,ids+j,sizeof(streamID));
}
/* Allocate our bkinfo structure, associated to each key the client
* is blocked for. */
bkinfo *bki = zmalloc(sizeof(*bki));
if (btype == BLOCKED_STREAM)
bki->stream_id = ids[j];
/* If the key already exists in the dictionary ignore it. */
if (dictAdd(c->bpop.keys,keys[j],key_data) != DICT_OK) {
zfree(key_data);
if (dictAdd(c->bpop.keys,keys[j],bki) != DICT_OK) {
zfree(bki);
continue;
}
incrRefCount(keys[j]);
@@ -534,6 +557,7 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
l = dictGetVal(de);
}
listAddNodeTail(l,c);
bki->listnode = listLast(l);
}
blockClient(c,btype);
}
@@ -550,11 +574,12 @@ void unblockClientWaitingData(client *c) {
/* The client may wait for multiple keys, so unblock it for every key. */
while((de = dictNext(di)) != NULL) {
robj *key = dictGetKey(de);
bkinfo *bki = dictGetVal(de);
/* Remove this client from the list of clients waiting for this key. */
l = dictFetchValue(c->db->blocking_keys,key);
serverAssertWithInfo(c,key,l != NULL);
listDelNode(l,listSearchKey(l,c));
listDelNode(l,bki->listnode);
/* If the list is empty we need to remove it to avoid wasting memory */
if (listLength(l) == 0)
dictDelete(c->db->blocking_keys,key);
+93 -28
View File
@@ -138,6 +138,7 @@ int clusterLoadConfig(char *filename) {
/* Handle the special "vars" line. Don't pretend it is the last
* line even if it actually is when generated by Redis. */
if (strcasecmp(argv[0],"vars") == 0) {
if (!(argc % 2)) goto fmterr;
for (j = 1; j < argc; j += 2) {
if (strcasecmp(argv[j],"currentEpoch") == 0) {
server.cluster->currentEpoch =
@@ -156,7 +157,10 @@ int clusterLoadConfig(char *filename) {
}
/* Regular config lines have at least eight fields */
if (argc < 8) goto fmterr;
if (argc < 8) {
sdsfreesplitres(argv,argc);
goto fmterr;
}
/* Create this node if it does not exist */
n = clusterLookupNode(argv[0]);
@@ -165,7 +169,10 @@ int clusterLoadConfig(char *filename) {
clusterAddNode(n);
}
/* Address and port */
if ((p = strrchr(argv[1],':')) == NULL) goto fmterr;
if ((p = strrchr(argv[1],':')) == NULL) {
sdsfreesplitres(argv,argc);
goto fmterr;
}
*p = '\0';
memcpy(n->ip,argv[1],strlen(argv[1])+1);
char *port = p+1;
@@ -246,7 +253,10 @@ int clusterLoadConfig(char *filename) {
*p = '\0';
direction = p[1]; /* Either '>' or '<' */
slot = atoi(argv[j]+1);
if (slot < 0 || slot >= CLUSTER_SLOTS) goto fmterr;
if (slot < 0 || slot >= CLUSTER_SLOTS) {
sdsfreesplitres(argv,argc);
goto fmterr;
}
p += 3;
cn = clusterLookupNode(p);
if (!cn) {
@@ -266,8 +276,12 @@ int clusterLoadConfig(char *filename) {
} else {
start = stop = atoi(argv[j]);
}
if (start < 0 || start >= CLUSTER_SLOTS) goto fmterr;
if (stop < 0 || stop >= CLUSTER_SLOTS) goto fmterr;
if (start < 0 || start >= CLUSTER_SLOTS ||
stop < 0 || stop >= CLUSTER_SLOTS)
{
sdsfreesplitres(argv,argc);
goto fmterr;
}
while(start <= stop) clusterAddSlot(n, start++);
}
@@ -707,6 +721,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
node->slaves = NULL;
node->slaveof = NULL;
node->ping_sent = node->pong_received = 0;
node->data_received = 0;
node->fail_time = 0;
node->link = NULL;
memset(node->ip,0,sizeof(node->ip));
@@ -1420,7 +1435,10 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
}
} else {
/* If it's not in NOADDR state and we don't have it, we
* start a handshake process against this IP/PORT pairs.
* add it to our trusted dict with exact nodeid and flag.
* Note that we cannot simply start a handshake against
* this IP/PORT pairs, since IP/PORT can be reused already,
* otherwise we risk joining another cluster.
*
* Note that we require that the sender of this gossip message
* is a well known node in our cluster, otherwise we risk
@@ -1429,7 +1447,12 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
!(flags & CLUSTER_NODE_NOADDR) &&
!clusterBlacklistExists(g->nodename))
{
clusterStartHandshake(g->ip,ntohs(g->port),ntohs(g->cport));
clusterNode *node;
node = createClusterNode(g->nodename, flags);
memcpy(node->ip,g->ip,NET_IP_STR_LEN);
node->port = ntohs(g->port);
node->cport = ntohs(g->cport);
clusterAddNode(node);
}
}
@@ -1636,6 +1659,7 @@ int clusterProcessPacket(clusterLink *link) {
clusterMsg *hdr = (clusterMsg*) link->rcvbuf;
uint32_t totlen = ntohl(hdr->totlen);
uint16_t type = ntohs(hdr->type);
mstime_t now = mstime();
if (type < CLUSTERMSG_TYPE_COUNT)
server.cluster->stats_bus_messages_received[type]++;
@@ -1699,6 +1723,13 @@ int clusterProcessPacket(clusterLink *link) {
/* Check if the sender is a known node. */
sender = clusterLookupNode(hdr->sender);
/* Update the last time we saw any data from this node. We
* use this in order to avoid detecting a timeout from a node that
* is just sending a lot of data in the cluster bus, for instance
* because of Pub/Sub. */
if (sender) sender->data_received = now;
if (sender && !nodeInHandshake(sender)) {
/* Update our curretEpoch if we see a newer epoch in the cluster. */
senderCurrentEpoch = ntohu64(hdr->currentEpoch);
@@ -1713,7 +1744,7 @@ int clusterProcessPacket(clusterLink *link) {
}
/* Update the replication offset info for this node. */
sender->repl_offset = ntohu64(hdr->offset);
sender->repl_offset_time = mstime();
sender->repl_offset_time = now;
/* If we are a slave performing a manual failover and our master
* sent its offset while already paused, populate the MF state. */
if (server.cluster->mf_end &&
@@ -1827,7 +1858,7 @@ int clusterProcessPacket(clusterLink *link) {
* address. */
serverLog(LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d",
link->node->name,
(int)(mstime()-(link->node->ctime)),
(int)(now-(link->node->ctime)),
link->node->flags);
link->node->flags |= CLUSTER_NODE_NOADDR;
link->node->ip[0] = '\0';
@@ -1862,7 +1893,7 @@ int clusterProcessPacket(clusterLink *link) {
/* Update our info about the node */
if (link->node && type == CLUSTERMSG_TYPE_PONG) {
link->node->pong_received = mstime();
link->node->pong_received = now;
link->node->ping_sent = 0;
/* The PFAIL condition can be reversed without external
@@ -2009,7 +2040,7 @@ int clusterProcessPacket(clusterLink *link) {
"FAIL message received from %.40s about %.40s",
hdr->sender, hdr->data.fail.about.nodename);
failing->flags |= CLUSTER_NODE_FAIL;
failing->fail_time = mstime();
failing->fail_time = now;
failing->flags &= ~CLUSTER_NODE_PFAIL;
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
CLUSTER_TODO_UPDATE_STATE);
@@ -2062,9 +2093,9 @@ int clusterProcessPacket(clusterLink *link) {
/* Manual failover requested from slaves. Initialize the state
* accordingly. */
resetManualFailover();
server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
server.cluster->mf_slave = sender;
pauseClients(mstime()+(CLUSTER_MF_TIMEOUT*2));
pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT));
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
sender->name);
} else if (type == CLUSTERMSG_TYPE_UPDATE) {
@@ -3472,7 +3503,6 @@ void clusterCron(void) {
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de);
now = mstime(); /* Use an updated time at every iteration. */
mstime_t delay;
if (node->flags &
(CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
@@ -3496,7 +3526,7 @@ void clusterCron(void) {
this_slaves = okslaves;
}
/* If we are waiting for the PONG more than half the cluster
/* If we are not receiving any data for more than half the cluster
* timeout, reconnect the link: maybe there is a connection
* issue even if the node is alive. */
if (node->link && /* is connected */
@@ -3505,7 +3535,9 @@ void clusterCron(void) {
node->ping_sent && /* we already sent a ping */
node->pong_received < node->ping_sent && /* still waiting pong */
/* and we are waiting for the pong more than timeout/2 */
now - node->ping_sent > server.cluster_node_timeout/2)
now - node->ping_sent > server.cluster_node_timeout/2 &&
/* and in such interval we are not seeing any traffic at all. */
now - node->data_received > server.cluster_node_timeout/2)
{
/* Disconnect the link, it will be reconnected automatically. */
freeClusterLink(node->link);
@@ -3540,7 +3572,13 @@ void clusterCron(void) {
/* Compute the delay of the PONG. Note that if we already received
* the PONG, then node->ping_sent is zero, so can't reach this
* code at all. */
delay = now - node->ping_sent;
mstime_t delay = now - node->ping_sent;
/* We consider every incoming data as proof of liveness, since
* our cluster bus link is also used for data: under heavy data
* load pong delays are possible. */
mstime_t data_delay = now - node->data_received;
if (data_delay < delay) delay = data_delay;
if (delay > server.cluster_node_timeout) {
/* Timeout reached. Set the node as possibly failing if it is
@@ -4134,11 +4172,17 @@ void clusterReplyMultiBulkSlots(client *c) {
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de);
int j = 0, start = -1;
int i, nested_elements = 0;
/* Skip slaves (that are iterated when producing the output of their
* master) and masters not serving any slot. */
if (!nodeIsMaster(node) || node->numslots == 0) continue;
for(i = 0; i < node->numslaves; i++) {
if (nodeFailed(node->slaves[i])) continue;
nested_elements++;
}
for (j = 0; j < CLUSTER_SLOTS; j++) {
int bit, i;
@@ -4146,8 +4190,7 @@ void clusterReplyMultiBulkSlots(client *c) {
if (start == -1) start = j;
}
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
int nested_elements = 3; /* slots (2) + master addr (1). */
void *nested_replylen = addDeferredMultiBulkLength(c);
addReplyMultiBulkLen(c, nested_elements + 3); /* slots (2) + master addr (1). */
if (bit && j == CLUSTER_SLOTS-1) j++;
@@ -4177,9 +4220,7 @@ void clusterReplyMultiBulkSlots(client *c) {
addReplyBulkCString(c, node->slaves[i]->ip);
addReplyLongLong(c, node->slaves[i]->port);
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
nested_elements++;
}
setDeferredMultiBulkLength(c, nested_replylen, nested_elements);
num_masters++;
}
}
@@ -4893,7 +4934,8 @@ void restoreCommand(client *c) {
}
/* Make sure this key does not already exist here... */
if (!replace && lookupKeyWrite(c->db,c->argv[1]) != NULL) {
robj *key = c->argv[1];
if (!replace && lookupKeyWrite(c->db,key) != NULL) {
addReply(c,shared.busykeyerr);
return;
}
@@ -4915,23 +4957,37 @@ void restoreCommand(client *c) {
rioInitWithBuffer(&payload,c->argv[3]->ptr);
if (((type = rdbLoadObjectType(&payload)) == -1) ||
((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
((obj = rdbLoadObject(type,&payload,key)) == NULL))
{
addReplyError(c,"Bad data format");
return;
}
/* Remove the old key if needed. */
if (replace) dbDelete(c->db,c->argv[1]);
int deleted = 0;
if (replace)
deleted = dbDelete(c->db,key);
if (ttl && !absttl) ttl+=mstime();
if (ttl && checkAlreadyExpired(ttl)) {
if (deleted) {
rewriteClientCommandVector(c,2,shared.del,key);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
server.dirty++;
}
decrRefCount(obj);
addReply(c, shared.ok);
return;
}
/* Create the key and set the TTL if any */
dbAdd(c->db,c->argv[1],obj);
dbAdd(c->db,key,obj);
if (ttl) {
if (!absttl) ttl+=mstime();
setExpire(c,c->db,c->argv[1],ttl);
setExpire(c,c->db,key,ttl);
}
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock);
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c->db,key);
addReply(c,shared.ok);
server.dirty++;
}
@@ -5685,6 +5741,15 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
int slot = keyHashSlot((char*)key->ptr, sdslen(key->ptr));
clusterNode *node = server.cluster->slots[slot];
/* if the client is read-only and attempting to access key that our
* replica can handle, allow it. */
if ((c->flags & CLIENT_READONLY) &&
(c->lastcmd->flags & CMD_READONLY) &&
nodeIsSlave(myself) && myself->slaveof == node)
{
node = myself;
}
/* We send an error and unblock the client if:
* 1) The slot is unassigned, emitting a cluster down error.
* 2) The slot is not handled by this node, nor being imported. */
+1
View File
@@ -128,6 +128,7 @@ typedef struct clusterNode {
tables. */
mstime_t ping_sent; /* Unix time we sent latest ping */
mstime_t pong_received; /* Unix time we received the pong */
mstime_t data_received; /* Unix time we received any data */
mstime_t fail_time; /* Unix time when FAIL flag was set */
mstime_t voted_time; /* Last time we voted for a slave of this master */
mstime_t repl_offset_time; /* Unix time we received offset for this node */
+21 -2
View File
@@ -542,7 +542,7 @@ void keysCommand(client *c) {
void *replylen = addDeferredMultiBulkLength(c);
di = dictGetSafeIterator(c->db->dict);
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
allkeys = (pattern[0] == '*' && plen == 1);
while((de = dictNext(di)) != NULL) {
sds key = dictGetKey(de);
robj *keyobj;
@@ -1129,6 +1129,7 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
/* Check if the key is expired. */
int keyIsExpired(redisDb *db, robj *key) {
mstime_t when = getExpire(db,key);
mstime_t now;
if (when < 0) return 0; /* No expire for this key */
@@ -1140,8 +1141,26 @@ int keyIsExpired(redisDb *db, robj *key) {
* 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. */
mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
if (server.lua_caller) {
now = server.lua_time_start;
}
/* If we are in the middle of a command execution, we still want to use
* a reference time that does not change: in that case we just use the
* cached time, that we update before each call in the call() function.
* This way we avoid that commands such as RPOPLPUSH or similar, that
* may re-open the same key multiple times, can invalidate an already
* open object in a next call, if the next call will see the key expired,
* while the first did not. */
else if (server.fixed_time_expire > 0) {
now = server.mstime;
}
/* For the other cases, we want to use the most fresh time we have. */
else {
now = mstime();
}
/* The key expired if the current (virtual or real) time is greater
* than the expire time of the key. */
return now > when;
}
+1 -1
View File
@@ -355,7 +355,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
sdsele = ln->value;
if ((newsds = activeDefragSds(sdsele))) {
/* When defragging an sds value, we need to update the dict key */
uint64_t hash = dictGetHash(d, sdsele);
uint64_t hash = dictGetHash(d, newsds);
replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
ln->value = newsds;
defragged++;
+11 -7
View File
@@ -391,6 +391,16 @@ void flushSlaveKeysWithExpireList(void) {
}
}
int checkAlreadyExpired(long long when) {
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
* should never be executed as a DEL when load the AOF or in the context
* of a slave instance.
*
* Instead we add the already expired key to the database with expire time
* (possibly in the past) and wait for an explicit DEL from the master. */
return (when <= mstime() && !server.loading && !server.masterhost);
}
/*-----------------------------------------------------------------------------
* Expires Commands
*----------------------------------------------------------------------------*/
@@ -418,13 +428,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
return;
}
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
* should never be executed as a DEL when load the AOF or in the context
* of a slave instance.
*
* Instead we take the other branch of the IF statement setting an expire
* (possibly in the past) and wait for an explicit DEL from the master. */
if (when <= mstime() && !server.loading && !server.masterhost) {
if (checkAlreadyExpired(when)) {
robj *aux;
int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
+2 -1
View File
@@ -700,7 +700,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
p += oplen;
first += span;
}
if (span == 0) return -1; /* Invalid format. */
if (span == 0 || p >= end) return -1; /* Invalid format. */
next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1;
if (next >= end) next = NULL;
@@ -1535,6 +1535,7 @@ void pfdebugCommand(client *c) {
sds decoded = sdsempty();
if (hdr->encoding != HLL_SPARSE) {
sdsfree(decoded);
addReplyError(c,"HLL encoding is not sparse");
return;
}
+24
View File
@@ -41,6 +41,30 @@ size_t lazyfreeGetFreeEffort(robj *obj) {
} else if (obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) {
dict *ht = obj->ptr;
return dictSize(ht);
} else if (obj->type == OBJ_STREAM) {
size_t effort = 0;
stream *s = obj->ptr;
/* Make a best effort estimate to maintain constant runtime. Every macro
* node in the Stream is one allocation. */
effort += s->rax->numnodes;
/* Every consumer group is an allocation and so are the entries in its
* PEL. We use size of the first group's PEL as an estimate for all
* others. */
if (s->cgroups) {
raxIterator ri;
streamCG *cg;
raxStart(&ri,s->cgroups);
raxSeek(&ri,"^",NULL,0);
/* There must be at least one group so the following should always
* work. */
serverAssert(raxNext(&ri));
cg = ri.data;
effort += raxSize(s->cgroups)*(1+raxSize(cg->pel));
raxStop(&ri);
}
return effort;
} else {
return 1; /* Everything else is a single allocation. */
}
+1 -1
View File
@@ -3,7 +3,7 @@ GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
BUILD_ID=`uname -n`"-"`date +%s`
if [ -n "$SOURCE_DATE_EPOCH" ]; then
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u %s)
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u +%s)
fi
test -f release.h || touch release.h
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
+222 -46
View File
@@ -29,6 +29,7 @@
#include "server.h"
#include "cluster.h"
#include "rdb.h"
#include <dlfcn.h>
#define REDISMODULE_CORE 1
@@ -132,10 +133,14 @@ struct RedisModuleCtx {
int keys_count;
struct RedisModulePoolAllocBlock *pa_head;
redisOpArray saved_oparray; /* When propagating commands in a callback
we reallocate the "also propagate" op
array. Here we save the old one to
restore it later. */
};
typedef struct RedisModuleCtx RedisModuleCtx;
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL}
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL, {0}}
#define REDISMODULE_CTX_MULTI_EMITTED (1<<0)
#define REDISMODULE_CTX_AUTO_MEMORY (1<<1)
#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2)
@@ -143,6 +148,7 @@ typedef struct RedisModuleCtx RedisModuleCtx;
#define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<4)
#define REDISMODULE_CTX_THREAD_SAFE (1<<5)
#define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<6)
#define REDISMODULE_CTX_MODULE_COMMAND_CALL (1<<7)
/* This represents a Redis key opened with RM_OpenKey(). */
struct RedisModuleKey {
@@ -291,6 +297,11 @@ typedef struct RedisModuleCommandFilter {
/* Registered filters */
static list *moduleCommandFilters;
/* Flags for moduleCreateArgvFromUserFormat(). */
#define REDISMODULE_ARGV_REPLICATE (1<<0)
#define REDISMODULE_ARGV_NO_AOF (1<<1)
#define REDISMODULE_ARGV_NO_REPLICAS (1<<2)
/* --------------------------------------------------------------------------
* Prototypes
* -------------------------------------------------------------------------- */
@@ -460,7 +471,8 @@ int moduleDelKeyIfEmpty(RedisModuleKey *key) {
case OBJ_LIST: isempty = listTypeLength(o) == 0; break;
case OBJ_SET: isempty = setTypeSize(o) == 0; break;
case OBJ_ZSET: isempty = zsetLength(o) == 0; break;
case OBJ_HASH : isempty = hashTypeLength(o) == 0; break;
case OBJ_HASH: isempty = hashTypeLength(o) == 0; break;
case OBJ_STREAM: isempty = streamLength(o) == 0; break;
default: isempty = 0;
}
@@ -496,8 +508,49 @@ int RM_GetApi(const char *funcname, void **targetPtrPtr) {
return REDISMODULE_OK;
}
/* Helper function for when a command callback is called, in order to handle
* details needed to correctly replicate commands. */
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
client *c = ctx->client;
/* We don't need to do anything here if the context was never used
* in order to propagate commands. */
if (!(ctx->flags & REDISMODULE_CTX_MULTI_EMITTED)) return;
if (c->flags & CLIENT_LUA) return;
/* Handle the replication of the final EXEC, since whatever a command
* emits is always wrapped around MULTI/EXEC. */
robj *propargv[1];
propargv[0] = createStringObject("EXEC",4);
alsoPropagate(server.execCommand,c->db->id,propargv,1,
PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(propargv[0]);
/* If this is not a module command context (but is instead a simple
* callback context), we have to handle directly the "also propagate"
* array and emit it. In a module command call this will be handled
* directly by call(). */
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL) &&
server.also_propagate.numops)
{
for (int j = 0; j < server.also_propagate.numops; j++) {
redisOp *rop = &server.also_propagate.ops[j];
int target = rop->target;
if (target)
propagate(rop->cmd,rop->dbid,rop->argv,rop->argc,target);
}
redisOpArrayFree(&server.also_propagate);
/* Restore the previous oparray in case of nexted use of the API. */
server.also_propagate = ctx->saved_oparray;
/* We're done with saved_oparray, let's invalidate it. */
redisOpArrayInit(&ctx->saved_oparray);
}
}
/* Free the context after the user function was called. */
void moduleFreeContext(RedisModuleCtx *ctx) {
moduleHandlePropagationAfterCommandCallback(ctx);
autoMemoryCollect(ctx);
poolAllocRelease(ctx);
if (ctx->postponed_arrays) {
@@ -513,34 +566,16 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) freeClient(ctx->client);
}
/* Helper function for when a command callback is called, in order to handle
* details needed to correctly replicate commands. */
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
client *c = ctx->client;
if (c->flags & CLIENT_LUA) return;
/* Handle the replication of the final EXEC, since whatever a command
* emits is always wrapped around MULTI/EXEC. */
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
robj *propargv[1];
propargv[0] = createStringObject("EXEC",4);
alsoPropagate(server.execCommand,c->db->id,propargv,1,
PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(propargv[0]);
}
}
/* This Redis command binds the normal Redis command invocation with commands
* exported by modules. */
void RedisModuleCommandDispatcher(client *c) {
RedisModuleCommandProxy *cp = (void*)(unsigned long)c->cmd->getkeys_proc;
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
ctx.flags |= REDISMODULE_CTX_MODULE_COMMAND_CALL;
ctx.module = cp->module;
ctx.client = c;
cp->func(&ctx,(void**)c->argv,c->argc);
moduleHandlePropagationAfterCommandCallback(&ctx);
moduleFreeContext(&ctx);
/* In some cases processMultibulkBuffer uses sdsMakeRoomFor to
@@ -615,9 +650,9 @@ void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) {
* flags into the command flags used by the Redis core.
*
* It returns the set of flags, or -1 if unknown flags are found. */
int commandFlagsFromString(char *s) {
int64_t commandFlagsFromString(char *s) {
int count, j;
int flags = 0;
int64_t flags = 0;
sds *tokens = sdssplitlen(s,strlen(s)," ",1,&count);
for (j = 0; j < count; j++) {
char *t = tokens[j];
@@ -695,7 +730,7 @@ int commandFlagsFromString(char *s) {
* other reason.
*/
int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
int flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
if (flags == -1) return REDISMODULE_ERR;
if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled)
return REDISMODULE_ERR;
@@ -1241,6 +1276,17 @@ int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) {
return REDISMODULE_OK;
}
/* Reply with a bulk string, taking in input a C buffer pointer that is
* assumed to be null-terminated.
*
* The function always returns REDISMODULE_OK. */
int RM_ReplyWithCString(RedisModuleCtx *ctx, const char *buf) {
client *c = moduleGetReplyClient(ctx);
if (c == NULL) return REDISMODULE_OK;
addReplyBulkCString(c,(char*)buf);
return REDISMODULE_OK;
}
/* Reply with a bulk string, taking in input a RedisModuleString object.
*
* The function always returns REDISMODULE_OK. */
@@ -1303,9 +1349,16 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
/* If we already emitted MULTI return ASAP. */
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
/* If this is a thread safe context, we do not want to wrap commands
* executed into MUTLI/EXEC, they are executed as single commands
* executed into MULTI/EXEC, they are executed as single commands
* from an external client in essence. */
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) return;
/* If this is a callback context, and not a module command execution
* context, we have to setup the op array for the "also propagate" API
* so that RM_Replicate() will work. */
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL)) {
ctx->saved_oparray = server.also_propagate;
redisOpArrayInit(&server.also_propagate);
}
execCommandPropagateMulti(ctx->client);
ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
}
@@ -1327,6 +1380,24 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
*
* Please refer to RedisModule_Call() for more information.
*
* Using the special "A" and "R" modifiers, the caller can exclude either
* the AOF or the replicas from the propagation of the specified command.
* Otherwise, by default, the command will be propagated in both channels.
*
* ## Note about calling this function from a thread safe context:
*
* Normally when you call this function from the callback implementing a
* module command, or any other callback provided by the Redis Module API,
* Redis will accumulate all the calls to this function in the context of
* the callback, and will propagate all the commands wrapped in a MULTI/EXEC
* transaction. However when calling this function from a threaded safe context
* that can live an undefined amount of time, and can be locked/unlocked in
* at will, the behavior is different: MULTI/EXEC wrapper is not emitted
* and the command specified is inserted in the AOF and replication stream
* immediately.
*
* ## Return value
*
* The command returns REDISMODULE_ERR if the format specifiers are invalid
* or the command name does not belong to a known command. */
int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
@@ -1344,10 +1415,23 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...)
va_end(ap);
if (argv == NULL) return REDISMODULE_ERR;
/* Replicate! */
moduleReplicateMultiIfNeeded(ctx);
alsoPropagate(cmd,ctx->client->db->id,argv,argc,
PROPAGATE_AOF|PROPAGATE_REPL);
/* Select the propagation target. Usually is AOF + replicas, however
* the caller can exclude one or the other using the "A" or "R"
* modifiers. */
int target = 0;
if (!(flags & REDISMODULE_ARGV_NO_AOF)) target |= PROPAGATE_AOF;
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS)) target |= PROPAGATE_REPL;
/* Replicate! When we are in a threaded context, we want to just insert
* the replicated command ASAP, since it is not clear when the context
* will stop being used, so accumulating stuff does not make much sense,
* nor we could easily use the alsoPropagate() API from threads. */
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) {
propagate(cmd,ctx->client->db->id,argv,argc,target);
} else {
moduleReplicateMultiIfNeeded(ctx);
alsoPropagate(cmd,ctx->client->db->id,argv,argc,target);
}
/* Release the argv. */
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
@@ -1436,6 +1520,23 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
*
* * REDISMODULE_CTX_FLAGS_OOM_WARNING: Less than 25% of memory remains before
* reaching the maxmemory level.
*
* * REDISMODULE_CTX_FLAGS_LOADING: Server is loading RDB/AOF
*
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE: No active link with the master.
*
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING: The replica is trying to
* connect with the master.
*
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING: Master -> Replica RDB
* transfer is in progress.
*
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE: The replica has an active link
* with its master. This is the
* contrary of STALE state.
*
* * REDISMODULE_CTX_FLAGS_ACTIVE_CHILD: There is currently some background
* process active (RDB, AUX or module).
*/
int RM_GetContextFlags(RedisModuleCtx *ctx) {
@@ -1454,6 +1555,9 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
if (server.cluster_enabled)
flags |= REDISMODULE_CTX_FLAGS_CLUSTER;
if (server.loading)
flags |= REDISMODULE_CTX_FLAGS_LOADING;
/* Maxmemory and eviction policy */
if (server.maxmemory > 0) {
flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY;
@@ -1475,6 +1579,20 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
flags |= REDISMODULE_CTX_FLAGS_SLAVE;
if (server.repl_slave_ro)
flags |= REDISMODULE_CTX_FLAGS_READONLY;
/* Replica state flags. */
if (server.repl_state == REPL_STATE_CONNECT ||
server.repl_state == REPL_STATE_CONNECTING)
{
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING;
} else if (server.repl_state == REPL_STATE_TRANSFER) {
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING;
} else if (server.repl_state == REPL_STATE_CONNECTED) {
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE;
}
if (server.repl_state != REPL_STATE_CONNECTED)
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE;
}
/* OOM flag. */
@@ -1483,6 +1601,9 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
if (retval == C_ERR) flags |= REDISMODULE_CTX_FLAGS_OOM;
if (level > 0.75) flags |= REDISMODULE_CTX_FLAGS_OOM_WARNING;
/* Presence of children processes. */
if (hasActiveChildProcess()) flags |= REDISMODULE_CTX_FLAGS_ACTIVE_CHILD;
return flags;
}
@@ -1566,6 +1687,7 @@ int RM_KeyType(RedisModuleKey *key) {
case OBJ_ZSET: return REDISMODULE_KEYTYPE_ZSET;
case OBJ_HASH: return REDISMODULE_KEYTYPE_HASH;
case OBJ_MODULE: return REDISMODULE_KEYTYPE_MODULE;
/* case OBJ_STREAM: return REDISMODULE_KEYTYPE_STREAM; - don't wanna add new API to 5.0 */
default: return 0;
}
}
@@ -1583,6 +1705,7 @@ size_t RM_ValueLength(RedisModuleKey *key) {
case OBJ_SET: return setTypeSize(key->value);
case OBJ_ZSET: return zsetLength(key->value);
case OBJ_HASH: return hashTypeLength(key->value);
case OBJ_STREAM: return streamLength(key->value);
default: return 0;
}
}
@@ -2662,12 +2785,11 @@ RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply) {
* to special modifiers in "fmt". For now only one exists:
*
* "!" -> REDISMODULE_ARGV_REPLICATE
* "A" -> REDISMODULE_ARGV_NO_AOF
* "R" -> REDISMODULE_ARGV_NO_REPLICAS
*
* On error (format specifier error) NULL is returned and nothing is
* allocated. On success the argument vector is returned. */
#define REDISMODULE_ARGV_REPLICATE (1<<0)
robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap) {
int argc = 0, argv_size, j;
robj **argv = NULL;
@@ -2696,7 +2818,7 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
size_t len = va_arg(ap,size_t);
argv[argc++] = createStringObject(buf,len);
} else if (*p == 'l') {
long ll = va_arg(ap,long long);
long long ll = va_arg(ap,long long);
argv[argc++] = createObject(OBJ_STRING,sdsfromlonglong(ll));
} else if (*p == 'v') {
/* A vector of strings */
@@ -2716,6 +2838,10 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
}
} else if (*p == '!') {
if (flags) (*flags) |= REDISMODULE_ARGV_REPLICATE;
} else if (*p == 'A') {
if (flags) (*flags) |= REDISMODULE_ARGV_NO_AOF;
} else if (*p == 'R') {
if (flags) (*flags) |= REDISMODULE_ARGV_NO_REPLICAS;
} else {
goto fmterr;
}
@@ -2736,7 +2862,10 @@ fmterr:
* NULL is returned and errno is set to the following values:
*
* EINVAL: command non existing, wrong arity, wrong format specifier.
* EPERM: operation in Cluster instance with key in non local slot. */
* EPERM: operation in Cluster instance with key in non local slot.
*
* This API is documented here: https://redis.io/topics/modules-intro
*/
RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
struct redisCommand *cmd;
client *c = NULL;
@@ -2806,8 +2935,10 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
/* Run the command */
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
if (replicate) {
call_flags |= CMD_CALL_PROPAGATE_AOF;
call_flags |= CMD_CALL_PROPAGATE_REPL;
if (!(flags & REDISMODULE_ARGV_NO_AOF))
call_flags |= CMD_CALL_PROPAGATE_AOF;
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS))
call_flags |= CMD_CALL_PROPAGATE_REPL;
}
call(c,call_flags);
@@ -3062,6 +3193,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
moduleTypeMemUsageFunc mem_usage;
moduleTypeDigestFunc digest;
moduleTypeFreeFunc free;
struct {
moduleTypeAuxLoadFunc aux_load;
moduleTypeAuxSaveFunc aux_save;
int aux_save_triggers;
} v2;
} *tms = (struct typemethods*) typemethods_ptr;
moduleType *mt = zcalloc(sizeof(*mt));
@@ -3073,6 +3209,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
mt->mem_usage = tms->mem_usage;
mt->digest = tms->digest;
mt->free = tms->free;
if (tms->version >= 2) {
mt->aux_load = tms->v2.aux_load;
mt->aux_save = tms->v2.aux_save;
mt->aux_save_triggers = tms->v2.aux_save_triggers;
}
memcpy(mt->name,name,sizeof(mt->name));
listAddNodeTail(ctx->module->types,mt);
return mt;
@@ -3339,6 +3480,36 @@ loaderr:
return 0; /* Never reached. */
}
/* Iterate over modules, and trigger rdb aux saving for the ones modules types
* who asked for it. */
ssize_t rdbSaveModulesAux(rio *rdb, int when) {
size_t total_written = 0;
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct RedisModule *module = dictGetVal(de);
listIter li;
listNode *ln;
listRewind(module->types,&li);
while((ln = listNext(&li))) {
moduleType *mt = ln->value;
if (!mt->aux_save || !(mt->aux_save_triggers & when))
continue;
ssize_t ret = rdbSaveSingleModuleAux(rdb, when, mt);
if (ret==-1) {
dictReleaseIterator(di);
return -1;
}
total_written += ret;
}
}
dictReleaseIterator(di);
return total_written;
}
/* --------------------------------------------------------------------------
* Key digest API (DEBUG DIGEST interface for modules types)
* -------------------------------------------------------------------------- */
@@ -3499,7 +3670,7 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
if (level < server.verbosity) return;
name_len = snprintf(msg, sizeof(msg),"<%s> ", module->name);
name_len = snprintf(msg, sizeof(msg),"<%s> ", module? module->name: "module");
vsnprintf(msg + name_len, sizeof(msg) - name_len, fmt, ap);
serverLogRaw(level,msg);
}
@@ -3517,13 +3688,15 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
* There is a fixed limit to the length of the log line this function is able
* to emit, this limit is not specified but is guaranteed to be more than
* a few lines of text.
*
* The ctx argument may be NULL if cannot be provided in the context of the
* caller for instance threads or callbacks, in which case a generic "module"
* will be used instead of the module name.
*/
void RM_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...) {
if (!ctx->module) return; /* Can only log if module is initialized */
va_list ap;
va_start(ap, fmt);
RM_LogRaw(ctx->module,levelstr,fmt,ap);
RM_LogRaw(ctx? ctx->module: NULL,levelstr,fmt,ap);
va_end(ap);
}
@@ -3725,7 +3898,6 @@ void moduleHandleBlockedClients(void) {
ctx.client = bc->client;
ctx.blocked_client = bc;
bc->reply_callback(&ctx,(void**)c->argv,c->argc);
moduleHandlePropagationAfterCommandCallback(&ctx);
moduleFreeContext(&ctx);
}
@@ -3840,9 +4012,9 @@ int RM_BlockedClientDisconnected(RedisModuleCtx *ctx) {
*
* To call non-reply APIs, the thread safe context must be prepared with:
*
* RedisModule_ThreadSafeCallStart(ctx);
* RedisModule_ThreadSafeContextLock(ctx);
* ... make your call here ...
* RedisModule_ThreadSafeCallStop(ctx);
* RedisModule_ThreadSafeContextUnlock(ctx);
*
* This is not needed when using `RedisModule_Reply*` functions, assuming
* that a blocked client was used when the context was created, otherwise
@@ -3866,7 +4038,7 @@ RedisModuleCtx *RM_GetThreadSafeContext(RedisModuleBlockedClient *bc) {
ctx->client = createClient(-1);
if (bc) {
selectDb(ctx->client,bc->dbid);
ctx->client->id = bc->client->id;
if (bc->client) ctx->client->id = bc->client->id;
}
return ctx;
}
@@ -4210,10 +4382,13 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m
UNUSED(ctx);
clusterNode *node = clusterLookupNode(id);
if (node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
if (node == NULL ||
node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
{
return REDISMODULE_ERR;
}
if (ip) memcpy(ip,node->name,REDISMODULE_NODE_ID_LEN);
if (ip) strncpy(ip,node->ip,NET_IP_STR_LEN);
if (master_id) {
/* If the information is not available, the function will set the
@@ -5323,6 +5498,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(ReplySetArrayLength);
REGISTER_API(ReplyWithString);
REGISTER_API(ReplyWithStringBuffer);
REGISTER_API(ReplyWithCString);
REGISTER_API(ReplyWithNull);
REGISTER_API(ReplyWithCallReply);
REGISTER_API(ReplyWithDouble);
+29 -21
View File
@@ -918,7 +918,7 @@ void freeClient(client *c) {
/* 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 & CLIENT_SLAVE && listLength(server.slaves) == 0)
if (getClientType(c) == CLIENT_TYPE_SLAVE && listLength(server.slaves) == 0)
server.repl_no_slaves_since = server.unixtime;
refreshGoodSlavesCount();
}
@@ -1030,8 +1030,8 @@ int writeToClient(int fd, client *c, int handler_installed) {
* just deliver as much data as it is possible to deliver.
*
* Moreover, we also send as much as possible if the client is
* a slave (otherwise, on high-speed traffic, the replication
* buffer will grow indefinitely) */
* a slave or a monitor (otherwise, on high-speed traffic, the
* replication/output buffer will grow indefinitely) */
if (totwritten > NET_MAX_WRITES_PER_EVENT &&
(server.maxmemory == 0 ||
zmalloc_used_memory() < server.maxmemory) &&
@@ -1216,7 +1216,7 @@ int processInlineBuffer(client *c) {
/* 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 & CLIENT_SLAVE)
if (querylen == 0 && getClientType(c) == CLIENT_TYPE_SLAVE)
c->repl_ack_time = server.unixtime;
/* Move querybuffer position to the next query in the buffer. */
@@ -1230,12 +1230,8 @@ int processInlineBuffer(client *c) {
/* Create redis objects for all arguments. */
for (c->argc = 0, j = 0; j < argc; j++) {
if (sdslen(argv[j])) {
c->argv[c->argc] = createObject(OBJ_STRING,argv[j]);
c->argc++;
} else {
sdsfree(argv[j]);
}
c->argv[c->argc] = createObject(OBJ_STRING,argv[j]);
c->argc++;
}
zfree(argv);
return C_OK;
@@ -2037,12 +2033,14 @@ unsigned long getClientOutputBufferMemoryUsage(client *c) {
*
* The function will return one of the following:
* CLIENT_TYPE_NORMAL -> Normal client
* CLIENT_TYPE_SLAVE -> Slave or client executing MONITOR command
* CLIENT_TYPE_SLAVE -> Slave
* CLIENT_TYPE_PUBSUB -> Client subscribed to Pub/Sub channels
* CLIENT_TYPE_MASTER -> The client representing our replication master.
*/
int getClientType(client *c) {
if (c->flags & CLIENT_MASTER) return CLIENT_TYPE_MASTER;
/* Even though MONITOR clients are marked as replicas, we
* want the expose them as normal clients. */
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR))
return CLIENT_TYPE_SLAVE;
if (c->flags & CLIENT_PUBSUB) return CLIENT_TYPE_PUBSUB;
@@ -2143,17 +2141,27 @@ void flushSlavesOutputBuffers(void) {
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
client *slave = listNodeValue(ln);
int events;
int events = aeGetFileEvents(server.el,slave->fd);
int can_receive_writes = (events & AE_WRITABLE) ||
(slave->flags & CLIENT_PENDING_WRITE);
/* Note that the following will not flush output buffers of slaves
* in STATE_ONLINE but having put_online_on_ack set to true: in this
* case the writable event is never installed, since the purpose
* of put_online_on_ack is to postpone the moment it is installed.
* This is what we want since slaves in this state should not receive
* writes before the first ACK. */
events = aeGetFileEvents(server.el,slave->fd);
if (events & AE_WRITABLE &&
slave->replstate == SLAVE_STATE_ONLINE &&
/* We don't want to send the pending data to the replica in a few
* cases:
*
* 1. For some reason there is neither the write handler installed
* nor the client is flagged as to have pending writes: for some
* reason this replica may not be set to receive data. This is
* just for the sake of defensive programming.
*
* 2. The put_online_on_ack flag is true. To know why we don't want
* to send data to the replica in this case, please grep for the
* flag for this flag.
*
* 3. Obviously if the slave is not ONLINE.
*/
if (slave->replstate == SLAVE_STATE_ONLINE &&
can_receive_writes &&
!slave->repl_put_online_on_ack &&
clientHasPendingReplies(slave))
{
writeToClient(slave->fd,slave,0);
+12 -5
View File
@@ -467,10 +467,15 @@ robj *tryObjectEncoding(robj *o) {
incrRefCount(shared.integers[value]);
return shared.integers[value];
} else {
if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
o->encoding = OBJ_ENCODING_INT;
o->ptr = (void*) value;
return o;
if (o->encoding == OBJ_ENCODING_RAW) {
sdsfree(o->ptr);
o->encoding = OBJ_ENCODING_INT;
o->ptr = (void*) value;
return o;
} else if (o->encoding == OBJ_ENCODING_EMBSTR) {
decrRefCount(o);
return createStringObjectFromLongLongForValue(value);
}
}
}
@@ -834,7 +839,9 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
d = ((zset*)o->ptr)->dict;
zskiplist *zsl = ((zset*)o->ptr)->zsl;
zskiplistNode *znode = zsl->header->level[0].forward;
asize = sizeof(*o)+sizeof(zset)+(sizeof(struct dictEntry*)*dictSlots(d));
asize = sizeof(*o)+sizeof(zset)+sizeof(zskiplist)+sizeof(dict)+
(sizeof(struct dictEntry*)*dictSlots(d))+
zmalloc_size(zsl->header);
while(znode != NULL && samples < sample_size) {
elesize += sdsAllocSize(znode->ele);
elesize += sizeof(struct dictEntry) + zmalloc_size(znode);
+86 -18
View File
@@ -966,7 +966,6 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
RedisModuleIO io;
moduleValue *mv = o->ptr;
moduleType *mt = mv->type;
moduleInitIOContext(io,mt,rdb,key);
/* Write the "module" identifier as prefix, so that we'll be able
* to call the right module during loading. */
@@ -975,10 +974,13 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
io.bytes += retval;
/* Then write the module-specific representation + EOF marker. */
moduleInitIOContext(io,mt,rdb,key);
mt->rdb_save(&io,mv->value);
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
if (retval == -1) return -1;
io.bytes += retval;
if (retval == -1)
io.error = 1;
else
io.bytes += retval;
if (io.ctx) {
moduleFreeContext(io.ctx);
@@ -1091,6 +1093,47 @@ int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
return 1;
}
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
/* Save a module-specific aux value. */
RedisModuleIO io;
int retval = rdbSaveType(rdb, RDB_OPCODE_MODULE_AUX);
if (retval == -1) return -1;
io.bytes += retval;
/* Write the "module" identifier as prefix, so that we'll be able
* to call the right module during loading. */
retval = rdbSaveLen(rdb,mt->id);
if (retval == -1) return -1;
io.bytes += retval;
/* write the 'when' so that we can provide it on loading. add a UINT opcode
* for backwards compatibility, everything after the MT needs to be prefixed
* by an opcode. */
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_UINT);
if (retval == -1) return -1;
io.bytes += retval;
retval = rdbSaveLen(rdb,when);
if (retval == -1) return -1;
io.bytes += retval;
/* Then write the module-specific representation + EOF marker. */
moduleInitIOContext(io,mt,rdb,NULL);
mt->aux_save(&io,when);
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
if (retval == -1)
io.error = 1;
else
io.bytes += retval;
if (io.ctx) {
moduleFreeContext(io.ctx);
zfree(io.ctx);
}
if (io.error)
return -1;
return io.bytes;
}
/* Produces a dump of the database in RDB format sending it to the specified
* Redis I/O channel. On success C_OK is returned, otherwise C_ERR
* is returned and part of the output, or all the output, can be
@@ -1112,6 +1155,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
for (j = 0; j < server.dbnum; j++) {
redisDb *db = server.db+j;
@@ -1173,6 +1217,8 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
di = NULL; /* So that we don't release it again on error. */
}
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr;
/* EOF opcode */
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
@@ -1724,8 +1770,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
rdbExitReportCorruptRDB(
"Error reading the consumer name from Stream group");
}
streamConsumer *consumer = streamLookupConsumer(cgroup,cname,
1);
streamConsumer *consumer =
streamLookupConsumer(cgroup,cname,SLC_NONE);
sdsfree(cname);
consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
@@ -1837,7 +1883,7 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
/* 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();
updateCachedTime(0);
if (server.masterhost && server.repl_state == REPL_STATE_TRANSFER)
replicationSendNewlineToMaster();
loadingProgress(r->processed_bytes);
@@ -1974,15 +2020,14 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
decrRefCount(auxval);
continue; /* Read type again. */
} else if (type == RDB_OPCODE_MODULE_AUX) {
/* This is just for compatibility with the future: we have plans
* to add the ability for modules to store anything in the RDB
* file, like data that is not related to the Redis key space.
* Such data will potentially be stored both before and after the
* RDB keys-values section. For this reason since RDB version 9,
* we have the ability to read a MODULE_AUX opcode followed by an
* identifier of the module, and a serialized value in "MODULE V2"
* format. */
/* Load module data that is not related to the Redis key space.
* Such data can be potentially be stored both before and after the
* RDB keys-values section. */
uint64_t moduleid = rdbLoadLen(rdb,NULL);
int when_opcode = rdbLoadLen(rdb,NULL);
int when = rdbLoadLen(rdb,NULL);
if (when_opcode != RDB_MODULE_OPCODE_UINT)
rdbExitReportCorruptRDB("bad when_opcode");
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
char name[10];
moduleTypeNameByID(name,moduleid);
@@ -1992,14 +2037,37 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load: no matching module '%s'", name);
exit(1);
} else if (!rdbCheckMode && mt != NULL) {
/* This version of Redis actually does not know what to do
* with modules AUX data... */
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load for the module '%s'. Probably you want to use a newer version of Redis which implements aux data callbacks", name);
exit(1);
if (!mt->aux_load) {
/* Module doesn't support AUX. */
serverLog(LL_WARNING,"The RDB file contains module AUX data, but the module '%s' doesn't seem to support it.", name);
exit(1);
}
RedisModuleIO io;
moduleInitIOContext(io,mt,rdb,NULL);
io.ver = 2;
/* Call the rdb_load method of the module providing the 10 bit
* encoding version in the lower 10 bits of the module ID. */
if (mt->aux_load(&io,moduleid&1023, when) || io.error) {
moduleTypeNameByID(name,moduleid);
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
exit(1);
}
if (io.ctx) {
moduleFreeContext(io.ctx);
zfree(io.ctx);
}
uint64_t eof = rdbLoadLen(rdb,NULL);
if (eof != RDB_MODULE_OPCODE_EOF) {
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module '%s' that is not terminated by the proper module value EOF marker", name);
exit(1);
}
continue;
} else {
/* RDB check mode. */
robj *aux = rdbLoadCheckModuleValue(rdb,name);
decrRefCount(aux);
continue; /* Read next opcode. */
}
}
+2
View File
@@ -145,6 +145,8 @@ size_t rdbSavedObjectLen(robj *o);
robj *rdbLoadObject(int type, rio *rdb, robj *key);
void backgroundSaveDoneHandler(int exitcode, int bysignal);
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename);
robj *rdbLoadStringObject(rio *rdb);
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
+18 -1
View File
@@ -58,6 +58,7 @@ struct {
#define RDB_CHECK_DOING_CHECK_SUM 5
#define RDB_CHECK_DOING_READ_LEN 6
#define RDB_CHECK_DOING_READ_AUX 7
#define RDB_CHECK_DOING_READ_MODULE_AUX 8
char *rdb_check_doing_string[] = {
"start",
@@ -67,7 +68,8 @@ char *rdb_check_doing_string[] = {
"read-object-value",
"check-sum",
"read-len",
"read-aux"
"read-aux",
"read-module-aux"
};
char *rdb_type_string[] = {
@@ -270,6 +272,21 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
decrRefCount(auxkey);
decrRefCount(auxval);
continue; /* Read type again. */
} else if (type == RDB_OPCODE_MODULE_AUX) {
/* AUX: Auxiliary data for modules. */
uint64_t moduleid, when_opcode, when;
rdbstate.doing = RDB_CHECK_DOING_READ_MODULE_AUX;
if ((moduleid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
if ((when_opcode = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
if ((when = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
char name[10];
moduleTypeNameByID(name,moduleid);
rdbCheckInfo("MODULE AUX for: %s", name);
robj *o = rdbLoadCheckModuleValue(&rdb,name);
decrRefCount(o);
continue; /* Read type again. */
} else {
if (!rdbIsObjectType(type)) {
rdbCheckError("Invalid object type: %d", type);
+19 -7
View File
@@ -1144,7 +1144,7 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
for (j = 0; j < argc; j++)
argvlen[j] = sdslen(argv[j]);
while(repeat-- > 0) {
while(repeat < 0 || repeat-- > 0) {
redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
while (config.monitor_mode) {
if (cliReadReply(output_raw) != REDIS_OK) exit(1);
@@ -1179,6 +1179,11 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
cliSelect();
}
}
if (config.cluster_reissue_command){
/* If we need to reissue the command, break to prevent a
further 'repeat' number of dud interations */
break;
}
if (config.interval) usleep(config.interval);
fflush(stdout); /* Make it grep friendly */
}
@@ -1589,12 +1594,12 @@ static int issueCommandRepeat(int argc, char **argv, long repeat) {
cliPrintContextError();
return REDIS_ERR;
}
}
/* Issue the command again if we got redirected in cluster mode */
if (config.cluster_mode && config.cluster_reissue_command) {
}
/* Issue the command again if we got redirected in cluster mode */
if (config.cluster_mode && config.cluster_reissue_command) {
cliConnect(CC_FORCE);
} else {
break;
} else {
break;
}
}
return REDIS_OK;
@@ -6073,6 +6078,7 @@ static void pipeMode(void) {
/* Handle the readable state: we can read replies from the server. */
if (mask & AE_READABLE) {
ssize_t nread;
int read_error = 0;
/* Read from socket and feed the hiredis reader. */
do {
@@ -6080,7 +6086,8 @@ static void pipeMode(void) {
if (nread == -1 && errno != EAGAIN && errno != EINTR) {
fprintf(stderr, "Error reading from the server: %s\n",
strerror(errno));
exit(1);
read_error = 1;
break;
}
if (nread > 0) {
redisReaderFeed(reader,ibuf,nread);
@@ -6113,6 +6120,11 @@ static void pipeMode(void) {
freeReplyObject(reply);
}
} while(reply);
/* Abort on read errors. We abort here because it is important
* to consume replies even after a read error: this way we can
* show a potential problem to the user. */
if (read_error) exit(1);
}
/* Handle the writable state: we can send protocol to the server. */
+30 -2
View File
@@ -87,7 +87,24 @@
#define REDISMODULE_CTX_FLAGS_OOM_WARNING (1<<11)
/* The command was sent over the replication link. */
#define REDISMODULE_CTX_FLAGS_REPLICATED (1<<12)
/* Redis is currently loading either from AOF or RDB. */
#define REDISMODULE_CTX_FLAGS_LOADING (1<<13)
/* The replica has no link with its master, note that
* there is the inverse flag as well:
*
* REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE
*
* The two flags are exclusive, one or the other can be set. */
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE (1<<14)
/* The replica is trying to connect with the master.
* (REPL_STATE_CONNECT and REPL_STATE_CONNECTING states) */
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING (1<<15)
/* THe replica is receiving an RDB file from its master. */
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING (1<<16)
/* The replica is online, receiving updates from its master. */
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE (1<<17)
/* There is currently some background process active. */
#define REDISMODULE_CTX_FLAGS_ACTIVE_CHILD (1<<18)
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */
@@ -126,6 +143,10 @@
#define REDISMODULE_NOT_USED(V) ((void) V)
/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
/* This type represents a timer handle, and is returned when a timer is
* registered and used in order to invalidate a timer. It's just a 64 bit
* number, because this is how each timer is represented inside the radix tree
@@ -163,6 +184,8 @@ typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlocke
typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
typedef int (*RedisModuleTypeAuxLoadFunc)(RedisModuleIO *rdb, int encver, int when);
typedef void (*RedisModuleTypeAuxSaveFunc)(RedisModuleIO *rdb, int when);
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value);
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
@@ -171,7 +194,7 @@ typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const cha
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
#define REDISMODULE_TYPE_METHOD_VERSION 1
#define REDISMODULE_TYPE_METHOD_VERSION 2
typedef struct RedisModuleTypeMethods {
uint64_t version;
RedisModuleTypeLoadFunc rdb_load;
@@ -180,6 +203,9 @@ typedef struct RedisModuleTypeMethods {
RedisModuleTypeMemUsageFunc mem_usage;
RedisModuleTypeDigestFunc digest;
RedisModuleTypeFreeFunc free;
RedisModuleTypeAuxLoadFunc aux_load;
RedisModuleTypeAuxSaveFunc aux_save;
int aux_save_triggers;
} RedisModuleTypeMethods;
#define REDISMODULE_GET_API(name) \
@@ -225,6 +251,7 @@ int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx,
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
@@ -375,6 +402,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(ReplyWithArray);
REDISMODULE_GET_API(ReplySetArrayLength);
REDISMODULE_GET_API(ReplyWithStringBuffer);
REDISMODULE_GET_API(ReplyWithCString);
REDISMODULE_GET_API(ReplyWithString);
REDISMODULE_GET_API(ReplyWithNull);
REDISMODULE_GET_API(ReplyWithCallReply);
+48 -10
View File
@@ -823,7 +823,9 @@ void replconfCommand(client *c) {
c->repl_ack_time = server.unixtime;
/* If this was a diskless replication, we need to really put
* the slave online when the first ACK is received (which
* confirms slave is online and ready to get more data). */
* confirms slave is online and ready to get more data). This
* allows for simpler and less CPU intensive EOF detection
* when streaming RDB files. */
if (c->repl_put_online_on_ack && c->replstate == SLAVE_STATE_ONLINE)
putSlaveOnline(c);
/* Note: this command does not reply anything! */
@@ -842,18 +844,20 @@ void replconfCommand(client *c) {
addReply(c,shared.ok);
}
/* This function puts a slave in the online state, and should be called just
* after a slave received the RDB file for the initial synchronization, and
/* This function puts a replica in the online state, and should be called just
* after a replica received the RDB file for the initial synchronization, and
* we are finally ready to send the incremental stream of commands.
*
* It does a few things:
*
* 1) Put the slave in ONLINE state (useless when the function is called
* because state is already ONLINE but repl_put_online_on_ack is true).
* 1) Put the slave in ONLINE state. Note that the function may also be called
* for a replicas that are already in ONLINE state, but having the flag
* repl_put_online_on_ack set to true: we still have to install the write
* handler in that case. This function will take care of that.
* 2) Make sure the writable event is re-installed, since calling the SYNC
* command disables it, so that we can accumulate output buffer without
* sending it to the slave.
* 3) Update the count of good slaves. */
* sending it to the replica.
* 3) Update the count of "good replicas". */
void putSlaveOnline(client *slave) {
slave->replstate = SLAVE_STATE_ONLINE;
slave->repl_put_online_on_ack = 0;
@@ -965,11 +969,31 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
serverLog(LL_NOTICE,
"Streamed RDB transfer with replica %s succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming",
replicationGetSlaveName(slave));
/* Note: we wait for a REPLCONF ACK message from slave in
/* Note: we wait for a REPLCONF ACK message from the replica in
* order to really put it online (install the write handler
* so that the accumulated data can be transferred). However
* we change the replication state ASAP, since our slave
* is technically online now. */
* is technically online now.
*
* So things work like that:
*
* 1. We end trasnferring the RDB file via socket.
* 2. The replica is put ONLINE but the write handler
* is not installed.
* 3. The replica however goes really online, and pings us
* back via REPLCONF ACK commands.
* 4. Now we finally install the write handler, and send
* the buffers accumulated so far to the replica.
*
* But why we do that? Because the replica, when we stream
* the RDB directly via the socket, must detect the RDB
* EOF (end of file), that is a special random string at the
* end of the RDB (for streamed RDBs we don't know the length
* in advance). Detecting such final EOF string is much
* simpler and less CPU intensive if no more data is sent
* after such final EOF. So we don't want to glue the end of
* the RDB trasfer with the start of the other replication
* data. */
slave->replstate = SLAVE_STATE_ONLINE;
slave->repl_put_online_on_ack = 1;
slave->repl_ack_time = server.unixtime; /* Timeout otherwise. */
@@ -1268,6 +1292,17 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
rdbRemoveTempFile(server.rdb_child_pid);
}
/* Make sure the new file (also used for persistence) is fully synced
* (not covered by earlier calls to rdb_fsync_range). */
if (fsync(server.repl_transfer_fd) == -1) {
serverLog(LL_WARNING,
"Failed trying to sync the temp DB to disk in "
"MASTER <-> REPLICA synchronization: %s",
strerror(errno));
cancelReplicationHandshake();
return;
}
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
serverLog(LL_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> REPLICA synchronization: %s", strerror(errno));
cancelReplicationHandshake();
@@ -1972,7 +2007,10 @@ void replicationSetMaster(char *ip, int port) {
cancelReplicationHandshake();
/* Before destroying our master state, create a cached master using
* our own parameters, to later PSYNC with the new master. */
if (was_master) replicationCacheMasterUsingMyself();
if (was_master) {
replicationDiscardCachedMaster();
replicationCacheMasterUsingMyself();
}
server.repl_state = REPL_STATE_CONNECT;
}
+4 -4
View File
@@ -521,12 +521,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
!server.loading && /* Don't care about mem if loading. */
!server.masterhost && /* Slave must execute the script. */
server.lua_write_dirty == 0 && /* Script had no side effects so far. */
server.lua_oom && /* Detected OOM when script start. */
(cmd->flags & CMD_DENYOOM))
{
if (getMaxmemoryState(NULL,NULL,NULL,NULL) != C_OK) {
luaPushError(lua, shared.oomerr->ptr);
goto cleanup;
}
luaPushError(lua, shared.oomerr->ptr);
goto cleanup;
}
if (cmd->flags & CMD_RANDOM) server.lua_random_dirty = 1;
@@ -2213,6 +2212,7 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
lua_pop(lua,1);
sdsfree(code);
sdsfree(expr);
return;
}
}
+1 -1
View File
@@ -34,7 +34,7 @@
#define __SDS_H
#define SDS_MAX_PREALLOC (1024*1024)
const char *SDS_NOINIT;
extern const char *SDS_NOINIT;
#include <sys/types.h>
#include <stdarg.h>
+2 -1
View File
@@ -1061,6 +1061,7 @@ int sentinelTryConnectionSharing(sentinelRedisInstance *ri) {
releaseInstanceLink(ri->link,NULL);
ri->link = match->link;
match->link->refcount++;
dictReleaseIterator(di);
return C_OK;
}
dictReleaseIterator(di);
@@ -4270,7 +4271,7 @@ void sentinelFailoverDetectEnd(sentinelRedisInstance *master) {
sentinelRedisInstance *slave = dictGetVal(de);
int retval;
if (slave->flags & (SRI_RECONF_DONE|SRI_RECONF_SENT)) continue;
if (slave->flags & (SRI_PROMOTED|SRI_RECONF_DONE|SRI_RECONF_SENT)) continue;
if (slave->link->disconnected) continue;
retval = sentinelSendSlaveOf(slave,
+69 -23
View File
@@ -236,7 +236,7 @@ struct redisCommand redisCommandTable[] = {
{"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0},
{"scan",scanCommand,-2,"rR",0,NULL,0,0,0,0,0},
{"dbsize",dbsizeCommand,1,"rF",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"sltMF",0,NULL,0,0,0,0,0},
{"ping",pingCommand,-1,"tF",0,NULL,0,0,0,0,0},
{"echo",echoCommand,2,"F",0,NULL,0,0,0,0,0},
{"save",saveCommand,1,"as",0,NULL,0,0,0,0,0},
@@ -779,6 +779,11 @@ void updateDictResizePolicy(void) {
dictDisableResize();
}
int hasActiveChildProcess() {
return server.rdb_child_pid != -1 ||
server.aof_child_pid != -1;
}
/* ======================= Cron: called every 100 ms ======================== */
/* Add a sample to the operations per second array of samples. */
@@ -816,7 +821,7 @@ int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
time_t now = now_ms/1000;
if (server.maxidletime &&
!(c->flags & CLIENT_SLAVE) && /* no timeout for slaves */
!(c->flags & CLIENT_SLAVE) && /* no timeout for slaves and monitors */
!(c->flags & CLIENT_MASTER) && /* no timeout for masters */
!(c->flags & CLIENT_BLOCKED) && /* no timeout for BLPOP */
!(c->flags & CLIENT_PUBSUB) && /* no timeout for Pub/Sub clients */
@@ -982,7 +987,7 @@ void clientsCron(void) {
/* Rotate the list, take the current head, process.
* This way if the client must be removed from the list it's the
* first element and we don't incur into O(N) computation. */
listRotate(server.clients);
listRotateTailToHead(server.clients);
head = listFirst(server.clients);
c = listNodeValue(head);
/* The following functions do different service checks on the client.
@@ -1054,19 +1059,30 @@ void databasesCron(void) {
/* We take a cached value of the unix time in the global state because with
* virtual memory and aging there is to store the current time in objects at
* every object access, and accuracy is not needed. To access a global var is
* a lot faster than calling time(NULL) */
void updateCachedTime(void) {
time_t unixtime = time(NULL);
* a lot faster than calling time(NULL).
*
* This function should be fast because it is called at every command execution
* in call(), so it is possible to decide if to update the daylight saving
* info or not using the 'update_daylight_info' argument. Normally we update
* such info only when calling this function from serverCron() but not when
* calling it from call(). */
void updateCachedTime(int update_daylight_info) {
server.ustime = ustime();
server.mstime = server.ustime / 1000;
time_t unixtime = server.mstime / 1000;
atomicSet(server.unixtime,unixtime);
server.mstime = mstime();
/* To get information about daylight saving time, we need to call localtime_r
* and cache the result. However calling localtime_r in this context is safe
* since we will never fork() while here, in the main thread. The logging
* function will call a thread safe version of localtime that has no locks. */
struct tm tm;
localtime_r(&server.unixtime,&tm);
server.daylight_active = tm.tm_isdst;
/* To get information about daylight saving time, we need to call
* localtime_r and cache the result. However calling localtime_r in this
* context is safe since we will never fork() while here, in the main
* thread. The logging function will call a thread safe version of
* localtime that has no locks. */
if (update_daylight_info) {
struct tm tm;
time_t ut = server.unixtime;
localtime_r(&ut,&tm);
server.daylight_active = tm.tm_isdst;
}
}
/* This is our timer interrupt, called server.hz times per second.
@@ -1099,7 +1115,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);
/* Update the time cache. */
updateCachedTime();
updateCachedTime(1);
server.hz = server.config_hz;
/* Adapt the server.hz value to the number of configured clients. If we have
@@ -1523,7 +1539,7 @@ void initServerConfig(void) {
pthread_mutex_init(&server.lruclock_mutex,NULL);
pthread_mutex_init(&server.unixtime_mutex,NULL);
updateCachedTime();
updateCachedTime(1);
getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
server.runid[CONFIG_RUN_ID_SIZE] = '\0';
changeReplicationId();
@@ -2027,6 +2043,7 @@ void initServer(void) {
server.hz = server.config_hz;
server.pid = getpid();
server.current_client = NULL;
server.fixed_time_expire = 0;
server.clients = listCreate();
server.clients_index = raxNew();
server.clients_to_close = listCreate();
@@ -2179,6 +2196,14 @@ void initServer(void) {
scriptingInit(1);
slowlogInit();
latencyMonitorInit();
}
/* Some steps in server initialization need to be done last (after modules
* are loaded).
* Specifically, creation of threads due to a race bug in ld.so, in which
* Thread Local Storage initialization collides with dlopen call.
* see: https://sourceware.org/bugzilla/show_bug.cgi?id=19329 */
void InitServerLast() {
bioInit();
server.initial_memory_usage = zmalloc_used_memory();
}
@@ -2311,8 +2336,13 @@ struct redisCommand *lookupCommandOrOriginal(sds name) {
* + PROPAGATE_AOF (propagate into the AOF file if is enabled)
* + PROPAGATE_REPL (propagate into the replication link)
*
* This should not be used inside commands implementation. Use instead
* alsoPropagate(), preventCommandPropagation(), forceCommandPropagation().
* This should not be used inside commands implementation since it will not
* wrap the resulting commands in MULTI/EXEC. Use instead alsoPropagate(),
* preventCommandPropagation(), forceCommandPropagation().
*
* However for functions that need to (also) propagate out of the context of a
* command execution, for example when serving a blocked client, you
* want to use propagate().
*/
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
int flags)
@@ -2414,10 +2444,13 @@ void preventCommandReplication(client *c) {
*
*/
void call(client *c, int flags) {
long long dirty, start, duration;
long long dirty;
ustime_t start, duration;
int client_old_flags = c->flags;
struct redisCommand *real_cmd = c->cmd;
server.fixed_time_expire++;
/* Sent the command to clients in MONITOR mode, only if the commands are
* not generated from reading an AOF. */
if (listLength(server.monitors) &&
@@ -2435,7 +2468,8 @@ void call(client *c, int flags) {
/* Call the command. */
dirty = server.dirty;
start = ustime();
updateCachedTime(0);
start = server.ustime;
c->cmd->proc(c);
duration = ustime()-start;
dirty = server.dirty-dirty;
@@ -2531,6 +2565,7 @@ void call(client *c, int flags) {
redisOpArrayFree(&server.also_propagate);
}
server.also_propagate = prev_also_propagate;
server.fixed_time_expire--;
server.stat_numcommands++;
}
@@ -2632,6 +2667,13 @@ int processCommand(client *c) {
addReply(c, shared.oomerr);
return C_OK;
}
/* Save out_of_memory result at script start, otherwise if we check OOM
* untill first write within script, memory used by lua stack and
* arguments might interfere. */
if (c->cmd->proc == evalCommand || c->cmd->proc == evalShaCommand) {
server.lua_oom = out_of_memory;
}
}
/* Don't accept write commands if there are problems persisting on disk
@@ -3864,12 +3906,14 @@ void loadDataFromDisk(void) {
(float)(ustime()-start)/1000000);
/* Restore the replication ID / offset from the RDB file. */
if ((server.masterhost || (server.cluster_enabled && nodeIsSlave(server.cluster->myself)))&&
if ((server.masterhost ||
(server.cluster_enabled &&
nodeIsSlave(server.cluster->myself))) &&
rsi.repl_id_is_set &&
rsi.repl_offset != -1 &&
/* Note that older implementations may save a repl_stream_db
* of -1 inside the RDB file in a wrong way, see more information
* in function rdbPopulateSaveInfo. */
* of -1 inside the RDB file in a wrong way, see more
* information in function rdbPopulateSaveInfo. */
rsi.repl_stream_db != -1)
{
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
@@ -4173,6 +4217,7 @@ int main(int argc, char **argv) {
linuxMemoryWarnings();
#endif
moduleLoadFromQueue();
InitServerLast();
loadDataFromDisk();
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
@@ -4187,6 +4232,7 @@ int main(int argc, char **argv) {
if (server.sofd > 0)
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
} else {
InitServerLast();
sentinelIsRunning();
}
+22 -4
View File
@@ -50,6 +50,7 @@
#include <signal.h>
typedef long long mstime_t; /* millisecond time type. */
typedef long long ustime_t; /* microsecond time type. */
#include "ae.h" /* Event driven programming library */
#include "sds.h" /* Dynamic safe strings */
@@ -489,6 +490,10 @@ typedef long long mstime_t; /* millisecond time type. */
#define REDISMODULE_TYPE_ENCVER(id) (id & REDISMODULE_TYPE_ENCVER_MASK)
#define REDISMODULE_TYPE_SIGN(id) ((id & ~((uint64_t)REDISMODULE_TYPE_ENCVER_MASK)) >>REDISMODULE_TYPE_ENCVER_BITS)
/* Bit flags for moduleTypeAuxSaveFunc */
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
struct RedisModule;
struct RedisModuleIO;
struct RedisModuleDigest;
@@ -501,6 +506,8 @@ struct redisObject;
* is deleted. */
typedef void *(*moduleTypeLoadFunc)(struct RedisModuleIO *io, int encver);
typedef void (*moduleTypeSaveFunc)(struct RedisModuleIO *io, void *value);
typedef int (*moduleTypeAuxLoadFunc)(struct RedisModuleIO *rdb, int encver, int when);
typedef void (*moduleTypeAuxSaveFunc)(struct RedisModuleIO *rdb, int when);
typedef void (*moduleTypeRewriteFunc)(struct RedisModuleIO *io, struct redisObject *key, void *value);
typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *value);
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
@@ -517,6 +524,9 @@ typedef struct RedisModuleType {
moduleTypeMemUsageFunc mem_usage;
moduleTypeDigestFunc digest;
moduleTypeFreeFunc free;
moduleTypeAuxLoadFunc aux_load;
moduleTypeAuxSaveFunc aux_save;
int aux_save_triggers;
char name[10]; /* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */
} moduleType;
@@ -739,7 +749,7 @@ typedef struct client {
int flags; /* Client flags: CLIENT_* macros. */
int authenticated; /* When requirepass is non-NULL. */
int replstate; /* Replication state if this is a slave. */
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
int repl_put_online_on_ack; /* Install slave write handler on first ACK. */
int repldbfd; /* Replication DB file descriptor. */
off_t repldboff; /* Replication DB file offset. */
off_t repldbsize; /* Replication DB file size. */
@@ -979,7 +989,8 @@ struct redisServer {
list *clients_to_close; /* Clients to close asynchronously */
list *clients_pending_write; /* There is to write or install handler. */
list *slaves, *monitors; /* List of slaves and MONITORs */
client *current_client; /* Current client, only used on crash report */
client *current_client; /* Current client executing the command. */
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
rax *clients_index; /* Active clients dictionary by client ID. */
int clients_paused; /* True if clients are currently paused */
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
@@ -1222,7 +1233,8 @@ struct redisServer {
time_t unixtime; /* Unix time sampled every cron cycle. */
time_t timezone; /* Cached timezone. As set by tzset(). */
int daylight_active; /* Currently in daylight saving time. */
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
mstime_t mstime; /* 'unixtime' in milliseconds. */
ustime_t ustime; /* 'unixtime' in microseconds. */
/* Pubsub */
dict *pubsub_channels; /* Map channels to list of subscribed clients */
list *pubsub_patterns; /* A list of pubsub_patterns */
@@ -1265,6 +1277,7 @@ struct redisServer {
execution. */
int lua_kill; /* Kill the script if true. */
int lua_always_replicate_commands; /* Default replication type. */
int lua_oom; /* OOM detected when script start? */
/* Lazy free */
int lazyfree_lazy_eviction;
int lazyfree_lazy_expire;
@@ -1411,6 +1424,7 @@ void moduleAcquireGIL(void);
void moduleReleaseGIL(void);
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
void moduleCallCommandFilters(client *c);
ssize_t rdbSaveModulesAux(rio *rdb, int when);
/* Utils */
long long ustime(void);
@@ -1640,6 +1654,7 @@ void openChildInfoPipe(void);
void closeChildInfoPipe(void);
void sendChildInfo(int process_type);
void receiveChildInfo(void);
int hasActiveChildProcess();
/* Sorted sets data type */
@@ -1718,6 +1733,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
void call(client *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 redisOpArrayInit(redisOpArray *oa);
void redisOpArrayFree(redisOpArray *oa);
void forceCommandPropagation(client *c, int flags);
void preventCommandPropagation(client *c);
void preventCommandAOF(client *c);
@@ -1738,7 +1755,7 @@ void populateCommandTable(void);
void resetCommandTableStats(void);
void adjustOpenFilesLimit(void);
void closeListeningSockets(int unlink_unix_socket);
void updateCachedTime(void);
void updateCachedTime(int update_daylight_info);
void resetServerStats(void);
void activeDefragCycle(void);
unsigned int getLRUClock(void);
@@ -1817,6 +1834,7 @@ void propagateExpire(redisDb *db, robj *key, int lazy);
int expireIfNeeded(redisDb *db, robj *key);
long long getExpire(redisDb *db, robj *key);
void setExpire(client *c, redisDb *db, robj *key, long long when);
int checkAlreadyExpired(long long when);
robj *lookupKey(redisDb *db, robj *key, int flags);
robj *lookupKeyRead(redisDb *db, robj *key);
robj *lookupKeyWrite(redisDb *db, robj *key);
+8 -1
View File
@@ -96,18 +96,25 @@ typedef struct sreamPropInfo {
/* Prototypes of exported APIs. */
struct client;
/* Flags for streamLookupConsumer */
#define SLC_NONE 0
#define SLC_NOCREAT (1<<0) /* Do not create the consumer if it doesn't exist */
#define SLC_NOREFRESH (1<<1) /* Do not update consumer's seen-time */
stream *streamNew(void);
void freeStream(stream *s);
unsigned long streamLength(const robj *subject);
size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi);
void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev);
int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields);
void streamIteratorGetField(streamIterator *si, unsigned char **fieldptr, unsigned char **valueptr, int64_t *fieldlen, int64_t *valuelen);
void streamIteratorStop(streamIterator *si);
streamCG *streamLookupCG(stream *s, sds groupname);
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create);
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int flags);
streamCG *streamCreateCG(stream *s, char *name, size_t namelen, streamID *id);
streamNACK *streamCreateNACK(streamConsumer *consumer);
void streamDecodeID(void *buf, streamID *id);
int streamCompareID(streamID *a, streamID *b);
void streamIncrID(streamID *id);
#endif
+107 -40
View File
@@ -67,6 +67,27 @@ void freeStream(stream *s) {
zfree(s);
}
/* Set 'id' to be its successor streamID */
void streamIncrID(streamID *id) {
if (id->seq == UINT64_MAX) {
if (id->ms == UINT64_MAX) {
/* Special case where 'id' is the last possible streamID... */
id->ms = id->seq = 0;
} else {
id->ms++;
id->seq = 0;
}
} else {
id->seq++;
}
}
/* Return the length of a stream. */
unsigned long streamLength(const robj *subject) {
stream *s = subject->ptr;
return s->length;
}
/* Generate the next stream item ID given the previous one. If the current
* milliseconds Unix time is greater than the previous one, just use this
* as time part and start with sequence part of zero. Otherwise we use the
@@ -77,8 +98,8 @@ void streamNextID(streamID *last_id, streamID *new_id) {
new_id->ms = ms;
new_id->seq = 0;
} else {
new_id->ms = last_id->ms;
new_id->seq = last_id->seq+1;
*new_id = *last_id;
streamIncrID(new_id);
}
}
@@ -173,9 +194,19 @@ int streamCompareID(streamID *a, streamID *b) {
* C_ERR if an ID was given via 'use_id', but adding it failed since the
* current top ID is greater or equal. */
int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
/* If an ID was given, check that it's greater than the last entry ID
* or return an error. */
if (use_id && streamCompareID(use_id,&s->last_id) <= 0) return C_ERR;
/* Generate the new entry ID. */
streamID id;
if (use_id)
id = *use_id;
else
streamNextID(&s->last_id,&id);
/* Check that the new ID is greater than the last entry ID
* or return an error. Automatically generated IDs might
* overflow (and wrap-around) when incrementing the sequence
part. */
if (streamCompareID(&id,&s->last_id) <= 0) return C_ERR;
/* Add the new entry. */
raxIterator ri;
@@ -192,13 +223,6 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
}
raxStop(&ri);
/* Generate the new entry ID. */
streamID id;
if (use_id)
id = *use_id;
else
streamNextID(&s->last_id,&id);
/* We have to add the key into the radix tree in lexicographic order,
* to do so we consider the ID as a single 128 bit number written in
* big endian, so that the most significant bytes are the first ones. */
@@ -242,17 +266,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
* the current node is full. */
if (lp != NULL) {
if (server.stream_node_max_bytes &&
lp_bytes > server.stream_node_max_bytes)
lp_bytes >= server.stream_node_max_bytes)
{
lp = NULL;
} else if (server.stream_node_max_entries) {
int64_t count = lpGetInteger(lpFirst(lp));
if (count > server.stream_node_max_entries) lp = NULL;
if (count >= server.stream_node_max_entries) lp = NULL;
}
}
int flags = STREAM_ITEM_FLAG_NONE;
if (lp == NULL || lp_bytes > server.stream_node_max_bytes) {
if (lp == NULL || lp_bytes >= server.stream_node_max_bytes) {
master_id = id;
streamEncodeID(rax_key,&id);
/* Create the listpack having the master entry ID and fields. */
@@ -773,6 +797,16 @@ int streamDeleteItem(stream *s, streamID *id) {
return deleted;
}
/* Get the last valid (non-tombstone) streamID of 's'. */
void streamLastValidID(stream *s, streamID *maxid)
{
streamIterator si;
streamIteratorStart(&si,s,NULL,NULL,1);
int64_t numfields;
streamIteratorGetID(&si,maxid,&numfields);
streamIteratorStop(&si);
}
/* Emit a reply in the client output buffer by formatting a Stream ID
* in the standard <ms>-<seq> format, using the simple string protocol
* of REPL. */
@@ -814,6 +848,11 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
argv[11] = createStringObject("JUSTID",6);
argv[12] = createStringObject("LASTID",6);
argv[13] = createObjectFromStreamID(&group->last_id);
/* We use progagate() because this code path is not always called from
* the command execution context. Moreover this will just alter the
* consumer group state, and we don't need MULTI/EXEC wrapping because
* there is no message state cross-message atomicity required. */
propagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(argv[0]);
decrRefCount(argv[3]);
@@ -841,6 +880,11 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
argv[2] = key;
argv[3] = groupname;
argv[4] = createObjectFromStreamID(&group->last_id);
/* We use progagate() because this code path is not always called from
* the command execution context. Moreover this will just alter the
* consumer group state, and we don't need MULTI/EXEC wrapping because
* there is no message state cross-message atomicity required. */
propagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
@@ -1216,12 +1260,27 @@ void xaddCommand(client *c) {
return;
}
/* Return ASAP if minimal ID (0-0) was given so we avoid possibly creating
* a new stream and have streamAppendItem fail, leaving an empty key in the
* database. */
if (id_given && id.ms == 0 && id.seq == 0) {
addReplyError(c,"The ID specified in XADD must be greater than 0-0");
return;
}
/* Lookup the stream at key. */
robj *o;
stream *s;
if ((o = streamTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
s = o->ptr;
/* Return ASAP if the stream has reached the last possible ID */
if (s->last_id.ms == UINT64_MAX && s->last_id.seq == UINT64_MAX) {
addReplyError(c,"The stream has exhausted the last possible ID, "
"unable to add more items");
return;
}
/* Append using the low level function and return the ID. */
if (streamAppendItem(s,c->argv+field_pos,(c->argc-field_pos)/2,
&id, id_given ? &id : NULL)
@@ -1485,20 +1544,23 @@ void xreadCommand(client *c) {
{
serve_synchronously = 1;
serve_history = 1;
} else {
} else if (s->length) {
/* We also want to serve a consumer in a consumer group
* synchronously in case the group top item delivered is smaller
* than what the stream has inside. */
streamID *last = &groups[i]->last_id;
if (s->length && (streamCompareID(&s->last_id, last) > 0)) {
streamID maxid, *last = &groups[i]->last_id;
streamLastValidID(s, &maxid);
if (streamCompareID(&maxid, last) > 0) {
serve_synchronously = 1;
*gt = *last;
}
}
} else {
} else if (s->length) {
/* For consumers without a group, we serve synchronously if we can
* actually provide at least one item from the stream. */
if (s->length && (streamCompareID(&s->last_id, gt) > 0)) {
streamID maxid;
streamLastValidID(s, &maxid);
if (streamCompareID(&maxid, gt) > 0) {
serve_synchronously = 1;
}
}
@@ -1510,7 +1572,7 @@ void xreadCommand(client *c) {
* so start from the next ID, since we want only messages with
* IDs greater than start. */
streamID start = *gt;
start.seq++; /* uint64_t can't overflow in this context. */
streamIncrID(&start);
/* Emit the two elements sub-array consisting of the name
* of the stream and the data we extracted from it. */
@@ -1518,7 +1580,8 @@ void xreadCommand(client *c) {
addReplyBulk(c,c->argv[streams_arg+i]);
streamConsumer *consumer = NULL;
if (groups) consumer = streamLookupConsumer(groups[i],
consumername->ptr,1);
consumername->ptr,
SLC_NONE);
streamPropInfo spi = {c->argv[i+streams_arg],groupname};
int flags = 0;
if (noack) flags |= STREAM_RWR_NOACK;
@@ -1651,7 +1714,9 @@ streamCG *streamLookupCG(stream *s, sds groupname) {
* consumer does not exist it is automatically created as a side effect
* of calling this function, otherwise its last seen time is updated and
* the existing consumer reference returned. */
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int flags) {
int create = !(flags & SLC_NOCREAT);
int refresh = !(flags & SLC_NOREFRESH);
streamConsumer *consumer = raxFind(cg->consumers,(unsigned char*)name,
sdslen(name));
if (consumer == raxNotFound) {
@@ -1662,7 +1727,7 @@ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
raxInsert(cg->consumers,(unsigned char*)name,sdslen(name),
consumer,NULL);
}
consumer->seen_time = mstime();
if (refresh) consumer->seen_time = mstime();
return consumer;
}
@@ -1670,7 +1735,8 @@ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
* may have pending messages: they are removed from the PEL, and the number
* of pending messages "lost" is returned. */
uint64_t streamDelConsumer(streamCG *cg, sds name) {
streamConsumer *consumer = streamLookupConsumer(cg,name,0);
streamConsumer *consumer =
streamLookupConsumer(cg,name,SLC_NOCREAT|SLC_NOREFRESH);
if (consumer == NULL) return 0;
uint64_t retval = raxSize(consumer->pel);
@@ -1847,11 +1913,7 @@ void xsetidCommand(client *c) {
* item, otherwise the fundamental ID monotonicity assumption is violated. */
if (s->length > 0) {
streamID maxid;
streamIterator si;
streamIteratorStart(&si,s,NULL,NULL,1);
int64_t numfields;
streamIteratorGetID(&si,&maxid,&numfields);
streamIteratorStop(&si);
streamLastValidID(s,&maxid);
if (streamCompareID(&id,&maxid) < 0) {
addReplyError(c,"The ID specified in XSETID is smaller than the "
@@ -2005,15 +2067,18 @@ void xpendingCommand(client *c) {
}
/* XPENDING <key> <group> <start> <stop> <count> [<consumer>] variant. */
else {
streamConsumer *consumer = consumername ?
streamLookupConsumer(group,consumername->ptr,0):
NULL;
streamConsumer *consumer = NULL;
if (consumername) {
consumer = streamLookupConsumer(group,
consumername->ptr,
SLC_NOCREAT|SLC_NOREFRESH);
/* If a consumer name was mentioned but it does not exist, we can
* just return an empty array. */
if (consumername && consumer == NULL) {
addReplyMultiBulkLen(c,0);
return;
/* If a consumer name was mentioned but it does not exist, we can
* just return an empty array. */
if (consumer == NULL) {
addReplyMultiBulkLen(c,0);
return;
}
}
rax *pel = consumer ? consumer->pel : group->pel;
@@ -2222,7 +2287,7 @@ void xclaimCommand(client *c) {
}
/* Do the actual claiming. */
streamConsumer *consumer = streamLookupConsumer(group,c->argv[3]->ptr,1);
streamConsumer *consumer = NULL;
void *arraylenptr = addDeferredMultiBulkLength(c);
size_t arraylen = 0;
for (int j = 5; j <= last_id_arg; j++) {
@@ -2274,9 +2339,11 @@ void xclaimCommand(client *c) {
if (nack->consumer)
raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
/* Update the consumer and idle time. */
if (consumer == NULL)
consumer = streamLookupConsumer(group,c->argv[3]->ptr,SLC_NONE);
nack->consumer = consumer;
nack->delivery_time = deliverytime;
/* Set the delivery attempts counter if given, otherwise
/* Set the delivery attempts counter if given, otherwise
* autoincrement unless JUSTID option provided */
if (retrycount >= 0) {
nack->delivery_count = retrycount;
+2 -3
View File
@@ -1357,9 +1357,8 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
/* Optimize: check if the element is too large or the list
* becomes too long *before* executing zzlInsert. */
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
if (sdslen(ele) > server.zset_max_ziplist_value)
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
sdslen(ele) > server.zset_max_ziplist_value)
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
if (newscore) *newscore = score;
*flags |= ZADD_ADDED;
+1 -1
View File
@@ -1 +1 @@
#define REDIS_VERSION "5.0.5"
#define REDIS_VERSION "5.0.10"
+7 -3
View File
@@ -177,9 +177,6 @@ void *zrealloc(void *ptr, size_t size) {
size_t zmalloc_size(void *ptr) {
void *realptr = (char*)ptr-PREFIX_SIZE;
size_t size = *((size_t*)realptr);
/* Assume at least that all the allocations are padded at sizeof(long) by
* the underlying allocator. */
if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
return size+PREFIX_SIZE;
}
size_t zmalloc_usable(void *ptr) {
@@ -323,6 +320,13 @@ int zmalloc_get_allocator_info(size_t *allocated,
je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
return 1;
}
void set_jemalloc_bg_thread(int enable) {
/* let jemalloc do purging asynchronously, required when there's no traffic
* after flushdb */
char val = !!enable;
je_mallctl("background_thread", NULL, 0, &val, 1);
}
#else
int zmalloc_get_allocator_info(size_t *allocated,
size_t *active,
@@ -0,0 +1,70 @@
# Check basic transactions on a replica.
source "../tests/includes/init-tests.tcl"
test "Create a primary with a replica" {
create_cluster 1 1
}
test "Cluster should start ok" {
assert_cluster_state ok
}
set primary [Rn 0]
set replica [Rn 1]
test "Cant read from replica without READONLY" {
$primary SET a 1
catch {$replica GET a} err
assert {[string range $err 0 4] eq {MOVED}}
}
test "Can read from replica after READONLY" {
$replica READONLY
assert {[$replica GET a] eq {1}}
}
test "Can preform HSET primary and HGET from replica" {
$primary HSET h a 1
$primary HSET h b 2
$primary HSET h c 3
assert {[$replica HGET h a] eq {1}}
assert {[$replica HGET h b] eq {2}}
assert {[$replica HGET h c] eq {3}}
}
# didn't cherry pick b120366d4 to 5.0 yet
#test "Can MULTI-EXEC transaction of HGET operations from replica" {
# $replica MULTI
# assert {[$replica HGET h a] eq {QUEUED}}
# assert {[$replica HGET h b] eq {QUEUED}}
# assert {[$replica HGET h c] eq {QUEUED}}
# assert {[$replica EXEC] eq {1 2 3}}
#}
test "MULTI-EXEC with write operations is MOVED" {
$replica MULTI
catch {$replica HSET h b 4} err
assert {[string range $err 0 4] eq {MOVED}}
catch {$replica exec} err
assert {[string range $err 0 8] eq {EXECABORT}}
}
test "read-only blocking operations from replica" {
set rd [redis_deferring_client redis 1]
$rd readonly
$rd read
$rd XREAD BLOCK 0 STREAMS k 0
wait_for_condition 1000 50 {
[RI 1 blocked_clients] eq {1}
} else {
fail "client wasn't blocked"
}
$primary XADD k * foo bar
set res [$rd read]
set res [lindex [lindex [lindex [lindex $res 0] 1] 0] 1]
assert {$res eq {foo bar}}
$rd close
}
+21 -2
View File
@@ -334,10 +334,16 @@ proc S {n args} {
[dict get $s link] {*}$args
}
# Returns a Redis instance by index.
# Example:
# [Rn 0] info
proc Rn {n} {
return [dict get [lindex $::redis_instances $n] link]
}
# Like R but to chat with Redis instances.
proc R {n args} {
set r [lindex $::redis_instances $n]
[dict get $r link] {*}$args
[Rn $n] {*}$args
}
proc get_info_field {info field} {
@@ -509,3 +515,16 @@ proc restart_instance {type id} {
}
}
proc redis_deferring_client {type id} {
set port [get_instance_attrib $type $id port]
set host [get_instance_attrib $type $id host]
set client [redis $host $port 1]
return $client
}
proc redis_client {type id} {
set port [get_instance_attrib $type $id port]
set host [get_instance_attrib $type $id host]
set client [redis $host $port 0]
return $client
}
+5 -1
View File
@@ -13,12 +13,16 @@ endif
.SUFFIXES: .c .so .xo .o
all: commandfilter.so
all: commandfilter.so testrdb.so
.c.xo:
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
commandfilter.xo: ../../src/redismodule.h
testrdb.xo: ../../src/redismodule.h
commandfilter.so: commandfilter.xo
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
testrdb.so: testrdb.xo
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
+229
View File
@@ -0,0 +1,229 @@
#include "redismodule.h"
#include <string.h>
#include <assert.h>
/* Module configuration, save aux or not? */
long long conf_aux_count = 0;
/* Registered type */
RedisModuleType *testrdb_type = NULL;
/* Global values to store and persist to aux */
RedisModuleString *before_str = NULL;
RedisModuleString *after_str = NULL;
void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
int count = RedisModule_LoadSigned(rdb);
assert(count==1);
assert(encver==1);
RedisModuleString *str = RedisModule_LoadString(rdb);
return str;
}
void testrdb_type_save(RedisModuleIO *rdb, void *value) {
RedisModuleString *str = (RedisModuleString*)value;
RedisModule_SaveSigned(rdb, 1);
RedisModule_SaveString(rdb, str);
}
void testrdb_aux_save(RedisModuleIO *rdb, int when) {
if (conf_aux_count==1) assert(when == REDISMODULE_AUX_AFTER_RDB);
if (conf_aux_count==0) assert(0);
if (when == REDISMODULE_AUX_BEFORE_RDB) {
if (before_str) {
RedisModule_SaveSigned(rdb, 1);
RedisModule_SaveString(rdb, before_str);
} else {
RedisModule_SaveSigned(rdb, 0);
}
} else {
if (after_str) {
RedisModule_SaveSigned(rdb, 1);
RedisModule_SaveString(rdb, after_str);
} else {
RedisModule_SaveSigned(rdb, 0);
}
}
}
int testrdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
assert(encver == 1);
if (conf_aux_count==1) assert(when == REDISMODULE_AUX_AFTER_RDB);
if (conf_aux_count==0) assert(0);
RedisModuleCtx *ctx = RedisModule_GetContextFromIO(rdb);
if (when == REDISMODULE_AUX_BEFORE_RDB) {
if (before_str)
RedisModule_FreeString(ctx, before_str);
before_str = NULL;
int count = RedisModule_LoadSigned(rdb);
if (count)
before_str = RedisModule_LoadString(rdb);
} else {
if (after_str)
RedisModule_FreeString(ctx, after_str);
after_str = NULL;
int count = RedisModule_LoadSigned(rdb);
if (count)
after_str = RedisModule_LoadString(rdb);
}
return REDISMODULE_OK;
}
void testrdb_type_free(void *value) {
RedisModule_FreeString(NULL, (RedisModuleString*)value);
}
int testrdb_set_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
if (argc != 2) {
RedisModule_WrongArity(ctx);
return REDISMODULE_OK;
}
if (before_str)
RedisModule_FreeString(ctx, before_str);
before_str = argv[1];
RedisModule_RetainString(ctx, argv[1]);
RedisModule_ReplyWithLongLong(ctx, 1);
return REDISMODULE_OK;
}
int testrdb_get_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
if (argc != 1){
RedisModule_WrongArity(ctx);
return REDISMODULE_OK;
}
if (before_str)
RedisModule_ReplyWithString(ctx, before_str);
else
RedisModule_ReplyWithStringBuffer(ctx, "", 0);
return REDISMODULE_OK;
}
int testrdb_set_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
if (argc != 2){
RedisModule_WrongArity(ctx);
return REDISMODULE_OK;
}
if (after_str)
RedisModule_FreeString(ctx, after_str);
after_str = argv[1];
RedisModule_RetainString(ctx, argv[1]);
RedisModule_ReplyWithLongLong(ctx, 1);
return REDISMODULE_OK;
}
int testrdb_get_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
if (argc != 1){
RedisModule_WrongArity(ctx);
return REDISMODULE_OK;
}
if (after_str)
RedisModule_ReplyWithString(ctx, after_str);
else
RedisModule_ReplyWithStringBuffer(ctx, "", 0);
return REDISMODULE_OK;
}
int testrdb_set_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
if (argc != 3){
RedisModule_WrongArity(ctx);
return REDISMODULE_OK;
}
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
if (str)
RedisModule_FreeString(ctx, str);
RedisModule_ModuleTypeSetValue(key, testrdb_type, argv[2]);
RedisModule_RetainString(ctx, argv[2]);
RedisModule_CloseKey(key);
RedisModule_ReplyWithLongLong(ctx, 1);
return REDISMODULE_OK;
}
int testrdb_get_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
if (argc != 2){
RedisModule_WrongArity(ctx);
return REDISMODULE_OK;
}
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
RedisModule_CloseKey(key);
RedisModule_ReplyWithString(ctx, str);
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx,"testrdb",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (argc > 0)
RedisModule_StringToLongLong(argv[0], &conf_aux_count);
if (conf_aux_count==0) {
RedisModuleTypeMethods datatype_methods = {
.version = 1,
.rdb_load = testrdb_type_load,
.rdb_save = testrdb_type_save,
.aof_rewrite = NULL,
.digest = NULL,
.free = testrdb_type_free,
};
testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
if (testrdb_type == NULL)
return REDISMODULE_ERR;
} else {
RedisModuleTypeMethods datatype_methods = {
.version = REDISMODULE_TYPE_METHOD_VERSION,
.rdb_load = testrdb_type_load,
.rdb_save = testrdb_type_save,
.aof_rewrite = NULL,
.digest = NULL,
.free = testrdb_type_free,
.aux_load = testrdb_aux_load,
.aux_save = testrdb_aux_save,
.aux_save_triggers = (conf_aux_count == 1 ?
REDISMODULE_AUX_AFTER_RDB :
REDISMODULE_AUX_BEFORE_RDB | REDISMODULE_AUX_AFTER_RDB)
};
testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
if (testrdb_type == NULL)
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx,"testrdb.set.before", testrdb_set_before,"deny-oom",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"testrdb.get.before", testrdb_get_before,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"testrdb.set.after", testrdb_set_after,"deny-oom",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"testrdb.get.after", testrdb_get_after,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"testrdb.set.key", testrdb_set_key,"deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"testrdb.get.key", testrdb_get_key,"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
+12 -1
View File
@@ -36,7 +36,18 @@ start_server {tags {"dump"}} {
assert {$ttl >= 2900 && $ttl <= 3100}
r get foo
} {bar}
test {RESTORE with ABSTTL in the past} {
r set foo bar
set encoded [r dump foo]
set now [clock milliseconds]
r debug set-active-expire 0
r restore foo [expr $now-3000] $encoded absttl REPLACE
catch {r debug object foo} e
r debug set-active-expire 1
set e
} {ERR no such key}
test {RESTORE can set LRU} {
r set foo bar
set encoded [r dump foo]
+62
View File
@@ -0,0 +1,62 @@
set testmodule [file normalize tests/modules/testrdb.so]
proc restart_and_wait {} {
catch {
r debug restart
}
# wait for the server to come back up
set retry 50
while {$retry} {
if {[catch { r ping }]} {
after 100
} else {
break
}
incr retry -1
}
}
tags "modules" {
start_server [list overrides [list loadmodule "$testmodule"]] {
test {modules are able to persist types} {
r testrdb.set.key key1 value1
assert_equal "value1" [r testrdb.get.key key1]
r debug reload
assert_equal "value1" [r testrdb.get.key key1]
}
test {modules global are lost without aux} {
r testrdb.set.before global1
assert_equal "global1" [r testrdb.get.before]
restart_and_wait
assert_equal "" [r testrdb.get.before]
}
}
start_server [list overrides [list loadmodule "$testmodule 2"]] {
test {modules are able to persist globals before and after} {
r testrdb.set.before global1
r testrdb.set.after global2
assert_equal "global1" [r testrdb.get.before]
assert_equal "global2" [r testrdb.get.after]
restart_and_wait
assert_equal "global1" [r testrdb.get.before]
assert_equal "global2" [r testrdb.get.after]
}
}
start_server [list overrides [list loadmodule "$testmodule 1"]] {
test {modules are able to persist globals just after} {
r testrdb.set.after global2
assert_equal "global2" [r testrdb.get.after]
restart_and_wait
assert_equal "global2" [r testrdb.get.after]
}
}
# TODO: test short read handling
}
+14
View File
@@ -147,6 +147,20 @@ start_server {
assert {[lindex $res 0 1 1] == {2-0 {field1 B}}}
}
test {Blocking XREADGROUP will not reply with an empty array} {
r del mystream
r XGROUP CREATE mystream mygroup $ MKSTREAM
r XADD mystream 666 f v
set res [r XREADGROUP GROUP mygroup Alice BLOCK 10 STREAMS mystream ">"]
assert {[lindex $res 0 1 0] == {666-0 {f v}}}
r XADD mystream 667 f2 v2
r XDEL mystream 667
set rd [redis_deferring_client]
$rd XREADGROUP GROUP mygroup Alice BLOCK 10 STREAMS mystream ">"
after 20
assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {mystream {}}
}
test {XCLAIM can claim PEL items from another consumer} {
# Add 3 items into the stream, and create a consumer group
r del mystream
+52 -2
View File
@@ -79,6 +79,12 @@ start_server {
assert {[streamCompareID $id2 $id3] == -1}
}
test {XADD IDs correctly report an error when overflowing} {
r DEL mystream
r xadd mystream 18446744073709551615-18446744073709551615 a b
assert_error ERR* {r xadd mystream * c d}
}
test {XADD with MAXLEN option} {
r DEL mystream
for {set j 0} {$j < 1000} {incr j} {
@@ -117,6 +123,12 @@ start_server {
assert {[r xlen mystream] == $j}
}
test {XADD with ID 0-0} {
r DEL otherstream
catch {r XADD otherstream 0-0 k v} err
assert {[r EXISTS otherstream] == 0}
}
test {XRANGE COUNT works as expected} {
assert {[llength [r xrange mystream - + COUNT 10]] == 10}
}
@@ -179,6 +191,17 @@ start_server {
assert {[lindex $res 0 1 0 1] eq {old abcd1234}}
}
test {Blocking XREAD will not reply with an empty array} {
r del s1
r XADD s1 666 f v
r XADD s1 667 f2 v2
r XDEL s1 667
set rd [redis_deferring_client]
$rd XREAD BLOCK 10 STREAMS s1 666
after 20
assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {s1 {}}
}
test "XREAD: XADD + DEL should not awake client" {
set rd [redis_deferring_client]
r del s1
@@ -316,6 +339,33 @@ start_server {
assert_equal [r xrevrange teststream2 1234567891245 -] {{1234567891240-0 {key1 value2}} {1234567891230-0 {key1 value1}}}
}
test {XREAD streamID edge (no-blocking)} {
r del x
r XADD x 1-1 f v
r XADD x 1-18446744073709551615 f v
r XADD x 2-1 f v
set res [r XREAD BLOCK 0 STREAMS x 1-18446744073709551615]
assert {[lindex $res 0 1 0] == {2-1 {f v}}}
}
test {XREAD streamID edge (blocking)} {
r del x
set rd [redis_deferring_client]
$rd XREAD BLOCK 0 STREAMS x 1-18446744073709551615
r XADD x 1-1 f v
r XADD x 1-18446744073709551615 f v
r XADD x 2-1 f v
set res [$rd read]
assert {[lindex $res 0 1 0] == {2-1 {f v}}}
}
test {XADD streamID edge} {
r del x
r XADD x 2577343934890-18446744073709551615 f v ;# we need the timestamp to be in the future
r XADD x * f2 v2
assert_equal [r XRANGE x - +] {{2577343934890-18446744073709551615 {f v}} {2577343934891-0 {f2 v2}}}
}
}
start_server {tags {"stream"} overrides {appendonly yes}} {
@@ -355,12 +405,12 @@ start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries
r XADD mystream * xitem v
}
r XTRIM mystream MAXLEN ~ 85
assert {[r xlen mystream] == 89}
assert {[r xlen mystream] == 90}
r config set stream-node-max-entries 1
r debug loadaof
r XADD mystream * xitem v
incr j
assert {[r xlen mystream] == 90}
assert {[r xlen mystream] == 91}
}
}