Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
89a9e5a9
Commit
89a9e5a9
authored
May 09, 2017
by
Guy Benoish
Browse files
Merge branch 'unstable' of
https://github.com/antirez/redis
into unstable
parents
71a8df6a
a4c7f34d
Changes
178
Hide whitespace changes
Inline
Side-by-side
src/module.c
View file @
89a9e5a9
...
@@ -3108,6 +3108,17 @@ void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ...
...
@@ -3108,6 +3108,17 @@ void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ...
* Blocking clients from modules
* Blocking clients from modules
* -------------------------------------------------------------------------- */
* -------------------------------------------------------------------------- */
/* Readable handler for the awake pipe. We do nothing here, the awake bytes
* will be actually read in a more appropriate place in the
* moduleHandleBlockedClients() function that is where clients are actually
* served. */
void
moduleBlockedClientPipeReadable
(
aeEventLoop
*
el
,
int
fd
,
void
*
privdata
,
int
mask
)
{
UNUSED
(
el
);
UNUSED
(
fd
);
UNUSED
(
mask
);
UNUSED
(
privdata
);
}
/* This is called from blocked.c in order to unblock a client: may be called
/* This is called from blocked.c in order to unblock a client: may be called
* for multiple reasons while the client is in the middle of being blocked
* for multiple reasons while the client is in the middle of being blocked
* because the client is terminated, but is also called for cleanup when a
* because the client is terminated, but is also called for cleanup when a
...
@@ -3171,6 +3182,9 @@ int RM_UnblockClient(RedisModuleBlockedClient *bc, void *privdata) {
...
@@ -3171,6 +3182,9 @@ int RM_UnblockClient(RedisModuleBlockedClient *bc, void *privdata) {
pthread_mutex_lock
(
&
moduleUnblockedClientsMutex
);
pthread_mutex_lock
(
&
moduleUnblockedClientsMutex
);
bc
->
privdata
=
privdata
;
bc
->
privdata
=
privdata
;
listAddNodeTail
(
moduleUnblockedClients
,
bc
);
listAddNodeTail
(
moduleUnblockedClients
,
bc
);
if
(
write
(
server
.
module_blocked_pipe
[
1
],
"A"
,
1
)
!=
1
)
{
/* Ignore the error, this is best-effort. */
}
pthread_mutex_unlock
(
&
moduleUnblockedClientsMutex
);
pthread_mutex_unlock
(
&
moduleUnblockedClientsMutex
);
return
REDISMODULE_OK
;
return
REDISMODULE_OK
;
}
}
...
@@ -3195,6 +3209,10 @@ void moduleHandleBlockedClients(void) {
...
@@ -3195,6 +3209,10 @@ void moduleHandleBlockedClients(void) {
RedisModuleBlockedClient
*
bc
;
RedisModuleBlockedClient
*
bc
;
pthread_mutex_lock
(
&
moduleUnblockedClientsMutex
);
pthread_mutex_lock
(
&
moduleUnblockedClientsMutex
);
/* Here we unblock all the pending clients blocked in modules operations
* so we can read every pending "awake byte" in the pipe. */
char
buf
[
1
];
while
(
read
(
server
.
module_blocked_pipe
[
0
],
buf
,
1
)
==
1
);
while
(
listLength
(
moduleUnblockedClients
))
{
while
(
listLength
(
moduleUnblockedClients
))
{
ln
=
listFirst
(
moduleUnblockedClients
);
ln
=
listFirst
(
moduleUnblockedClients
);
bc
=
ln
->
value
;
bc
=
ln
->
value
;
...
@@ -3217,8 +3235,11 @@ void moduleHandleBlockedClients(void) {
...
@@ -3217,8 +3235,11 @@ void moduleHandleBlockedClients(void) {
}
}
if
(
bc
->
privdata
&&
bc
->
free_privdata
)
if
(
bc
->
privdata
&&
bc
->
free_privdata
)
bc
->
free_privdata
(
bc
->
privdata
);
bc
->
free_privdata
(
bc
->
privdata
);
zfree
(
bc
);
if
(
c
!=
NULL
)
unblockClient
(
c
);
if
(
c
!=
NULL
)
unblockClient
(
c
);
/* Free 'bc' only after unblocking the client, since it is
* referenced in the client blocking context, and must be valid
* when calling unblockClient(). */
zfree
(
bc
);
/* Lock again before to iterate the loop. */
/* Lock again before to iterate the loop. */
pthread_mutex_lock
(
&
moduleUnblockedClientsMutex
);
pthread_mutex_lock
(
&
moduleUnblockedClientsMutex
);
...
@@ -3298,6 +3319,16 @@ void moduleInitModulesSystem(void) {
...
@@ -3298,6 +3319,16 @@ void moduleInitModulesSystem(void) {
server
.
loadmodule_queue
=
listCreate
();
server
.
loadmodule_queue
=
listCreate
();
modules
=
dictCreate
(
&
modulesDictType
,
NULL
);
modules
=
dictCreate
(
&
modulesDictType
,
NULL
);
moduleRegisterCoreAPI
();
moduleRegisterCoreAPI
();
if
(
pipe
(
server
.
module_blocked_pipe
)
==
-
1
)
{
serverLog
(
LL_WARNING
,
"Can't create the pipe for module blocking commands: %s"
,
strerror
(
errno
));
exit
(
1
);
}
/* Make the pipe non blocking. This is just a best effort aware mechanism
* and we do not want to block not in the read nor in the write half. */
anetNonBlock
(
NULL
,
server
.
module_blocked_pipe
[
0
]);
anetNonBlock
(
NULL
,
server
.
module_blocked_pipe
[
1
]);
}
}
/* Load all the modules in the server.loadmodule_queue list, which is
/* Load all the modules in the server.loadmodule_queue list, which is
...
...
src/networking.c
View file @
89a9e5a9
...
@@ -93,6 +93,7 @@ client *createClient(int fd) {
...
@@ -93,6 +93,7 @@ client *createClient(int fd) {
c
->
name
=
NULL
;
c
->
name
=
NULL
;
c
->
bufpos
=
0
;
c
->
bufpos
=
0
;
c
->
querybuf
=
sdsempty
();
c
->
querybuf
=
sdsempty
();
c
->
pending_querybuf
=
sdsempty
();
c
->
querybuf_peak
=
0
;
c
->
querybuf_peak
=
0
;
c
->
reqtype
=
0
;
c
->
reqtype
=
0
;
c
->
argc
=
0
;
c
->
argc
=
0
;
...
@@ -107,6 +108,7 @@ client *createClient(int fd) {
...
@@ -107,6 +108,7 @@ client *createClient(int fd) {
c
->
replstate
=
REPL_STATE_NONE
;
c
->
replstate
=
REPL_STATE_NONE
;
c
->
repl_put_online_on_ack
=
0
;
c
->
repl_put_online_on_ack
=
0
;
c
->
reploff
=
0
;
c
->
reploff
=
0
;
c
->
read_reploff
=
0
;
c
->
repl_ack_off
=
0
;
c
->
repl_ack_off
=
0
;
c
->
repl_ack_time
=
0
;
c
->
repl_ack_time
=
0
;
c
->
slave_listening_port
=
0
;
c
->
slave_listening_port
=
0
;
...
@@ -796,6 +798,7 @@ void freeClient(client *c) {
...
@@ -796,6 +798,7 @@ void freeClient(client *c) {
/* Free the query buffer */
/* Free the query buffer */
sdsfree
(
c
->
querybuf
);
sdsfree
(
c
->
querybuf
);
sdsfree
(
c
->
pending_querybuf
);
c
->
querybuf
=
NULL
;
c
->
querybuf
=
NULL
;
/* Deallocate structures used to block on blocking ops. */
/* Deallocate structures used to block on blocking ops. */
...
@@ -1027,6 +1030,13 @@ void resetClient(client *c) {
...
@@ -1027,6 +1030,13 @@ void resetClient(client *c) {
}
}
}
}
/* Like processMultibulkBuffer(), but for the inline protocol instead of RESP,
* this function consumes the client query buffer and creates a command ready
* to be executed inside the client structure. Returns C_OK if the command
* is ready to be executed, or C_ERR if there is still protocol to read to
* have a well formed command. The function also returns C_ERR when there is
* a protocol error: in such a case the client structure is setup to reply
* with the error and close the connection. */
int
processInlineBuffer
(
client
*
c
)
{
int
processInlineBuffer
(
client
*
c
)
{
char
*
newline
;
char
*
newline
;
int
argc
,
j
;
int
argc
,
j
;
...
@@ -1119,6 +1129,17 @@ static void setProtocolError(const char *errstr, client *c, int pos) {
...
@@ -1119,6 +1129,17 @@ static void setProtocolError(const char *errstr, client *c, int pos) {
sdsrange
(
c
->
querybuf
,
pos
,
-
1
);
sdsrange
(
c
->
querybuf
,
pos
,
-
1
);
}
}
/* Process the query buffer for client 'c', setting up the client argument
* vector for command execution. Returns C_OK if after running the function
* the client has a well-formed ready to be processed command, otherwise
* C_ERR if there is still to read more buffer to get the full command.
* The function also returns C_ERR when there is a protocol error: in such a
* case the client structure is setup to reply with the error and close
* the connection.
*
* This function is called if processInputBuffer() detects that the next
* command is in RESP format, so the first byte in the command is found
* to be '*'. Otherwise for inline commands processInlineBuffer() is called. */
int
processMultibulkBuffer
(
client
*
c
)
{
int
processMultibulkBuffer
(
client
*
c
)
{
char
*
newline
=
NULL
;
char
*
newline
=
NULL
;
int
pos
=
0
,
ok
;
int
pos
=
0
,
ok
;
...
@@ -1253,10 +1274,14 @@ int processMultibulkBuffer(client *c) {
...
@@ -1253,10 +1274,14 @@ int processMultibulkBuffer(client *c) {
/* We're done when c->multibulk == 0 */
/* We're done when c->multibulk == 0 */
if
(
c
->
multibulklen
==
0
)
return
C_OK
;
if
(
c
->
multibulklen
==
0
)
return
C_OK
;
/* Still not read to process the command */
/* Still not read
y
to process the command */
return
C_ERR
;
return
C_ERR
;
}
}
/* This function is called every time, in the client structure 'c', there is
* more query buffer to process, because we read more data from the socket
* or because a client was blocked and later reactivated, so there could be
* pending query buffer, already representing a full command, to process. */
void
processInputBuffer
(
client
*
c
)
{
void
processInputBuffer
(
client
*
c
)
{
server
.
current_client
=
c
;
server
.
current_client
=
c
;
/* Keep processing while there is something in the input buffer */
/* Keep processing while there is something in the input buffer */
...
@@ -1296,8 +1321,13 @@ void processInputBuffer(client *c) {
...
@@ -1296,8 +1321,13 @@ void processInputBuffer(client *c) {
resetClient
(
c
);
resetClient
(
c
);
}
else
{
}
else
{
/* Only reset the client when the command was executed. */
/* Only reset the client when the command was executed. */
if
(
processCommand
(
c
)
==
C_OK
)
if
(
processCommand
(
c
)
==
C_OK
)
{
if
(
c
->
flags
&
CLIENT_MASTER
&&
!
(
c
->
flags
&
CLIENT_MULTI
))
{
/* Update the applied replication offset of our master. */
c
->
reploff
=
c
->
read_reploff
-
sdslen
(
c
->
querybuf
);
}
resetClient
(
c
);
resetClient
(
c
);
}
/* freeMemoryIfNeeded may flush slave output buffers. This may result
/* freeMemoryIfNeeded may flush slave output buffers. This may result
* into a slave, that may be the active client, to be freed. */
* into a slave, that may be the active client, to be freed. */
if
(
server
.
current_client
==
NULL
)
break
;
if
(
server
.
current_client
==
NULL
)
break
;
...
@@ -1344,15 +1374,17 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
...
@@ -1344,15 +1374,17 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
serverLog
(
LL_VERBOSE
,
"Client closed connection"
);
serverLog
(
LL_VERBOSE
,
"Client closed connection"
);
freeClient
(
c
);
freeClient
(
c
);
return
;
return
;
}
else
if
(
c
->
flags
&
CLIENT_MASTER
)
{
/* Append the query buffer to the pending (not applied) buffer
* of the master. We'll use this buffer later in order to have a
* copy of the string applied by the last command executed. */
c
->
pending_querybuf
=
sdscatlen
(
c
->
pending_querybuf
,
c
->
querybuf
+
qblen
,
nread
);
}
}
sdsIncrLen
(
c
->
querybuf
,
nread
);
sdsIncrLen
(
c
->
querybuf
,
nread
);
c
->
lastinteraction
=
server
.
unixtime
;
c
->
lastinteraction
=
server
.
unixtime
;
if
(
c
->
flags
&
CLIENT_MASTER
)
{
if
(
c
->
flags
&
CLIENT_MASTER
)
c
->
read_reploff
+=
nread
;
c
->
reploff
+=
nread
;
replicationFeedSlavesFromMasterStream
(
server
.
slaves
,
c
->
querybuf
+
qblen
,
nread
);
}
server
.
stat_net_input_bytes
+=
nread
;
server
.
stat_net_input_bytes
+=
nread
;
if
(
sdslen
(
c
->
querybuf
)
>
server
.
client_max_querybuf_len
)
{
if
(
sdslen
(
c
->
querybuf
)
>
server
.
client_max_querybuf_len
)
{
sds
ci
=
catClientInfoString
(
sdsempty
(),
c
),
bytes
=
sdsempty
();
sds
ci
=
catClientInfoString
(
sdsempty
(),
c
),
bytes
=
sdsempty
();
...
@@ -1364,7 +1396,25 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
...
@@ -1364,7 +1396,25 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
freeClient
(
c
);
freeClient
(
c
);
return
;
return
;
}
}
processInputBuffer
(
c
);
/* Time to process the buffer. If the client is a master we need to
* compute the difference between the applied offset before and after
* processing the buffer, to understand how much of the replication stream
* was actually applied to the master state: this quantity, and its
* corresponding part of the replication stream, will be propagated to
* the sub-slaves and to the replication backlog. */
if
(
!
(
c
->
flags
&
CLIENT_MASTER
))
{
processInputBuffer
(
c
);
}
else
{
size_t
prev_offset
=
c
->
reploff
;
processInputBuffer
(
c
);
size_t
applied
=
c
->
reploff
-
prev_offset
;
if
(
applied
)
{
replicationFeedSlavesFromMasterStream
(
server
.
slaves
,
c
->
pending_querybuf
,
applied
);
sdsrange
(
c
->
pending_querybuf
,
applied
,
-
1
);
}
}
}
}
void
getClientsMaxBuffers
(
unsigned
long
*
longest_output_list
,
void
getClientsMaxBuffers
(
unsigned
long
*
longest_output_list
,
...
...
src/rax.c
0 → 100644
View file @
89a9e5a9
/* Rax -- A radix tree implementation.
*
* Copyright (c) 2017, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include "rax.h"
#ifndef RAX_MALLOC_INCLUDE
#define RAX_MALLOC_INCLUDE "rax_malloc.h"
#endif
#include RAX_MALLOC_INCLUDE
/* This is a special pointer that is guaranteed to never have the same value
* of a radix tree node. It's used in order to report "not found" error without
* requiring the function to have multiple return values. */
void
*
raxNotFound
=
(
void
*
)
"rax-not-found-pointer"
;
/* -------------------------------- Debugging ------------------------------ */
void
raxDebugShowNode
(
const
char
*
msg
,
raxNode
*
n
);
/* Turn debugging messages on/off. */
#if 0
#define debugf(...) \
do { \
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
printf(__VA_ARGS__); \
fflush(stdout); \
} while (0);
#define debugnode(msg,n) raxDebugShowNode(msg,n)
#else
#define debugf(...)
#define debugnode(msg,n)
#endif
/* ------------------------- raxStack functions --------------------------
* The raxStack is a simple stack of pointers that is capable of switching
* from using a stack-allocated array to dynamic heap once a given number of
* items are reached. It is used in order to retain the list of parent nodes
* while walking the radix tree in order to implement certain operations that
* need to navigate the tree upward.
* ------------------------------------------------------------------------- */
/* Initialize the stack. */
static
inline
void
raxStackInit
(
raxStack
*
ts
)
{
ts
->
stack
=
ts
->
static_items
;
ts
->
items
=
0
;
ts
->
maxitems
=
RAX_STACK_STATIC_ITEMS
;
ts
->
oom
=
0
;
}
/* Push an item into the stack, returns 1 on success, 0 on out of memory. */
static
inline
int
raxStackPush
(
raxStack
*
ts
,
void
*
ptr
)
{
if
(
ts
->
items
==
ts
->
maxitems
)
{
if
(
ts
->
stack
==
ts
->
static_items
)
{
ts
->
stack
=
rax_malloc
(
sizeof
(
void
*
)
*
ts
->
maxitems
*
2
);
if
(
ts
->
stack
==
NULL
)
{
ts
->
stack
=
ts
->
static_items
;
ts
->
oom
=
1
;
errno
=
ENOMEM
;
return
0
;
}
memcpy
(
ts
->
stack
,
ts
->
static_items
,
sizeof
(
void
*
)
*
ts
->
maxitems
);
}
else
{
void
**
newalloc
=
rax_realloc
(
ts
->
stack
,
sizeof
(
void
*
)
*
ts
->
maxitems
*
2
);
if
(
newalloc
==
NULL
)
{
ts
->
oom
=
1
;
errno
=
ENOMEM
;
return
0
;
}
ts
->
stack
=
newalloc
;
}
ts
->
maxitems
*=
2
;
}
ts
->
stack
[
ts
->
items
]
=
ptr
;
ts
->
items
++
;
return
1
;
}
/* Pop an item from the stack, the function returns NULL if there are no
* items to pop. */
static
inline
void
*
raxStackPop
(
raxStack
*
ts
)
{
if
(
ts
->
items
==
0
)
return
NULL
;
ts
->
items
--
;
return
ts
->
stack
[
ts
->
items
];
}
/* Return the stack item at the top of the stack without actually consuming
* it. */
static
inline
void
*
raxStackPeek
(
raxStack
*
ts
)
{
if
(
ts
->
items
==
0
)
return
NULL
;
return
ts
->
stack
[
ts
->
items
-
1
];
}
/* Free the stack in case we used heap allocation. */
static
inline
void
raxStackFree
(
raxStack
*
ts
)
{
if
(
ts
->
stack
!=
ts
->
static_items
)
rax_free
(
ts
->
stack
);
}
/* ----------------------------------------------------------------------------
* Radis tree implementation
* --------------------------------------------------------------------------*/
/* Allocate a new non compressed node with the specified number of children.
* If datafiled is true, the allocation is made large enough to hold the
* associated data pointer.
* Returns the new node pointer. On out of memory NULL is returned. */
raxNode
*
raxNewNode
(
size_t
children
,
int
datafield
)
{
size_t
nodesize
=
sizeof
(
raxNode
)
+
children
+
sizeof
(
raxNode
*
)
*
children
;
if
(
datafield
)
nodesize
+=
sizeof
(
void
*
);
raxNode
*
node
=
rax_malloc
(
nodesize
);
if
(
node
==
NULL
)
return
NULL
;
node
->
iskey
=
0
;
node
->
isnull
=
0
;
node
->
iscompr
=
0
;
node
->
size
=
children
;
return
node
;
}
/* Allocate a new rax and return its pointer. On out of memory the function
* returns NULL. */
rax
*
raxNew
(
void
)
{
rax
*
rax
=
rax_malloc
(
sizeof
(
*
rax
));
if
(
rax
==
NULL
)
return
NULL
;
rax
->
numele
=
0
;
rax
->
numnodes
=
1
;
rax
->
head
=
raxNewNode
(
0
,
0
);
if
(
rax
->
head
==
NULL
)
{
rax_free
(
rax
);
return
NULL
;
}
else
{
return
rax
;
}
}
/* Return the current total size of the node. */
#define raxNodeCurrentLength(n) ( \
sizeof(raxNode)+(n)->size+ \
((n)->iscompr ? sizeof(raxNode*) : sizeof(raxNode*)*(n)->size)+ \
(((n)->iskey && !(n)->isnull)*sizeof(void*)) \
)
/* realloc the node to make room for auxiliary data in order
* to store an item in that node. On out of memory NULL is returned. */
raxNode
*
raxReallocForData
(
raxNode
*
n
,
void
*
data
)
{
if
(
data
==
NULL
)
return
n
;
/* No reallocation needed, setting isnull=1 */
size_t
curlen
=
raxNodeCurrentLength
(
n
);
return
rax_realloc
(
n
,
curlen
+
sizeof
(
void
*
));
}
/* Set the node auxiliary data to the specified pointer. */
void
raxSetData
(
raxNode
*
n
,
void
*
data
)
{
n
->
iskey
=
1
;
if
(
data
!=
NULL
)
{
n
->
isnull
=
0
;
void
**
ndata
=
(
void
**
)
((
char
*
)
n
+
raxNodeCurrentLength
(
n
)
-
sizeof
(
void
*
));
memcpy
(
ndata
,
&
data
,
sizeof
(
data
));
}
else
{
n
->
isnull
=
1
;
}
}
/* Get the node auxiliary data. */
void
*
raxGetData
(
raxNode
*
n
)
{
if
(
n
->
isnull
)
return
NULL
;
void
**
ndata
=
(
void
**
)((
char
*
)
n
+
raxNodeCurrentLength
(
n
)
-
sizeof
(
void
*
));
void
*
data
;
memcpy
(
&
data
,
ndata
,
sizeof
(
data
));
return
data
;
}
/* Add a new child to the node 'n' representing the character 'c' and return
* its new pointer, as well as the child pointer by reference. Additionally
* '***parentlink' is populated with the raxNode pointer-to-pointer of where
* the new child was stored, which is useful for the caller to replace the
* child pointer if it gets reallocated.
*
* On success the new parent node pointer is returned (it may change because
* of the realloc, so the caller should discard 'n' and use the new value).
* On out of memory NULL is returned, and the old node is still valid. */
raxNode
*
raxAddChild
(
raxNode
*
n
,
unsigned
char
c
,
raxNode
**
childptr
,
raxNode
***
parentlink
)
{
assert
(
n
->
iscompr
==
0
);
size_t
curlen
=
sizeof
(
raxNode
)
+
n
->
size
+
sizeof
(
raxNode
*
)
*
n
->
size
;
size_t
newlen
;
/* Alloc the new child we will link to 'n'. */
raxNode
*
child
=
raxNewNode
(
0
,
0
);
if
(
child
==
NULL
)
return
NULL
;
/* Make space in the original node. */
if
(
n
->
iskey
)
curlen
+=
sizeof
(
void
*
);
newlen
=
curlen
+
sizeof
(
raxNode
*
)
+
1
;
/* Add 1 char and 1 pointer. */
raxNode
*
newn
=
rax_realloc
(
n
,
newlen
);
if
(
newn
==
NULL
)
{
rax_free
(
child
);
return
NULL
;
}
n
=
newn
;
/* After the reallocation, we have 5/9 (depending on the system
* pointer size) bytes at the end, that is, the additional char
* in the 'data' section, plus one pointer to the new child:
*
* [numc][abx][ap][bp][xp]|auxp|.....
*
* Let's find where to insert the new child in order to make sure
* it is inserted in-place lexicographically. */
int
pos
;
for
(
pos
=
0
;
pos
<
n
->
size
;
pos
++
)
{
if
(
n
->
data
[
pos
]
>
c
)
break
;
}
/* Now, if present, move auxiliary data pointer at the end
* so that we can mess with the other data without overwriting it.
* We will obtain something like that:
*
* [numc][abx][ap][bp][xp].....|auxp| */
unsigned
char
*
src
;
if
(
n
->
iskey
&&
!
n
->
isnull
)
{
src
=
n
->
data
+
n
->
size
+
sizeof
(
raxNode
*
)
*
n
->
size
;
memmove
(
src
+
1
+
sizeof
(
raxNode
*
),
src
,
sizeof
(
void
*
));
}
/* Now imagine we are adding a node with edge 'c'. The insertion
* point is between 'b' and 'x', so the 'pos' variable value is
* To start, move all the child pointers after the insertion point
* of 1+sizeof(pointer) bytes on the right, to obtain:
*
* [numc][abx][ap][bp].....[xp]|auxp| */
src
=
n
->
data
+
n
->
size
+
sizeof
(
raxNode
*
)
*
pos
;
memmove
(
src
+
1
+
sizeof
(
raxNode
*
),
src
,
sizeof
(
raxNode
*
)
*
(
n
->
size
-
pos
));
/* Now make the space for the additional char in the data section,
* but also move the pointers before the insertion point in the right
* by 1 byte, in order to obtain the following:
*
* [numc][ab.x][ap][bp]....[xp]|auxp| */
src
=
n
->
data
+
pos
;
memmove
(
src
+
1
,
src
,
n
->
size
-
pos
+
sizeof
(
raxNode
*
)
*
pos
);
/* We can now set the character and its child node pointer to get:
*
* [numc][abcx][ap][bp][cp]....|auxp|
* [numc][abcx][ap][bp][cp][xp]|auxp| */
n
->
data
[
pos
]
=
c
;
n
->
size
++
;
raxNode
**
childfield
=
(
raxNode
**
)(
n
->
data
+
n
->
size
+
sizeof
(
raxNode
*
)
*
pos
);
memcpy
(
childfield
,
&
child
,
sizeof
(
child
));
*
childptr
=
child
;
*
parentlink
=
childfield
;
return
n
;
}
/* Return the pointer to the last child pointer in a node. For the compressed
* nodes this is the only child pointer. */
#define raxNodeLastChildPtr(n) ((raxNode**) ( \
((char*)(n)) + \
raxNodeCurrentLength(n) - \
sizeof(raxNode*) - \
(((n)->iskey && !(n)->isnull) ? sizeof(void*) : 0) \
))
/* Return the pointer to the first child pointer. */
#define raxNodeFirstChildPtr(n) ((raxNode**)((n)->data+(n)->size))
/* Turn the node 'n', that must be a node without any children, into a
* compressed node representing a set of nodes linked one after the other
* and having exactly one child each. The node can be a key or not: this
* property and the associated value if any will be preserved.
*
* The function also returns a child node, since the last node of the
* compressed chain cannot be part of the chain: it has zero children while
* we can only compress inner nodes with exactly one child each. */
raxNode
*
raxCompressNode
(
raxNode
*
n
,
unsigned
char
*
s
,
size_t
len
,
raxNode
**
child
)
{
assert
(
n
->
size
==
0
&&
n
->
iscompr
==
0
);
void
*
data
=
NULL
;
/* Initialized only to avoid warnings. */
size_t
newsize
;
debugf
(
"Compress node: %.*s
\n
"
,
(
int
)
len
,
s
);
/* Allocate the child to link to this node. */
*
child
=
raxNewNode
(
0
,
0
);
if
(
*
child
==
NULL
)
return
NULL
;
/* Make space in the parent node. */
newsize
=
sizeof
(
raxNode
)
+
len
+
sizeof
(
raxNode
*
);
if
(
n
->
iskey
)
{
data
=
raxGetData
(
n
);
/* To restore it later. */
if
(
!
n
->
isnull
)
newsize
+=
sizeof
(
void
*
);
}
raxNode
*
newn
=
rax_realloc
(
n
,
newsize
);
if
(
newn
==
NULL
)
{
rax_free
(
*
child
);
return
NULL
;
}
n
=
newn
;
n
->
iscompr
=
1
;
n
->
size
=
len
;
memcpy
(
n
->
data
,
s
,
len
);
if
(
n
->
iskey
)
raxSetData
(
n
,
data
);
raxNode
**
childfield
=
raxNodeLastChildPtr
(
n
);
memcpy
(
childfield
,
child
,
sizeof
(
*
child
));
return
n
;
}
/* Low level function that walks the tree looking for the string
* 's' of 'len' bytes. The function returns the number of characters
* of the key that was possible to process: if the returned integer
* is the same as 'len', then it means that the node corresponding to the
* string was found (however it may not be a key in case the node->iskey is
* zero or if simply we stopped in the middle of a compressed node, so that
* 'splitpos' is non zero).
*
* Otherwise if the returned integer is not the same as 'len', there was an
* early stop during the tree walk because of a character mismatch.
*
* The node where the search ended (because the full string was processed
* or because there was an early stop) is returned by reference as
* '*stopnode' if the passed pointer is not NULL. This node link in the
* parent's node is returned as '*plink' if not NULL. Finally, if the
* search stopped in a compressed node, '*splitpos' returns the index
* inside the compressed node where the search ended. This is useful to
* know where to split the node for insertion. */
static
inline
size_t
raxLowWalk
(
rax
*
rax
,
unsigned
char
*
s
,
size_t
len
,
raxNode
**
stopnode
,
raxNode
***
plink
,
int
*
splitpos
,
raxStack
*
ts
)
{
raxNode
*
h
=
rax
->
head
;
raxNode
**
parentlink
=
&
rax
->
head
;
size_t
i
=
0
;
/* Position in the string. */
size_t
j
=
0
;
/* Position in the node children (or bytes if compressed).*/
while
(
h
->
size
&&
i
<
len
)
{
debugnode
(
"Lookup current node"
,
h
);
unsigned
char
*
v
=
h
->
data
;
if
(
h
->
iscompr
)
{
for
(
j
=
0
;
j
<
h
->
size
&&
i
<
len
;
j
++
,
i
++
)
{
if
(
v
[
j
]
!=
s
[
i
])
break
;
}
if
(
j
!=
h
->
size
)
break
;
}
else
{
/* Even when h->size is large, linear scan provides good
* performances compared to other approaches that are in theory
* more sounding, like performing a binary search. */
for
(
j
=
0
;
j
<
h
->
size
;
j
++
)
{
if
(
v
[
j
]
==
s
[
i
])
break
;
}
if
(
j
==
h
->
size
)
break
;
i
++
;
}
if
(
ts
)
raxStackPush
(
ts
,
h
);
/* Save stack of parent nodes. */
raxNode
**
children
=
raxNodeFirstChildPtr
(
h
);
if
(
h
->
iscompr
)
j
=
0
;
/* Compressed node only child is at index 0. */
memcpy
(
&
h
,
children
+
j
,
sizeof
(
h
));
parentlink
=
children
+
j
;
j
=
0
;
/* If the new node is compressed and we do not
iterate again (since i == l) set the split
position to 0 to signal this node represents
the searched key. */
}
debugnode
(
"Lookup stop node is"
,
h
);
if
(
stopnode
)
*
stopnode
=
h
;
if
(
plink
)
*
plink
=
parentlink
;
if
(
splitpos
&&
h
->
iscompr
)
*
splitpos
=
j
;
return
i
;
}
/* Insert the element 's' of size 'len', setting as auxiliary data
* the pointer 'data'. If the element is already present, the associated
* data is updated, and 0 is returned, otherwise the element is inserted
* and 1 is returned. On out of memory the function returns 0 as well but
* sets errno to ENOMEM, otherwise errno will be set to 0. */
int
raxInsert
(
rax
*
rax
,
unsigned
char
*
s
,
size_t
len
,
void
*
data
,
void
**
old
)
{
size_t
i
;
int
j
=
0
;
/* Split position. If raxLowWalk() stops in a compressed
node, the index 'j' represents the char we stopped within the
compressed node, that is, the position where to split the
node for insertion. */
raxNode
*
h
,
**
parentlink
;
debugf
(
"### Insert %.*s with value %p
\n
"
,
(
int
)
len
,
s
,
data
);
i
=
raxLowWalk
(
rax
,
s
,
len
,
&
h
,
&
parentlink
,
&
j
,
NULL
);
/* If i == len we walked following the whole string. If we are not
* in the middle of a compressed node, the string is either already
* inserted or this middle node is currently not a key, but can represent
* our key. We have just to reallocate the node and make space for the
* data pointer. */
if
(
i
==
len
&&
(
!
h
->
iscompr
||
j
==
0
/* not in the middle if j is 0 */
))
{
debugf
(
"### Insert: node representing key exists
\n
"
);
if
(
!
h
->
iskey
||
h
->
isnull
)
{
h
=
raxReallocForData
(
h
,
data
);
if
(
h
)
memcpy
(
parentlink
,
&
h
,
sizeof
(
h
));
}
if
(
h
==
NULL
)
{
errno
=
ENOMEM
;
return
0
;
}
if
(
h
->
iskey
)
{
if
(
old
)
*
old
=
raxGetData
(
h
);
raxSetData
(
h
,
data
);
errno
=
0
;
return
0
;
/* Element already exists. */
}
raxSetData
(
h
,
data
);
rax
->
numele
++
;
return
1
;
/* Element inserted. */
}
/* If the node we stopped at is a compressed node, we need to
* split it before to continue.
*
* Splitting a compressed node have a few possibile cases.
* Imagine that the node 'h' we are currently at is a compressed
* node contaning the string "ANNIBALE" (it means that it represents
* nodes A -> N -> N -> I -> B -> A -> L -> E with the only child
* pointer of this node pointing at the 'E' node, because remember that
* we have characters at the edges of the graph, not inside the nodes
* themselves.
*
* In order to show a real case imagine our node to also point to
* another compressed node, that finally points at the node without
* children, representing 'O':
*
* "ANNIBALE" -> "SCO" -> []
*
* When inserting we may face the following cases. Note that all the cases
* require the insertion of a non compressed node with exactly two
* children, except for the last case which just requires splitting a
* compressed node.
*
* 1) Inserting "ANNIENTARE"
*
* |B| -> "ALE" -> "SCO" -> []
* "ANNI" -> |-|
* |E| -> (... continue algo ...) "NTARE" -> []
*
* 2) Inserting "ANNIBALI"
*
* |E| -> "SCO" -> []
* "ANNIBAL" -> |-|
* |I| -> (... continue algo ...) []
*
* 3) Inserting "AGO" (Like case 1, but set iscompr = 0 into original node)
*
* |N| -> "NIBALE" -> "SCO" -> []
* |A| -> |-|
* |G| -> (... continue algo ...) |O| -> []
*
* 4) Inserting "CIAO"
*
* |A| -> "NNIBALE" -> "SCO" -> []
* |-|
* |C| -> (... continue algo ...) "IAO" -> []
*
* 5) Inserting "ANNI"
*
* "ANNI" -> "BALE" -> "SCO" -> []
*
* The final algorithm for insertion covering all the above cases is as
* follows.
*
* ============================= ALGO 1 =============================
*
* For the above cases 1 to 4, that is, all cases where we stopped in
* the middle of a compressed node for a character mismatch, do:
*
* Let $SPLITPOS be the zero-based index at which, in the
* compressed node array of characters, we found the mismatching
* character. For example if the node contains "ANNIBALE" and we add
* "ANNIENTARE" the $SPLITPOS is 4, that is, the index at which the
* mismatching character is found.
*
* 1. Save the current compressed node $NEXT pointer (the pointer to the
* child element, that is always present in compressed nodes).
*
* 2. Create "split node" having as child the non common letter
* at the compressed node. The other non common letter (at the key)
* will be added later as we continue the normal insertion algorithm
* at step "6".
*
* 3a. IF $SPLITPOS == 0:
* Replace the old node with the split node, by copying the auxiliary
* data if any. Fix parent's reference. Free old node eventually
* (we still need its data for the next steps of the algorithm).
*
* 3b. IF $SPLITPOS != 0:
* Trim the compressed node (reallocating it as well) in order to
* contain $splitpos characters. Change chilid pointer in order to link
* to the split node. If new compressed node len is just 1, set
* iscompr to 0 (layout is the same). Fix parent's reference.
*
* 4a. IF the postfix len (the length of the remaining string of the
* original compressed node after the split character) is non zero,
* create a "postfix node". If the postfix node has just one character
* set iscompr to 0, otherwise iscompr to 1. Set the postfix node
* child pointer to $NEXT.
*
* 4b. IF the postfix len is zero, just use $NEXT as postfix pointer.
*
* 5. Set child[0] of split node to postfix node.
*
* 6. Set the split node as the current node, set current index at child[1]
* and continue insertion algorithm as usually.
*
* ============================= ALGO 2 =============================
*
* For case 5, that is, if we stopped in the middle of a compressed
* node but no mismatch was found, do:
*
* Let $SPLITPOS be the zero-based index at which, in the
* compressed node array of characters, we stopped iterating because
* there were no more keys character to match. So in the example of
* the node "ANNIBALE", addig the string "ANNI", the $SPLITPOS is 4.
*
* 1. Save the current compressed node $NEXT pointer (the pointer to the
* child element, that is always present in compressed nodes).
*
* 2. Create a "postfix node" containing all the characters from $SPLITPOS
* to the end. Use $NEXT as the postfix node child pointer.
* If the postfix node length is 1, set iscompr to 0.
* Set the node as a key with the associated value of the new
* inserted key.
*
* 3. Trim the current node to contain the first $SPLITPOS characters.
* As usually if the new node length is just 1, set iscompr to 0.
* Take the iskey / associated value as it was in the orignal node.
* Fix the parent's reference.
*
* 4. Set the postfix node as the only child pointer of the trimmed
* node created at step 1.
*/
/* ------------------------- ALGORITHM 1 --------------------------- */
if
(
h
->
iscompr
&&
i
!=
len
)
{
debugf
(
"ALGO 1: Stopped at compressed node %.*s (%p)
\n
"
,
h
->
size
,
h
->
data
,
(
void
*
)
h
);
debugf
(
"Still to insert: %.*s
\n
"
,
(
int
)(
len
-
i
),
s
+
i
);
debugf
(
"Splitting at %d: '%c'
\n
"
,
j
,
((
char
*
)
h
->
data
)[
j
]);
debugf
(
"Other (key) letter is '%c'
\n
"
,
s
[
i
]);
/* 1: Save next pointer. */
raxNode
**
childfield
=
raxNodeLastChildPtr
(
h
);
raxNode
*
next
;
memcpy
(
&
next
,
childfield
,
sizeof
(
next
));
debugf
(
"Next is %p
\n
"
,
(
void
*
)
next
);
debugf
(
"iskey %d
\n
"
,
h
->
iskey
);
if
(
h
->
iskey
)
{
debugf
(
"key value is %p
\n
"
,
raxGetData
(
h
));
}
/* Set the length of the additional nodes we will need. */
size_t
trimmedlen
=
j
;
size_t
postfixlen
=
h
->
size
-
j
-
1
;
int
split_node_is_key
=
!
trimmedlen
&&
h
->
iskey
&&
!
h
->
isnull
;
size_t
nodesize
;
/* 2: Create the split node. Also allocate the other nodes we'll need
* ASAP, so that it will be simpler to handle OOM. */
raxNode
*
splitnode
=
raxNewNode
(
1
,
split_node_is_key
);
raxNode
*
trimmed
=
NULL
;
raxNode
*
postfix
=
NULL
;
if
(
trimmedlen
)
{
nodesize
=
sizeof
(
raxNode
)
+
trimmedlen
+
sizeof
(
raxNode
*
);
if
(
h
->
iskey
&&
!
h
->
isnull
)
nodesize
+=
sizeof
(
void
*
);
trimmed
=
rax_malloc
(
nodesize
);
}
if
(
postfixlen
)
{
nodesize
=
sizeof
(
raxNode
)
+
postfixlen
+
sizeof
(
raxNode
*
);
postfix
=
rax_malloc
(
nodesize
);
}
/* OOM? Abort now that the tree is untouched. */
if
(
splitnode
==
NULL
||
(
trimmedlen
&&
trimmed
==
NULL
)
||
(
postfixlen
&&
postfix
==
NULL
))
{
rax_free
(
splitnode
);
rax_free
(
trimmed
);
rax_free
(
postfix
);
errno
=
ENOMEM
;
return
0
;
}
splitnode
->
data
[
0
]
=
h
->
data
[
j
];
if
(
j
==
0
)
{
/* 3a: Replace the old node with the split node. */
if
(
h
->
iskey
)
{
void
*
ndata
=
raxGetData
(
h
);
raxSetData
(
splitnode
,
ndata
);
}
memcpy
(
parentlink
,
&
splitnode
,
sizeof
(
splitnode
));
}
else
{
/* 3b: Trim the compressed node. */
trimmed
->
size
=
j
;
memcpy
(
trimmed
->
data
,
h
->
data
,
j
);
trimmed
->
iscompr
=
j
>
1
?
1
:
0
;
trimmed
->
iskey
=
h
->
iskey
;
trimmed
->
isnull
=
h
->
isnull
;
if
(
h
->
iskey
&&
!
h
->
isnull
)
{
void
*
ndata
=
raxGetData
(
h
);
raxSetData
(
trimmed
,
ndata
);
}
raxNode
**
cp
=
raxNodeLastChildPtr
(
trimmed
);
memcpy
(
cp
,
&
splitnode
,
sizeof
(
splitnode
));
memcpy
(
parentlink
,
&
trimmed
,
sizeof
(
trimmed
));
parentlink
=
cp
;
/* Set parentlink to splitnode parent. */
rax
->
numnodes
++
;
}
/* 4: Create the postfix node: what remains of the original
* compressed node after the split. */
if
(
postfixlen
)
{
/* 4a: create a postfix node. */
postfix
->
iskey
=
0
;
postfix
->
isnull
=
0
;
postfix
->
size
=
postfixlen
;
postfix
->
iscompr
=
postfixlen
>
1
;
memcpy
(
postfix
->
data
,
h
->
data
+
j
+
1
,
postfixlen
);
raxNode
**
cp
=
raxNodeLastChildPtr
(
postfix
);
memcpy
(
cp
,
&
next
,
sizeof
(
next
));
rax
->
numnodes
++
;
}
else
{
/* 4b: just use next as postfix node. */
postfix
=
next
;
}
/* 5: Set splitnode first child as the postfix node. */
raxNode
**
splitchild
=
raxNodeLastChildPtr
(
splitnode
);
memcpy
(
splitchild
,
&
postfix
,
sizeof
(
postfix
));
/* 6. Continue insertion: this will cause the splitnode to
* get a new child (the non common character at the currently
* inserted key). */
rax_free
(
h
);
h
=
splitnode
;
}
else
if
(
h
->
iscompr
&&
i
==
len
)
{
/* ------------------------- ALGORITHM 2 --------------------------- */
debugf
(
"ALGO 2: Stopped at compressed node %.*s (%p) j = %d
\n
"
,
h
->
size
,
h
->
data
,
(
void
*
)
h
,
j
);
/* Allocate postfix & trimmed nodes ASAP to fail for OOM gracefully. */
size_t
postfixlen
=
h
->
size
-
j
;
size_t
nodesize
=
sizeof
(
raxNode
)
+
postfixlen
+
sizeof
(
raxNode
*
);
if
(
data
!=
NULL
)
nodesize
+=
sizeof
(
void
*
);
raxNode
*
postfix
=
rax_malloc
(
nodesize
);
nodesize
=
sizeof
(
raxNode
)
+
j
+
sizeof
(
raxNode
*
);
if
(
h
->
iskey
&&
!
h
->
isnull
)
nodesize
+=
sizeof
(
void
*
);
raxNode
*
trimmed
=
rax_malloc
(
nodesize
);
if
(
postfix
==
NULL
||
trimmed
==
NULL
)
{
rax_free
(
postfix
);
rax_free
(
trimmed
);
errno
=
ENOMEM
;
return
0
;
}
/* 1: Save next pointer. */
raxNode
**
childfield
=
raxNodeLastChildPtr
(
h
);
raxNode
*
next
;
memcpy
(
&
next
,
childfield
,
sizeof
(
next
));
/* 2: Create the postfix node. */
postfix
->
size
=
postfixlen
;
postfix
->
iscompr
=
postfixlen
>
1
;
postfix
->
iskey
=
1
;
postfix
->
isnull
=
0
;
memcpy
(
postfix
->
data
,
h
->
data
+
j
,
postfixlen
);
raxSetData
(
postfix
,
data
);
raxNode
**
cp
=
raxNodeLastChildPtr
(
postfix
);
memcpy
(
cp
,
&
next
,
sizeof
(
next
));
rax
->
numnodes
++
;
/* 3: Trim the compressed node. */
trimmed
->
size
=
j
;
trimmed
->
iscompr
=
j
>
1
;
trimmed
->
iskey
=
0
;
trimmed
->
isnull
=
0
;
memcpy
(
trimmed
->
data
,
h
->
data
,
j
);
memcpy
(
parentlink
,
&
trimmed
,
sizeof
(
trimmed
));
if
(
h
->
iskey
)
{
void
*
aux
=
raxGetData
(
h
);
raxSetData
(
trimmed
,
aux
);
}
/* Fix the trimmed node child pointer to point to
* the postfix node. */
cp
=
raxNodeLastChildPtr
(
trimmed
);
memcpy
(
cp
,
&
postfix
,
sizeof
(
postfix
));
/* Finish! We don't need to contine with the insertion
* algorithm for ALGO 2. The key is already inserted. */
rax
->
numele
++
;
rax_free
(
h
);
return
1
;
/* Key inserted. */
}
/* We walked the radix tree as far as we could, but still there are left
* chars in our string. We need to insert the missing nodes. */
while
(
i
<
len
)
{
raxNode
*
child
;
/* If this node is going to have a single child, and there
* are other characters, so that that would result in a chain
* of single-childed nodes, turn it into a compressed node. */
if
(
h
->
size
==
0
&&
len
-
i
>
1
)
{
debugf
(
"Inserting compressed node
\n
"
);
size_t
comprsize
=
len
-
i
;
if
(
comprsize
>
RAX_NODE_MAX_SIZE
)
comprsize
=
RAX_NODE_MAX_SIZE
;
raxNode
*
newh
=
raxCompressNode
(
h
,
s
+
i
,
comprsize
,
&
child
);
if
(
newh
==
NULL
)
goto
oom
;
h
=
newh
;
memcpy
(
parentlink
,
&
h
,
sizeof
(
h
));
parentlink
=
raxNodeLastChildPtr
(
h
);
i
+=
comprsize
;
}
else
{
debugf
(
"Inserting normal node
\n
"
);
raxNode
**
new_parentlink
;
raxNode
*
newh
=
raxAddChild
(
h
,
s
[
i
],
&
child
,
&
new_parentlink
);
if
(
newh
==
NULL
)
goto
oom
;
h
=
newh
;
memcpy
(
parentlink
,
&
h
,
sizeof
(
h
));
parentlink
=
new_parentlink
;
i
++
;
}
rax
->
numnodes
++
;
h
=
child
;
}
raxNode
*
newh
=
raxReallocForData
(
h
,
data
);
if
(
newh
==
NULL
)
goto
oom
;
h
=
newh
;
if
(
!
h
->
iskey
)
rax
->
numele
++
;
raxSetData
(
h
,
data
);
memcpy
(
parentlink
,
&
h
,
sizeof
(
h
));
return
1
;
/* Element inserted. */
oom:
/* This code path handles out of memory after part of the sub-tree was
* already modified. Set the node as a key, and then remove it. However we
* do that only if the node is a terminal node, otherwise if the OOM
* happened reallocating a node in the middle, we don't need to free
* anything. */
if
(
h
->
size
==
0
)
{
h
->
isnull
=
1
;
h
->
iskey
=
1
;
rax
->
numele
++
;
/* Compensate the next remove. */
assert
(
raxRemove
(
rax
,
s
,
i
,
NULL
)
!=
0
);
}
errno
=
ENOMEM
;
return
0
;
}
/* Find a key in the rax, returns raxNotFound special void pointer value
* if the item was not found, otherwise the value associated with the
* item is returned. */
void
*
raxFind
(
rax
*
rax
,
unsigned
char
*
s
,
size_t
len
)
{
raxNode
*
h
;
debugf
(
"### Lookup: %.*s
\n
"
,
(
int
)
len
,
s
);
int
splitpos
=
0
;
size_t
i
=
raxLowWalk
(
rax
,
s
,
len
,
&
h
,
NULL
,
&
splitpos
,
NULL
);
if
(
i
!=
len
||
(
h
->
iscompr
&&
splitpos
!=
0
)
||
!
h
->
iskey
)
return
raxNotFound
;
return
raxGetData
(
h
);
}
/* Return the memory address where the 'parent' node stores the specified
* 'child' pointer, so that the caller can update the pointer with another
* one if needed. The function assumes it will find a match, otherwise the
* operation is an undefined behavior (it will continue scanning the
* memory without any bound checking). */
raxNode
**
raxFindParentLink
(
raxNode
*
parent
,
raxNode
*
child
)
{
raxNode
**
cp
=
raxNodeFirstChildPtr
(
parent
);
raxNode
*
c
;
while
(
1
)
{
memcpy
(
&
c
,
cp
,
sizeof
(
c
));
if
(
c
==
child
)
break
;
cp
++
;
}
return
cp
;
}
/* Low level child removal from node. The new node pointer (after the child
* removal) is returned. Note that this function does not fix the pointer
* of the parent node in its parent, so this task is up to the caller.
* The function never fails for out of memory. */
raxNode
*
raxRemoveChild
(
raxNode
*
parent
,
raxNode
*
child
)
{
debugnode
(
"raxRemoveChild before"
,
parent
);
/* If parent is a compressed node (having a single child, as for definition
* of the data structure), the removal of the child consists into turning
* it into a normal node without children. */
if
(
parent
->
iscompr
)
{
void
*
data
=
NULL
;
if
(
parent
->
iskey
)
data
=
raxGetData
(
parent
);
parent
->
isnull
=
0
;
parent
->
iscompr
=
0
;
parent
->
size
=
0
;
if
(
parent
->
iskey
)
raxSetData
(
parent
,
data
);
debugnode
(
"raxRemoveChild after"
,
parent
);
return
parent
;
}
/* Otherwise we need to scan for the children pointer and memmove()
* accordingly.
*
* 1. To start we seek the first element in both the children
* pointers and edge bytes in the node. */
raxNode
**
cp
=
raxNodeFirstChildPtr
(
parent
);
raxNode
**
c
=
cp
;
unsigned
char
*
e
=
parent
->
data
;
/* 2. Search the child pointer to remove inside the array of children
* pointers. */
while
(
1
)
{
raxNode
*
aux
;
memcpy
(
&
aux
,
c
,
sizeof
(
aux
));
if
(
aux
==
child
)
break
;
c
++
;
e
++
;
}
/* 3. Remove the edge and the pointer by memmoving the remaining children
* pointer and edge bytes one position before. */
int
taillen
=
parent
->
size
-
(
e
-
parent
->
data
)
-
1
;
debugf
(
"raxRemoveChild tail len: %d
\n
"
,
taillen
);
memmove
(
e
,
e
+
1
,
taillen
);
/* Since we have one data byte less, also child pointers start one byte
* before now. */
memmove
(((
char
*
)
cp
)
-
1
,
cp
,(
parent
->
size
-
taillen
-
1
)
*
sizeof
(
raxNode
**
));
/* Move the remaining "tail" pointer at the right position as well. */
memmove
(((
char
*
)
c
)
-
1
,
c
+
1
,
taillen
*
sizeof
(
raxNode
**
)
+
parent
->
iskey
*
sizeof
(
void
*
));
/* 4. Update size. */
parent
->
size
--
;
/* realloc the node according to the theoretical memory usage, to free
* data if we are over-allocating right now. */
raxNode
*
newnode
=
rax_realloc
(
parent
,
raxNodeCurrentLength
(
parent
));
if
(
newnode
)
{
debugnode
(
"raxRemoveChild after"
,
newnode
);
}
/* Note: if rax_realloc() fails we just return the old address, which
* is valid. */
return
newnode
?
newnode
:
parent
;
}
/* Remove the specified item. Returns 1 if the item was found and
* deleted, 0 otherwise. */
int
raxRemove
(
rax
*
rax
,
unsigned
char
*
s
,
size_t
len
,
void
**
old
)
{
raxNode
*
h
;
raxStack
ts
;
debugf
(
"### Delete: %.*s
\n
"
,
(
int
)
len
,
s
);
raxStackInit
(
&
ts
);
int
splitpos
=
0
;
size_t
i
=
raxLowWalk
(
rax
,
s
,
len
,
&
h
,
NULL
,
&
splitpos
,
&
ts
);
if
(
i
!=
len
||
(
h
->
iscompr
&&
splitpos
!=
0
)
||
!
h
->
iskey
)
{
raxStackFree
(
&
ts
);
return
0
;
}
if
(
old
)
*
old
=
raxGetData
(
h
);
h
->
iskey
=
0
;
rax
->
numele
--
;
/* If this node has no children, the deletion needs to reclaim the
* no longer used nodes. This is an iterative process that needs to
* walk the three upward, deleting all the nodes with just one child
* that are not keys, until the head of the rax is reached or the first
* node with more than one child is found. */
int
trycompress
=
0
;
/* Will be set to 1 if we should try to optimize the
tree resulting from the deletion. */
if
(
h
->
size
==
0
)
{
debugf
(
"Key deleted in node without children. Cleanup needed.
\n
"
);
raxNode
*
child
=
NULL
;
while
(
h
!=
rax
->
head
)
{
child
=
h
;
debugf
(
"Freeing child %p [%.*s] key:%d
\n
"
,
(
void
*
)
child
,
(
int
)
child
->
size
,
(
char
*
)
child
->
data
,
child
->
iskey
);
rax_free
(
child
);
rax
->
numnodes
--
;
h
=
raxStackPop
(
&
ts
);
/* If this node has more then one child, or actually holds
* a key, stop here. */
if
(
h
->
iskey
||
(
!
h
->
iscompr
&&
h
->
size
!=
1
))
break
;
}
if
(
child
)
{
debugf
(
"Unlinking child %p from parent %p
\n
"
,
(
void
*
)
child
,
(
void
*
)
h
);
raxNode
*
new
=
raxRemoveChild
(
h
,
child
);
if
(
new
!=
h
)
{
raxNode
*
parent
=
raxStackPeek
(
&
ts
);
raxNode
**
parentlink
;
if
(
parent
==
NULL
)
{
parentlink
=
&
rax
->
head
;
}
else
{
parentlink
=
raxFindParentLink
(
parent
,
h
);
}
memcpy
(
parentlink
,
&
new
,
sizeof
(
new
));
}
/* If after the removal the node has just a single child
* and is not a key, we need to try to compress it. */
if
(
new
->
size
==
1
&&
new
->
iskey
==
0
)
{
trycompress
=
1
;
h
=
new
;
}
}
}
else
if
(
h
->
size
==
1
)
{
/* If the node had just one child, after the removal of the key
* further compression with adjacent nodes is pontentially possible. */
trycompress
=
1
;
}
/* Don't try node compression if our nodes pointers stack is not
* complete because of OOM while executing raxLowWalk() */
if
(
trycompress
&&
ts
.
oom
)
trycompress
=
0
;
/* Recompression: if trycompress is true, 'h' points to a radix tree node
* that changed in a way that could allow to compress nodes in this
* sub-branch. Compressed nodes represent chains of nodes that are not
* keys and have a single child, so there are two deletion events that
* may alter the tree so that further compression is needed:
*
* 1) A node with a single child was a key and now no longer is a key.
* 2) A node with two children now has just one child.
*
* We try to navigate upward till there are other nodes that can be
* compressed, when we reach the upper node which is not a key and has
* a single child, we scan the chain of children to collect the
* compressable part of the tree, and replace the current node with the
* new one, fixing the child pointer to reference the first non
* compressable node.
*
* Example of case "1". A tree stores the keys "FOO" = 1 and
* "FOOBAR" = 2:
*
*
* "FOO" -> "BAR" -> [] (2)
* (1)
*
* After the removal of "FOO" the tree can be compressed as:
*
* "FOOBAR" -> [] (2)
*
*
* Example of case "2". A tree stores the keys "FOOBAR" = 1 and
* "FOOTER" = 2:
*
* |B| -> "AR" -> [] (1)
* "FOO" -> |-|
* |T| -> "ER" -> [] (2)
*
* After the removal of "FOOTER" the resulting tree is:
*
* "FOO" -> |B| -> "AR" -> [] (1)
*
* That can be compressed into:
*
* "FOOBAR" -> [] (1)
*/
if
(
trycompress
)
{
debugf
(
"After removing %.*s:
\n
"
,
(
int
)
len
,
s
);
debugnode
(
"Compression may be needed"
,
h
);
debugf
(
"Seek start node
\n
"
);
/* Try to reach the upper node that is compressible.
* At the end of the loop 'h' will point to the first node we
* can try to compress and 'parent' to its parent. */
raxNode
*
parent
;
while
(
1
)
{
parent
=
raxStackPop
(
&
ts
);
if
(
!
parent
||
parent
->
iskey
||
(
!
parent
->
iscompr
&&
parent
->
size
!=
1
))
break
;
h
=
parent
;
debugnode
(
"Going up to"
,
h
);
}
raxNode
*
start
=
h
;
/* Compression starting node. */
/* Scan chain of nodes we can compress. */
size_t
comprsize
=
h
->
size
;
int
nodes
=
1
;
while
(
h
->
size
!=
0
)
{
raxNode
**
cp
=
raxNodeLastChildPtr
(
h
);
memcpy
(
&
h
,
cp
,
sizeof
(
h
));
if
(
h
->
iskey
||
(
!
h
->
iscompr
&&
h
->
size
!=
1
))
break
;
/* Stop here if going to the next node would result into
* a compressed node larger than h->size can hold. */
if
(
comprsize
+
h
->
size
>
RAX_NODE_MAX_SIZE
)
break
;
nodes
++
;
comprsize
+=
h
->
size
;
}
if
(
nodes
>
1
)
{
/* If we can compress, create the new node and populate it. */
size_t
nodesize
=
sizeof
(
raxNode
)
+
comprsize
+
sizeof
(
raxNode
*
);
raxNode
*
new
=
rax_malloc
(
nodesize
);
/* An out of memory here just means we cannot optimize this
* node, but the tree is left in a consistent state. */
if
(
new
==
NULL
)
{
raxStackFree
(
&
ts
);
return
1
;
}
new
->
iskey
=
0
;
new
->
isnull
=
0
;
new
->
iscompr
=
1
;
new
->
size
=
comprsize
;
rax
->
numnodes
++
;
/* Scan again, this time to populate the new node content and
* to fix the new node child pointer. At the same time we free
* all the nodes that we'll no longer use. */
comprsize
=
0
;
h
=
start
;
while
(
h
->
size
!=
0
)
{
memcpy
(
new
->
data
+
comprsize
,
h
->
data
,
h
->
size
);
comprsize
+=
h
->
size
;
raxNode
**
cp
=
raxNodeLastChildPtr
(
h
);
raxNode
*
tofree
=
h
;
memcpy
(
&
h
,
cp
,
sizeof
(
h
));
rax_free
(
tofree
);
rax
->
numnodes
--
;
if
(
h
->
iskey
||
(
!
h
->
iscompr
&&
h
->
size
!=
1
))
break
;
}
debugnode
(
"New node"
,
new
);
/* Now 'h' points to the first node that we still need to use,
* so our new node child pointer will point to it. */
raxNode
**
cp
=
raxNodeLastChildPtr
(
new
);
memcpy
(
cp
,
&
h
,
sizeof
(
h
));
/* Fix parent link. */
if
(
parent
)
{
raxNode
**
parentlink
=
raxFindParentLink
(
parent
,
start
);
memcpy
(
parentlink
,
&
new
,
sizeof
(
new
));
}
else
{
rax
->
head
=
new
;
}
debugf
(
"Compressed %d nodes, %d total bytes
\n
"
,
nodes
,
(
int
)
comprsize
);
}
}
raxStackFree
(
&
ts
);
return
1
;
}
/* This is the core of raxFree(): performs a depth-first scan of the
* tree and releases all the nodes found. */
void
raxRecursiveFree
(
rax
*
rax
,
raxNode
*
n
)
{
debugnode
(
"free traversing"
,
n
);
int
numchildren
=
n
->
iscompr
?
1
:
n
->
size
;
raxNode
**
cp
=
raxNodeLastChildPtr
(
n
);
while
(
numchildren
--
)
{
raxNode
*
child
;
memcpy
(
&
child
,
cp
,
sizeof
(
child
));
raxRecursiveFree
(
rax
,
child
);
cp
--
;
}
debugnode
(
"free depth-first"
,
n
);
rax_free
(
n
);
rax
->
numnodes
--
;
}
/* Free a whole radix tree. */
void
raxFree
(
rax
*
rax
)
{
raxRecursiveFree
(
rax
,
rax
->
head
);
assert
(
rax
->
numnodes
==
0
);
rax_free
(
rax
);
}
/* ------------------------------- Iterator --------------------------------- */
/* Initialize a Rax iterator. This call should be performed a single time
* to initialize the iterator, and must be followed by a raxSeek() call,
* otherwise the raxPrev()/raxNext() functions will just return EOF. */
void
raxStart
(
raxIterator
*
it
,
rax
*
rt
)
{
it
->
flags
=
RAX_ITER_EOF
;
/* No crash if the iterator is not seeked. */
it
->
rt
=
rt
;
it
->
key_len
=
0
;
it
->
key
=
it
->
key_static_string
;
it
->
key_max
=
RAX_ITER_STATIC_LEN
;
it
->
data
=
NULL
;
raxStackInit
(
&
it
->
stack
);
}
/* Append characters at the current key string of the iterator 'it'. This
* is a low level function used to implement the iterator, not callable by
* the user. Returns 0 on out of memory, otherwise 1 is returned. */
int
raxIteratorAddChars
(
raxIterator
*
it
,
unsigned
char
*
s
,
size_t
len
)
{
if
(
it
->
key_max
<
it
->
key_len
+
len
)
{
unsigned
char
*
old
=
(
it
->
key
==
it
->
key_static_string
)
?
NULL
:
it
->
key
;
size_t
new_max
=
(
it
->
key_len
+
len
)
*
2
;
it
->
key
=
rax_realloc
(
old
,
new_max
);
if
(
it
->
key
==
NULL
)
{
it
->
key
=
(
!
old
)
?
it
->
key_static_string
:
old
;
errno
=
ENOMEM
;
return
0
;
}
if
(
old
==
NULL
)
memcpy
(
it
->
key
,
it
->
key_static_string
,
it
->
key_len
);
it
->
key_max
=
new_max
;
}
/* Use memmove since there could be an overlap between 's' and
* it->key when we use the current key in order to re-seek. */
memmove
(
it
->
key
+
it
->
key_len
,
s
,
len
);
it
->
key_len
+=
len
;
return
1
;
}
/* Remove the specified number of chars from the right of the current
* iterator key. */
void
raxIteratorDelChars
(
raxIterator
*
it
,
size_t
count
)
{
it
->
key_len
-=
count
;
}
/* Do an iteration step towards the next element. At the end of the step the
* iterator key will represent the (new) current key. If it is not possible
* to step in the specified direction since there are no longer elements, the
* iterator is flagged with RAX_ITER_EOF.
*
* If 'noup' is true the function starts directly scanning for the next
* lexicographically smaller children, and the current node is already assumed
* to be the parent of the last key node, so the first operation to go back to
* the parent will be skipped. This option is used by raxSeek() when
* implementing seeking a non existing element with the ">" or "<" options:
* the starting node is not a key in that particular case, so we start the scan
* from a node that does not represent the key set.
*
* The function returns 1 on success or 0 on out of memory. */
int
raxIteratorNextStep
(
raxIterator
*
it
,
int
noup
)
{
if
(
it
->
flags
&
RAX_ITER_EOF
)
{
return
0
;
}
else
if
(
it
->
flags
&
RAX_ITER_JUST_SEEKED
)
{
it
->
flags
&=
~
RAX_ITER_JUST_SEEKED
;
return
1
;
}
/* Save key len, stack items and the node where we are currently
* so that on iterator EOF we can restore the current key and state. */
size_t
orig_key_len
=
it
->
key_len
;
size_t
orig_stack_items
=
it
->
stack
.
items
;
raxNode
*
orig_node
=
it
->
node
;
/* Clear the EOF flag: it will be set again if the EOF condition
* is still valid. */
it
->
flags
&=
~
RAX_ITER_EOF
;
while
(
1
)
{
int
children
=
it
->
node
->
iscompr
?
1
:
it
->
node
->
size
;
if
(
!
noup
&&
children
)
{
debugf
(
"GO DEEPER
\n
"
);
/* Seek the lexicographically smaller key in this subtree, which
* is the first one found always going torwards the first child
* of every successive node. */
if
(
!
raxStackPush
(
&
it
->
stack
,
it
->
node
))
return
0
;
raxNode
**
cp
=
raxNodeFirstChildPtr
(
it
->
node
);
if
(
!
raxIteratorAddChars
(
it
,
it
->
node
->
data
,
it
->
node
->
iscompr
?
it
->
node
->
size
:
1
))
return
0
;
memcpy
(
&
it
->
node
,
cp
,
sizeof
(
it
->
node
));
/* For "next" step, stop every time we find a key along the
* way, since the key is lexicograhically smaller compared to
* what follows in the sub-children. */
if
(
it
->
node
->
iskey
)
{
it
->
data
=
raxGetData
(
it
->
node
);
return
1
;
}
}
else
{
/* If we finished exporing the previous sub-tree, switch to the
* new one: go upper until a node is found where there are
* children representing keys lexicographically greater than the
* current key. */
while
(
1
)
{
int
old_noup
=
noup
;
/* Already on head? Can't go up, iteration finished. */
if
(
!
noup
&&
it
->
node
==
it
->
rt
->
head
)
{
it
->
flags
|=
RAX_ITER_EOF
;
it
->
stack
.
items
=
orig_stack_items
;
it
->
key_len
=
orig_key_len
;
it
->
node
=
orig_node
;
return
1
;
}
/* If there are no children at the current node, try parent's
* next child. */
unsigned
char
prevchild
=
it
->
key
[
it
->
key_len
-
1
];
if
(
!
noup
)
{
it
->
node
=
raxStackPop
(
&
it
->
stack
);
}
else
{
noup
=
0
;
}
/* Adjust the current key to represent the node we are
* at. */
int
todel
=
it
->
node
->
iscompr
?
it
->
node
->
size
:
1
;
raxIteratorDelChars
(
it
,
todel
);
/* Try visiting the next child if there was at least one
* additional child. */
if
(
!
it
->
node
->
iscompr
&&
it
->
node
->
size
>
(
old_noup
?
0
:
1
))
{
raxNode
**
cp
=
raxNodeFirstChildPtr
(
it
->
node
);
int
i
=
0
;
while
(
i
<
it
->
node
->
size
)
{
debugf
(
"SCAN NEXT %c
\n
"
,
it
->
node
->
data
[
i
]);
if
(
it
->
node
->
data
[
i
]
>
prevchild
)
break
;
i
++
;
cp
++
;
}
if
(
i
!=
it
->
node
->
size
)
{
debugf
(
"SCAN found a new node
\n
"
);
raxIteratorAddChars
(
it
,
it
->
node
->
data
+
i
,
1
);
if
(
!
raxStackPush
(
&
it
->
stack
,
it
->
node
))
return
0
;
memcpy
(
&
it
->
node
,
cp
,
sizeof
(
it
->
node
));
if
(
it
->
node
->
iskey
)
{
it
->
data
=
raxGetData
(
it
->
node
);
return
1
;
}
break
;
}
}
}
}
}
}
/* Seek the grestest key in the subtree at the current node. Return 0 on
* out of memory, otherwise 1. This is an helper function for different
* iteration functions below. */
int
raxSeekGreatest
(
raxIterator
*
it
)
{
while
(
it
->
node
->
size
)
{
if
(
it
->
node
->
iscompr
)
{
if
(
!
raxIteratorAddChars
(
it
,
it
->
node
->
data
,
it
->
node
->
size
))
return
0
;
}
else
{
if
(
!
raxIteratorAddChars
(
it
,
it
->
node
->
data
+
it
->
node
->
size
-
1
,
1
))
return
0
;
}
raxNode
**
cp
=
raxNodeLastChildPtr
(
it
->
node
);
if
(
!
raxStackPush
(
&
it
->
stack
,
it
->
node
))
return
0
;
memcpy
(
&
it
->
node
,
cp
,
sizeof
(
it
->
node
));
}
return
1
;
}
/* Like raxIteratorNextStep() but implements an iteration step moving
* to the lexicographically previous element. The 'noup' option has a similar
* effect to the one of raxIteratorPrevSte(). */
int
raxIteratorPrevStep
(
raxIterator
*
it
,
int
noup
)
{
if
(
it
->
flags
&
RAX_ITER_EOF
)
{
return
0
;
}
else
if
(
it
->
flags
&
RAX_ITER_JUST_SEEKED
)
{
it
->
flags
&=
~
RAX_ITER_JUST_SEEKED
;
return
1
;
}
/* Save key len, stack items and the node where we are currently
* so that on iterator EOF we can restore the current key and state. */
size_t
orig_key_len
=
it
->
key_len
;
size_t
orig_stack_items
=
it
->
stack
.
items
;
raxNode
*
orig_node
=
it
->
node
;
while
(
1
)
{
int
old_noup
=
noup
;
/* Already on head? Can't go up, iteration finished. */
if
(
!
noup
&&
it
->
node
==
it
->
rt
->
head
)
{
it
->
flags
|=
RAX_ITER_EOF
;
it
->
stack
.
items
=
orig_stack_items
;
it
->
key_len
=
orig_key_len
;
it
->
node
=
orig_node
;
return
1
;
}
unsigned
char
prevchild
=
it
->
key
[
it
->
key_len
-
1
];
if
(
!
noup
)
{
it
->
node
=
raxStackPop
(
&
it
->
stack
);
}
else
{
noup
=
0
;
}
/* Adjust the current key to represent the node we are
* at. */
int
todel
=
it
->
node
->
iscompr
?
it
->
node
->
size
:
1
;
raxIteratorDelChars
(
it
,
todel
);
/* Try visiting the prev child if there is at least one
* child. */
if
(
!
it
->
node
->
iscompr
&&
it
->
node
->
size
>
(
old_noup
?
0
:
1
))
{
raxNode
**
cp
=
raxNodeLastChildPtr
(
it
->
node
);
int
i
=
it
->
node
->
size
-
1
;
while
(
i
>=
0
)
{
debugf
(
"SCAN PREV %c
\n
"
,
it
->
node
->
data
[
i
]);
if
(
it
->
node
->
data
[
i
]
<
prevchild
)
break
;
i
--
;
cp
--
;
}
/* If we found a new subtree to explore in this node,
* go deeper following all the last children in order to
* find the key lexicographically greater. */
if
(
i
!=
-
1
)
{
debugf
(
"SCAN found a new node
\n
"
);
/* Enter the node we just found. */
if
(
!
raxIteratorAddChars
(
it
,
it
->
node
->
data
+
i
,
1
))
return
0
;
if
(
!
raxStackPush
(
&
it
->
stack
,
it
->
node
))
return
0
;
memcpy
(
&
it
->
node
,
cp
,
sizeof
(
it
->
node
));
/* Seek sub-tree max. */
if
(
!
raxSeekGreatest
(
it
))
return
0
;
}
}
/* Return the key: this could be the key we found scanning a new
* subtree, or if we did not find a new subtree to explore here,
* before giving up with this node, check if it's a key itself. */
if
(
it
->
node
->
iskey
)
{
it
->
data
=
raxGetData
(
it
->
node
);
return
1
;
}
}
}
/* Seek an iterator at the specified element.
* Return 0 if the seek failed for syntax error or out of memory. Otherwise
* 1 is returned. When 0 is returned for out of memory, errno is set to
* the ENOMEM value. */
int
raxSeek
(
raxIterator
*
it
,
const
char
*
op
,
unsigned
char
*
ele
,
size_t
len
)
{
int
eq
=
0
,
lt
=
0
,
gt
=
0
,
first
=
0
,
last
=
0
;
it
->
stack
.
items
=
0
;
/* Just resetting. Intialized by raxStart(). */
it
->
flags
|=
RAX_ITER_JUST_SEEKED
;
it
->
flags
&=
~
RAX_ITER_EOF
;
it
->
key_len
=
0
;
it
->
node
=
NULL
;
/* Set flags according to the operator used to perform the seek. */
if
(
op
[
0
]
==
'>'
)
{
gt
=
1
;
if
(
op
[
1
]
==
'='
)
eq
=
1
;
}
else
if
(
op
[
0
]
==
'<'
)
{
lt
=
1
;
if
(
op
[
1
]
==
'='
)
eq
=
1
;
}
else
if
(
op
[
0
]
==
'='
)
{
eq
=
1
;
}
else
if
(
op
[
0
]
==
'^'
)
{
first
=
1
;
}
else
if
(
op
[
0
]
==
'$'
)
{
last
=
1
;
}
else
{
errno
=
0
;
return
0
;
/* Error. */
}
/* If there are no elements, set the EOF condition immediately and
* return. */
if
(
it
->
rt
->
numele
==
0
)
{
it
->
flags
|=
RAX_ITER_EOF
;
return
1
;
}
if
(
first
)
{
/* Seeking the first key greater or equal to the empty string
* is equivalent to seeking the smaller key available. */
return
raxSeek
(
it
,
">="
,
NULL
,
0
);
}
if
(
last
)
{
/* Find the greatest key taking always the last child till a
* final node is found. */
it
->
node
=
it
->
rt
->
head
;
if
(
!
raxSeekGreatest
(
it
))
return
0
;
assert
(
it
->
node
->
iskey
);
return
1
;
}
/* We need to seek the specified key. What we do here is to actually
* perform a lookup, and later invoke the prev/next key code that
* we already use for iteration. */
int
splitpos
=
0
;
size_t
i
=
raxLowWalk
(
it
->
rt
,
ele
,
len
,
&
it
->
node
,
NULL
,
&
splitpos
,
&
it
->
stack
);
/* Return OOM on incomplete stack info. */
if
(
it
->
stack
.
oom
)
return
0
;
if
(
eq
&&
i
==
len
&&
(
!
it
->
node
->
iscompr
||
splitpos
==
0
)
&&
it
->
node
->
iskey
)
{
/* We found our node, since the key matches and we have an
* "equal" condition. */
if
(
!
raxIteratorAddChars
(
it
,
ele
,
len
))
return
0
;
/* OOM. */
}
else
if
(
lt
||
gt
)
{
/* Exact key not found or eq flag not set. We have to set as current
* key the one represented by the node we stopped at, and perform
* a next/prev operation to seek. To reconstruct the key at this node
* we start from the parent and go to the current node, accumulating
* the characters found along the way. */
if
(
!
raxStackPush
(
&
it
->
stack
,
it
->
node
))
return
0
;
for
(
size_t
j
=
1
;
j
<
it
->
stack
.
items
;
j
++
)
{
raxNode
*
parent
=
it
->
stack
.
stack
[
j
-
1
];
raxNode
*
child
=
it
->
stack
.
stack
[
j
];
if
(
parent
->
iscompr
)
{
if
(
!
raxIteratorAddChars
(
it
,
parent
->
data
,
parent
->
size
))
return
0
;
}
else
{
raxNode
**
cp
=
raxNodeFirstChildPtr
(
parent
);
unsigned
char
*
p
=
parent
->
data
;
while
(
1
)
{
raxNode
*
aux
;
memcpy
(
&
aux
,
cp
,
sizeof
(
aux
));
if
(
aux
==
child
)
break
;
cp
++
;
p
++
;
}
if
(
!
raxIteratorAddChars
(
it
,
p
,
1
))
return
0
;
}
}
raxStackPop
(
&
it
->
stack
);
/* We need to set the iterator in the correct state to call next/prev
* step in order to seek the desired element. */
debugf
(
"After initial seek: i=%d len=%d key=%.*s
\n
"
,
(
int
)
i
,
(
int
)
len
,
(
int
)
it
->
key_len
,
it
->
key
);
if
(
i
!=
len
&&
!
it
->
node
->
iscompr
)
{
/* If we stopped in the middle of a normal node because of a
* mismatch, add the mismatching character to the current key
* and call the iterator with the 'noup' flag so that it will try
* to seek the next/prev child in the current node directly based
* on the mismatching character. */
if
(
!
raxIteratorAddChars
(
it
,
ele
+
i
,
1
))
return
0
;
debugf
(
"Seek normal node on mismatch: %.*s
\n
"
,
(
int
)
it
->
key_len
,
(
char
*
)
it
->
key
);
it
->
flags
&=
~
RAX_ITER_JUST_SEEKED
;
if
(
lt
&&
!
raxIteratorPrevStep
(
it
,
1
))
return
0
;
if
(
gt
&&
!
raxIteratorNextStep
(
it
,
1
))
return
0
;
it
->
flags
|=
RAX_ITER_JUST_SEEKED
;
/* Ignore next call. */
}
else
if
(
i
!=
len
&&
it
->
node
->
iscompr
)
{
debugf
(
"Compressed mismatch: %.*s
\n
"
,
(
int
)
it
->
key_len
,
(
char
*
)
it
->
key
);
/* In case of a mismatch within a compressed node. */
int
nodechar
=
it
->
node
->
data
[
splitpos
];
int
keychar
=
ele
[
i
];
it
->
flags
&=
~
RAX_ITER_JUST_SEEKED
;
if
(
gt
)
{
/* If the key the compressed node represents is greater
* than our seek element, continue forward, otherwise set the
* state in order to go back to the next sub-tree. */
if
(
nodechar
>
keychar
)
{
if
(
!
raxIteratorNextStep
(
it
,
0
))
return
0
;
}
else
{
if
(
!
raxIteratorAddChars
(
it
,
it
->
node
->
data
,
it
->
node
->
size
))
return
0
;
if
(
!
raxIteratorNextStep
(
it
,
1
))
return
0
;
}
}
if
(
lt
)
{
/* If the key the compressed node represents is smaller
* than our seek element, seek the greater key in this
* subtree, otherwise set the state in order to go back to
* the previous sub-tree. */
if
(
nodechar
<
keychar
)
{
if
(
!
raxSeekGreatest
(
it
))
return
0
;
}
else
{
if
(
!
raxIteratorAddChars
(
it
,
it
->
node
->
data
,
it
->
node
->
size
))
return
0
;
if
(
!
raxIteratorPrevStep
(
it
,
1
))
return
0
;
}
}
it
->
flags
|=
RAX_ITER_JUST_SEEKED
;
/* Ignore next call. */
}
else
{
debugf
(
"No mismatch: %.*s
\n
"
,
(
int
)
it
->
key_len
,
(
char
*
)
it
->
key
);
/* If there was no mismatch we are into a node representing the
* key, (but which is not a key or the seek operator does not
* include 'eq'), or we stopped in the middle of a compressed node
* after processing all the key. Cotinue iterating as this was
* a legitimate key we stopped at. */
it
->
flags
&=
~
RAX_ITER_JUST_SEEKED
;
if
(
gt
&&
!
raxIteratorNextStep
(
it
,
0
))
return
0
;
if
(
lt
&&
!
raxIteratorPrevStep
(
it
,
0
))
return
0
;
it
->
flags
|=
RAX_ITER_JUST_SEEKED
;
/* Ignore next call. */
}
}
else
{
/* If we are here just eq was set but no match was found. */
it
->
flags
|=
RAX_ITER_EOF
;
return
1
;
}
return
1
;
}
/* Go to the next element in the scope of the iterator 'it'.
* If EOF (or out of memory) is reached, 0 is returned, otherwise 1 is
* returned. In case 0 is returned because of OOM, errno is set to ENOMEM. */
int
raxNext
(
raxIterator
*
it
)
{
if
(
!
raxIteratorNextStep
(
it
,
0
))
{
errno
=
ENOMEM
;
return
0
;
}
if
(
it
->
flags
&
RAX_ITER_EOF
)
{
errno
=
0
;
return
0
;
}
return
1
;
}
/* Go to the previous element in the scope of the iterator 'it'.
* If EOF (or out of memory) is reached, 0 is returned, otherwise 1 is
* returned. In case 0 is returned because of OOM, errno is set to ENOMEM. */
int
raxPrev
(
raxIterator
*
it
)
{
if
(
!
raxIteratorPrevStep
(
it
,
0
))
{
errno
=
ENOMEM
;
return
0
;
}
if
(
it
->
flags
&
RAX_ITER_EOF
)
{
errno
=
0
;
return
0
;
}
return
1
;
}
/* Perform a random walk starting in the current position of the iterator.
* Return 0 if the tree is empty or on out of memory. Otherwise 1 is returned
* and the iterator is set to the node reached after doing a random walk
* of 'steps' steps. If the 'steps' argument is 0, the random walk is performed
* using a random number of steps between 1 and two times the logarithm of
* the number of elements.
*
* NOTE: if you use this function to generate random elements from the radix
* tree, expect a disappointing distribution. A random walk produces good
* random elements if the tree is not sparse, however in the case of a radix
* tree certain keys will be reported much more often than others. At least
* this function should be able to expore every possible element eventually. */
int
raxRandomWalk
(
raxIterator
*
it
,
size_t
steps
)
{
if
(
it
->
rt
->
numele
==
0
)
{
it
->
flags
|=
RAX_ITER_EOF
;
return
0
;
}
if
(
steps
==
0
)
{
size_t
fle
=
floor
(
log
(
it
->
rt
->
numele
));
fle
*=
2
;
steps
=
1
+
rand
()
%
fle
;
}
raxNode
*
n
=
it
->
node
;
while
(
steps
>
0
||
!
n
->
iskey
)
{
int
numchildren
=
n
->
iscompr
?
1
:
n
->
size
;
int
r
=
rand
()
%
(
numchildren
+
(
n
!=
it
->
rt
->
head
));
if
(
r
==
numchildren
)
{
/* Go up to parent. */
n
=
raxStackPop
(
&
it
->
stack
);
int
todel
=
n
->
iscompr
?
n
->
size
:
1
;
raxIteratorDelChars
(
it
,
todel
);
}
else
{
/* Select a random child. */
if
(
n
->
iscompr
)
{
if
(
!
raxIteratorAddChars
(
it
,
n
->
data
,
n
->
size
))
return
0
;
}
else
{
if
(
!
raxIteratorAddChars
(
it
,
n
->
data
+
r
,
1
))
return
0
;
}
raxNode
**
cp
=
raxNodeFirstChildPtr
(
n
)
+
r
;
if
(
!
raxStackPush
(
&
it
->
stack
,
n
))
return
0
;
memcpy
(
&
n
,
cp
,
sizeof
(
n
));
}
if
(
n
->
iskey
)
steps
--
;
}
it
->
node
=
n
;
return
1
;
}
/* Compare the key currently pointed by the iterator to the specified
* key according to the specified operator. Returns 1 if the comparison is
* true, otherwise 0 is returned. */
int
raxCompare
(
raxIterator
*
iter
,
const
char
*
op
,
unsigned
char
*
key
,
size_t
key_len
)
{
int
eq
=
0
,
lt
=
0
,
gt
=
0
;
if
(
op
[
0
]
==
'='
||
op
[
1
]
==
'='
)
eq
=
1
;
if
(
op
[
1
]
==
'>'
)
gt
=
1
;
else
if
(
op
[
1
]
==
'<'
)
lt
=
1
;
else
if
(
op
[
1
]
!=
'='
)
return
0
;
/* Syntax error. */
size_t
minlen
=
key_len
<
iter
->
key_len
?
key_len
:
iter
->
key_len
;
int
cmp
=
memcmp
(
iter
->
key
,
key
,
minlen
);
/* Handle == */
if
(
lt
==
0
&&
gt
==
0
)
return
cmp
==
0
&&
key_len
==
iter
->
key_len
;
/* Handle >, >=, <, <= */
if
(
cmp
==
0
)
{
/* Same prefix: longer wins. */
if
(
eq
&&
key_len
==
iter
->
key_len
)
return
1
;
else
if
(
lt
)
return
iter
->
key_len
<
key_len
;
else
if
(
gt
)
return
iter
->
key_len
>
key_len
;
}
if
(
cmp
>
0
)
{
return
gt
?
1
:
0
;
}
else
/* (cmp < 0) */
{
return
lt
?
1
:
0
;
}
}
/* Free the iterator. */
void
raxStop
(
raxIterator
*
it
)
{
if
(
it
->
key
!=
it
->
key_static_string
)
rax_free
(
it
->
key
);
raxStackFree
(
&
it
->
stack
);
}
/* ----------------------------- Introspection ------------------------------ */
/* This function is mostly used for debugging and learning purposes.
* It shows an ASCII representation of a tree on standard output, outling
* all the nodes and the contained keys.
*
* The representation is as follow:
*
* "foobar" (compressed node)
* [abc] (normal node with three children)
* [abc]=0x12345678 (node is a key, pointing to value 0x12345678)
* [] (a normal empty node)
*
* Children are represented in new idented lines, each children prefixed by
* the "`-(x)" string, where "x" is the edge byte.
*
* [abc]
* `-(a) "ladin"
* `-(b) [kj]
* `-(c) []
*
* However when a node has a single child the following representation
* is used instead:
*
* [abc] -> "ladin" -> []
*/
/* The actual implementation of raxShow(). */
void
raxRecursiveShow
(
int
level
,
int
lpad
,
raxNode
*
n
)
{
char
s
=
n
->
iscompr
?
'"'
:
'['
;
char
e
=
n
->
iscompr
?
'"'
:
']'
;
int
numchars
=
printf
(
"%c%.*s%c"
,
s
,
n
->
size
,
n
->
data
,
e
);
if
(
n
->
iskey
)
{
numchars
+=
printf
(
"=%p"
,
raxGetData
(
n
));
}
int
numchildren
=
n
->
iscompr
?
1
:
n
->
size
;
/* Note that 7 and 4 magic constants are the string length
* of " `-(x) " and " -> " respectively. */
if
(
level
)
{
lpad
+=
(
numchildren
>
1
)
?
7
:
4
;
if
(
numchildren
==
1
)
lpad
+=
numchars
;
}
raxNode
**
cp
=
raxNodeFirstChildPtr
(
n
);
for
(
int
i
=
0
;
i
<
numchildren
;
i
++
)
{
char
*
branch
=
" `-(%c) "
;
if
(
numchildren
>
1
)
{
printf
(
"
\n
"
);
for
(
int
j
=
0
;
j
<
lpad
;
j
++
)
putchar
(
' '
);
printf
(
branch
,
n
->
data
[
i
]);
}
else
{
printf
(
" -> "
);
}
raxNode
*
child
;
memcpy
(
&
child
,
cp
,
sizeof
(
child
));
raxRecursiveShow
(
level
+
1
,
lpad
,
child
);
cp
++
;
}
}
/* Show a tree, as outlined in the comment above. */
void
raxShow
(
rax
*
rax
)
{
raxRecursiveShow
(
0
,
0
,
rax
->
head
);
putchar
(
'\n'
);
}
/* Used by debugnode() macro to show info about a given node. */
void
raxDebugShowNode
(
const
char
*
msg
,
raxNode
*
n
)
{
printf
(
"%s: %p [%.*s] key:%d size:%d children:"
,
msg
,
(
void
*
)
n
,
(
int
)
n
->
size
,
(
char
*
)
n
->
data
,
n
->
iskey
,
n
->
size
);
int
numcld
=
n
->
iscompr
?
1
:
n
->
size
;
raxNode
**
cldptr
=
raxNodeLastChildPtr
(
n
)
-
(
numcld
-
1
);
while
(
numcld
--
)
{
raxNode
*
child
;
memcpy
(
&
child
,
cldptr
,
sizeof
(
child
));
cldptr
++
;
printf
(
"%p "
,
(
void
*
)
child
);
}
printf
(
"
\n
"
);
fflush
(
stdout
);
}
src/rax.h
0 → 100644
View file @
89a9e5a9
#ifndef RAX_H
#define RAX_H
#include <stdint.h>
/* Representation of a radix tree as implemented in this file, that contains
* the strings "foo", "foobar" and "footer" after the insertion of each
* word. When the node represents a key inside the radix tree, we write it
* between [], otherwise it is written between ().
*
* This is the vanilla representation:
*
* (f) ""
* \
* (o) "f"
* \
* (o) "fo"
* \
* [t b] "foo"
* / \
* "foot" (e) (a) "foob"
* / \
* "foote" (r) (r) "fooba"
* / \
* "footer" [] [] "foobar"
*
* However, this implementation implements a very common optimization where
* successive nodes having a single child are "compressed" into the node
* itself as a string of characters, each representing a next-level child,
* and only the link to the node representing the last character node is
* provided inside the representation. So the above representation is turend
* into:
*
* ["foo"] ""
* |
* [t b] "foo"
* / \
* "foot" ("er") ("ar") "foob"
* / \
* "footer" [] [] "foobar"
*
* However this optimization makes the implementation a bit more complex.
* For instance if a key "first" is added in the above radix tree, a
* "node splitting" operation is needed, since the "foo" prefix is no longer
* composed of nodes having a single child one after the other. This is the
* above tree and the resulting node splitting after this event happens:
*
*
* (f) ""
* /
* (i o) "f"
* / \
* "firs" ("rst") (o) "fo"
* / \
* "first" [] [t b] "foo"
* / \
* "foot" ("er") ("ar") "foob"
* / \
* "footer" [] [] "foobar"
*
* Similarly after deletion, if a new chain of nodes having a single child
* is created (the chain must also not include nodes that represent keys),
* it must be compressed back into a single node.
*
*/
#define RAX_NODE_MAX_SIZE ((1<<29)-1)
typedef
struct
raxNode
{
uint32_t
iskey
:
1
;
/* Does this node contain a key? */
uint32_t
isnull
:
1
;
/* Associated value is NULL (don't store it). */
uint32_t
iscompr
:
1
;
/* Node is compressed. */
uint32_t
size
:
29
;
/* Number of children, or compressed string len. */
/* Data layout is as follows:
*
* If node is not compressed we have 'size' bytes, one for each children
* character, and 'size' raxNode pointers, point to each child node.
* Note how the character is not stored in the children but in the
* edge of the parents:
*
* [header strlen=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)
*
* if node is compressed (strlen != 0) the node has 1 children.
* In that case the 'size' bytes of the string stored immediately at
* the start of the data section, represent a sequence of successive
* nodes linked one after the other, for which only the last one in
* the sequence is actually represented as a node, and pointed to by
* the current compressed node.
*
* [header strlen=3][xyz][z-ptr](value-ptr?)
*
* Both compressed and not compressed nodes can represent a key
* with associated data in the radix tree at any level (not just terminal
* nodes).
*
* If the node has an associated key (iskey=1) and is not NULL
* (isnull=0), then after the raxNode pointers poiting to the
* childen, an additional value pointer is present (as you can see
* in the representation above as "value-ptr" field).
*/
unsigned
char
data
[];
}
raxNode
;
typedef
struct
rax
{
raxNode
*
head
;
uint64_t
numele
;
uint64_t
numnodes
;
}
rax
;
/* Stack data structure used by raxLowWalk() in order to, optionally, return
* a list of parent nodes to the caller. The nodes do not have a "parent"
* field for space concerns, so we use the auxiliary stack when needed. */
#define RAX_STACK_STATIC_ITEMS 32
typedef
struct
raxStack
{
void
**
stack
;
/* Points to static_items or an heap allocated array. */
size_t
items
,
maxitems
;
/* Number of items contained and total space. */
/* Up to RAXSTACK_STACK_ITEMS items we avoid to allocate on the heap
* and use this static array of pointers instead. */
void
*
static_items
[
RAX_STACK_STATIC_ITEMS
];
int
oom
;
/* True if pushing into this stack failed for OOM at some point. */
}
raxStack
;
/* Radix tree iterator state is encapsulated into this data structure. */
#define RAX_ITER_STATIC_LEN 128
#define RAX_ITER_JUST_SEEKED (1<<0)
/* Iterator was just seeked. Return current
element for the first iteration and
clear the flag. */
#define RAX_ITER_EOF (1<<1)
/* End of iteration reached. */
#define RAX_ITER_SAFE (1<<2)
/* Safe iterator, allows operations while
iterating. But it is slower. */
typedef
struct
raxIterator
{
int
flags
;
rax
*
rt
;
/* Radix tree we are iterating. */
unsigned
char
*
key
;
/* The current string. */
void
*
data
;
/* Data associated to this key. */
size_t
key_len
;
/* Current key length. */
size_t
key_max
;
/* Max key len the current key buffer can hold. */
unsigned
char
key_static_string
[
RAX_ITER_STATIC_LEN
];
raxNode
*
node
;
/* Current node. Only for unsafe iteration. */
raxStack
stack
;
/* Stack used for unsafe iteration. */
}
raxIterator
;
/* A special pointer returned for not found items. */
extern
void
*
raxNotFound
;
/* Exported API. */
rax
*
raxNew
(
void
);
int
raxInsert
(
rax
*
rax
,
unsigned
char
*
s
,
size_t
len
,
void
*
data
,
void
**
old
);
int
raxRemove
(
rax
*
rax
,
unsigned
char
*
s
,
size_t
len
,
void
**
old
);
void
*
raxFind
(
rax
*
rax
,
unsigned
char
*
s
,
size_t
len
);
void
raxFree
(
rax
*
rax
);
void
raxStart
(
raxIterator
*
it
,
rax
*
rt
);
int
raxSeek
(
raxIterator
*
it
,
const
char
*
op
,
unsigned
char
*
ele
,
size_t
len
);
int
raxNext
(
raxIterator
*
it
);
int
raxPrev
(
raxIterator
*
it
);
int
raxRandomWalk
(
raxIterator
*
it
,
size_t
steps
);
int
raxCompare
(
raxIterator
*
iter
,
const
char
*
op
,
unsigned
char
*
key
,
size_t
key_len
);
void
raxStop
(
raxIterator
*
it
);
void
raxShow
(
rax
*
rax
);
#endif
src/rax_malloc.h
0 → 100644
View file @
89a9e5a9
/* Rax -- A radix tree implementation.
*
* Copyright (c) 2017, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/* Allocator selection.
*
* This file is used in order to change the Rax allocator at compile time.
* Just define the following defines to what you want to use. Also add
* the include of your alternate allocator if needed (not needed in order
* to use the default libc allocator). */
#ifndef RAX_ALLOC_H
#define RAX_ALLOC_H
#include "zmalloc.h"
#define rax_malloc zmalloc
#define rax_realloc zrealloc
#define rax_free zfree
#endif
src/rdb.c
View file @
89a9e5a9
...
@@ -704,23 +704,30 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
...
@@ -704,23 +704,30 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
nwritten
+=
n
;
nwritten
+=
n
;
}
else
if
(
o
->
encoding
==
OBJ_ENCODING_SKIPLIST
)
{
}
else
if
(
o
->
encoding
==
OBJ_ENCODING_SKIPLIST
)
{
zset
*
zs
=
o
->
ptr
;
zset
*
zs
=
o
->
ptr
;
dictIterator
*
di
=
dictGetIterator
(
zs
->
dict
);
zskiplist
*
zsl
=
zs
->
zsl
;
dictEntry
*
de
;
if
((
n
=
rdbSaveLen
(
rdb
,
dictSize
(
zs
->
dict
)
))
==
-
1
)
return
-
1
;
if
((
n
=
rdbSaveLen
(
rdb
,
zs
l
->
length
))
==
-
1
)
return
-
1
;
nwritten
+=
n
;
nwritten
+=
n
;
while
((
de
=
dictNext
(
di
))
!=
NULL
)
{
/* We save the skiplist elements from the greatest to the smallest
sds
ele
=
dictGetKey
(
de
);
* (that's trivial since the elements are already ordered in the
double
*
score
=
dictGetVal
(
de
);
* skiplist): this improves the load process, since the next loaded
* element will always be the smaller, so adding to the skiplist
if
((
n
=
rdbSaveRawString
(
rdb
,(
unsigned
char
*
)
ele
,
sdslen
(
ele
)))
* will always immediately stop at the head, making the insertion
==
-
1
)
return
-
1
;
* O(1) instead of O(log(N)). */
zskiplistNode
*
zn
=
zsl
->
tail
;
while
(
zn
!=
NULL
)
{
if
((
n
=
rdbSaveRawString
(
rdb
,
(
unsigned
char
*
)
zn
->
ele
,
sdslen
(
zn
->
ele
)))
==
-
1
)
{
return
-
1
;
}
nwritten
+=
n
;
nwritten
+=
n
;
if
((
n
=
rdbSaveBinaryDoubleValue
(
rdb
,
*
score
))
==
-
1
)
return
-
1
;
if
((
n
=
rdbSaveBinaryDoubleValue
(
rdb
,
zn
->
score
))
==
-
1
)
return
-
1
;
nwritten
+=
n
;
nwritten
+=
n
;
zn
=
zn
->
backward
;
}
}
dictReleaseIterator
(
di
);
}
else
{
}
else
{
serverPanic
(
"Unknown sorted set encoding"
);
serverPanic
(
"Unknown sorted set encoding"
);
}
}
...
...
src/replication.c
View file @
89a9e5a9
...
@@ -122,7 +122,7 @@ void freeReplicationBacklog(void) {
...
@@ -122,7 +122,7 @@ void freeReplicationBacklog(void) {
/* Add data to the replication backlog.
/* Add data to the replication backlog.
* This function also increments the global replication offset stored at
* This function also increments the global replication offset stored at
* server.master_repl_offset, because there is no case where we want to feed
* server.master_repl_offset, because there is no case where we want to feed
* the backlog without incrementing the
buffer
. */
* the backlog without incrementing the
offset
. */
void
feedReplicationBacklog
(
void
*
ptr
,
size_t
len
)
{
void
feedReplicationBacklog
(
void
*
ptr
,
size_t
len
)
{
unsigned
char
*
p
=
ptr
;
unsigned
char
*
p
=
ptr
;
...
@@ -1078,6 +1078,7 @@ void replicationCreateMasterClient(int fd, int dbid) {
...
@@ -1078,6 +1078,7 @@ void replicationCreateMasterClient(int fd, int dbid) {
server
.
master
->
flags
|=
CLIENT_MASTER
;
server
.
master
->
flags
|=
CLIENT_MASTER
;
server
.
master
->
authenticated
=
1
;
server
.
master
->
authenticated
=
1
;
server
.
master
->
reploff
=
server
.
master_initial_offset
;
server
.
master
->
reploff
=
server
.
master_initial_offset
;
server
.
master
->
read_reploff
=
server
.
master
->
reploff
;
memcpy
(
server
.
master
->
replid
,
server
.
master_replid
,
memcpy
(
server
.
master
->
replid
,
server
.
master_replid
,
sizeof
(
server
.
master_replid
));
sizeof
(
server
.
master_replid
));
/* If master offset is set to -1, this master is old and is not
/* If master offset is set to -1, this master is old and is not
...
@@ -2118,6 +2119,18 @@ void replicationCacheMaster(client *c) {
...
@@ -2118,6 +2119,18 @@ void replicationCacheMaster(client *c) {
/* Unlink the client from the server structures. */
/* Unlink the client from the server structures. */
unlinkClient
(
c
);
unlinkClient
(
c
);
/* Reset the master client so that's ready to accept new commands:
* we want to discard te non processed query buffers and non processed
* offsets, including pending transactions, already populated arguments,
* pending outputs to the master. */
sdsclear
(
server
.
master
->
querybuf
);
sdsclear
(
server
.
master
->
pending_querybuf
);
server
.
master
->
read_reploff
=
server
.
master
->
reploff
;
if
(
c
->
flags
&
CLIENT_MULTI
)
discardTransaction
(
c
);
listEmpty
(
c
->
reply
);
c
->
bufpos
=
0
;
resetClient
(
c
);
/* Save the master. Server.master will be set to null later by
/* Save the master. Server.master will be set to null later by
* replicationHandleMasterDisconnection(). */
* replicationHandleMasterDisconnection(). */
server
.
cached_master
=
server
.
master
;
server
.
cached_master
=
server
.
master
;
...
...
src/scripting.c
View file @
89a9e5a9
...
@@ -900,7 +900,6 @@ void scriptingInit(int setup) {
...
@@ -900,7 +900,6 @@ void scriptingInit(int setup) {
server
.
lua_caller
=
NULL
;
server
.
lua_caller
=
NULL
;
server
.
lua_timedout
=
0
;
server
.
lua_timedout
=
0
;
server
.
lua_always_replicate_commands
=
0
;
/* Only DEBUG can change it.*/
server
.
lua_always_replicate_commands
=
0
;
/* Only DEBUG can change it.*/
server
.
lua_time_limit
=
LUA_SCRIPT_TIME_LIMIT
;
ldbInit
();
ldbInit
();
}
}
...
...
src/server.c
View file @
89a9e5a9
...
@@ -1412,6 +1412,7 @@ void initServerConfig(void) {
...
@@ -1412,6 +1412,7 @@ void initServerConfig(void) {
server
.
lazyfree_lazy_expire
=
CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE
;
server
.
lazyfree_lazy_expire
=
CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE
;
server
.
lazyfree_lazy_server_del
=
CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL
;
server
.
lazyfree_lazy_server_del
=
CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL
;
server
.
always_show_logo
=
CONFIG_DEFAULT_ALWAYS_SHOW_LOGO
;
server
.
always_show_logo
=
CONFIG_DEFAULT_ALWAYS_SHOW_LOGO
;
server
.
lua_time_limit
=
LUA_SCRIPT_TIME_LIMIT
;
server
.
lruclock
=
getLRUClock
();
server
.
lruclock
=
getLRUClock
();
resetServerSaveParams
();
resetServerSaveParams
();
...
@@ -1781,6 +1782,12 @@ void initServer(void) {
...
@@ -1781,6 +1782,12 @@ void initServer(void) {
createSharedObjects
();
createSharedObjects
();
adjustOpenFilesLimit
();
adjustOpenFilesLimit
();
server
.
el
=
aeCreateEventLoop
(
server
.
maxclients
+
CONFIG_FDSET_INCR
);
server
.
el
=
aeCreateEventLoop
(
server
.
maxclients
+
CONFIG_FDSET_INCR
);
if
(
server
.
el
==
NULL
)
{
serverLog
(
LL_WARNING
,
"Failed creating the event loop. Error message: '%s'"
,
strerror
(
errno
));
exit
(
1
);
}
server
.
db
=
zmalloc
(
sizeof
(
redisDb
)
*
server
.
dbnum
);
server
.
db
=
zmalloc
(
sizeof
(
redisDb
)
*
server
.
dbnum
);
/* Open the TCP listening socket for the user commands. */
/* Open the TCP listening socket for the user commands. */
...
@@ -1870,6 +1877,16 @@ void initServer(void) {
...
@@ -1870,6 +1877,16 @@ void initServer(void) {
if
(
server
.
sofd
>
0
&&
aeCreateFileEvent
(
server
.
el
,
server
.
sofd
,
AE_READABLE
,
if
(
server
.
sofd
>
0
&&
aeCreateFileEvent
(
server
.
el
,
server
.
sofd
,
AE_READABLE
,
acceptUnixHandler
,
NULL
)
==
AE_ERR
)
serverPanic
(
"Unrecoverable error creating server.sofd file event."
);
acceptUnixHandler
,
NULL
)
==
AE_ERR
)
serverPanic
(
"Unrecoverable error creating server.sofd file event."
);
/* Register a readable event for the pipe used to awake the event loop
* when a blocked client in a module needs attention. */
if
(
aeCreateFileEvent
(
server
.
el
,
server
.
module_blocked_pipe
[
0
],
AE_READABLE
,
moduleBlockedClientPipeReadable
,
NULL
)
==
AE_ERR
)
{
serverPanic
(
"Error registering the readable event for the module "
"blocked clients subsystem."
);
}
/* Open the AOF file if needed. */
/* Open the AOF file if needed. */
if
(
server
.
aof_state
==
AOF_ON
)
{
if
(
server
.
aof_state
==
AOF_ON
)
{
server
.
aof_fd
=
open
(
server
.
aof_filename
,
server
.
aof_fd
=
open
(
server
.
aof_filename
,
...
...
src/server.h
View file @
89a9e5a9
...
@@ -63,7 +63,9 @@ typedef long long mstime_t; /* millisecond time type. */
...
@@ -63,7 +63,9 @@ typedef long long mstime_t; /* millisecond time type. */
#include "util.h"
/* Misc functions useful in many places */
#include "util.h"
/* Misc functions useful in many places */
#include "latency.h"
/* Latency monitor API */
#include "latency.h"
/* Latency monitor API */
#include "sparkline.h"
/* ASCII graphs API */
#include "sparkline.h"
/* ASCII graphs API */
#include "quicklist.h"
#include "quicklist.h"
/* Lists are encoded as linked lists of
N-elements flat arrays */
#include "rax.h"
/* Radix tree */
/* Following includes allow test functions to be called from Redis main() */
/* Following includes allow test functions to be called from Redis main() */
#include "zipmap.h"
#include "zipmap.h"
...
@@ -661,6 +663,9 @@ typedef struct client {
...
@@ -661,6 +663,9 @@ typedef struct client {
redisDb
*
db
;
/* Pointer to currently SELECTed DB. */
redisDb
*
db
;
/* Pointer to currently SELECTed DB. */
robj
*
name
;
/* As set by CLIENT SETNAME. */
robj
*
name
;
/* As set by CLIENT SETNAME. */
sds
querybuf
;
/* Buffer we use to accumulate client queries. */
sds
querybuf
;
/* Buffer we use to accumulate client queries. */
sds
pending_querybuf
;
/* If this is a master, this buffer represents the
yet not applied replication stream that we
are receiving from the master. */
size_t
querybuf_peak
;
/* Recent (100ms or more) peak of querybuf size. */
size_t
querybuf_peak
;
/* Recent (100ms or more) peak of querybuf size. */
int
argc
;
/* Num of arguments of current command. */
int
argc
;
/* Num of arguments of current command. */
robj
**
argv
;
/* Arguments of current command. */
robj
**
argv
;
/* Arguments of current command. */
...
@@ -683,7 +688,8 @@ typedef struct client {
...
@@ -683,7 +688,8 @@ typedef struct client {
off_t
repldboff
;
/* Replication DB file offset. */
off_t
repldboff
;
/* Replication DB file offset. */
off_t
repldbsize
;
/* Replication DB file size. */
off_t
repldbsize
;
/* Replication DB file size. */
sds
replpreamble
;
/* Replication DB preamble. */
sds
replpreamble
;
/* Replication DB preamble. */
long
long
reploff
;
/* Replication offset if this is our master. */
long
long
read_reploff
;
/* Read replication offset if this is a master. */
long
long
reploff
;
/* Applied replication offset if this is a master. */
long
long
repl_ack_off
;
/* Replication ack offset, if this is a slave. */
long
long
repl_ack_off
;
/* Replication ack offset, if this is a slave. */
long
long
repl_ack_time
;
/* Replication ack time, if this is a slave. */
long
long
repl_ack_time
;
/* Replication ack time, if this is a slave. */
long
long
psync_initial_offset
;
/* FULLRESYNC reply offset other slaves
long
long
psync_initial_offset
;
/* FULLRESYNC reply offset other slaves
...
@@ -875,6 +881,9 @@ struct redisServer {
...
@@ -875,6 +881,9 @@ struct redisServer {
/* Modules */
/* Modules */
dict
*
moduleapi
;
/* Exported APIs dictionary for modules. */
dict
*
moduleapi
;
/* Exported APIs dictionary for modules. */
list
*
loadmodule_queue
;
/* List of modules to load at startup. */
list
*
loadmodule_queue
;
/* List of modules to load at startup. */
int
module_blocked_pipe
[
2
];
/* Pipe used to awake the event loop if a
client blocked on a module command needs
to be processed. */
/* Networking */
/* Networking */
int
port
;
/* TCP listening port */
int
port
;
/* TCP listening port */
int
tcp_backlog
;
/* TCP listen() backlog */
int
tcp_backlog
;
/* TCP listen() backlog */
...
@@ -1284,6 +1293,7 @@ void moduleFreeContext(struct RedisModuleCtx *ctx);
...
@@ -1284,6 +1293,7 @@ void moduleFreeContext(struct RedisModuleCtx *ctx);
void
unblockClientFromModule
(
client
*
c
);
void
unblockClientFromModule
(
client
*
c
);
void
moduleHandleBlockedClients
(
void
);
void
moduleHandleBlockedClients
(
void
);
void
moduleBlockedClientTimedOut
(
client
*
c
);
void
moduleBlockedClientTimedOut
(
client
*
c
);
void
moduleBlockedClientPipeReadable
(
aeEventLoop
*
el
,
int
fd
,
void
*
privdata
,
int
mask
);
/* Utils */
/* Utils */
long
long
ustime
(
void
);
long
long
ustime
(
void
);
...
...
src/zmalloc.c
View file @
89a9e5a9
...
@@ -418,8 +418,9 @@ size_t zmalloc_get_memory_size(void) {
...
@@ -418,8 +418,9 @@ size_t zmalloc_get_memory_size(void) {
if
(
sysctl
(
mib
,
2
,
&
size
,
&
len
,
NULL
,
0
)
==
0
)
if
(
sysctl
(
mib
,
2
,
&
size
,
&
len
,
NULL
,
0
)
==
0
)
return
(
size_t
)
size
;
return
(
size_t
)
size
;
return
0L
;
/* Failed? */
return
0L
;
/* Failed? */
#endif
/* sysctl and sysconf variants */
#else
return
0L
;
/* Unknown method to get the data. */
#endif
#else
#else
return
0L
;
/* Unknown OS. */
return
0L
;
/* Unknown OS. */
#endif
#endif
...
...
tests/integration/psync2-reg.tcl
0 → 100644
View file @
89a9e5a9
# Issue 3899 regression test.
# We create a chain of three instances: master -> slave -> slave2
# and continuously break the link while traffic is generated by
# redis-benchmark. At the end we check that the data is the same
# everywhere.
start_server
{
tags
{
"psync2"
}}
{
start_server
{}
{
start_server
{}
{
# Config
set debug_msg 0
;
# Enable additional debug messages
set no_exit 0
;
# Do not exit at end of the test
set duration 20
;
# Total test seconds
for
{
set j 0
}
{
$j
< 3
}
{
incr j
}
{
set R
(
$j
)
[
srv
[
expr 0-$j
]
client
]
set R_host
(
$j
)
[
srv
[
expr 0-$j
]
host
]
set R_port
(
$j
)
[
srv
[
expr 0-$j
]
port
]
if
{
$debug
_msg
}
{
puts
"Log file:
[
srv
[
expr 0-$j
]
stdout
]
"
}
}
# Setup the replication and backlog parameters
test
"PSYNC2 #3899 regression: setup"
{
$R
(
1
)
slaveof $R_host
(
0
)
$R_port
(
0
)
$R
(
2
)
slaveof $R_host
(
0
)
$R_port
(
0
)
$R
(
0
)
set foo bar
wait_for_condition 50 1000
{
[
$R
(
1
)
dbsize
]
== 1 &&
[
$R
(
2
)
dbsize
]
== 1
}
else
{
fail
"Slaves not replicating from master"
}
$R
(
0
)
config set repl-backlog-size 10mb
$R
(
1
)
config set repl-backlog-size 10mb
}
set cycle_start_time
[
clock milliseconds
]
set bench_pid
[
exec src/redis-benchmark -p $R_port
(
0
)
-n 10000000 -r 1000 incr __rand_int__ > /dev/null &
]
while 1
{
set elapsed
[
expr
{[
clock milliseconds
]
-$cycle_start_time
}]
if
{
$elapsed
> $duration*1000
}
break
if
{
rand
()
< .05
}
{
test
"PSYNC2 #3899 regression: kill first slave"
{
$R
(
1
)
client kill type master
}
}
if
{
rand
()
< .05
}
{
test
"PSYNC2 #3899 regression: kill chained slave"
{
$R
(
2
)
client kill type master
}
}
after 100
}
exec kill -9 $bench_pid
if
{
$debug
_msg
}
{
for
{
set j 0
}
{
$j
< 100
}
{
incr j
}
{
if
{
[
$R
(
0
)
debug digest
]
==
[
$R
(
1
)
debug digest
]
&&
[
$R
(
1
)
debug digest
]
==
[
$R
(
2
)
debug digest
]
}
break
puts
[
$R
(
0
)
debug digest
]
puts
[
$R
(
1
)
debug digest
]
puts
[
$R
(
2
)
debug digest
]
after 1000
}
}
test
"PSYNC2 #3899 regression: verify consistency"
{
wait_for_condition 50 1000
{
([
$R
(
0
)
debug digest
]
eq
[
$R
(
1
)
debug digest
])
&&
([
$R
(
1
)
debug digest
]
eq
[
$R
(
2
)
debug digest
])
}
else
{
fail
"The three instances have different data sets"
}
}
}}}
tests/integration/replication-psync.tcl
View file @
89a9e5a9
...
@@ -110,7 +110,7 @@ foreach diskless {no yes} {
...
@@ -110,7 +110,7 @@ foreach diskless {no yes} {
test_psync
{
no reconnection, just sync
}
6 1000000 3600 0
{
test_psync
{
no reconnection, just sync
}
6 1000000 3600 0
{
}
$diskless 0
}
$diskless 0
test_psync
{
ok psync
}
6 1000000 3600 0
{
test_psync
{
ok psync
}
6 1000000
00
3600 0
{
assert
{[
s -1 sync_partial_ok
]
> 0
}
assert
{[
s -1 sync_partial_ok
]
> 0
}
}
$diskless 1
}
$diskless 1
...
...
tests/test_helper.tcl
View file @
89a9e5a9
...
@@ -42,6 +42,7 @@ set ::all_tests {
...
@@ -42,6 +42,7 @@ set ::all_tests {
integration/convert-zipmap-hash-on-load
integration/convert-zipmap-hash-on-load
integration/logging
integration/logging
integration/psync2
integration/psync2
integration/psync2-reg
unit/pubsub
unit/pubsub
unit/slowlog
unit/slowlog
unit/scripting
unit/scripting
...
...
tests/unit/maxmemory.tcl
View file @
89a9e5a9
...
@@ -24,7 +24,7 @@ start_server {tags {"maxmemory"}} {
...
@@ -24,7 +24,7 @@ start_server {tags {"maxmemory"}} {
}
}
foreach policy
{
foreach policy
{
allkeys-random allkeys-lru
volatile-lr
u volatile-random volatile-ttl
allkeys-random allkeys-lru
allkeys-lfu volatile-lru volatile-lf
u volatile-random volatile-ttl
}
{
}
{
test
"maxmemory - is the memory limit honoured? (policy
$policy
)"
{
test
"maxmemory - is the memory limit honoured? (policy
$policy
)"
{
# make sure to start with a blank instance
# make sure to start with a blank instance
...
@@ -98,7 +98,7 @@ start_server {tags {"maxmemory"}} {
...
@@ -98,7 +98,7 @@ start_server {tags {"maxmemory"}} {
}
}
foreach policy
{
foreach policy
{
volatile-lru volatile-random volatile-ttl
volatile-lru
volatile-lfu
volatile-random volatile-ttl
}
{
}
{
test
"maxmemory - policy
$policy
should only remove volatile keys."
{
test
"maxmemory - policy
$policy
should only remove volatile keys."
{
# make sure to start with a blank instance
# make sure to start with a blank instance
...
...
tests/unit/memefficiency.tcl
View file @
89a9e5a9
...
@@ -35,3 +35,51 @@ start_server {tags {"memefficiency"}} {
...
@@ -35,3 +35,51 @@ start_server {tags {"memefficiency"}} {
}
}
}
}
}
}
if 0
{
start_server
{
tags
{
"defrag"
}}
{
if
{[
string match
{
*jemalloc*
}
[
s mem_allocator
]]}
{
test
"Active defrag"
{
r config set activedefrag no
r config set active-defrag-threshold-lower 5
r config set active-defrag-ignore-bytes 2mb
r config set maxmemory 100mb
r config set maxmemory-policy allkeys-lru
r debug populate 700000 asdf 150
r debug populate 170000 asdf 300
set frag
[
s mem_fragmentation_ratio
]
assert
{
$frag
>= 1.7
}
r config set activedefrag yes
after 1500
;
# active defrag tests the status once a second.
set hits
[
s active_defrag_hits
]
# wait for the active defrag to stop working
set tries 0
while
{
True
}
{
incr tries
after 500
set prev_hits $hits
set hits
[
s active_defrag_hits
]
if
{
$hits
== $prev_hits
}
{
break
}
assert
{
$tries
< 100
}
}
# TODO: we need to expose more accurate fragmentation info
# i.e. the allocator used and active pages
# instead we currently look at RSS so we need to ask for purge
r memory purge
# Test the the fragmentation is lower and that the defragger
# stopped working
set frag
[
s mem_fragmentation_ratio
]
assert
{
$frag
< 1.55
}
set misses
[
s active_defrag_misses
]
after 500
set misses2
[
s active_defrag_misses
]
assert
{
$misses2
== $misses
}
}
}
}
}
utils/create-cluster/create-cluster
View file @
89a9e5a9
...
@@ -86,6 +86,12 @@ then
...
@@ -86,6 +86,12 @@ then
exit
0
exit
0
fi
fi
if
[
"
$1
"
==
"clean-logs"
]
then
rm
-rf
*
.log
exit
0
fi
echo
"Usage:
$0
[start|create|stop|watch|tail|clean]"
echo
"Usage:
$0
[start|create|stop|watch|tail|clean]"
echo
"start -- Launch Redis Cluster instances."
echo
"start -- Launch Redis Cluster instances."
echo
"create -- Create a cluster using redis-trib create."
echo
"create -- Create a cluster using redis-trib create."
...
@@ -93,3 +99,4 @@ echo "stop -- Stop Redis Cluster instances."
...
@@ -93,3 +99,4 @@ echo "stop -- Stop Redis Cluster instances."
echo
"watch -- Show CLUSTER NODES output (first 30 lines) of first node."
echo
"watch -- Show CLUSTER NODES output (first 30 lines) of first node."
echo
"tail <id> -- Run tail -f of instance at base port + ID."
echo
"tail <id> -- Run tail -f of instance at base port + ID."
echo
"clean -- Remove all instances data, logs, configs."
echo
"clean -- Remove all instances data, logs, configs."
echo
"clean-logs -- Remove just instances logs."
utils/releasetools/04_release_hash.sh
View file @
89a9e5a9
#!/bin/bash
#!/bin/bash
SHA
=
$(
curl
-s
http://download.redis.io/releases/redis-
${
1
}
.tar.gz | shasum |
cut
-f
1
-d
' '
)
SHA
=
$(
curl
-s
http://download.redis.io/releases/redis-
${
1
}
.tar.gz | shasum
-a
256
|
cut
-f
1
-d
' '
)
ENTRY
=
"hash redis-
${
1
}
.tar.gz sha
1
$SHA
http://download.redis.io/releases/redis-
${
1
}
.tar.gz"
ENTRY
=
"hash redis-
${
1
}
.tar.gz sha
256
$SHA
http://download.redis.io/releases/redis-
${
1
}
.tar.gz"
echo
$ENTRY
>>
~/hack/redis-hashes/README
echo
$ENTRY
>>
~/hack/redis-hashes/README
vi ~/hack/redis-hashes/README
vi ~/hack/redis-hashes/README
echo
"Press any key to commit, Ctrl-C to abort)."
echo
"Press any key to commit, Ctrl-C to abort)."
...
...
Prev
1
…
5
6
7
8
9
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment