diff --git a/deps/jemalloc-win/projects/jemalloc/proj.win32/vc2013/jemalloc.vcxproj b/deps/jemalloc-win/projects/jemalloc/proj.win32/vc2013/jemalloc.vcxproj
index d0158cd0..425a08be 100644
--- a/deps/jemalloc-win/projects/jemalloc/proj.win32/vc2013/jemalloc.vcxproj
+++ b/deps/jemalloc-win/projects/jemalloc/proj.win32/vc2013/jemalloc.vcxproj
@@ -23,7 +23,7 @@
{8B897E33-6428-4254-8335-4911D179BAD1}
Jemalloc_vc2008
Win32Proj
- 10.0.14393.0
+ 10.0.17134.0
diff --git a/msvs/RedisBenchmark/RedisBenchmark.vcxproj b/msvs/RedisBenchmark/RedisBenchmark.vcxproj
index 64284512..45da755b 100644
--- a/msvs/RedisBenchmark/RedisBenchmark.vcxproj
+++ b/msvs/RedisBenchmark/RedisBenchmark.vcxproj
@@ -22,7 +22,7 @@
{B00D4BB5-44DE-405E-839C-D16F547006CF}
Win32Proj
RedisBenchmark
- 10.0.14393.0
+ 10.0.17134.0
diff --git a/msvs/RedisCli/RedisCli.vcxproj b/msvs/RedisCli/RedisCli.vcxproj
index fb1d4421..8f372232 100644
--- a/msvs/RedisCli/RedisCli.vcxproj
+++ b/msvs/RedisCli/RedisCli.vcxproj
@@ -21,7 +21,7 @@
{392BBB91-3934-4A56-AF42-65C5728311E8}
RedisCli
- 10.0.14393.0
+ 10.0.17134.0
diff --git a/msvs/RedisForWindows.aps b/msvs/RedisForWindows.aps
index eeeff945..47e5aaca 100644
Binary files a/msvs/RedisForWindows.aps and b/msvs/RedisForWindows.aps differ
diff --git a/msvs/RedisServer.vcxproj b/msvs/RedisServer.vcxproj
index dfbc94c1..3a3f5f19 100644
--- a/msvs/RedisServer.vcxproj
+++ b/msvs/RedisServer.vcxproj
@@ -21,7 +21,7 @@
Win32Proj
{46842776-68A5-EC98-6A09-1859BBFC73AA}
- 10.0.14393.0
+ 10.0.17134.0
diff --git a/msvs/geohash/geohash.vcxproj b/msvs/geohash/geohash.vcxproj
index 5f291422..9b043a37 100644
--- a/msvs/geohash/geohash.vcxproj
+++ b/msvs/geohash/geohash.vcxproj
@@ -30,7 +30,7 @@
{2B888C28-52CF-4E94-993C-B43D77E0B91A}
Win32Proj
geohash
- 10.0.14393.0
+ 10.0.17134.0
diff --git a/msvs/hiredis/hiredis.vcxproj b/msvs/hiredis/hiredis.vcxproj
index 25c8f59a..58bf09d9 100644
--- a/msvs/hiredis/hiredis.vcxproj
+++ b/msvs/hiredis/hiredis.vcxproj
@@ -22,7 +22,7 @@
{13E85053-54B3-487B-8DDB-3430B1C1B3BF}
Win32Proj
hiredis
- 10.0.14393.0
+ 10.0.17134.0
diff --git a/msvs/lua/lua/lua.vcxproj b/msvs/lua/lua/lua.vcxproj
index 150603dc..72acdc93 100644
--- a/msvs/lua/lua/lua.vcxproj
+++ b/msvs/lua/lua/lua.vcxproj
@@ -23,7 +23,7 @@
v4.5
ManagedCProj
lua
- 10.0.14393.0
+ 10.0.17134.0
diff --git a/src/Win32_Interop/Win32_Interop.vcxproj b/src/Win32_Interop/Win32_Interop.vcxproj
index 24ddff26..242cec1b 100644
--- a/src/Win32_Interop/Win32_Interop.vcxproj
+++ b/src/Win32_Interop/Win32_Interop.vcxproj
@@ -108,7 +108,7 @@ link.exe -dll -noentry resources/EventLog.res -out:$(TargetDir)EventLog.dll
Win32Proj
Win32_Redis
Win32_Interop
- 10.0.14393.0
+ 10.0.17134.0
diff --git a/src/adlist.c b/src/adlist.c
index d7e1ffca..02f31e7a 100644
--- a/src/adlist.c
+++ b/src/adlist.c
@@ -356,7 +356,7 @@ void listJoin(list *l, list *o) {
else
l->head = o->head;
- l->tail = o->tail;
+ if (o->tail) l->tail = o->tail;
l->len += o->len;
/* Setup other as an empty list. */
diff --git a/src/adlist.c.bak b/src/adlist.c.bak
new file mode 100644
index 00000000..d7e1ffca
--- /dev/null
+++ b/src/adlist.c.bak
@@ -0,0 +1,365 @@
+/* adlist.c - A generic doubly linked list implementation
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * 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.
+ */
+#ifdef _WIN32
+#include "Win32_Interop/Win32_Portability.h"
+#include "Win32_Interop/win32_types.h"
+#endif
+
+#include
+#include "adlist.h"
+#include "zmalloc.h"
+
+/* Create a new list. The created list can be freed with
+ * AlFreeList(), but private value of every node need to be freed
+ * by the user before to call AlFreeList().
+ *
+ * On error, NULL is returned. Otherwise the pointer to the new list. */
+list *listCreate(void)
+{
+ struct list *list;
+
+ if ((list = zmalloc(sizeof(*list))) == NULL)
+ return NULL;
+ list->head = list->tail = NULL;
+ list->len = 0;
+ list->dup = NULL;
+ list->free = NULL;
+ list->match = NULL;
+ return list;
+}
+
+/* Remove all the elements from the list without destroying the list itself. */
+void listEmpty(list *list)
+{
+ PORT_ULONG len;
+ listNode *current, *next;
+
+ current = list->head;
+ len = list->len;
+ while(len--) {
+ next = current->next;
+ if (list->free) list->free(current->value);
+ zfree(current);
+ current = next;
+ }
+ list->head = list->tail = NULL;
+ list->len = 0;
+}
+
+/* Free the whole list.
+ *
+ * This function can't fail. */
+void listRelease(list *list)
+{
+ listEmpty(list);
+ zfree(list);
+}
+
+/* Add a new node to the list, to head, containing the specified 'value'
+ * pointer as value.
+ *
+ * On error, NULL is returned and no operation is performed (i.e. the
+ * list remains unaltered).
+ * On success the 'list' pointer you pass to the function is returned. */
+list *listAddNodeHead(list *list, void *value)
+{
+ listNode *node;
+
+ if ((node = zmalloc(sizeof(*node))) == NULL)
+ return NULL;
+ node->value = value;
+ if (list->len == 0) {
+ list->head = list->tail = node;
+ node->prev = node->next = NULL;
+ } else {
+ node->prev = NULL;
+ node->next = list->head;
+ list->head->prev = node;
+ list->head = node;
+ }
+ list->len++;
+ return list;
+}
+
+/* Add a new node to the list, to tail, containing the specified 'value'
+ * pointer as value.
+ *
+ * On error, NULL is returned and no operation is performed (i.e. the
+ * list remains unaltered).
+ * On success the 'list' pointer you pass to the function is returned. */
+list *listAddNodeTail(list *list, void *value)
+{
+ listNode *node;
+
+ if ((node = zmalloc(sizeof(*node))) == NULL)
+ return NULL;
+ node->value = value;
+ if (list->len == 0) {
+ list->head = list->tail = node;
+ node->prev = node->next = NULL;
+ } else {
+ node->prev = list->tail;
+ node->next = NULL;
+ list->tail->next = node;
+ list->tail = node;
+ }
+ list->len++;
+ return list;
+}
+
+list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
+ listNode *node;
+
+ if ((node = zmalloc(sizeof(*node))) == NULL)
+ return NULL;
+ node->value = value;
+ if (after) {
+ node->prev = old_node;
+ node->next = old_node->next;
+ if (list->tail == old_node) {
+ list->tail = node;
+ }
+ } else {
+ node->next = old_node;
+ node->prev = old_node->prev;
+ if (list->head == old_node) {
+ list->head = node;
+ }
+ }
+ if (node->prev != NULL) {
+ node->prev->next = node;
+ }
+ if (node->next != NULL) {
+ node->next->prev = node;
+ }
+ list->len++;
+ return list;
+}
+
+/* Remove the specified node from the specified list.
+ * It's up to the caller to free the private value of the node.
+ *
+ * This function can't fail. */
+void listDelNode(list *list, listNode *node)
+{
+ if (node->prev)
+ node->prev->next = node->next;
+ else
+ list->head = node->next;
+ if (node->next)
+ node->next->prev = node->prev;
+ else
+ list->tail = node->prev;
+ if (list->free) list->free(node->value);
+ zfree(node);
+ list->len--;
+}
+
+/* Returns a list iterator 'iter'. After the initialization every
+ * call to listNext() will return the next element of the list.
+ *
+ * This function can't fail. */
+listIter *listGetIterator(list *list, int direction)
+{
+ listIter *iter;
+
+ if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
+ if (direction == AL_START_HEAD)
+ iter->next = list->head;
+ else
+ iter->next = list->tail;
+ iter->direction = direction;
+ return iter;
+}
+
+/* Release the iterator memory */
+void listReleaseIterator(listIter *iter) {
+ zfree(iter);
+}
+
+/* Create an iterator in the list private iterator structure */
+void listRewind(list *list, listIter *li) {
+ li->next = list->head;
+ li->direction = AL_START_HEAD;
+}
+
+void listRewindTail(list *list, listIter *li) {
+ li->next = list->tail;
+ li->direction = AL_START_TAIL;
+}
+
+/* Return the next element of an iterator.
+ * It's valid to remove the currently returned element using
+ * listDelNode(), but not to remove other elements.
+ *
+ * The function returns a pointer to the next element of the list,
+ * or NULL if there are no more elements, so the classical usage patter
+ * is:
+ *
+ * iter = listGetIterator(list,);
+ * while ((node = listNext(iter)) != NULL) {
+ * doSomethingWith(listNodeValue(node));
+ * }
+ *
+ * */
+listNode *listNext(listIter *iter)
+{
+ listNode *current = iter->next;
+
+ if (current != NULL) {
+ if (iter->direction == AL_START_HEAD)
+ iter->next = current->next;
+ else
+ iter->next = current->prev;
+ }
+ return current;
+}
+
+/* Duplicate the whole list. On out of memory NULL is returned.
+ * On success a copy of the original list is returned.
+ *
+ * The 'Dup' method set with listSetDupMethod() function is used
+ * to copy the node value. Otherwise the same pointer value of
+ * the original node is used as value of the copied node.
+ *
+ * The original list both on success or error is never modified. */
+list *listDup(list *orig)
+{
+ list *copy;
+ listIter iter;
+ listNode *node;
+
+ if ((copy = listCreate()) == NULL)
+ return NULL;
+ copy->dup = orig->dup;
+ copy->free = orig->free;
+ copy->match = orig->match;
+ listRewind(orig, &iter);
+ while((node = listNext(&iter)) != NULL) {
+ void *value;
+
+ if (copy->dup) {
+ value = copy->dup(node->value);
+ if (value == NULL) {
+ listRelease(copy);
+ return NULL;
+ }
+ } else
+ value = node->value;
+ if (listAddNodeTail(copy, value) == NULL) {
+ listRelease(copy);
+ return NULL;
+ }
+ }
+ return copy;
+}
+
+/* Search the list for a node matching a given key.
+ * The match is performed using the 'match' method
+ * set with listSetMatchMethod(). If no 'match' method
+ * is set, the 'value' pointer of every node is directly
+ * compared with the 'key' pointer.
+ *
+ * On success the first matching node pointer is returned
+ * (search starts from head). If no matching node exists
+ * NULL is returned. */
+listNode *listSearchKey(list *list, void *key)
+{
+ listIter iter;
+ listNode *node;
+
+ listRewind(list, &iter);
+ while((node = listNext(&iter)) != NULL) {
+ if (list->match) {
+ if (list->match(node->value, key)) {
+ return node;
+ }
+ } else {
+ if (key == node->value) {
+ return node;
+ }
+ }
+ }
+ return NULL;
+}
+
+/* Return the element at the specified zero-based index
+ * where 0 is the head, 1 is the element next to head
+ * and so on. Negative integers are used in order to count
+ * from the tail, -1 is the last element, -2 the penultimate
+ * and so on. If the index is out of range NULL is returned. */
+listNode *listIndex(list *list, PORT_LONG index) {
+ listNode *n;
+
+ if (index < 0) {
+ index = (-index)-1;
+ n = list->tail;
+ while(index-- && n) n = n->prev;
+ } else {
+ n = list->head;
+ while(index-- && n) n = n->next;
+ }
+ return n;
+}
+
+/* Rotate the list removing the tail node and inserting it to the head. */
+void listRotate(list *list) {
+ listNode *tail = list->tail;
+
+ if (listLength(list) <= 1) return;
+
+ /* Detach current tail */
+ list->tail = tail->prev;
+ list->tail->next = NULL;
+ /* Move it as head */
+ list->head->prev = tail;
+ tail->prev = NULL;
+ tail->next = list->head;
+ list->head = tail;
+}
+
+/* Add all the elements of the list 'o' at the end of the
+ * list 'l'. The list 'other' remains empty but otherwise valid. */
+void listJoin(list *l, list *o) {
+ if (o->head)
+ o->head->prev = l->tail;
+
+ if (l->tail)
+ l->tail->next = o->head;
+ else
+ l->head = o->head;
+
+ l->tail = o->tail;
+ l->len += o->len;
+
+ /* Setup other as an empty list. */
+ o->head = o->tail = NULL;
+ o->len = 0;
+}
diff --git a/src/ae.c b/src/ae.c
index ff4662f6..1d2b03af 100644
--- a/src/ae.c
+++ b/src/ae.c
@@ -51,6 +51,7 @@
#include
#include
+#include "ae.h"
#include "zmalloc.h"
#include "config.h"
@@ -173,6 +174,10 @@ void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
aeFileEvent *fe = &eventLoop->events[fd];
if (fe->mask == AE_NONE) return;
+ /* We want to always remove AE_BARRIER if set when AE_WRITABLE
+ * is removed. */
+ if (mask & AE_WRITABLE) mask |= AE_BARRIER;
+
aeApiDelEvent(eventLoop, fd, mask);
fe->mask = fe->mask & (~mask);
if (fd == eventLoop->maxfd && fe->mask == AE_NONE) {
@@ -238,7 +243,10 @@ PORT_LONGLONG aeCreateTimeEvent(aeEventLoop *eventLoop, PORT_LONGLONG millisecon
te->timeProc = proc;
te->finalizerProc = finalizerProc;
te->clientData = clientData;
+ te->prev = NULL;
te->next = eventLoop->timeEventHead;
+ if (te->next)
+ te->next->prev = te;
eventLoop->timeEventHead = te;
return id;
}
@@ -285,7 +293,7 @@ static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
/* Process time events */
static int processTimeEvents(aeEventLoop *eventLoop) {
int processed = 0;
- aeTimeEvent *te, *prev;
+ aeTimeEvent *te;
PORT_LONGLONG maxId;
time_t now = time(NULL);
@@ -306,7 +314,6 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
}
eventLoop->lastTime = now;
- prev = NULL;
te = eventLoop->timeEventHead;
maxId = eventLoop->timeEventNextId - 1;
while (te) {
@@ -316,10 +323,12 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
/* Remove events scheduled for deletion. */
if (te->id == AE_DELETED_EVENT_ID) {
aeTimeEvent *next = te->next;
- if (prev == NULL)
- eventLoop->timeEventHead = te->next;
+ if (te->prev)
+ te->prev->next = te->next;
else
- prev->next = te->next;
+ eventLoop->timeEventHead = te->next;
+ if (te->next)
+ te->next->prev = te->prev;
if (te->finalizerProc)
te->finalizerProc(eventLoop, te->clientData);
zfree(te);
@@ -347,12 +356,10 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
processed++;
if (retval != AE_NOMORE) {
aeAddMillisecondsToNow(retval, &te->when_sec, &te->when_ms);
- }
- else {
+ } else {
te->id = AE_DELETED_EVENT_ID;
}
}
- prev = te;
te = te->next;
}
return processed;
@@ -386,7 +393,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
/* Note that we want call select() even if there are no
- * file events to process as PORT_LONG as we want to process time
+ * file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
if (eventLoop->maxfd != -1 ||
@@ -412,21 +419,18 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
if (ms > 0) {
tvp->tv_sec = ms / 1000;
tvp->tv_usec = (ms % 1000) * 1000;
- }
- else {
+ } else {
tvp->tv_sec = 0;
tvp->tv_usec = 0;
}
- }
- else {
+ } else {
/* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to set the timeout
* to zero */
if (flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
- }
- else {
+ } else {
/* Otherwise we can block */
tvp = NULL; /* wait forever */
}
@@ -444,19 +448,49 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
int mask = eventLoop->fired[j].mask;
int fd = eventLoop->fired[j].fd;
- int rfired = 0;
+ int fired = 0; /* Number of events fired for current fd. */
- /* note the fe->mask & mask & ... code: maybe an already processed
- * event removed an element that fired and we still didn't
- * processed, so we check if the event is still valid. */
- if (fe->mask & mask & AE_READABLE) {
- rfired = 1;
+ /* Normally we execute the readable event first, and the writable
+ * event laster. This is useful as sometimes we may be able
+ * to serve the reply of a query immediately after processing the
+ * query.
+ *
+ * However if AE_BARRIER is set in the mask, our application is
+ * asking us to do the reverse: never fire the writable event
+ * after the readable. In such a case, we invert the calls.
+ * This is useful when, for instance, we want to do things
+ * in the beforeSleep() hook, like fsynching a file to disk,
+ * before replying to a client. */
+ int invert = fe->mask & AE_BARRIER;
+
+ /* Note the "fe->mask & mask & ..." code: maybe an already
+ * processed event removed an element that fired and we still
+ * didn't processed, so we check if the event is still valid.
+ *
+ * Fire the readable event if the call sequence is not
+ * inverted. */
+ if (!invert && fe->mask & mask & AE_READABLE) {
fe->rfileProc(eventLoop, fd, fe->clientData, mask);
+ fired++;
}
+
+ /* Fire the writable event. */
if (fe->mask & mask & AE_WRITABLE) {
- if (!rfired || fe->wfileProc != fe->rfileProc)
+ if (!fired || fe->wfileProc != fe->rfileProc) {
fe->wfileProc(eventLoop, fd, fe->clientData, mask);
+ fired++;
+ }
}
+
+ /* If we have to invert the call, fire the readable event now
+ * after the writable one. */
+ if (invert && fe->mask & mask & AE_READABLE) {
+ if (!fired || fe->wfileProc != fe->rfileProc) {
+ fe->rfileProc(eventLoop,fd,fe->clientData,mask);
+ fired++;
+ }
+ }
+
processed++;
}
}
@@ -478,15 +512,13 @@ int aeWait(int fd, int mask, PORT_LONGLONG milliseconds) {
if (mask & AE_READABLE) pfd.events |= POLLIN;
if (mask & AE_WRITABLE) pfd.events |= POLLOUT;
- if ((retval = poll(&pfd, 1, (int) milliseconds)) == 1) {
- WIN_PORT_FIX /* cast (int) */
+ if ((retval = poll(&pfd, 1, (int) milliseconds)) == 1) { WIN_PORT_FIX /* cast (int) */
if (pfd.revents & POLLIN) retmask |= AE_READABLE;
if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE;
if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE;
return retmask;
- }
- else {
+ } else {
return retval;
}
}
@@ -510,4 +542,4 @@ void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep) {
eventLoop->aftersleep = aftersleep;
-}
\ No newline at end of file
+}
diff --git a/src/ae.c.bak b/src/ae.c.bak
new file mode 100644
index 00000000..c44fa388
--- /dev/null
+++ b/src/ae.c.bak
@@ -0,0 +1,515 @@
+/* A simple event-driven programming library. Originally I wrote this code
+ * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
+ * it in form of a library for easy reuse.
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * 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.
+ */
+
+#ifdef _WIN32
+#include "Win32_Interop\win32_types_hiredis.h"
+#endif
+#include "ae.h"
+#include
+#include
+#ifdef _WIN32
+#include
+#include
+#include "../src/Win32_Interop/Win32_FDAPI.h"
+#include "../src/Win32_Interop/Win32_Service.h"
+#else
+#include
+#include
+#include
+#endif
+#include
+#include
+#include
+#include
+
+#include "ae.h"
+#include "zmalloc.h"
+#include "config.h"
+
+ /* Include the best multiplexing layer supported by this system.
+ * The following should be ordered by performances, descending. */
+#ifdef _WIN32
+#include "ae_wsiocp.c"
+#else
+#ifdef HAVE_EVPORT
+#include "ae_evport.c"
+#else
+#ifdef HAVE_EPOLL
+#include "ae_epoll.c"
+#else
+#ifdef HAVE_KQUEUE
+#include "ae_kqueue.c"
+#else
+#include "ae_select.c"
+#endif
+#endif
+#endif
+#endif
+
+aeEventLoop *aeCreateEventLoop(int setsize) {
+ aeEventLoop *eventLoop;
+ int i;
+
+ if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
+ eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
+ eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
+ if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
+ eventLoop->setsize = setsize;
+ eventLoop->lastTime = time(NULL);
+ eventLoop->timeEventHead = NULL;
+ eventLoop->timeEventNextId = 0;
+ eventLoop->stop = 0;
+ eventLoop->maxfd = -1;
+ eventLoop->beforesleep = NULL;
+ eventLoop->aftersleep = NULL;
+ if (aeApiCreate(eventLoop) == -1) goto err;
+ /* Events with mask == AE_NONE are not set. So let's initialize the
+ * vector with it. */
+ for (i = 0; i < setsize; i++)
+ eventLoop->events[i].mask = AE_NONE;
+ return eventLoop;
+
+err:
+ if (eventLoop) {
+ zfree(eventLoop->events);
+ zfree(eventLoop->fired);
+ zfree(eventLoop);
+ }
+ return NULL;
+}
+
+/* Return the current set size. */
+int aeGetSetSize(aeEventLoop *eventLoop) {
+ return eventLoop->setsize;
+}
+
+/* Resize the maximum set size of the event loop.
+ * If the requested set size is smaller than the current set size, but
+ * there is already a file descriptor in use that is >= the requested
+ * set size minus one, AE_ERR is returned and the operation is not
+ * performed at all.
+ *
+ * Otherwise AE_OK is returned and the operation is successful. */
+int aeResizeSetSize(aeEventLoop *eventLoop, int setsize) {
+ int i;
+
+ if (setsize == eventLoop->setsize) return AE_OK;
+ if (eventLoop->maxfd >= setsize) return AE_ERR;
+ if (aeApiResize(eventLoop, setsize) == -1) return AE_ERR;
+
+ eventLoop->events = zrealloc(eventLoop->events, sizeof(aeFileEvent)*setsize);
+ eventLoop->fired = zrealloc(eventLoop->fired, sizeof(aeFiredEvent)*setsize);
+ eventLoop->setsize = setsize;
+
+ /* Make sure that if we created new slots, they are initialized with
+ * an AE_NONE mask. */
+ for (i = eventLoop->maxfd + 1; i < setsize; i++)
+ eventLoop->events[i].mask = AE_NONE;
+ return AE_OK;
+}
+
+void aeDeleteEventLoop(aeEventLoop *eventLoop) {
+ aeApiFree(eventLoop);
+ zfree(eventLoop->events);
+ zfree(eventLoop->fired);
+ zfree(eventLoop);
+}
+
+void aeStop(aeEventLoop *eventLoop) {
+ eventLoop->stop = 1;
+}
+
+int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
+ aeFileProc *proc, void *clientData)
+{
+ if (fd >= eventLoop->setsize) {
+ errno = ERANGE;
+ return AE_ERR;
+ }
+ aeFileEvent *fe = &eventLoop->events[fd];
+
+ if (aeApiAddEvent(eventLoop, fd, mask) == -1)
+ return AE_ERR;
+ fe->mask |= mask;
+ if (mask & AE_READABLE) fe->rfileProc = proc;
+ if (mask & AE_WRITABLE) fe->wfileProc = proc;
+ fe->clientData = clientData;
+ if (fd > eventLoop->maxfd)
+ eventLoop->maxfd = fd;
+ return AE_OK;
+}
+
+void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
+{
+ if (fd >= eventLoop->setsize) return;
+ aeFileEvent *fe = &eventLoop->events[fd];
+ if (fe->mask == AE_NONE) return;
+
+ /* We want to always remove AE_BARRIER if set when AE_WRITABLE
+ * is removed. */
+ if (mask & AE_WRITABLE) mask |= AE_BARRIER;
+
+ aeApiDelEvent(eventLoop, fd, mask);
+ fe->mask = fe->mask & (~mask);
+ if (fd == eventLoop->maxfd && fe->mask == AE_NONE) {
+ /* Update the max fd */
+ int j;
+
+ for (j = eventLoop->maxfd - 1; j >= 0; j--)
+ if (eventLoop->events[j].mask != AE_NONE) break;
+ eventLoop->maxfd = j;
+ }
+}
+
+int aeGetFileEvents(aeEventLoop *eventLoop, int fd) {
+ if (fd >= eventLoop->setsize) return 0;
+ aeFileEvent *fe = &eventLoop->events[fd];
+
+ return fe->mask;
+}
+
+static void aeGetTime(PORT_LONG *seconds, PORT_LONG *milliseconds)
+{
+#ifdef _WIN32
+ struct _timeb tb;
+
+ memset(&tb, 0, sizeof(struct _timeb));
+ _ftime_s(&tb);
+ (*seconds) = tb.time;
+ (*milliseconds) = tb.millitm;
+#else
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ *seconds = tv.tv_sec;
+ *milliseconds = tv.tv_usec / 1000;
+#endif
+}
+
+static void aeAddMillisecondsToNow(PORT_LONGLONG milliseconds, PORT_LONG *sec, PORT_LONG *ms) {
+ PORT_LONG cur_sec, cur_ms, when_sec, when_ms;
+
+ aeGetTime(&cur_sec, &cur_ms);
+ when_sec = (PORT_LONG) (cur_sec + milliseconds / 1000);
+ when_ms = cur_ms + milliseconds % 1000;
+ if (when_ms >= 1000) {
+ when_sec++;
+ when_ms -= 1000;
+ }
+ *sec = when_sec;
+ *ms = when_ms;
+}
+
+PORT_LONGLONG aeCreateTimeEvent(aeEventLoop *eventLoop, PORT_LONGLONG milliseconds,
+ aeTimeProc *proc, void *clientData,
+ aeEventFinalizerProc *finalizerProc)
+{
+ PORT_LONGLONG id = eventLoop->timeEventNextId++;
+ aeTimeEvent *te;
+
+ te = zmalloc(sizeof(*te));
+ if (te == NULL) return AE_ERR;
+ te->id = id;
+ aeAddMillisecondsToNow(milliseconds, &te->when_sec, &te->when_ms);
+ te->timeProc = proc;
+ te->finalizerProc = finalizerProc;
+ te->clientData = clientData;
+ te->prev = NULL;
+ te->next = eventLoop->timeEventHead;
+ if (te->next)
+ te->next->prev = te;
+ eventLoop->timeEventHead = te;
+ return id;
+}
+
+int aeDeleteTimeEvent(aeEventLoop *eventLoop, PORT_LONGLONG id)
+{
+ aeTimeEvent *te = eventLoop->timeEventHead;
+ while (te) {
+ if (te->id == id) {
+ te->id = AE_DELETED_EVENT_ID;
+ return AE_OK;
+ }
+ te = te->next;
+ }
+ return AE_ERR; /* NO event with the specified ID found */
+}
+
+/* Search the first timer to fire.
+ * This operation is useful to know how many time the select can be
+ * put in sleep without to delay any event.
+ * If there are no timers NULL is returned.
+ *
+ * Note that's O(N) since time events are unsorted.
+ * Possible optimizations (not needed by Redis so far, but...):
+ * 1) Insert the event in order, so that the nearest is just the head.
+ * Much better but still insertion or deletion of timers is O(N).
+ * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
+ */
+static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
+{
+ aeTimeEvent *te = eventLoop->timeEventHead;
+ aeTimeEvent *nearest = NULL;
+
+ while (te) {
+ if (!nearest || te->when_sec < nearest->when_sec ||
+ (te->when_sec == nearest->when_sec &&
+ te->when_ms < nearest->when_ms))
+ nearest = te;
+ te = te->next;
+ }
+ return nearest;
+}
+
+/* Process time events */
+static int processTimeEvents(aeEventLoop *eventLoop) {
+ int processed = 0;
+ aeTimeEvent *te;
+ PORT_LONGLONG maxId;
+ time_t now = time(NULL);
+
+ /* If the system clock is moved to the future, and then set back to the
+ * right value, time events may be delayed in a random way. Often this
+ * means that scheduled operations will not be performed soon enough.
+ *
+ * Here we try to detect system clock skews, and force all the time
+ * events to be processed ASAP when this happens: the idea is that
+ * processing events earlier is less dangerous than delaying them
+ * indefinitely, and practice suggests it is. */
+ if (now < eventLoop->lastTime) {
+ te = eventLoop->timeEventHead;
+ while (te) {
+ te->when_sec = 0;
+ te = te->next;
+ }
+ }
+ eventLoop->lastTime = now;
+
+ te = eventLoop->timeEventHead;
+ maxId = eventLoop->timeEventNextId - 1;
+ while (te) {
+ PORT_LONG now_sec, now_ms;
+ PORT_LONGLONG id;
+
+ /* Remove events scheduled for deletion. */
+ if (te->id == AE_DELETED_EVENT_ID) {
+ aeTimeEvent *next = te->next;
+ if (te->prev)
+ te->prev->next = te->next;
+ else
+ eventLoop->timeEventHead = te->next;
+ if (te->next)
+ te->next->prev = te->prev;
+ if (te->finalizerProc)
+ te->finalizerProc(eventLoop, te->clientData);
+ zfree(te);
+ te = next;
+ continue;
+ }
+
+ /* Make sure we don't process time events created by time events in
+ * this iteration. Note that this check is currently useless: we always
+ * add new timers on the head, however if we change the implementation
+ * detail, this check may be useful again: we keep it here for future
+ * defense. */
+ if (te->id > maxId) {
+ te = te->next;
+ continue;
+ }
+ aeGetTime(&now_sec, &now_ms);
+ if (now_sec > te->when_sec ||
+ (now_sec == te->when_sec && now_ms >= te->when_ms))
+ {
+ int retval;
+
+ id = te->id;
+ retval = te->timeProc(eventLoop, id, te->clientData);
+ processed++;
+ if (retval != AE_NOMORE) {
+ aeAddMillisecondsToNow(retval, &te->when_sec, &te->when_ms);
+ } else {
+ te->id = AE_DELETED_EVENT_ID;
+ }
+ }
+ te = te->next;
+ }
+ return processed;
+}
+
+/* Process every pending time event, then every pending file event
+ * (that may be registered by time event callbacks just processed).
+ * Without special flags the function sleeps until some file event
+ * fires, or when the next time event occurs (if any).
+ *
+ * If flags is 0, the function does nothing and returns.
+ * if flags has AE_ALL_EVENTS set, all the kind of events are processed.
+ * if flags has AE_FILE_EVENTS set, file events are processed.
+ * if flags has AE_TIME_EVENTS set, time events are processed.
+ * if flags has AE_DONT_WAIT set the function returns ASAP until all
+ * if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
+ * the events that's possible to process without to wait are processed.
+ *
+ * The function returns the number of events processed. */
+int aeProcessEvents(aeEventLoop *eventLoop, int flags)
+{
+ int processed = 0, numevents;
+
+#ifdef _WIN32
+ if (ServiceStopIssued() == TRUE) {
+ aeStop(eventLoop);
+ }
+#endif
+
+ /* Nothing to do? return ASAP */
+ if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
+
+ /* Note that we want call select() even if there are no
+ * file events to process as long as we want to process time
+ * events, in order to sleep until the next time event is ready
+ * to fire. */
+ if (eventLoop->maxfd != -1 ||
+ ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
+ int j;
+ aeTimeEvent *shortest = NULL;
+ struct timeval tv, *tvp;
+
+ if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
+ shortest = aeSearchNearestTimer(eventLoop);
+ if (shortest) {
+ PORT_LONG now_sec, now_ms;
+
+ aeGetTime(&now_sec, &now_ms);
+ tvp = &tv;
+
+ /* How many milliseconds we need to wait for the next
+ * time event to fire? */
+ PORT_LONGLONG ms =
+ (shortest->when_sec - now_sec) * 1000 +
+ shortest->when_ms - now_ms;
+
+ if (ms > 0) {
+ tvp->tv_sec = ms / 1000;
+ tvp->tv_usec = (ms % 1000) * 1000;
+ } else {
+ tvp->tv_sec = 0;
+ tvp->tv_usec = 0;
+ }
+ } else {
+ /* If we have to check for events but need to return
+ * ASAP because of AE_DONT_WAIT we need to set the timeout
+ * to zero */
+ if (flags & AE_DONT_WAIT) {
+ tv.tv_sec = tv.tv_usec = 0;
+ tvp = &tv;
+ } else {
+ /* Otherwise we can block */
+ tvp = NULL; /* wait forever */
+ }
+ }
+
+ /* Call the multiplexing API, will return only on timeout or when
+ * some event fires. */
+ numevents = aeApiPoll(eventLoop, tvp);
+
+ /* After sleep callback. */
+ if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
+ eventLoop->aftersleep(eventLoop);
+
+ for (j = 0; j < numevents; j++) {
+ aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
+ int mask = eventLoop->fired[j].mask;
+ int fd = eventLoop->fired[j].fd;
+ int rfired = 0;
+
+ /* note the fe->mask & mask & ... code: maybe an already processed
+ * event removed an element that fired and we still didn't
+ * processed, so we check if the event is still valid. */
+ if (fe->mask & mask & AE_READABLE) {
+ rfired = 1;
+ fe->rfileProc(eventLoop, fd, fe->clientData, mask);
+ }
+ if (fe->mask & mask & AE_WRITABLE) {
+ if (!rfired || fe->wfileProc != fe->rfileProc)
+ fe->wfileProc(eventLoop, fd, fe->clientData, mask);
+ }
+ processed++;
+ }
+ }
+ /* Check time events */
+ if (flags & AE_TIME_EVENTS)
+ processed += processTimeEvents(eventLoop);
+
+ return processed; /* return the number of processed file/time events */
+}
+
+/* Wait for milliseconds until the given file descriptor becomes
+ * writable/readable/exception */
+int aeWait(int fd, int mask, PORT_LONGLONG milliseconds) {
+ struct pollfd pfd;
+ int retmask = 0, retval;
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fd;
+ if (mask & AE_READABLE) pfd.events |= POLLIN;
+ if (mask & AE_WRITABLE) pfd.events |= POLLOUT;
+
+ if ((retval = poll(&pfd, 1, (int) milliseconds)) == 1) { WIN_PORT_FIX /* cast (int) */
+ if (pfd.revents & POLLIN) retmask |= AE_READABLE;
+ if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE;
+ if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
+ if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE;
+ return retmask;
+ } else {
+ return retval;
+ }
+}
+
+void aeMain(aeEventLoop *eventLoop) {
+ eventLoop->stop = 0;
+ while (!eventLoop->stop) {
+ if (eventLoop->beforesleep != NULL)
+ eventLoop->beforesleep(eventLoop);
+ aeProcessEvents(eventLoop, AE_ALL_EVENTS | AE_CALL_AFTER_SLEEP);
+ }
+}
+
+char *aeGetApiName(void) {
+ return aeApiName();
+}
+
+void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) {
+ eventLoop->beforesleep = beforesleep;
+}
+
+void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep) {
+ eventLoop->aftersleep = aftersleep;
+}
diff --git a/src/ae.h b/src/ae.h
index 4403b5c8..39bf9ea6 100644
--- a/src/ae.h
+++ b/src/ae.h
@@ -38,9 +38,14 @@
#define AE_OK 0
#define AE_ERR -1
-#define AE_NONE 0
-#define AE_READABLE 1
-#define AE_WRITABLE 2
+#define AE_NONE 0 /* No events registered. */
+#define AE_READABLE 1 /* Fire when descriptor is readable. */
+#define AE_WRITABLE 2 /* Fire when descriptor is writable. */
+#define AE_BARRIER 4 /* With WRITABLE, never fire the event if the
+ READABLE event already fired in the same event
+ loop iteration. Useful when you want to persist
+ things to disk before sending replies, and want
+ to do that in a group fashion. */
#define AE_FILE_EVENTS 1
#define AE_TIME_EVENTS 2
@@ -64,7 +69,7 @@ typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop);
/* File event structure */
typedef struct aeFileEvent {
- int mask; /* one of AE_(READABLE|WRITABLE) */
+ int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */
aeFileProc *rfileProc;
aeFileProc *wfileProc;
void *clientData;
@@ -78,6 +83,7 @@ typedef struct aeTimeEvent {
aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
+ struct aeTimeEvent *prev;
struct aeTimeEvent *next;
} aeTimeEvent;
diff --git a/src/ae.h.bak b/src/ae.h.bak
new file mode 100644
index 00000000..1b5abe4d
--- /dev/null
+++ b/src/ae.h.bak
@@ -0,0 +1,131 @@
+/* A simple event-driven programming library. Originally I wrote this code
+ * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
+ * it in form of a library for easy reuse.
+ *
+ * Copyright (c) 2006-2012, Salvatore Sanfilippo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __AE_H__
+#define __AE_H__
+
+#include
+
+#define AE_OK 0
+#define AE_ERR -1
+
+#define AE_NONE 0 /* No events registered. */
+#define AE_READABLE 1 /* Fire when descriptor is readable. */
+#define AE_WRITABLE 2 /* Fire when descriptor is writable. */
+#define AE_BARRIER 4 /* With WRITABLE, never fire the event if the
+ READABLE event already fired in the same event
+ loop iteration. Useful when you want to persist
+ things to disk before sending replies, and want
+ to do that in a group fashion. */
+
+#define AE_FILE_EVENTS 1
+#define AE_TIME_EVENTS 2
+#define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS)
+#define AE_DONT_WAIT 4
+#define AE_CALL_AFTER_SLEEP 8
+
+#define AE_NOMORE -1
+#define AE_DELETED_EVENT_ID -1
+
+/* Macros */
+#define AE_NOTUSED(V) ((void) V)
+
+struct aeEventLoop;
+
+/* Types and data structures */
+typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
+typedef int aeTimeProc(struct aeEventLoop *eventLoop, PORT_LONGLONG id, void *clientData);
+typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
+typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop);
+
+/* File event structure */
+typedef struct aeFileEvent {
+ int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */
+ aeFileProc *rfileProc;
+ aeFileProc *wfileProc;
+ void *clientData;
+} aeFileEvent;
+
+/* Time event structure */
+typedef struct aeTimeEvent {
+ PORT_LONGLONG id; /* time event identifier. */
+ PORT_LONG when_sec; /* seconds */
+ PORT_LONG when_ms; /* milliseconds */
+ aeTimeProc *timeProc;
+ aeEventFinalizerProc *finalizerProc;
+ void *clientData;
+ struct aeTimeEvent *next;
+} aeTimeEvent;
+
+/* A fired event */
+typedef struct aeFiredEvent {
+ int fd;
+ int mask;
+} aeFiredEvent;
+
+/* State of an event based program */
+typedef struct aeEventLoop {
+ int maxfd; /* highest file descriptor currently registered */
+ int setsize; /* max number of file descriptors tracked */
+ PORT_LONGLONG timeEventNextId;
+ time_t lastTime; /* Used to detect system clock skew */
+ aeFileEvent *events; /* Registered events */
+ aeFiredEvent *fired; /* Fired events */
+ aeTimeEvent *timeEventHead;
+ int stop;
+ void *apidata; /* This is used for polling API specific data */
+ aeBeforeSleepProc *beforesleep;
+ aeBeforeSleepProc *aftersleep;
+} aeEventLoop;
+
+/* Prototypes */
+aeEventLoop *aeCreateEventLoop(int setsize);
+void aeDeleteEventLoop(aeEventLoop *eventLoop);
+void aeStop(aeEventLoop *eventLoop);
+int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
+ aeFileProc *proc, void *clientData);
+void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask);
+int aeGetFileEvents(aeEventLoop *eventLoop, int fd);
+PORT_LONGLONG aeCreateTimeEvent(aeEventLoop *eventLoop, PORT_LONGLONG milliseconds,
+ aeTimeProc *proc, void *clientData,
+ aeEventFinalizerProc *finalizerProc);
+int aeDeleteTimeEvent(aeEventLoop *eventLoop, PORT_LONGLONG id);
+int aeProcessEvents(aeEventLoop *eventLoop, int flags);
+int aeWait(int fd, int mask, PORT_LONGLONG milliseconds);
+void aeMain(aeEventLoop *eventLoop);
+char *aeGetApiName(void);
+void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep);
+void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep);
+int aeGetSetSize(aeEventLoop *eventLoop);
+int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
+
+#endif
diff --git a/src/aof.c b/src/aof.c
index ec5dab8e..42de79a5 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -214,25 +214,20 @@ ssize_t aofRewriteBufferWrite(int fd) {
/* Starts a background task that performs fsync() against the specified
* file descriptor (the one of the AOF file) in another thread. */
void aof_background_fsync(int fd) {
- bioCreateBackgroundJob(BIO_AOF_FSYNC, (void*) (PORT_LONG) fd, NULL, NULL);
+ bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
}
-/* Called when the user switches from "appendonly yes" to "appendonly no"
- * at runtime using the CONFIG command. */
-void stopAppendOnly(void) {
- serverAssert(server.aof_state != AOF_OFF);
- flushAppendOnlyFile(1);
- aof_fsync(server.aof_fd);
- close(server.aof_fd);
+/* Kills an AOFRW child process if exists */
+static void killAppendOnlyChild(void) {
- server.aof_fd = -1;
- server.aof_selected_db = -1;
- server.aof_state = AOF_OFF;
- /* rewrite operation in progress? kill it, wait child exit */
- if (server.aof_child_pid != -1) {
- POSIX_ONLY(int statloc;)
+ POSIX_ONLY(int statloc;)
- serverLog(LL_NOTICE, "Killing running AOF rewrite child: %Id", WIN_PORT_FIX /* %ld -> %Id */
+ /* No AOFRW child? return. */
+ if (server.aof_child_pid != -1)return;
+
+ /* Kill AOFRW child, wait for child exit. */
+
+ serverLog(LL_NOTICE, "Killing running AOF rewrite child: %Id", WIN_PORT_FIX /* %ld -> %Id */
(PORT_LONG) server.aof_child_pid);
#ifdef _WIN32
AbortForkOperation();
@@ -246,20 +241,36 @@ void stopAppendOnly(void) {
aofRemoveTempFile(server.aof_child_pid);
server.aof_child_pid = -1;
server.aof_rewrite_time_start = -1;
- /* close pipes used for IPC between the two processes. */
+ /* Close pipes used for IPC between the two processes. */
aofClosePipes();
- }
+
+}
+
+/* Called when the user switches from "appendonly yes" to "appendonly no"
+ * at runtime using the CONFIG command. */
+void stopAppendOnly(void) {
+ serverAssert(server.aof_state != AOF_OFF);
+ flushAppendOnlyFile(1);
+ aof_fsync(server.aof_fd);
+ close(server.aof_fd);
+
+ server.aof_fd = -1;
+ server.aof_selected_db = -1;
+ server.aof_state = AOF_OFF;
+ killAppendOnlyChild();
+
+
}
/* Called when the user switches from "appendonly no" to "appendonly yes"
* at runtime using the CONFIG command. */
int startAppendOnly(void) {
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
+ int newfd;
- server.aof_last_fsync = server.unixtime;
- server.aof_fd = open(server.aof_filename, O_WRONLY | O_APPEND | O_CREAT WIN32_ONLY(| _O_BINARY), IF_WIN32(_S_IREAD | _S_IWRITE, 0644));
+ newfd = open(server.aof_filename, O_WRONLY | O_APPEND | O_CREAT WIN32_ONLY(| _O_BINARY), IF_WIN32(_S_IREAD | _S_IWRITE, 0644));
serverAssert(server.aof_state == AOF_OFF);
- if (server.aof_fd == -1) {
+ if (newfd == -1) {
char *cwdp = IF_WIN32(_getcwd, getcwd)(cwd, MAXPATHLEN);
serverLog(LL_WARNING,
@@ -273,19 +284,56 @@ int startAppendOnly(void) {
if (server.rdb_child_pid != -1) {
server.aof_rewrite_scheduled = 1;
serverLog(LL_WARNING, "AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
- }
- else if (rewriteAppendOnlyFileBackground() == C_ERR) {
- close(server.aof_fd);
- WIN32_ONLY(server.aof_fd = -1;)
- serverLog(LL_WARNING, "Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.");
- return C_ERR;
+ } else {
+ /* If there is a pending AOF rewrite, we need to switch it off and
+ * start a new one: the old one cannot be reused becuase it is not
+ * accumulating the AOF buffer. */
+ if (server.aof_child_pid != -1) {
+ serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
+ killAppendOnlyChild();
+ }
+ if (rewriteAppendOnlyFileBackground() == C_ERR) {
+ close(newfd);
+ serverLog(LL_WARNING, "Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.");
+ return C_ERR;
+ }
}
/* We correctly switched on AOF, now wait for the rewrite to be complete
* in order to append data on disk. */
server.aof_state = AOF_WAIT_REWRITE;
+ server.aof_last_fsync = server.unixtime;
+ server.aof_fd = newfd;
return C_OK;
}
+/* This is a wrapper to the write syscall in order to retry on short writes
+ * or if the syscall gets interrupted. It could look strange that we retry
+ * on short writes given that we are writing to a block device: normally if
+ * the first call is short, there is a end-of-space condition, so the next
+ * is likely to fail. However apparently in modern systems this is no longer
+ * true, and in general it looks just more resilient to retry the write. If
+ * there is an actual error condition we'll get it at the next try. */
+ssize_t aofWrite(int fd, const char *buf, size_t len) {
+ ssize_t nwritten = 0, totwritten = 0;
+
+ while(len) {
+ nwritten = write(fd, buf, len);
+
+ if (nwritten < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return totwritten ? totwritten : -1;
+ }
+
+ len -= nwritten;
+ buf += nwritten;
+ totwritten += nwritten;
+ }
+
+ return totwritten;
+}
+
/* Write the append only file buffer on disk.
*
* Since we are required to write the AOF before replying to the client,
@@ -344,7 +392,7 @@ void flushAppendOnlyFile(int force) {
* or alike */
latencyStartMonitor(latency);
- nwritten = write(server.aof_fd, server.aof_buf, sdslen(server.aof_buf));
+ nwritten = aofWrite(server.aof_fd, server.aof_buf, sdslen(server.aof_buf));
latencyEndMonitor(latency);
/* We want to capture different events for delayed writes:
* when the delay happens with a pending fsync, or with a saving child
@@ -353,11 +401,9 @@ void flushAppendOnlyFile(int force) {
* useful for graphing / monitoring purposes. */
if (sync_in_progress) {
latencyAddSampleIfNeeded("aof-write-pending-fsync", latency);
- }
- else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) {
+ } else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) {
latencyAddSampleIfNeeded("aof-write-active-child", latency);
- }
- else {
+ } else {
latencyAddSampleIfNeeded("aof-write-alone", latency);
}
latencyAddSampleIfNeeded("aof-write", latency);
@@ -365,7 +411,7 @@ void flushAppendOnlyFile(int force) {
/* We performed the write so reset the postponed flush sentinel to zero. */
server.aof_flush_postponed_start = 0;
- if (nwritten != (signed) sdslen(server.aof_buf)) {
+ if (nwritten != (ssize_t)sdslen(server.aof_buf)) {
static time_t last_write_error_log = 0;
int can_log = 0;
@@ -382,8 +428,7 @@ void flushAppendOnlyFile(int force) {
IF_WIN32(wsa_strerror(errno), strerror(errno)));
server.aof_last_write_errno = errno;
}
- }
- else {
+ } else {
if (can_log) {
serverLog(LL_WARNING, "Short write while writing to "
"the AOF file: (nwritten=%lld, "
@@ -468,8 +513,7 @@ void flushAppendOnlyFile(int force) {
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("aof-fsync-always", latency);
server.aof_last_fsync = server.unixtime;
- }
- else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
+ } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
server.unixtime > server.aof_last_fsync)) {
if (!sync_in_progress) aof_background_fsync(server.aof_fd);
server.aof_last_fsync = server.unixtime;
@@ -557,8 +601,7 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
cmd->proc == expireatCommand) {
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
buf = catAppendOnlyExpireAtCommand(buf, cmd, argv[1], argv[2]);
- }
- else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
+ } else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
/* Translate SETEX/PSETEX to SET and PEXPIREAT */
tmpargv[0] = createStringObject("SET", 3);
tmpargv[1] = argv[1];
@@ -566,8 +609,7 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
buf = catAppendOnlyGenericCommand(buf, 3, tmpargv);
decrRefCount(tmpargv[0]);
buf = catAppendOnlyExpireAtCommand(buf, cmd, argv[1], argv[2]);
- }
- else if (cmd->proc == setCommand && argc > 3) {
+ } else if (cmd->proc == setCommand && argc > 3) {
int i;
robj *exarg = NULL, *pxarg = NULL;
/* Translate SET [EX seconds][PX milliseconds] to SET and PEXPIREAT */
@@ -583,8 +625,7 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
if (pxarg)
buf = catAppendOnlyExpireAtCommand(buf, server.pexpireCommand, argv[1],
pxarg);
- }
- else {
+ } else {
/* All the other commands don't need translation or need the
* same translation already operated in the command vector
* for the replication itself. */
@@ -703,7 +744,7 @@ int loadAppendOnlyFile(char *filename) {
serverLog(LL_NOTICE, "Reading RDB preamble from AOF file...");
if (fseek(fp, 0, SEEK_SET) == -1) goto readerr;
rioInitWithFile(&rdb, fp);
- if (rdbLoadRio(&rdb, NULL) != C_OK) {
+ if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
serverLog(LL_WARNING, "Error reading the RDB preamble of the AOF file, AOF loading aborted");
goto readerr;
}
@@ -804,7 +845,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
if (!feof(fp)) {
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
- serverLog(LL_WARNING, "Unrecoverable error reading the append only file: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ serverLog(LL_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
exit(1);
}
@@ -1080,6 +1121,22 @@ int rewriteHashObject(rio *r, robj *key, robj *o) {
return 1;
}
+/* Call the module type callback in order to rewrite a data type
+ * that is exported by a module and is not handled by Redis itself.
+ * The function returns 0 on error, 1 on success. */
+int rewriteModuleObject(rio *r, robj *key, robj *o) {
+ RedisModuleIO io;
+ moduleValue *mv = o->ptr;
+ moduleType *mt = mv->type;
+ moduleInitIOContext(io,mt,r);
+ mt->aof_rewrite(&io,key,mv->value);
+ if (io.ctx) {
+ moduleFreeContext(io.ctx);
+ zfree(io.ctx);
+ }
+ return io.error ? 0 : 1;
+}
+
/* This function is called by the child rewriting the AOF file to read
* the difference accumulated from the parent into a buffer, that is
* concatenated at the end of the rewrite. */
@@ -1738,20 +1795,3 @@ cleanup:
if (server.aof_state == AOF_WAIT_REWRITE)
server.aof_rewrite_scheduled = 1;
}
-
-void aofProcessDiffRewriteEvents(aeEventLoop* eventLoop)
-{
- // only do these checks in the parent process and if an aof rewrite is in progress
- if (server.aof_child_pid != -1 && server.aof_pipe_read_ack_from_child != -1) {
- //1) check if more data can be written to the child and write it.
- // in which case we dont need to send any more diffs to the parent
- if (server.aof_stop_sending_diff == 0) {
- aofChildWriteDiffData(eventLoop, server.aof_pipe_write_data_to_child, NULL, 0);
- }
-
- //2) check if child has signaled parent to stop sending diffs
- if (server.aof_stop_sending_diff == 0) {
- aofChildPipeReadable(eventLoop, server.aof_pipe_read_ack_from_child, NULL, 0);
- }
- }
-}
\ No newline at end of file
diff --git a/src/aof.c.bak b/src/aof.c.bak
new file mode 100644
index 00000000..1c5e7283
--- /dev/null
+++ b/src/aof.c.bak
@@ -0,0 +1,1814 @@
+/*
+ * Copyright (c) 2009-2012, Salvatore Sanfilippo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * 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.
+ */
+
+#ifdef _WIN32
+#include "Win32_Interop/win32_types.h"
+#include "Win32_Interop/Win32_Error.h"
+#endif
+
+#include "server.h"
+#include "bio.h"
+#include "rio.h"
+
+#include
+#include
+#include
+#include
+#ifndef _WIN32
+#include
+#include
+#include
+#include
+#else
+#include "Win32_Interop/Win32_QFork.h"
+#include
+#define MAXPATHLEN 1024
+#endif
+
+void aofUpdateCurrentSize(void);
+void aofClosePipes(void);
+
+/* ----------------------------------------------------------------------------
+ * AOF rewrite buffer implementation.
+ *
+ * The following code implement a simple buffer used in order to accumulate
+ * changes while the background process is rewriting the AOF file.
+ *
+ * We only need to append, but can't just use realloc with a large block
+ * because 'huge' reallocs are not always handled as one could expect
+ * (via remapping of pages at OS level) but may involve copying data.
+ *
+ * For this reason we use a list of blocks, every block is
+ * AOF_RW_BUF_BLOCK_SIZE bytes.
+ * ------------------------------------------------------------------------- */
+
+#define AOF_RW_BUF_BLOCK_SIZE (1024*1024*10) /* 10 MB per block */
+
+typedef struct aofrwblock {
+ PORT_ULONG used, free;
+ char buf[AOF_RW_BUF_BLOCK_SIZE];
+} aofrwblock;
+
+/* This function free the old AOF rewrite buffer if needed, and initialize
+ * a fresh new one. It tests for server.aof_rewrite_buf_blocks equal to NULL
+ * so can be used for the first initialization as well. */
+void aofRewriteBufferReset(void) {
+ if (server.aof_rewrite_buf_blocks)
+ listRelease(server.aof_rewrite_buf_blocks);
+
+ server.aof_rewrite_buf_blocks = listCreate();
+ listSetFreeMethod(server.aof_rewrite_buf_blocks, zfree);
+}
+
+/* Return the current size of the AOF rewrite buffer. */
+PORT_ULONG aofRewriteBufferSize(void) {
+ listNode *ln;
+ listIter li;
+ PORT_ULONG size = 0;
+
+ listRewind(server.aof_rewrite_buf_blocks, &li);
+ while ((ln = listNext(&li))) {
+ aofrwblock *block = listNodeValue(ln);
+ size += block->used;
+ }
+ return size;
+}
+
+/* Event handler used to send data to the child process doing the AOF
+ * rewrite. We send pieces of our AOF differences buffer so that the final
+ * write when the child finishes the rewrite will be small. */
+void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
+ listNode *ln;
+ aofrwblock *block;
+ ssize_t nwritten;
+ UNUSED(el);
+ UNUSED(fd);
+ UNUSED(privdata);
+ UNUSED(mask);
+
+ while (1) {
+ ln = listFirst(server.aof_rewrite_buf_blocks);
+ block = ln ? ln->value : NULL;
+ if (server.aof_stop_sending_diff || !block) {
+#ifndef _WIN32
+ aeDeleteFileEvent(server.el, server.aof_pipe_write_data_to_child,
+ AE_WRITABLE);
+#endif
+ return;
+ }
+ if (block->used > 0) {
+ nwritten = write(server.aof_pipe_write_data_to_child,
+ block->buf, block->used);
+ if (nwritten <= 0) return;
+ memmove(block->buf, block->buf + nwritten, block->used - nwritten);
+ block->used -= nwritten;
+ block->free += nwritten;
+ }
+ if (block->used == 0) listDelNode(server.aof_rewrite_buf_blocks, ln);
+ }
+}
+
+/* Append data to the AOF rewrite buffer, allocating new blocks if needed. */
+void aofRewriteBufferAppend(unsigned char *s, PORT_ULONG len) {
+ listNode *ln = listLast(server.aof_rewrite_buf_blocks);
+ aofrwblock *block = ln ? ln->value : NULL;
+
+ while (len) {
+ /* If we already got at least an allocated block, try appending
+ * at least some piece into it. */
+ if (block) {
+ PORT_ULONG thislen = (block->free < len) ? block->free : len;
+ if (thislen) { /* The current block is not already full. */
+ memcpy(block->buf + block->used, s, thislen);
+ block->used += thislen;
+ block->free -= thislen;
+ s += thislen;
+ len -= thislen;
+ }
+ }
+
+ if (len) { /* First block to allocate, or need another block. */
+ int numblocks;
+
+ block = zmalloc(sizeof(*block));
+ block->free = AOF_RW_BUF_BLOCK_SIZE;
+ block->used = 0;
+ listAddNodeTail(server.aof_rewrite_buf_blocks, block);
+
+ /* Log every time we cross more 10 or 100 blocks, respectively
+ * as a notice or warning. */
+ numblocks = (int) listLength(server.aof_rewrite_buf_blocks); WIN_PORT_FIX /* cast (int) */
+ if (((numblocks + 1) % 10) == 0) {
+ int level = ((numblocks + 1) % 100) == 0 ? LL_WARNING :
+ LL_NOTICE;
+ serverLog(level, "Background AOF buffer size: %Iu MB", WIN_PORT_FIX /* %lu -> %Iu */
+ aofRewriteBufferSize() / (1024 * 1024));
+ }
+ }
+ }
+#ifndef _WIN32
+ /* Install a file event to send data to the rewrite child if there is
+ * not one already. */
+ if (aeGetFileEvents(server.el, server.aof_pipe_write_data_to_child) == 0) {
+ aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,
+ AE_WRITABLE, aofChildWriteDiffData, NULL);
+ }
+#endif
+}
+
+/* Write the buffer (possibly composed of multiple blocks) into the specified
+ * fd. If a short write or any other error happens -1 is returned,
+ * otherwise the number of bytes written is returned. */
+ssize_t aofRewriteBufferWrite(int fd) {
+ listNode *ln;
+ listIter li;
+ ssize_t count = 0;
+
+ listRewind(server.aof_rewrite_buf_blocks, &li);
+ while ((ln = listNext(&li))) {
+ aofrwblock *block = listNodeValue(ln);
+ ssize_t nwritten;
+
+ if (block->used) {
+ nwritten = write(fd, block->buf, block->used);
+ if (nwritten != (ssize_t) block->used) {
+ if (nwritten == 0) errno = EIO;
+ return -1;
+ }
+ count += nwritten;
+ }
+ }
+ return count;
+}
+
+/* ----------------------------------------------------------------------------
+ * AOF file implementation
+ * ------------------------------------------------------------------------- */
+
+ /* Starts a background task that performs fsync() against the specified
+ * file descriptor (the one of the AOF file) in another thread. */
+void aof_background_fsync(int fd) {
+ bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
+}
+
+/* Kills an AOFRW child process if exists */
+static void killAppendOnlyChild(void) {
+
+ POSIX_ONLY(int statloc;)
+
+ /* No AOFRW child? return. */
+ if (server.aof_child_pid != -1)return;
+
+ /* Kill AOFRW child, wait for child exit. */
+
+ serverLog(LL_NOTICE, "Killing running AOF rewrite child: %Id", WIN_PORT_FIX /* %ld -> %Id */
+ (PORT_LONG) server.aof_child_pid);
+#ifdef _WIN32
+ AbortForkOperation();
+#else
+ if (kill(server.aof_child_pid, SIGUSR1) != -1) {
+ while (wait3(&statloc, 0, NULL) != server.aof_child_pid);
+ }
+#endif
+ /* reset the buffer accumulating changes while the child saves */
+ aofRewriteBufferReset();
+ aofRemoveTempFile(server.aof_child_pid);
+ server.aof_child_pid = -1;
+ server.aof_rewrite_time_start = -1;
+ /* Close pipes used for IPC between the two processes. */
+ aofClosePipes();
+
+}
+
+/* Called when the user switches from "appendonly yes" to "appendonly no"
+ * at runtime using the CONFIG command. */
+void stopAppendOnly(void) {
+ serverAssert(server.aof_state != AOF_OFF);
+ flushAppendOnlyFile(1);
+ aof_fsync(server.aof_fd);
+ close(server.aof_fd);
+
+ server.aof_fd = -1;
+ server.aof_selected_db = -1;
+ server.aof_state = AOF_OFF;
+ killAppendOnlyChild();
+
+
+}
+
+/* Called when the user switches from "appendonly no" to "appendonly yes"
+ * at runtime using the CONFIG command. */
+int startAppendOnly(void) {
+ char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
+ int newfd;
+
+ newfd = open(server.aof_filename, O_WRONLY | O_APPEND | O_CREAT WIN32_ONLY(| _O_BINARY), IF_WIN32(_S_IREAD | _S_IWRITE, 0644));
+ serverAssert(server.aof_state == AOF_OFF);
+ if (newfd == -1) {
+ char *cwdp = IF_WIN32(_getcwd, getcwd)(cwd, MAXPATHLEN);
+
+ serverLog(LL_WARNING,
+ "Redis needs to enable the AOF but can't open the "
+ "append only file %s (in server root dir %s): %s",
+ server.aof_filename,
+ cwdp ? cwdp : "unknown",
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ return C_ERR;
+ }
+ if (server.rdb_child_pid != -1) {
+ server.aof_rewrite_scheduled = 1;
+ serverLog(LL_WARNING, "AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
+ } else {
+ /* If there is a pending AOF rewrite, we need to switch it off and
+ * start a new one: the old one cannot be reused becuase it is not
+ * accumulating the AOF buffer. */
+ if (server.aof_child_pid != -1) {
+ serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
+ killAppendOnlyChild();
+ }
+ if (rewriteAppendOnlyFileBackground() == C_ERR) {
+ close(newfd);
+ serverLog(LL_WARNING, "Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.");
+ return C_ERR;
+ }
+ }
+ /* We correctly switched on AOF, now wait for the rewrite to be complete
+ * in order to append data on disk. */
+ server.aof_state = AOF_WAIT_REWRITE;
+ server.aof_last_fsync = server.unixtime;
+ server.aof_fd = newfd;
+ return C_OK;
+}
+
+/* This is a wrapper to the write syscall in order to retry on short writes
+ * or if the syscall gets interrupted. It could look strange that we retry
+ * on short writes given that we are writing to a block device: normally if
+ * the first call is short, there is a end-of-space condition, so the next
+ * is likely to fail. However apparently in modern systems this is no longer
+ * true, and in general it looks just more resilient to retry the write. If
+ * there is an actual error condition we'll get it at the next try. */
+ssize_t aofWrite(int fd, const char *buf, size_t len) {
+ ssize_t nwritten = 0, totwritten = 0;
+
+ while(len) {
+ nwritten = write(fd, buf, len);
+
+ if (nwritten < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return totwritten ? totwritten : -1;
+ }
+
+ len -= nwritten;
+ buf += nwritten;
+ totwritten += nwritten;
+ }
+
+ return totwritten;
+}
+
+/* Write the append only file buffer on disk.
+ *
+ * Since we are required to write the AOF before replying to the client,
+ * and the only way the client socket can get a write is entering when the
+ * the event loop, we accumulate all the AOF writes in a memory
+ * buffer and write it on disk using this function just before entering
+ * the event loop again.
+ *
+ * About the 'force' argument:
+ *
+ * When the fsync policy is set to 'everysec' we may delay the flush if there
+ * is still an fsync() going on in the background thread, since for instance
+ * on Linux write(2) will be blocked by the background fsync anyway.
+ * When this happens we remember that there is some aof buffer to be
+ * flushed ASAP, and will try to do that in the serverCron() function.
+ *
+ * However if force is set to 1 we'll write regardless of the background
+ * fsync. */
+#define AOF_WRITE_LOG_ERROR_RATE 30 /* Seconds between errors logging. */
+void flushAppendOnlyFile(int force) {
+ ssize_t nwritten;
+ int sync_in_progress = 0;
+ mstime_t latency;
+
+ if (sdslen(server.aof_buf) == 0) return;
+
+ if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
+ sync_in_progress = bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
+
+ if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
+ /* With this append fsync policy we do background fsyncing.
+ * If the fsync is still in progress we can try to delay
+ * the write for a couple of seconds. */
+ if (sync_in_progress) {
+ if (server.aof_flush_postponed_start == 0) {
+ /* No previous write postponing, remember that we are
+ * postponing the flush and return. */
+ server.aof_flush_postponed_start = server.unixtime;
+ return;
+ }
+ else if (server.unixtime - server.aof_flush_postponed_start < 2) {
+ /* We were already waiting for fsync to finish, but for less
+ * than two seconds this is still ok. Postpone again. */
+ return;
+ }
+ /* Otherwise fall trough, and go write since we can't wait
+ * over two seconds. */
+ server.aof_delayed_fsync++;
+ serverLog(LL_NOTICE, "Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");
+ }
+ }
+ /* We want to perform a single write. This should be guaranteed atomic
+ * at least if the filesystem we are writing is a real physical one.
+ * While this will save us against the server being killed I don't think
+ * there is much to do about the whole server stopping for power problems
+ * or alike */
+
+ latencyStartMonitor(latency);
+ nwritten = aofWrite(server.aof_fd, server.aof_buf, sdslen(server.aof_buf));
+ latencyEndMonitor(latency);
+ /* We want to capture different events for delayed writes:
+ * when the delay happens with a pending fsync, or with a saving child
+ * active, and when the above two conditions are missing.
+ * We also use an additional event name to save all samples which is
+ * useful for graphing / monitoring purposes. */
+ if (sync_in_progress) {
+ latencyAddSampleIfNeeded("aof-write-pending-fsync", latency);
+ } else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) {
+ latencyAddSampleIfNeeded("aof-write-active-child", latency);
+ } else {
+ latencyAddSampleIfNeeded("aof-write-alone", latency);
+ }
+ latencyAddSampleIfNeeded("aof-write", latency);
+
+ /* We performed the write so reset the postponed flush sentinel to zero. */
+ server.aof_flush_postponed_start = 0;
+
+ if (nwritten != (ssize_t)sdslen(server.aof_buf)) {
+ static time_t last_write_error_log = 0;
+ int can_log = 0;
+
+ /* Limit logging rate to 1 line per AOF_WRITE_LOG_ERROR_RATE seconds. */
+ if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
+ can_log = 1;
+ last_write_error_log = server.unixtime;
+ }
+
+ /* Log the AOF write error and record the error code. */
+ if (nwritten == -1) {
+ if (can_log) {
+ serverLog(LL_WARNING, "Error writing to the AOF file: %s",
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ server.aof_last_write_errno = errno;
+ }
+ } else {
+ if (can_log) {
+ serverLog(LL_WARNING, "Short write while writing to "
+ "the AOF file: (nwritten=%lld, "
+ "expected=%lld)",
+ (PORT_LONGLONG) nwritten,
+ (PORT_LONGLONG) sdslen(server.aof_buf));
+ }
+
+ if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {
+ if (can_log) {
+ serverLog(LL_WARNING, "Could not remove short write "
+ "from the append-only file. Redis may refuse "
+ "to load the AOF the next time it starts. "
+ "ftruncate: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ }
+ }
+ else {
+ /* If the ftruncate() succeeded we can set nwritten to
+ * -1 since there is no longer partial data into the AOF. */
+ nwritten = -1;
+ }
+ server.aof_last_write_errno = ENOSPC;
+ }
+
+ /* Handle the AOF write error. */
+ if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
+ /* We can't recover when the fsync policy is ALWAYS since the
+ * reply for the client is already in the output buffers, and we
+ * have the contract with the user that on acknowledged write data
+ * is synced on disk. */
+ serverLog(LL_WARNING, "Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
+ exit(1);
+ }
+ else {
+ /* Recover from failed write leaving data into the buffer. However
+ * set an error to stop accepting writes as PORT_LONG as the error
+ * condition is not cleared. */
+ server.aof_last_write_status = C_ERR;
+
+ /* Trim the sds buffer if there was a partial write, and there
+ * was no way to undo it with ftruncate(2). */
+ if (nwritten > 0) {
+ server.aof_current_size += nwritten;
+ sdsrange(server.aof_buf, (int) nwritten, -1); WIN_PORT_FIX /* cast (int) */
+ }
+ return; /* We'll try again on the next call... */
+ }
+ }
+ else {
+ /* Successful write(2). If AOF was in error state, restore the
+ * OK state and log the event. */
+ if (server.aof_last_write_status == C_ERR) {
+ serverLog(LL_WARNING,
+ "AOF write error looks solved, Redis can write again.");
+ server.aof_last_write_status = C_OK;
+ }
+ }
+ server.aof_current_size += nwritten;
+
+ /* Re-use AOF buffer when it is small enough. The maximum comes from the
+ * arena size of 4k minus some overhead (but is otherwise arbitrary). */
+ if ((sdslen(server.aof_buf) + sdsavail(server.aof_buf)) < 4000) {
+ sdsclear(server.aof_buf);
+ }
+ else {
+ sdsfree(server.aof_buf);
+ server.aof_buf = sdsempty();
+ }
+
+ /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
+ * children doing I/O in the background. */
+ if (server.aof_no_fsync_on_rewrite &&
+ (server.aof_child_pid != -1 || server.rdb_child_pid != -1))
+ return;
+
+ /* Perform the fsync if needed. */
+ if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
+ /* aof_fsync is defined as fdatasync() for Linux in order to avoid
+ * flushing metadata. */
+ latencyStartMonitor(latency);
+ aof_fsync(server.aof_fd); /* Let's try to get this data on the disk */
+ latencyEndMonitor(latency);
+ latencyAddSampleIfNeeded("aof-fsync-always", latency);
+ server.aof_last_fsync = server.unixtime;
+ } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
+ server.unixtime > server.aof_last_fsync)) {
+ if (!sync_in_progress) aof_background_fsync(server.aof_fd);
+ server.aof_last_fsync = server.unixtime;
+ }
+}
+
+sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
+ char buf[32];
+ int len, j;
+ robj *o;
+
+ buf[0] = '*';
+ len = 1 + ll2string(buf + 1, sizeof(buf) - 1, argc);
+ buf[len++] = '\r';
+ buf[len++] = '\n';
+ dst = sdscatlen(dst, buf, len);
+
+ for (j = 0; j < argc; j++) {
+ o = getDecodedObject(argv[j]);
+ buf[0] = '$';
+ len = 1 + ll2string(buf + 1, sizeof(buf) - 1, sdslen(o->ptr));
+ buf[len++] = '\r';
+ buf[len++] = '\n';
+ dst = sdscatlen(dst, buf, len);
+ dst = sdscatlen(dst, o->ptr, sdslen(o->ptr));
+ dst = sdscatlen(dst, "\r\n", 2);
+ decrRefCount(o);
+ }
+ return dst;
+}
+
+/* Create the sds representation of an PEXPIREAT command, using
+ * 'seconds' as time to live and 'cmd' to understand what command
+ * we are translating into a PEXPIREAT.
+ *
+ * This command is used in order to translate EXPIRE and PEXPIRE commands
+ * into PEXPIREAT command so that we retain precision in the append only
+ * file, and the time is always absolute and not relative. */
+sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, robj *seconds) {
+ PORT_LONGLONG when;
+ robj *argv[3];
+
+ /* Make sure we can use strtoll */
+ seconds = getDecodedObject(seconds);
+ when = strtoll(seconds->ptr, NULL, 10);
+ /* Convert argument into milliseconds for EXPIRE, SETEX, EXPIREAT */
+ if (cmd->proc == expireCommand || cmd->proc == setexCommand ||
+ cmd->proc == expireatCommand)
+ {
+ when *= 1000;
+ }
+ /* Convert into absolute time for EXPIRE, PEXPIRE, SETEX, PSETEX */
+ if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
+ cmd->proc == setexCommand || cmd->proc == psetexCommand)
+ {
+ when += mstime();
+ }
+ decrRefCount(seconds);
+
+ argv[0] = createStringObject("PEXPIREAT", 9);
+ argv[1] = key;
+ argv[2] = createStringObjectFromLongLong(when);
+ buf = catAppendOnlyGenericCommand(buf, 3, argv);
+ decrRefCount(argv[0]);
+ decrRefCount(argv[2]);
+ return buf;
+}
+
+void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
+ sds buf = sdsempty();
+ robj *tmpargv[3];
+
+ /* The DB this command was targeting is not the same as the last command
+ * we appended. To issue a SELECT command is needed. */
+ if (dictid != server.aof_selected_db) {
+ char seldb[64];
+
+ snprintf(seldb, sizeof(seldb), "%d", dictid);
+ buf = sdscatprintf(buf, "*2\r\n$6\r\nSELECT\r\n$%Iu\r\n%s\r\n", WIN_PORT_FIX /* %lu -> %Iu */
+ (PORT_ULONG)strlen(seldb), seldb);
+ server.aof_selected_db = dictid;
+ }
+
+ if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
+ cmd->proc == expireatCommand) {
+ /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
+ buf = catAppendOnlyExpireAtCommand(buf, cmd, argv[1], argv[2]);
+ } else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
+ /* Translate SETEX/PSETEX to SET and PEXPIREAT */
+ tmpargv[0] = createStringObject("SET", 3);
+ tmpargv[1] = argv[1];
+ tmpargv[2] = argv[3];
+ buf = catAppendOnlyGenericCommand(buf, 3, tmpargv);
+ decrRefCount(tmpargv[0]);
+ buf = catAppendOnlyExpireAtCommand(buf, cmd, argv[1], argv[2]);
+ } else if (cmd->proc == setCommand && argc > 3) {
+ int i;
+ robj *exarg = NULL, *pxarg = NULL;
+ /* Translate SET [EX seconds][PX milliseconds] to SET and PEXPIREAT */
+ buf = catAppendOnlyGenericCommand(buf, 3, argv);
+ for (i = 3; i < argc; i++) {
+ if (!strcasecmp(argv[i]->ptr, "ex")) exarg = argv[i + 1];
+ if (!strcasecmp(argv[i]->ptr, "px")) pxarg = argv[i + 1];
+ }
+ serverAssert(!(exarg && pxarg));
+ if (exarg)
+ buf = catAppendOnlyExpireAtCommand(buf, server.expireCommand, argv[1],
+ exarg);
+ if (pxarg)
+ buf = catAppendOnlyExpireAtCommand(buf, server.pexpireCommand, argv[1],
+ pxarg);
+ } else {
+ /* All the other commands don't need translation or need the
+ * same translation already operated in the command vector
+ * for the replication itself. */
+ buf = catAppendOnlyGenericCommand(buf, argc, argv);
+ }
+
+ /* Append to the AOF buffer. This will be flushed on disk just before
+ * of re-entering the event loop, so before the client will get a
+ * positive reply about the operation performed. */
+ if (server.aof_state == AOF_ON)
+ server.aof_buf = sdscatlen(server.aof_buf, buf, sdslen(buf));
+
+ /* If a background append only file rewriting is in progress we want to
+ * accumulate the differences between the child DB and the current one
+ * in a buffer, so that when the child process will do its work we
+ * can append the differences to the new append only file. */
+ if (server.aof_child_pid != -1)
+ aofRewriteBufferAppend((unsigned char*) buf, (PORT_ULONG) sdslen(buf)); WIN_PORT_FIX /* cast (PORT_ULONG) */
+
+ sdsfree(buf);
+}
+
+/* ----------------------------------------------------------------------------
+ * AOF loading
+ * ------------------------------------------------------------------------- */
+
+ /* In Redis commands are always executed in the context of a client, so in
+ * order to load the append only file we need to create a fake client. */
+struct client *createFakeClient(void) {
+ struct client *c = zmalloc(sizeof(*c));
+
+ selectDb(c, 0);
+ c->fd = -1;
+ c->name = NULL;
+ c->querybuf = sdsempty();
+ c->querybuf_peak = 0;
+ c->argc = 0;
+ c->argv = NULL;
+ c->bufpos = 0;
+ c->flags = 0;
+ c->btype = BLOCKED_NONE;
+ /* We set the fake client as a slave waiting for the synchronization
+ * so that Redis will not try to send replies to this client. */
+ c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
+ c->reply = listCreate();
+ c->reply_bytes = 0;
+ c->obuf_soft_limit_reached_time = 0;
+ c->watched_keys = listCreate();
+ c->peerid = NULL;
+ listSetFreeMethod(c->reply, decrRefCountVoid);
+ listSetDupMethod(c->reply, dupClientReplyValue);
+ initClientMultiState(c);
+ return c;
+}
+
+void freeFakeClientArgv(struct client *c) {
+ int j;
+
+ for (j = 0; j < c->argc; j++)
+ decrRefCount(c->argv[j]);
+ zfree(c->argv);
+}
+
+void freeFakeClient(struct client *c) {
+ sdsfree(c->querybuf);
+ listRelease(c->reply);
+ listRelease(c->watched_keys);
+ freeClientMultiState(c);
+ zfree(c);
+}
+
+/* Replay the append log file. On success C_OK is returned. On non fatal
+ * error (the append only file is zero-length) C_ERR is returned. On
+ * fatal error an error message is logged and the program exists. */
+int loadAppendOnlyFile(char *filename) {
+ struct client *fakeClient;
+ FILE *fp = fopen(filename, IF_WIN32("rb", "r"));
+ struct redis_stat sb;
+ int old_aof_state = server.aof_state;
+ PORT_LONG loops = 0;
+ off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */
+
+ if (fp == NULL) {
+ serverLog(LL_WARNING, "Fatal error: can't open the append log file for reading: %s", strerror(errno));
+ exit(1);
+ }
+
+ /* Handle a zero-length AOF file as a special case. An emtpy AOF file
+ * is a valid AOF because an empty server with AOF enabled will create
+ * a zero length file at startup, that will remain like that if no write
+ * operation is received. */
+ if (fp && redis_fstat(fileno(fp), &sb) != -1 && sb.st_size == 0) {
+ server.aof_current_size = 0;
+ fclose(fp);
+ return C_ERR;
+ }
+
+ /* Temporarily disable AOF, to prevent EXEC from feeding a MULTI
+ * to the same file we're about to read. */
+ server.aof_state = AOF_OFF;
+
+ fakeClient = createFakeClient();
+ startLoading(fp);
+
+ /* Check if this AOF file has an RDB preamble. In that case we need to
+ * load the RDB file and later continue loading the AOF tail. */
+ char sig[5]; /* "REDIS" */
+ if (fread(sig, 1, 5, fp) != 5 || memcmp(sig, "REDIS", 5) != 0) {
+ /* No RDB preamble, seek back at 0 offset. */
+ if (fseek(fp, 0, SEEK_SET) == -1) goto readerr;
+ }
+ else {
+ /* RDB preamble. Pass loading the RDB functions. */
+ rio rdb;
+
+ serverLog(LL_NOTICE, "Reading RDB preamble from AOF file...");
+ if (fseek(fp, 0, SEEK_SET) == -1) goto readerr;
+ rioInitWithFile(&rdb, fp);
+ if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
+ serverLog(LL_WARNING, "Error reading the RDB preamble of the AOF file, AOF loading aborted");
+ goto readerr;
+ }
+ else {
+ serverLog(LL_NOTICE, "Reading the remaining AOF tail...");
+ }
+ }
+
+ /* Read the actual AOF file, in REPL format, command by command. */
+ while (1) {
+ int argc, j;
+ PORT_ULONG len;
+ robj **argv;
+ char buf[128];
+ sds argsds;
+ struct redisCommand *cmd;
+
+ /* Serve the clients from time to time */
+ if (!(loops++ % 1000)) {
+ loadingProgress((off_t) ftello(fp)); WIN_PORT_FIX /* cast (off_t) */
+ processEventsWhileBlocked();
+ }
+
+ if (fgets(buf, sizeof(buf), fp) == NULL) {
+ if (feof(fp))
+ break;
+ else
+ goto readerr;
+ }
+ if (buf[0] != '*') goto fmterr;
+ if (buf[1] == '\0') goto readerr;
+ argc = atoi(buf + 1);
+ if (argc < 1) goto fmterr;
+
+ argv = zmalloc(sizeof(robj*)*argc);
+ fakeClient->argc = argc;
+ fakeClient->argv = argv;
+
+ for (j = 0; j < argc; j++) {
+ if (fgets(buf, sizeof(buf), fp) == NULL) {
+ fakeClient->argc = j; /* Free up to j-1. */
+ freeFakeClientArgv(fakeClient);
+ goto readerr;
+ }
+ if (buf[0] != '$') goto fmterr;
+ len = strtol(buf + 1, NULL, 10);
+ argsds = sdsnewlen(NULL, len);
+ if (len && fread(argsds, len, 1, fp) == 0) {
+ sdsfree(argsds);
+ fakeClient->argc = j; /* Free up to j-1. */
+ freeFakeClientArgv(fakeClient);
+ goto readerr;
+ }
+ argv[j] = createObject(OBJ_STRING, argsds);
+ if (fread(buf, 2, 1, fp) == 0) {
+ fakeClient->argc = j + 1; /* Free up to j. */
+ freeFakeClientArgv(fakeClient);
+ goto readerr; /* discard CRLF */
+ }
+ }
+
+ /* Command lookup */
+ cmd = lookupCommand(argv[0]->ptr);
+ if (!cmd) {
+ serverLog(LL_WARNING, "Unknown command '%s' reading the append only file", (char*) argv[0]->ptr);
+ exit(1);
+ }
+
+ /* Run the command in the context of a fake client */
+ fakeClient->cmd = cmd;
+ cmd->proc(fakeClient);
+
+ /* The fake client should not have a reply */
+ serverAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
+ /* The fake client should never get blocked */
+ serverAssert((fakeClient->flags & CLIENT_BLOCKED) == 0);
+
+ /* Clean up. Command code may have changed argv/argc so we use the
+ * argv/argc of the client instead of the local variables. */
+ freeFakeClientArgv(fakeClient);
+ fakeClient->cmd = NULL;
+ if (server.aof_load_truncated) valid_up_to = ftello(fp);
+ }
+
+ /* This point can only be reached when EOF is reached without errors.
+ * If the client is in the middle of a MULTI/EXEC, log error and quit. */
+ if (fakeClient->flags & CLIENT_MULTI) goto uxeof;
+
+loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
+ fclose(fp);
+ freeFakeClient(fakeClient);
+ server.aof_state = old_aof_state;
+ stopLoading();
+ aofUpdateCurrentSize();
+ server.aof_rewrite_base_size = server.aof_current_size;
+ return C_OK;
+
+readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
+ if (!feof(fp)) {
+ if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
+ serverLog(LL_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
+ exit(1);
+ }
+
+uxeof: /* Unexpected AOF end of file. */
+ if (server.aof_load_truncated) {
+ serverLog(LL_WARNING, "!!! Warning: short read while loading the AOF file !!!");
+ serverLog(LL_WARNING, "!!! Truncating the AOF at offset %llu !!!",
+ (PORT_ULONGLONG) valid_up_to);
+ if (valid_up_to == -1 || truncate(filename, valid_up_to) == -1) {
+ if (valid_up_to == -1) {
+ serverLog(LL_WARNING, "Last valid command offset is invalid");
+ }
+ else {
+ serverLog(LL_WARNING, "Error truncating the AOF file: %s",
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ }
+ }
+ else {
+ /* Make sure the AOF file descriptor points to the end of the
+ * file after the truncate call. */
+ if (server.aof_fd != -1 && lseek(server.aof_fd, 0, SEEK_END) == -1) {
+ serverLog(LL_WARNING, "Can't seek the end of the AOF file: %s",
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ }
+ else {
+ serverLog(LL_WARNING,
+ "AOF loaded anyway because aof-load-truncated is enabled");
+ goto loaded_ok;
+ }
+ }
+ }
+ if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
+ serverLog(LL_WARNING, "Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix . 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
+ exit(1);
+
+fmterr: /* Format error. */
+ if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
+ serverLog(LL_WARNING, "Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix ");
+ exit(1);
+}
+
+/* ----------------------------------------------------------------------------
+ * AOF rewrite
+ * ------------------------------------------------------------------------- */
+
+ /* Delegate writing an object to writing a bulk string or bulk PORT_LONGLONG.
+ * This is not placed in rio.c since that adds the server.h dependency. */
+int rioWriteBulkObject(rio *r, robj *obj) {
+ /* Avoid using getDecodedObject to help copy-on-write (we are often
+ * in a child process when this function is called). */
+ if (obj->encoding == OBJ_ENCODING_INT) {
+ return (int) rioWriteBulkLongLong(r, (PORT_LONG) obj->ptr); WIN_PORT_FIX /* cast (int) */
+ }
+ else if (sdsEncodedObject(obj)) {
+ return (int) rioWriteBulkString(r, obj->ptr, sdslen(obj->ptr)); WIN_PORT_FIX /* cast (int) */
+ }
+ else {
+ serverPanic("Unknown string encoding");
+ }
+}
+
+/* Emit the commands needed to rebuild a list object.
+ * The function returns 0 on error, 1 on success. */
+int rewriteListObject(rio *r, robj *key, robj *o) {
+ PORT_LONGLONG count = 0, items = listTypeLength(o);
+
+ if (o->encoding == OBJ_ENCODING_QUICKLIST) {
+ quicklist *list = o->ptr;
+ quicklistIter *li = quicklistGetIterator(list, AL_START_HEAD);
+ quicklistEntry entry;
+
+ while (quicklistNext(li, &entry)) {
+ if (count == 0) {
+ int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
+ AOF_REWRITE_ITEMS_PER_CMD : items;
+ if (rioWriteBulkCount(r, '*', 2 + cmd_items) == 0) return 0;
+ if (rioWriteBulkString(r, "RPUSH", 5) == 0) return 0;
+ if (rioWriteBulkObject(r, key) == 0) return 0;
+ }
+
+ if (entry.value) {
+ if (rioWriteBulkString(r, (char*) entry.value, entry.sz) == 0) return 0;
+ }
+ else {
+ if (rioWriteBulkLongLong(r, entry.longval) == 0) return 0;
+ }
+ if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
+ items--;
+ }
+ quicklistReleaseIterator(li);
+ }
+ else {
+ serverPanic("Unknown list encoding");
+ }
+ return 1;
+}
+
+/* Emit the commands needed to rebuild a set object.
+ * The function returns 0 on error, 1 on success. */
+int rewriteSetObject(rio *r, robj *key, robj *o) {
+ PORT_LONGLONG count = 0, items = setTypeSize(o);
+
+ if (o->encoding == OBJ_ENCODING_INTSET) {
+ int ii = 0;
+ int64_t llval;
+
+ while (intsetGet(o->ptr, ii++, &llval)) {
+ if (count == 0) {
+ int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
+ AOF_REWRITE_ITEMS_PER_CMD : (int) items; WIN_PORT_FIX /* cast (int) */
+
+ if (rioWriteBulkCount(r, '*', 2 + cmd_items) == 0) return 0;
+ if (rioWriteBulkString(r, "SADD", 4) == 0) return 0;
+ if (rioWriteBulkObject(r, key) == 0) return 0;
+ }
+ if (rioWriteBulkLongLong(r, llval) == 0) return 0;
+ if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
+ items--;
+ }
+ }
+ else if (o->encoding == OBJ_ENCODING_HT) {
+ dictIterator *di = dictGetIterator(o->ptr);
+ dictEntry *de;
+
+ while ((de = dictNext(di)) != NULL) {
+ sds ele = dictGetKey(de);
+ if (count == 0) {
+ int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
+ AOF_REWRITE_ITEMS_PER_CMD : (int) items; WIN_PORT_FIX /* cast (int) */
+
+ if (rioWriteBulkCount(r, '*', 2 + cmd_items) == 0) return 0;
+ if (rioWriteBulkString(r, "SADD", 4) == 0) return 0;
+ if (rioWriteBulkObject(r, key) == 0) return 0;
+ }
+ if (rioWriteBulkString(r, ele, sdslen(ele)) == 0) return 0;
+ if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
+ items--;
+ }
+ dictReleaseIterator(di);
+ }
+ else {
+ serverPanic("Unknown set encoding");
+ }
+ return 1;
+}
+
+/* Emit the commands needed to rebuild a sorted set object.
+ * The function returns 0 on error, 1 on success. */
+int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
+ PORT_LONGLONG count = 0, items = zsetLength(o);
+
+ if (o->encoding == OBJ_ENCODING_ZIPLIST) {
+ unsigned char *zl = o->ptr;
+ unsigned char *eptr, *sptr;
+ unsigned char *vstr;
+ unsigned int vlen;
+ PORT_LONGLONG vll;
+ double score;
+
+ eptr = ziplistIndex(zl, 0);
+ serverAssert(eptr != NULL);
+ sptr = ziplistNext(zl, eptr);
+ serverAssert(sptr != NULL);
+
+ while (eptr != NULL) {
+ serverAssert(ziplistGet(eptr, &vstr, &vlen, &vll));
+ score = zzlGetScore(sptr);
+
+ if (count == 0) {
+ int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
+ AOF_REWRITE_ITEMS_PER_CMD : (int) items; WIN_PORT_FIX /* cast (int) */
+
+ if (rioWriteBulkCount(r, '*', 2 + cmd_items * 2) == 0) return 0;
+ if (rioWriteBulkString(r, "ZADD", 4) == 0) return 0;
+ if (rioWriteBulkObject(r, key) == 0) return 0;
+ }
+ if (rioWriteBulkDouble(r, score) == 0) return 0;
+ if (vstr != NULL) {
+ if (rioWriteBulkString(r, (char*) vstr, vlen) == 0) return 0;
+ }
+ else {
+ if (rioWriteBulkLongLong(r, vll) == 0) return 0;
+ }
+ zzlNext(zl, &eptr, &sptr);
+ if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
+ items--;
+ }
+ }
+ else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
+ zset *zs = o->ptr;
+ dictIterator *di = dictGetIterator(zs->dict);
+ dictEntry *de;
+
+ while ((de = dictNext(di)) != NULL) {
+ sds ele = dictGetKey(de);
+ double *score = dictGetVal(de);
+
+ if (count == 0) {
+ int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
+ AOF_REWRITE_ITEMS_PER_CMD : (int) items; WIN_PORT_FIX /* cast (int) */
+
+ if (rioWriteBulkCount(r, '*', 2 + cmd_items * 2) == 0) return 0;
+ if (rioWriteBulkString(r, "ZADD", 4) == 0) return 0;
+ if (rioWriteBulkObject(r, key) == 0) return 0;
+ }
+ if (rioWriteBulkDouble(r, *score) == 0) return 0;
+ if (rioWriteBulkString(r, ele, sdslen(ele)) == 0) return 0;
+ if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
+ items--;
+ }
+ dictReleaseIterator(di);
+ }
+ else {
+ serverPanic("Unknown sorted zset encoding");
+ }
+ return 1;
+}
+
+/* Write either the key or the value of the currently selected item of a hash.
+ * The 'hi' argument passes a valid Redis hash iterator.
+ * The 'what' filed specifies if to write a key or a value and can be
+ * either OBJ_HASH_KEY or OBJ_HASH_VALUE.
+ *
+ * The function returns 0 on error, non-zero on success. */
+static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) {
+ if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
+ unsigned char *vstr = NULL;
+ unsigned int vlen = UINT_MAX;
+ PORT_LONGLONG vll = LLONG_MAX;
+
+ hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
+ if (vstr) {
+ return (int) rioWriteBulkString(r, (char*) vstr, vlen); WIN_PORT_FIX /* cast (int) */
+ }
+ else {
+ return (int) rioWriteBulkLongLong(r, vll); WIN_PORT_FIX /* cast (int) */
+ }
+ }
+ else if (hi->encoding == OBJ_ENCODING_HT) {
+ sds value = hashTypeCurrentFromHashTable(hi, what);
+ return rioWriteBulkString(r, value, sdslen(value));
+ }
+
+ serverPanic("Unknown hash encoding");
+ return 0;
+}
+
+/* Emit the commands needed to rebuild a hash object.
+ * The function returns 0 on error, 1 on success. */
+int rewriteHashObject(rio *r, robj *key, robj *o) {
+ hashTypeIterator *hi;
+ PORT_LONGLONG count = 0, items = hashTypeLength(o);
+
+ hi = hashTypeInitIterator(o);
+ while (hashTypeNext(hi) != C_ERR) {
+ if (count == 0) {
+ int cmd_items = (int) ((items > AOF_REWRITE_ITEMS_PER_CMD) ? WIN_PORT_FIX /* cast (int) */
+ AOF_REWRITE_ITEMS_PER_CMD : items);
+
+ if (rioWriteBulkCount(r, '*', 2 + cmd_items * 2) == 0) return 0;
+ if (rioWriteBulkString(r, "HMSET", 5) == 0) return 0;
+ if (rioWriteBulkObject(r, key) == 0) return 0;
+ }
+
+ if (rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) == 0) return 0;
+ if (rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE) == 0) return 0;
+ if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
+ items--;
+ }
+
+ hashTypeReleaseIterator(hi);
+
+ return 1;
+}
+
+/* Call the module type callback in order to rewrite a data type
+ * that is exported by a module and is not handled by Redis itself.
+ * The function returns 0 on error, 1 on success. */
+int rewriteModuleObject(rio *r, robj *key, robj *o) {
+ RedisModuleIO io;
+ moduleValue *mv = o->ptr;
+ moduleType *mt = mv->type;
+ moduleInitIOContext(io,mt,r);
+ mt->aof_rewrite(&io,key,mv->value);
+ if (io.ctx) {
+ moduleFreeContext(io.ctx);
+ zfree(io.ctx);
+ }
+ return io.error ? 0 : 1;
+}
+
+/* This function is called by the child rewriting the AOF file to read
+ * the difference accumulated from the parent into a buffer, that is
+ * concatenated at the end of the rewrite. */
+ssize_t aofReadDiffFromParent(void) {
+ char buf[65536]; /* Default pipe buffer size on most Linux systems. */
+ ssize_t nread, total = 0;
+
+ while ((nread =
+ read(server.aof_pipe_read_data_from_parent, buf, sizeof(buf))) > 0) {
+ server.aof_child_diff = sdscatlen(server.aof_child_diff, buf, nread);
+ total += nread;
+ }
+ return total;
+}
+
+int rewriteAppendOnlyFileRio(rio *aof) {
+ dictIterator *di = NULL;
+ dictEntry *de;
+ size_t processed = 0;
+ PORT_LONGLONG now = mstime();
+ int j;
+
+ for (j = 0; j < server.dbnum; j++) {
+ char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
+ redisDb *db = server.db + j;
+ dict *d = db->dict;
+ if (dictSize(d) == 0) continue;
+ di = dictGetSafeIterator(d);
+
+ /* SELECT the new DB */
+ if (rioWrite(aof, selectcmd, sizeof(selectcmd) - 1) == 0) goto werr;
+ if (rioWriteBulkLongLong(aof, j) == 0) goto werr;
+
+ /* Iterate this DB writing every entry */
+ while ((de = dictNext(di)) != NULL) {
+ sds keystr;
+ robj key, *o;
+ PORT_LONGLONG expiretime;
+
+ keystr = dictGetKey(de);
+ o = dictGetVal(de);
+ initStaticStringObject(key, keystr);
+
+ expiretime = getExpire(db, &key);
+
+ /* If this key is already expired skip it */
+ if (expiretime != -1 && expiretime < now) continue;
+
+ /* Save the key and associated value */
+ if (o->type == OBJ_STRING) {
+ /* Emit a SET command */
+ char cmd[] = "*3\r\n$3\r\nSET\r\n";
+ if (rioWrite(aof, cmd, sizeof(cmd) - 1) == 0) goto werr;
+ /* Key and value */
+ if (rioWriteBulkObject(aof, &key) == 0) goto werr;
+ if (rioWriteBulkObject(aof, o) == 0) goto werr;
+ }
+ else if (o->type == OBJ_LIST) {
+ if (rewriteListObject(aof, &key, o) == 0) goto werr;
+ }
+ else if (o->type == OBJ_SET) {
+ if (rewriteSetObject(aof, &key, o) == 0) goto werr;
+ }
+ else if (o->type == OBJ_ZSET) {
+ if (rewriteSortedSetObject(aof, &key, o) == 0) goto werr;
+ }
+ else if (o->type == OBJ_HASH) {
+ if (rewriteHashObject(aof, &key, o) == 0) goto werr;
+ }
+ /* TODO: [tporadowski] modules support
+ else if (o->type == OBJ_MODULE) {
+ if (rewriteModuleObject(aof, &key, o) == 0) goto werr;
+ }
+ */
+ else {
+ serverPanic("Unknown object type");
+ }
+ /* Save the expire time */
+ if (expiretime != -1) {
+ char cmd[] = "*3\r\n$9\r\nPEXPIREAT\r\n";
+ if (rioWrite(aof, cmd, sizeof(cmd) - 1) == 0) goto werr;
+ if (rioWriteBulkObject(aof, &key) == 0) goto werr;
+ if (rioWriteBulkLongLong(aof, expiretime) == 0) goto werr;
+ }
+ /* Read some diff from the parent process from time to time. */
+ if (aof->processed_bytes > processed + AOF_READ_DIFF_INTERVAL_BYTES) {
+ processed = aof->processed_bytes;
+ aofReadDiffFromParent();
+ }
+ }
+ dictReleaseIterator(di);
+ di = NULL;
+ }
+ return C_OK;
+
+werr:
+ if (di) dictReleaseIterator(di);
+ return C_ERR;
+}
+
+/* Write a sequence of commands able to fully rebuild the dataset into
+ * "filename". Used both by REWRITEAOF and BGREWRITEAOF.
+ *
+ * In order to minimize the number of commands needed in the rewritten
+ * log Redis uses variadic commands when possible, such as RPUSH, SADD
+ * and ZADD. However at max AOF_REWRITE_ITEMS_PER_CMD items per time
+ * are inserted using a single command. */
+int rewriteAppendOnlyFile(char *filename) {
+ rio aof;
+ FILE *fp;
+ char tmpfile[256];
+ char byte;
+
+ /* Note that we have to use a different temp name here compared to the
+ * one used by rewriteAppendOnlyFileBackground() function. */
+ snprintf(tmpfile, 256, "temp-rewriteaof-%d.aof", (int) getpid());
+ fp = fopen(tmpfile, IF_WIN32("wb", "w"));
+ if (!fp) {
+ serverLog(LL_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ return C_ERR;
+ }
+
+ server.aof_child_diff = sdsempty();
+ rioInitWithFile(&aof, fp);
+
+ if (server.aof_rewrite_incremental_fsync)
+ rioSetAutoSync(&aof, AOF_AUTOSYNC_BYTES);
+
+ if (server.aof_use_rdb_preamble) {
+ int error;
+ if (rdbSaveRio(&aof, &error, RDB_SAVE_AOF_PREAMBLE, NULL) == C_ERR) {
+ errno = error;
+ goto werr;
+ }
+ }
+ else {
+ if (rewriteAppendOnlyFileRio(&aof) == C_ERR) goto werr;
+ }
+
+ /* Do an initial slow fsync here while the parent is still sending
+ * data, in order to make the next final fsync faster. */
+ if (fflush(fp) == EOF) goto werr;
+ if (fsync(fileno(fp)) == -1) goto werr;
+
+ /* Read again a few times to get more data from the parent.
+ * We can't read forever (the server may receive data from clients
+ * faster than it is able to send data to the child), so we try to read
+ * some more data in a loop as soon as there is a good chance more data
+ * will come. If it looks like we are wasting time, we abort (this
+ * happens after 20 ms without new data). */
+ int nodata = 0;
+ mstime_t start = mstime();
+ while (mstime() - start < 1000 && nodata < 20) {
+ if (aeWait(server.aof_pipe_read_data_from_parent, AE_READABLE, 1) <= 0)
+ {
+ nodata++;
+ continue;
+ }
+ nodata = 0; /* Start counting from zero, we stop on N *contiguous*
+ timeouts. */
+ aofReadDiffFromParent();
+ }
+
+ /* Ask the master to stop sending diffs. */
+ if (write(server.aof_pipe_write_ack_to_parent, "!", 1) != 1) goto werr;
+#ifndef _WIN32
+ if (anetNonBlock(NULL, server.aof_pipe_read_ack_from_parent) != ANET_OK)
+ goto werr;
+#endif
+ /* We read the ACK from the server using a 10 seconds timeout. Normally
+ * it should reply ASAP, but just in case we lose its reply, we are sure
+ * the child will eventually get terminated. */
+ if (syncRead(server.aof_pipe_read_ack_from_parent, &byte, 1, 5000) != 1 ||
+ byte != '!') goto werr;
+ serverLog(LL_NOTICE, "Parent agreed to stop sending diffs. Finalizing AOF...");
+
+ /* Read the final diff if any. */
+ aofReadDiffFromParent();
+
+ /* Write the received diff to the file. */
+ serverLog(LL_NOTICE,
+ "Concatenating %.2f MB of AOF diff received from parent.",
+ (double) sdslen(server.aof_child_diff) / (1024 * 1024));
+ if (rioWrite(&aof, server.aof_child_diff, sdslen(server.aof_child_diff)) == 0)
+ goto werr;
+
+ /* Make sure data will not remain on the OS's output buffers */
+ if (fflush(fp) == EOF) goto werr;
+ if (fsync(fileno(fp)) == -1) goto werr;
+ if (fclose(fp) == EOF) goto werr;
+
+ /* Use RENAME to make sure the DB file is changed atomically only
+ * if the generate DB file is ok. */
+ if (rename(tmpfile, filename) == -1) {
+ serverLog(LL_WARNING, "Error moving temp append only file on the final destination: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ unlink(tmpfile);
+ return C_ERR;
+ }
+ serverLog(LL_NOTICE, "SYNC append only file rewrite performed");
+ return C_OK;
+
+werr:
+ serverLog(LL_WARNING, "Write error writing append only file on disk: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ fclose(fp);
+ unlink(tmpfile);
+ return C_ERR;
+}
+
+/* ----------------------------------------------------------------------------
+ * AOF rewrite pipes for IPC
+ * -------------------------------------------------------------------------- */
+
+ /* This event handler is called when the AOF rewriting child sends us a
+ * single '!' char to signal we should stop sending buffer diffs. The
+ * parent sends a '!' as well to acknowledge. */
+void aofChildPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
+ char byte;
+ UNUSED(el);
+ UNUSED(privdata);
+ UNUSED(mask);
+
+ if (read(fd, &byte, 1) == 1 && byte == '!') {
+ serverLog(LL_NOTICE, "AOF rewrite child asks to stop sending diffs.");
+ server.aof_stop_sending_diff = 1;
+ if (write(server.aof_pipe_write_ack_to_child, "!", 1) != 1) {
+ /* If we can't send the ack, inform the user, but don't try again
+ * since in the other side the children will use a timeout if the
+ * kernel can't buffer our write, or, the children was
+ * terminated. */
+ serverLog(LL_WARNING, "Can't send ACK to AOF child: %s",
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ }
+ }
+#ifndef _WIN32
+ /* Remove the handler since this can be called only one time during a
+ * rewrite. */
+ aeDeleteFileEvent(server.el, server.aof_pipe_read_ack_from_child, AE_READABLE);
+#endif
+}
+
+/* Create the pipes used for parent - child process IPC during rewrite.
+ * We have a data pipe used to send AOF incremental diffs to the child,
+ * and two other pipes used by the children to signal it finished with
+ * the rewrite so no more data should be written, and another for the
+ * parent to acknowledge it understood this new condition. */
+int aofCreatePipes(void) {
+ int fds[6] = { -1, -1, -1, -1, -1, -1 };
+ int j;
+
+ if (pipe(fds) == -1) goto error; /* parent -> children data. */
+ if (pipe(fds + 2) == -1) goto error; /* children -> parent ack. */
+ if (pipe(fds + 4) == -1) goto error; /* children -> parent ack. */
+ /* Parent -> children data is non blocking. */
+#ifndef WIN32
+ if (anetNonBlock(NULL, fds[0]) != ANET_OK) goto error;
+ if (anetNonBlock(NULL, fds[1]) != ANET_OK) goto error;
+ if (aeCreateFileEvent(server.el, fds[2], AE_READABLE, aofChildPipeReadable, NULL) == AE_ERR) goto error;
+#else
+ if (FDAPI_PipeSetNonBlock(fds[0], 1) != 0) goto error;
+ if (FDAPI_PipeSetNonBlock(fds[1], 1) != 0) goto error;
+ if (FDAPI_PipeSetNonBlock(fds[2], 1) != 0) goto error;
+#endif
+
+ server.aof_pipe_write_data_to_child = fds[1];
+ server.aof_pipe_read_data_from_parent = fds[0];
+ server.aof_pipe_write_ack_to_parent = fds[3];
+ server.aof_pipe_read_ack_from_child = fds[2];
+ server.aof_pipe_write_ack_to_child = fds[5];
+ server.aof_pipe_read_ack_from_parent = fds[4];
+ server.aof_stop_sending_diff = 0;
+ return C_OK;
+
+error:
+ serverLog(LL_WARNING, "Error opening /setting AOF rewrite IPC pipes: %s",
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ for (j = 0; j < 6; j++) if (fds[j] != -1) close(fds[j]);
+ return C_ERR;
+}
+
+void aofClosePipes(void) {
+ aeDeleteFileEvent(server.el, server.aof_pipe_read_ack_from_child, AE_READABLE);
+ aeDeleteFileEvent(server.el, server.aof_pipe_write_data_to_child, AE_WRITABLE);
+ close(server.aof_pipe_write_data_to_child);
+ close(server.aof_pipe_read_data_from_parent);
+ close(server.aof_pipe_write_ack_to_parent);
+ close(server.aof_pipe_read_ack_from_child);
+ close(server.aof_pipe_write_ack_to_child);
+ close(server.aof_pipe_read_ack_from_parent);
+}
+
+/* ----------------------------------------------------------------------------
+ * AOF background rewrite
+ * ------------------------------------------------------------------------- */
+
+ /* This is how rewriting of the append only file in background works:
+ *
+ * 1) The user calls BGREWRITEAOF
+ * 2) Redis calls this function, that forks():
+ * 2a) the child rewrite the append only file in a temp file.
+ * 2b) the parent accumulates differences in server.aof_rewrite_buf.
+ * 3) When the child finished '2a' exists.
+ * 4) The parent will trap the exit code, if it's OK, will append the
+ * data accumulated into server.aof_rewrite_buf into the temp file, and
+ * finally will rename(2) the temp file in the actual file name.
+ * The the new file is reopened as the new append only file. Profit!
+ */
+int rewriteAppendOnlyFileBackground(void) {
+ pid_t childpid;
+ PORT_LONGLONG start;
+
+ if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
+ if (aofCreatePipes() != C_OK) return C_ERR;
+ openChildInfoPipe();
+ start = ustime();
+
+#ifndef _WIN32
+ if ((childpid = fork()) == 0) {
+#endif
+ char tmpfile[256];
+
+#ifndef _WIN32
+ /* Child */
+ closeListeningSockets(0);
+ redisSetProcTitle("redis-aof-rewrite");
+#endif
+ snprintf(tmpfile, 256, "temp-rewriteaof-bg-%d.aof", (int) getpid());
+#ifdef _WIN32
+ childpid = BeginForkOperation_Aof(server.aof_pipe_write_ack_to_parent,
+ server.aof_pipe_read_ack_from_parent,
+ server.aof_pipe_read_data_from_parent,
+ tmpfile,
+ &server,
+ sizeof(server),
+ dictGetHashFunctionSeed());
+#else
+ if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
+ size_t private_dirty = zmalloc_get_private_dirty(-1);
+
+ if (private_dirty) {
+ serverLog(LL_NOTICE,
+ "AOF rewrite: %Iu MB of memory used by copy-on-write", WIN_PORT_FIX /* %lu -> %Iu */
+ private_dirty / (1024 * 1024));
+ }
+
+ server.child_info_data.cow_size = private_dirty;
+ sendChildInfo(CHILD_INFO_TYPE_AOF);
+ exitFromChild(0);
+ }
+ else {
+ exitFromChild(1);
+ }
+ }
+ else {
+#endif
+ /* Parent */
+ server.stat_fork_time = ustime() - start;
+ server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024 * 1024 * 1024); /* GB per second. */
+ latencyAddSampleIfNeeded("fork", server.stat_fork_time / 1000);
+ if (childpid == -1) {
+ closeChildInfoPipe();
+ serverLog(LL_WARNING,
+ "Can't rewrite append only file in background: fork: %s",
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ aofClosePipes();
+ return C_ERR;
+ }
+ serverLog(LL_NOTICE,
+ "Background append only file rewriting started by pid %d", childpid);
+ server.aof_rewrite_scheduled = 0;
+ server.aof_rewrite_time_start = time(NULL);
+ server.aof_child_pid = childpid;
+ updateDictResizePolicy();
+ /* We set appendseldb to -1 in order to force the next call to the
+ * feedAppendOnlyFile() to issue a SELECT command, so the differences
+ * accumulated by the parent into server.aof_rewrite_buf will start
+ * with a SELECT statement and it will be safe to merge. */
+ server.aof_selected_db = -1;
+ replicationScriptCacheFlush();
+ return C_OK;
+#ifndef _WIN32
+ }
+#endif
+ return C_OK; /* unreached */
+}
+
+void bgrewriteaofCommand(client *c) {
+ if (server.aof_child_pid != -1) {
+ addReplyError(c, "Background append only file rewriting already in progress");
+ }
+ else if (server.rdb_child_pid != -1) {
+ server.aof_rewrite_scheduled = 1;
+ addReplyStatus(c, "Background append only file rewriting scheduled");
+ }
+ else if (rewriteAppendOnlyFileBackground() == C_OK) {
+ addReplyStatus(c, "Background append only file rewriting started");
+ }
+ else {
+ addReply(c, shared.err);
+ }
+}
+
+void aofRemoveTempFile(pid_t childpid) {
+ char tmpfile[256];
+
+ snprintf(tmpfile, 256, "temp-rewriteaof-bg-%d.aof", (int) childpid);
+ unlink(tmpfile);
+}
+
+/* Update the server.aof_current_size field explicitly using stat(2)
+ * to check the size of the file. This is useful after a rewrite or after
+ * a restart, normally the size is updated just adding the write length
+ * to the current length, that is much faster. */
+void aofUpdateCurrentSize(void) {
+ struct redis_stat sb;
+ mstime_t latency;
+
+#ifdef _WIN32
+ if (server.aof_fd == -1) {
+ serverLog(LL_NOTICE, "Unable to check the AOF length: %s", "appendfd is -1");
+ return;
+ }
+#endif
+
+ latencyStartMonitor(latency);
+ if (redis_fstat(server.aof_fd, &sb) == -1) {
+ serverLog(LL_WARNING, "Unable to obtain the AOF file length. stat: %s",
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ }
+ else {
+ server.aof_current_size = sb.st_size;
+ }
+ latencyEndMonitor(latency);
+ latencyAddSampleIfNeeded("aof-fstat", latency);
+}
+
+/* A background append only file rewriting (BGREWRITEAOF) terminated its work.
+ * Handle this. */
+void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
+ if (!bysignal && exitcode == 0) {
+ int newfd, oldfd;
+ char tmpfile[256];
+ PORT_LONGLONG now = ustime();
+ mstime_t latency;
+
+ serverLog(LL_NOTICE,
+ "Background AOF rewrite terminated with success");
+
+ /* Flush the differences accumulated by the parent to the
+ * rewritten AOF. */
+ latencyStartMonitor(latency);
+#ifdef _WIN32
+ snprintf(tmpfile, 256, "temp-rewriteaof-bg-%d.aof",
+ getpid());
+ newfd = open(tmpfile, O_WRONLY | O_APPEND | O_CREAT | _O_BINARY, _S_IREAD | _S_IWRITE);
+#else
+ snprintf(tmpfile, 256, "temp-rewriteaof-bg-%d.aof",
+ (int) server.aof_child_pid);
+ newfd = open(tmpfile, O_WRONLY | O_APPEND);
+#endif
+ if (newfd == -1) {
+ serverLog(LL_WARNING,
+ "Unable to open the temporary AOF produced by the child: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ goto cleanup;
+ }
+
+ if (aofRewriteBufferWrite(newfd) == -1) {
+ serverLog(LL_WARNING,
+ "Error trying to flush the parent diff to the rewritten AOF: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ close(newfd);
+ goto cleanup;
+ }
+ latencyEndMonitor(latency);
+ latencyAddSampleIfNeeded("aof-rewrite-diff-write", latency);
+
+ serverLog(LL_NOTICE,
+ "Residual parent diff successfully flushed to the rewritten AOF (%.2f MB)", (double) aofRewriteBufferSize() / (1024 * 1024));
+
+ /* The only remaining thing to do is to rename the temporary file to
+ * the configured file and switch the file descriptor used to do AOF
+ * writes. We don't want close(2) or rename(2) calls to block the
+ * server on old file deletion.
+ *
+ * There are two possible scenarios:
+ *
+ * 1) AOF is DISABLED and this was a one time rewrite. The temporary
+ * file will be renamed to the configured file. When this file already
+ * exists, it will be unlinked, which may block the server.
+ *
+ * 2) AOF is ENABLED and the rewritten AOF will immediately start
+ * receiving writes. After the temporary file is renamed to the
+ * configured file, the original AOF file descriptor will be closed.
+ * Since this will be the last reference to that file, closing it
+ * causes the underlying file to be unlinked, which may block the
+ * server.
+ *
+ * To mitigate the blocking effect of the unlink operation (either
+ * caused by rename(2) in scenario 1, or by close(2) in scenario 2), we
+ * use a background thread to take care of this. First, we
+ * make scenario 1 identical to scenario 2 by opening the target file
+ * when it exists. The unlink operation after the rename(2) will then
+ * be executed upon calling close(2) for its descriptor. Everything to
+ * guarantee atomicity for this switch has already happened by then, so
+ * we don't care what the outcome or duration of that close operation
+ * is, as PORT_LONG as the file descriptor is released again. */
+#ifdef _WIN32
+ // On Windows, if AOF is disabled and an AOF file already exists,
+ // we don't need to open it since it will have to be closed anyway
+ // before renaming the temp AOF.
+ oldfd = -1; /* We'll set this to the current AOF filedes later. */
+
+ char tmpfile_win_old[256];
+ snprintf(tmpfile_win_old, 256, "temp-rewriteaof-old-%d.aof",
+ (int) server.aof_child_pid);
+
+ if (server.aof_fd != -1) {
+ // AOF enabled, close the existing AOF file
+ close(server.aof_fd);
+ // Now rename the existing AOF file to allow the new file to be renamed
+ if (rename(server.aof_filename, tmpfile_win_old) == -1) {
+ serverLog(LL_WARNING,
+ "Error trying to rename the existing AOF to old tempfile: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ // Let's clean the Windows-specific temp file here
+ unlink(tmpfile_win_old);
+ goto cleanup;
+ }
+ }
+ latencyStartMonitor(latency);
+ // Close the temp AOF file before renaming it
+ close(newfd);
+ if (rename(tmpfile, server.aof_filename) == -1) {
+ serverLog(LL_WARNING,
+ "Error trying to rename the temporary AOF file %s into %s: %s",
+ tmpfile,
+ server.aof_filename,
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ if (server.aof_fd != -1) {
+ if (rename(tmpfile_win_old, server.aof_filename) == -1) {
+ serverLog(LL_WARNING,
+ "Error trying to rename the old tempfile %s into the existing AOF file %s: %s",
+ tmpfile,
+ server.aof_filename,
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ serverLog(LL_WARNING,
+ "Error trying to rename the existing AOF from old tempfile: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ // The Windows-specific temp file couldn't be renamed to
+ // the configured AOF file, that should never happen but
+ // if it happens we leave the file behind in case the user
+ // needs it
+ }
+ }
+ goto cleanup;
+ }
+ /* now open the files again with new names */
+ newfd = open(server.aof_filename, O_WRONLY | O_APPEND | _O_BINARY, 0);
+ if (newfd == -1) {
+ /* Windows fix: More info */
+ serverLog(LL_WARNING, "Not able to reopen the temporary AOF file after rename");
+ goto cleanup;
+ }
+ if (server.aof_fd != -1) {
+ server.aof_fd = open(
+ tmpfile_win_old,
+ O_WRONLY | O_APPEND | O_CREAT | _O_BINARY | _O_TEMPORARY, // _O_TEMPORARY forces delete on close flag in CreateFile call. File will be deleted in BIO_CLOSE_FILE job.
+ 0644);
+ }
+#else
+ if (server.aof_fd == -1) {
+ /* AOF disabled */
+
+ /* Don't care if this fails: oldfd will be -1 and we handle that.
+ * One notable case of -1 return is if the old file does
+ * not exist. */
+ oldfd = open(server.aof_filename, O_RDONLY | O_NONBLOCK);
+ }
+ else {
+ /* AOF enabled */
+ oldfd = -1; /* We'll set this to the current AOF filedes later. */
+ }
+
+ /* Rename the temporary file. This will not unlink the target file if
+ * it exists, because we reference it with "oldfd". */
+ latencyStartMonitor(latency);
+ if (rename(tmpfile, server.aof_filename) == -1) {
+ serverLog(LL_WARNING,
+ "Error trying to rename the temporary AOF file %s into %s: %s",
+ tmpfile,
+ server.aof_filename,
+ strerror(errno));
+ close(newfd);
+ if (oldfd != -1) close(oldfd);
+ goto cleanup;
+ }
+#endif
+ latencyEndMonitor(latency);
+ latencyAddSampleIfNeeded("aof-rename", latency);
+
+ if (server.aof_fd == -1) {
+ /* AOF disabled, we don't need to set the AOF file descriptor
+ * to this new file, so we can close it. */
+ close(newfd);
+ }
+ else {
+ /* AOF enabled, replace the old fd with the new one. */
+ oldfd = server.aof_fd;
+ server.aof_fd = newfd;
+ if (server.aof_fsync == AOF_FSYNC_ALWAYS)
+ aof_fsync(newfd);
+ else if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
+ aof_background_fsync(newfd);
+ server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
+ aofUpdateCurrentSize();
+ server.aof_rewrite_base_size = server.aof_current_size;
+
+ /* Clear regular AOF buffer since its contents was just written to
+ * the new AOF from the background rewrite buffer. */
+ sdsfree(server.aof_buf);
+ server.aof_buf = sdsempty();
+ }
+
+ server.aof_lastbgrewrite_status = C_OK;
+
+ serverLog(LL_NOTICE, "Background AOF rewrite finished successfully");
+ /* Change state from WAIT_REWRITE to ON if needed */
+ if (server.aof_state == AOF_WAIT_REWRITE)
+ server.aof_state = AOF_ON;
+
+ /* Asynchronously close the overwritten AOF. */
+ if (oldfd != -1) bioCreateBackgroundJob(BIO_CLOSE_FILE, (void*) (PORT_LONG) oldfd, NULL, NULL);
+
+ serverLog(LL_VERBOSE,
+ "Background AOF rewrite signal handler took %lldus", ustime() - now);
+ }
+ else if (!bysignal && exitcode != 0) {
+ /* SIGUSR1 is whitelisted, so we have a way to kill a child without
+ * tirggering an error conditon. */
+ if (bysignal != SIGUSR1)
+ server.aof_lastbgrewrite_status = C_ERR;
+ serverLog(LL_WARNING,
+ "Background AOF rewrite terminated with error");
+ }
+ else {
+ server.aof_lastbgrewrite_status = C_ERR;
+
+ serverLog(LL_WARNING,
+ "Background AOF rewrite terminated by signal %d", bysignal);
+ }
+
+cleanup:
+ aofClosePipes();
+ aofRewriteBufferReset();
+ aofRemoveTempFile(IF_WIN32(getpid(), server.aof_child_pid));
+ server.aof_child_pid = -1;
+ server.aof_rewrite_time_last = time(NULL) - server.aof_rewrite_time_start;
+ server.aof_rewrite_time_start = -1;
+ /* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */
+ if (server.aof_state == AOF_WAIT_REWRITE)
+ server.aof_rewrite_scheduled = 1;
+}
+
+void aofProcessDiffRewriteEvents(aeEventLoop* eventLoop)
+{
+ // only do these checks in the parent process and if an aof rewrite is in progress
+ if (server.aof_child_pid != -1 && server.aof_pipe_read_ack_from_child != -1) {
+ //1) check if more data can be written to the child and write it.
+ // in which case we dont need to send any more diffs to the parent
+ if (server.aof_stop_sending_diff == 0) {
+ aofChildWriteDiffData(eventLoop, server.aof_pipe_write_data_to_child, NULL, 0);
+ }
+
+ //2) check if child has signaled parent to stop sending diffs
+ if (server.aof_stop_sending_diff == 0) {
+ aofChildPipeReadable(eventLoop, server.aof_pipe_read_ack_from_child, NULL, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/bitops.c b/src/bitops.c
index 9ebc549e..68eecf6d 100644
--- a/src/bitops.c
+++ b/src/bitops.c
@@ -34,7 +34,7 @@
* Helpers and low level bit functions.
* -------------------------------------------------------------------------- */
-/* Count number of bits set in the binary array pointed by 's' and PORT_LONG
+/* Count number of bits set in the binary array pointed by 's' and long
* 'count' bytes. The implementation of this function is required to
* work with a input string length up to 512 MB. */
size_t redisPopcount(void *s, PORT_LONG count) {
@@ -92,7 +92,7 @@ size_t redisPopcount(void *s, PORT_LONG count) {
}
/* Return the position of the first bit set to one (if 'bit' is 1) or
- * zero (if 'bit' is 0) in the bitmap starting at 's' and PORT_LONG 'count' bytes.
+ * zero (if 'bit' is 0) in the bitmap starting at 's' and long 'count' bytes.
*
* The function is guaranteed to return a value >= 0 if 'bit' is 0 since if
* no zero bit is found, it returns count*8 assuming the string is zero
@@ -167,7 +167,7 @@ PORT_LONG redisBitpos(void *s, PORT_ULONG count, int bit) {
/* Last word left, scan bit by bit. The first thing we need is to
* have a single "1" set in the most significant position in an
- * PORT_ULONG. We don't know the size of the PORT_LONG so we use a
+ * unsigned long. We don't know the size of the long so we use a
* simple trick. */
one = PORT_ULONG_MAX; /* All bits set to 1.*/
one >>= 1; /* All bits set to 1 but the MSB. */
diff --git a/src/bitops.c.bak b/src/bitops.c.bak
new file mode 100644
index 00000000..9ebc549e
--- /dev/null
+++ b/src/bitops.c.bak
@@ -0,0 +1,1125 @@
+/* Bit operations.
+ *
+ * Copyright (c) 2009-2012, Salvatore Sanfilippo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * 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 "server.h"
+
+/* -----------------------------------------------------------------------------
+ * Helpers and low level bit functions.
+ * -------------------------------------------------------------------------- */
+
+/* Count number of bits set in the binary array pointed by 's' and PORT_LONG
+ * 'count' bytes. The implementation of this function is required to
+ * work with a input string length up to 512 MB. */
+size_t redisPopcount(void *s, PORT_LONG count) {
+ size_t bits = 0;
+ unsigned char *p = s;
+ uint32_t *p4;
+ static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
+
+ /* Count initial bytes not aligned to 32 bit. */
+ while((PORT_ULONG)p & 3 && count) {
+ bits += bitsinbyte[*p++];
+ count--;
+ }
+
+ /* Count bits 28 bytes at a time */
+ p4 = (uint32_t*)p;
+ while(count>=28) {
+ uint32_t aux1, aux2, aux3, aux4, aux5, aux6, aux7;
+
+ aux1 = *p4++;
+ aux2 = *p4++;
+ aux3 = *p4++;
+ aux4 = *p4++;
+ aux5 = *p4++;
+ aux6 = *p4++;
+ aux7 = *p4++;
+ count -= 28;
+
+ aux1 = aux1 - ((aux1 >> 1) & 0x55555555);
+ aux1 = (aux1 & 0x33333333) + ((aux1 >> 2) & 0x33333333);
+ aux2 = aux2 - ((aux2 >> 1) & 0x55555555);
+ aux2 = (aux2 & 0x33333333) + ((aux2 >> 2) & 0x33333333);
+ aux3 = aux3 - ((aux3 >> 1) & 0x55555555);
+ aux3 = (aux3 & 0x33333333) + ((aux3 >> 2) & 0x33333333);
+ aux4 = aux4 - ((aux4 >> 1) & 0x55555555);
+ aux4 = (aux4 & 0x33333333) + ((aux4 >> 2) & 0x33333333);
+ aux5 = aux5 - ((aux5 >> 1) & 0x55555555);
+ aux5 = (aux5 & 0x33333333) + ((aux5 >> 2) & 0x33333333);
+ aux6 = aux6 - ((aux6 >> 1) & 0x55555555);
+ aux6 = (aux6 & 0x33333333) + ((aux6 >> 2) & 0x33333333);
+ aux7 = aux7 - ((aux7 >> 1) & 0x55555555);
+ aux7 = (aux7 & 0x33333333) + ((aux7 >> 2) & 0x33333333);
+ bits += ((((aux1 + (aux1 >> 4)) & 0x0F0F0F0F) +
+ ((aux2 + (aux2 >> 4)) & 0x0F0F0F0F) +
+ ((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) +
+ ((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) +
+ ((aux5 + (aux5 >> 4)) & 0x0F0F0F0F) +
+ ((aux6 + (aux6 >> 4)) & 0x0F0F0F0F) +
+ ((aux7 + (aux7 >> 4)) & 0x0F0F0F0F))* 0x01010101) >> 24;
+ }
+ /* Count the remaining bytes. */
+ p = (unsigned char*)p4;
+ while(count--) bits += bitsinbyte[*p++];
+ return bits;
+}
+
+/* Return the position of the first bit set to one (if 'bit' is 1) or
+ * zero (if 'bit' is 0) in the bitmap starting at 's' and PORT_LONG 'count' bytes.
+ *
+ * The function is guaranteed to return a value >= 0 if 'bit' is 0 since if
+ * no zero bit is found, it returns count*8 assuming the string is zero
+ * padded on the right. However if 'bit' is 1 it is possible that there is
+ * not a single set bit in the bitmap. In this special case -1 is returned. */
+PORT_LONG redisBitpos(void *s, PORT_ULONG count, int bit) {
+ PORT_ULONG *l;
+ unsigned char *c;
+ PORT_ULONG skipval, word = 0, one;
+ PORT_LONG pos = 0; /* Position of bit, to return to the caller. */
+ PORT_ULONG j;
+ int found;
+
+ /* Process whole words first, seeking for first word that is not
+ * all ones or all zeros respectively if we are lookig for zeros
+ * or ones. This is much faster with large strings having contiguous
+ * blocks of 1 or 0 bits compared to the vanilla bit per bit processing.
+ *
+ * Note that if we start from an address that is not aligned
+ * to sizeof(PORT_ULONG) we consume it byte by byte until it is
+ * aligned. */
+
+ /* Skip initial bits not aligned to sizeof(PORT_ULONG) byte by byte. */
+ skipval = bit ? 0 : UCHAR_MAX;
+ c = (unsigned char*) s;
+ found = 0;
+ while((PORT_ULONG)c & (sizeof(*l)-1) && count) {
+ if (*c != skipval) {
+ found = 1;
+ break;
+ }
+ c++;
+ count--;
+ pos += 8;
+ }
+
+ /* Skip bits with full word step. */
+ l = (PORT_ULONG*) c;
+ if (!found) {
+ skipval = bit ? 0 : PORT_ULONG_MAX;
+ while (count >= sizeof(*l)) {
+ if (*l != skipval) break;
+ l++;
+ count -= sizeof(*l);
+ pos += sizeof(*l)*8;
+ }
+ }
+
+ /* Load bytes into "word" considering the first byte as the most significant
+ * (we basically consider it as written in big endian, since we consider the
+ * string as a set of bits from left to right, with the first bit at position
+ * zero.
+ *
+ * Note that the loading is designed to work even when the bytes left
+ * (count) are less than a full word. We pad it with zero on the right. */
+ c = (unsigned char*)l;
+ for (j = 0; j < sizeof(*l); j++) {
+ word <<= 8;
+ if (count) {
+ word |= *c;
+ c++;
+ count--;
+ }
+ }
+
+ /* Special case:
+ * If bits in the string are all zero and we are looking for one,
+ * return -1 to signal that there is not a single "1" in the whole
+ * string. This can't happen when we are looking for "0" as we assume
+ * that the right of the string is zero padded. */
+ if (bit == 1 && word == 0) return -1;
+
+ /* Last word left, scan bit by bit. The first thing we need is to
+ * have a single "1" set in the most significant position in an
+ * PORT_ULONG. We don't know the size of the PORT_LONG so we use a
+ * simple trick. */
+ one = PORT_ULONG_MAX; /* All bits set to 1.*/
+ one >>= 1; /* All bits set to 1 but the MSB. */
+ one = ~one; /* All bits set to 0 but the MSB. */
+
+ while(one) {
+ if (((one & word) != 0) == bit) return pos;
+ pos++;
+ one >>= 1;
+ }
+
+ /* If we reached this point, there is a bug in the algorithm, since
+ * the case of no match is handled as a special case before. */
+ serverPanic("End of redisBitpos() reached.");
+ return 0; /* Just to avoid warnings. */
+}
+
+/* The following set.*Bitfield and get.*Bitfield functions implement setting
+ * and getting arbitrary size (up to 64 bits) signed and unsigned integers
+ * at arbitrary positions into a bitmap.
+ *
+ * The representation considers the bitmap as having the bit number 0 to be
+ * the most significant bit of the first byte, and so forth, so for example
+ * setting a 5 bits unsigned integer to value 23 at offset 7 into a bitmap
+ * previously set to all zeroes, will produce the following representation:
+ *
+ * +--------+--------+
+ * |00000001|01110000|
+ * +--------+--------+
+ *
+ * When offsets and integer sizes are aligned to bytes boundaries, this is the
+ * same as big endian, however when such alignment does not exist, its important
+ * to also understand how the bits inside a byte are ordered.
+ *
+ * Note that this format follows the same convention as SETBIT and related
+ * commands.
+ */
+
+void setUnsignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits, uint64_t value) {
+ uint64_t byte, bit, byteval, bitval, j;
+
+ for (j = 0; j < bits; j++) {
+ bitval = (value & ((uint64_t)1<<(bits-1-j))) != 0;
+ byte = offset >> 3;
+ bit = 7 - (offset & 0x7);
+ byteval = p[byte];
+ byteval &= ~(1 << bit);
+ byteval |= bitval << bit;
+ p[byte] = byteval & 0xff;
+ offset++;
+ }
+}
+
+void setSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits, int64_t value) {
+ uint64_t uv = value; /* Casting will add UINT64_MAX + 1 if v is negative. */
+ setUnsignedBitfield(p,offset,bits,uv);
+}
+
+uint64_t getUnsignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
+ uint64_t byte, bit, byteval, bitval, j, value = 0;
+
+ for (j = 0; j < bits; j++) {
+ byte = offset >> 3;
+ bit = 7 - (offset & 0x7);
+ byteval = p[byte];
+ bitval = (byteval >> bit) & 1;
+ value = (value<<1) | bitval;
+ offset++;
+ }
+ return value;
+}
+
+int64_t getSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
+ int64_t value;
+ union {uint64_t u; int64_t i;} conv;
+
+ /* Converting from unsigned to signed is undefined when the value does
+ * not fit, however here we assume two's complement and the original value
+ * was obtained from signed -> unsigned conversion, so we'll find the
+ * most significant bit set if the original value was negative.
+ *
+ * Note that two's complement is mandatory for exact-width types
+ * according to the C99 standard. */
+ conv.u = getUnsignedBitfield(p,offset,bits);
+ value = conv.i;
+
+ /* If the top significant bit is 1, propagate it to all the
+ * higher bits for two's complement representation of signed
+ * integers. */
+ if (value & ((uint64_t)1 << (bits-1)))
+ value |= ((uint64_t)-1) << bits;
+ return value;
+}
+
+/* The following two functions detect overflow of a value in the context
+ * of storing it as an unsigned or signed integer with the specified
+ * number of bits. The functions both take the value and a possible increment.
+ * If no overflow could happen and the value+increment fit inside the limits,
+ * then zero is returned, otherwise in case of overflow, 1 is returned,
+ * otherwise in case of underflow, -1 is returned.
+ *
+ * When non-zero is returned (oferflow or underflow), if not NULL, *limit is
+ * set to the value the operation should result when an overflow happens,
+ * depending on the specified overflow semantics:
+ *
+ * For BFOVERFLOW_SAT if 1 is returned, *limit it is set maximum value that
+ * you can store in that integer. when -1 is returned, *limit is set to the
+ * minimum value that an integer of that size can represent.
+ *
+ * For BFOVERFLOW_WRAP *limit is set by performing the operation in order to
+ * "wrap" around towards zero for unsigned integers, or towards the most
+ * negative number that is possible to represent for signed integers. */
+
+#define BFOVERFLOW_WRAP 0
+#define BFOVERFLOW_SAT 1
+#define BFOVERFLOW_FAIL 2 /* Used by the BITFIELD command implementation. */
+
+int checkUnsignedBitfieldOverflow(uint64_t value, int64_t incr, uint64_t bits, int owtype, uint64_t *limit) {
+ uint64_t max = (bits == 64) ? UINT64_MAX : (((uint64_t)1< max || (incr > 0 && incr > maxincr)) {
+ if (limit) {
+ if (owtype == BFOVERFLOW_WRAP) {
+ goto handle_wrap;
+ } else if (owtype == BFOVERFLOW_SAT) {
+ *limit = max;
+ }
+ }
+ return 1;
+ } else if (incr < 0 && incr < minincr) {
+ if (limit) {
+ if (owtype == BFOVERFLOW_WRAP) {
+ goto handle_wrap;
+ } else if (owtype == BFOVERFLOW_SAT) {
+ *limit = 0;
+ }
+ }
+ return -1;
+ }
+ return 0;
+
+handle_wrap:
+ {
+ uint64_t mask = ((uint64_t)-1) << bits;
+ uint64_t res = value+incr;
+
+ res &= ~mask;
+ *limit = res;
+ }
+ return 1;
+}
+
+int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int owtype, int64_t *limit) {
+ int64_t max = (bits == 64) ? INT64_MAX : (((int64_t)1<<(bits-1))-1);
+ int64_t min = (-max)-1;
+
+ /* Note that maxincr and minincr could overflow, but we use the values
+ * only after checking 'value' range, so when we use it no overflow
+ * happens. */
+ int64_t maxincr = max-value;
+ int64_t minincr = min-value;
+
+ if (value > max || (bits != 64 && incr > maxincr) || (value >= 0 && incr > 0 && incr > maxincr))
+ {
+ if (limit) {
+ if (owtype == BFOVERFLOW_WRAP) {
+ goto handle_wrap;
+ } else if (owtype == BFOVERFLOW_SAT) {
+ *limit = max;
+ }
+ }
+ return 1;
+ } else if (value < min || (bits != 64 && incr < minincr) || (value < 0 && incr < 0 && incr < minincr)) {
+ if (limit) {
+ if (owtype == BFOVERFLOW_WRAP) {
+ goto handle_wrap;
+ } else if (owtype == BFOVERFLOW_SAT) {
+ *limit = min;
+ }
+ }
+ return -1;
+ }
+ return 0;
+
+handle_wrap:
+ {
+ uint64_t mask = ((uint64_t)-1) << bits;
+ uint64_t msb = (uint64_t)1 << (bits-1);
+ uint64_t a = value, b = incr, c;
+ c = a+b; /* Perform addition as unsigned so that's defined. */
+
+ /* If the sign bit is set, propagate to all the higher order
+ * bits, to cap the negative value. If it's clear, mask to
+ * the positive integer limit. */
+ if (c & msb) {
+ c |= mask;
+ } else {
+ c &= ~mask;
+ }
+ *limit = c;
+ }
+ return 1;
+}
+
+/* Debugging function. Just show bits in the specified bitmap. Not used
+ * but here for not having to rewrite it when debugging is needed. */
+void printBits(unsigned char *p, PORT_ULONG count) {
+ PORT_ULONG j, i, byte;
+
+ for (j = 0; j < count; j++) {
+ byte = p[j];
+ for (i = 0x80; i > 0; i /= 2)
+ printf("%c", (byte & i) ? '1' : '0');
+ printf("|");
+ }
+ printf("\n");
+}
+
+/* -----------------------------------------------------------------------------
+ * Bits related string commands: GETBIT, SETBIT, BITCOUNT, BITOP.
+ * -------------------------------------------------------------------------- */
+
+#define BITOP_AND 0
+#define BITOP_OR 1
+#define BITOP_XOR 2
+#define BITOP_NOT 3
+
+#define BITFIELDOP_GET 0
+#define BITFIELDOP_SET 1
+#define BITFIELDOP_INCRBY 2
+
+/* This helper function used by GETBIT / SETBIT parses the bit offset argument
+ * making sure an error is returned if it is negative or if it overflows
+ * Redis 512 MB limit for the string value.
+ *
+ * If the 'hash' argument is true, and 'bits is positive, then the command
+ * will also parse bit offsets prefixed by "#". In such a case the offset
+ * is multiplied by 'bits'. This is useful for the BITFIELD command. */
+int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int bits) {
+ PORT_LONGLONG loffset;
+ char *err = "bit offset is not an integer or out of range";
+ char *p = o->ptr;
+ size_t plen = sdslen(p);
+ int usehash = 0;
+
+ /* Handle # form. */
+ if (p[0] == '#' && hash && bits > 0) usehash = 1;
+
+ if (string2ll(p+usehash,plen-usehash,&loffset) == 0) {
+ addReplyError(c,err);
+ return C_ERR;
+ }
+
+ /* Adjust the offset by 'bits' for # form. */
+ if (usehash) loffset *= bits;
+
+ /* Limit offset to 512MB in bytes */
+ if ((loffset < 0) || ((PORT_ULONGLONG)loffset >> 3) >= (512*1024*1024))
+ {
+ addReplyError(c,err);
+ return C_ERR;
+ }
+
+ *offset = (size_t)loffset;
+ return C_OK;
+}
+
+/* This helper function for BITFIELD parses a bitfield type in the form
+ * where sign is 'u' or 'i' for unsigned and signed, and
+ * the bits is a value between 1 and 64. However 64 bits unsigned integers
+ * are reported as an error because of current limitations of Redis protocol
+ * to return unsigned integer values greater than INT64_MAX.
+ *
+ * On error C_ERR is returned and an error is sent to the client. */
+int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
+ char *p = o->ptr;
+ char *err = "Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.";
+ PORT_LONGLONG llbits;
+
+ if (p[0] == 'i') {
+ *sign = 1;
+ } else if (p[0] == 'u') {
+ *sign = 0;
+ } else {
+ addReplyError(c,err);
+ return C_ERR;
+ }
+
+ if ((string2ll(p+1,strlen(p+1),&llbits)) == 0 ||
+ llbits < 1 ||
+ (*sign == 1 && llbits > 64) ||
+ (*sign == 0 && llbits > 63))
+ {
+ addReplyError(c,err);
+ return C_ERR;
+ }
+ *bits = llbits;
+ return C_OK;
+}
+
+/* This is an helper function for commands implementations that need to write
+ * bits to a string object. The command creates or pad with zeroes the string
+ * so that the 'maxbit' bit can be addressed. The object is finally
+ * returned. Otherwise if the key holds a wrong type NULL is returned and
+ * an error is sent to the client. */
+robj *lookupStringForBitCommand(client *c, size_t maxbit) {
+ size_t byte = maxbit >> 3;
+ robj *o = lookupKeyWrite(c->db,c->argv[1]);
+
+ if (o == NULL) {
+ o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1));
+ dbAdd(c->db,c->argv[1],o);
+ } else {
+ if (checkType(c,o,OBJ_STRING)) return NULL;
+ o = dbUnshareStringValue(c->db,c->argv[1],o);
+ o->ptr = sdsgrowzero(o->ptr,byte+1);
+ }
+ return o;
+}
+
+/* Return a pointer to the string object content, and stores its length
+ * in 'len'. The user is required to pass (likely stack allocated) buffer
+ * 'llbuf' of at least LONG_STR_SIZE bytes. Such a buffer is used in the case
+ * the object is integer encoded in order to provide the representation
+ * without usign heap allocation.
+ *
+ * The function returns the pointer to the object array of bytes representing
+ * the string it contains, that may be a pointer to 'llbuf' or to the
+ * internal object representation. As a side effect 'len' is filled with
+ * the length of such buffer.
+ *
+ * If the source object is NULL the function is guaranteed to return NULL
+ * and set 'len' to 0. */
+unsigned char *getObjectReadOnlyString(robj *o, PORT_LONG *len, char *llbuf) {
+ serverAssert(o->type == OBJ_STRING);
+ unsigned char *p = NULL;
+
+ /* Set the 'p' pointer to the string, that can be just a stack allocated
+ * array if our string was integer encoded. */
+ if (o && o->encoding == OBJ_ENCODING_INT) {
+ p = (unsigned char*) llbuf;
+ if (len) *len = ll2string(llbuf,LONG_STR_SIZE,(PORT_LONG)o->ptr);
+ } else if (o) {
+ p = (unsigned char*) o->ptr;
+ if (len) *len = sdslen(o->ptr);
+ } else {
+ if (len) *len = 0;
+ }
+ return p;
+}
+
+/* SETBIT key offset bitvalue */
+void setbitCommand(client *c) {
+ robj *o;
+ char *err = "bit is not an integer or out of range";
+ size_t bitoffset;
+ ssize_t byte, bit;
+ int byteval, bitval;
+ PORT_LONG on;
+
+ if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK)
+ return;
+
+ if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != C_OK)
+ return;
+
+ /* Bits can only be set or cleared... */
+ if (on & ~1) {
+ addReplyError(c,err);
+ return;
+ }
+
+ if ((o = lookupStringForBitCommand(c,bitoffset)) == NULL) return;
+
+ /* Get current values */
+ byte = bitoffset >> 3;
+ byteval = ((uint8_t*)o->ptr)[byte];
+ bit = 7 - (bitoffset & 0x7);
+ bitval = byteval & (1 << bit);
+
+ /* Update byte with new bit value and return original value */
+ byteval &= ~(1 << bit);
+ byteval |= ((on & 0x1) << bit);
+ ((uint8_t*)o->ptr)[byte] = byteval;
+ signalModifiedKey(c->db,c->argv[1]);
+ notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
+ server.dirty++;
+ addReply(c, bitval ? shared.cone : shared.czero);
+}
+
+/* GETBIT key offset */
+void getbitCommand(client *c) {
+ robj *o;
+ char llbuf[32];
+ size_t bitoffset;
+ size_t byte, bit;
+ size_t bitval = 0;
+
+ if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK)
+ return;
+
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
+ checkType(c,o,OBJ_STRING)) return;
+
+ byte = bitoffset >> 3;
+ bit = 7 - (bitoffset & 0x7);
+ if (sdsEncodedObject(o)) {
+ if (byte < sdslen(o->ptr))
+ bitval = ((uint8_t*)o->ptr)[byte] & (1 << bit);
+ } else {
+ if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(PORT_LONG)o->ptr))
+ bitval = llbuf[byte] & (1 << bit);
+ }
+
+ addReply(c, bitval ? shared.cone : shared.czero);
+}
+
+/* BITOP op_name target_key src_key1 src_key2 src_key3 ... src_keyN */
+void bitopCommand(client *c) {
+ char *opname = c->argv[1]->ptr;
+ robj *o, *targetkey = c->argv[2];
+ PORT_ULONG op, j, numkeys;
+ robj **objects; /* Array of source objects. */
+ unsigned char **src; /* Array of source strings pointers. */
+ PORT_ULONG *len, maxlen = 0; /* Array of length of src strings,
+ and max len. */
+ PORT_ULONG minlen = 0; /* Min len among the input keys. */
+ unsigned char *res = NULL; /* Resulting string. */
+
+ /* Parse the operation name. */
+ if ((opname[0] == 'a' || opname[0] == 'A') && !strcasecmp(opname,"and"))
+ op = BITOP_AND;
+ else if((opname[0] == 'o' || opname[0] == 'O') && !strcasecmp(opname,"or"))
+ op = BITOP_OR;
+ else if((opname[0] == 'x' || opname[0] == 'X') && !strcasecmp(opname,"xor"))
+ op = BITOP_XOR;
+ else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not"))
+ op = BITOP_NOT;
+ else {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+
+ /* Sanity check: NOT accepts only a single key argument. */
+ if (op == BITOP_NOT && c->argc != 4) {
+ addReplyError(c,"BITOP NOT must be called with a single source key.");
+ return;
+ }
+
+ /* Lookup keys, and store pointers to the string objects into an array. */
+ numkeys = c->argc - 3;
+ src = zmalloc(sizeof(unsigned char*) * numkeys);
+ len = zmalloc(sizeof(PORT_LONG) * numkeys);
+ objects = zmalloc(sizeof(robj*) * numkeys);
+ for (j = 0; j < numkeys; j++) {
+ o = lookupKeyRead(c->db,c->argv[j+3]);
+ /* Handle non-existing keys as empty strings. */
+ if (o == NULL) {
+ objects[j] = NULL;
+ src[j] = NULL;
+ len[j] = 0;
+ minlen = 0;
+ continue;
+ }
+ /* Return an error if one of the keys is not a string. */
+ if (checkType(c,o,OBJ_STRING)) {
+ PORT_ULONG i;
+ for (i = 0; i < j; i++) {
+ if (objects[i])
+ decrRefCount(objects[i]);
+ }
+ zfree(src);
+ zfree(len);
+ zfree(objects);
+ return;
+ }
+ objects[j] = getDecodedObject(o);
+ src[j] = objects[j]->ptr;
+ len[j] = (PORT_LONG) sdslen(objects[j]->ptr); WIN_PORT_FIX /* cast (PORT_LONG) */
+ if (len[j] > maxlen) maxlen = len[j];
+ if (j == 0 || len[j] < minlen) minlen = len[j];
+ }
+
+ /* Compute the bit operation, if at least one string is not empty. */
+ if (maxlen) {
+ res = (unsigned char*) sdsnewlen(NULL,maxlen);
+ unsigned char output, byte;
+ PORT_ULONG i;
+
+ /* Fast path: as far as we have data for all the input bitmaps we
+ * can take a fast path that performs much better than the
+ * vanilla algorithm. On ARM we skip the fast path since it will
+ * result in GCC compiling the code using multiple-words load/store
+ * operations that are not supported even in ARM >= v6. */
+ j = 0;
+ #ifndef USE_ALIGNED_ACCESS
+ if (minlen >= sizeof(PORT_ULONG)*4 && numkeys <= 16) {
+ PORT_ULONG *lp[16];
+ PORT_ULONG *lres = (PORT_ULONG*) res;
+
+ /* Note: sds pointer is always aligned to 8 byte boundary. */
+ memcpy(lp,src,sizeof(PORT_ULONG*)*numkeys);
+ memcpy(res,src[0],minlen);
+
+ /* Different branches per different operations for speed (sorry). */
+ if (op == BITOP_AND) {
+ while(minlen >= sizeof(PORT_ULONG)*4) {
+ for (i = 1; i < numkeys; i++) {
+ lres[0] &= lp[i][0];
+ lres[1] &= lp[i][1];
+ lres[2] &= lp[i][2];
+ lres[3] &= lp[i][3];
+ lp[i]+=4;
+ }
+ lres+=4;
+ j += sizeof(PORT_ULONG)*4;
+ minlen -= sizeof(PORT_ULONG)*4;
+ }
+ } else if (op == BITOP_OR) {
+ while(minlen >= sizeof(PORT_ULONG)*4) {
+ for (i = 1; i < numkeys; i++) {
+ lres[0] |= lp[i][0];
+ lres[1] |= lp[i][1];
+ lres[2] |= lp[i][2];
+ lres[3] |= lp[i][3];
+ lp[i]+=4;
+ }
+ lres+=4;
+ j += sizeof(PORT_ULONG)*4;
+ minlen -= sizeof(PORT_ULONG)*4;
+ }
+ } else if (op == BITOP_XOR) {
+ while(minlen >= sizeof(PORT_ULONG)*4) {
+ for (i = 1; i < numkeys; i++) {
+ lres[0] ^= lp[i][0];
+ lres[1] ^= lp[i][1];
+ lres[2] ^= lp[i][2];
+ lres[3] ^= lp[i][3];
+ lp[i]+=4;
+ }
+ lres+=4;
+ j += sizeof(PORT_ULONG)*4;
+ minlen -= sizeof(PORT_ULONG)*4;
+ }
+ } else if (op == BITOP_NOT) {
+ while(minlen >= sizeof(PORT_ULONG)*4) {
+ lres[0] = ~lres[0];
+ lres[1] = ~lres[1];
+ lres[2] = ~lres[2];
+ lres[3] = ~lres[3];
+ lres+=4;
+ j += sizeof(PORT_ULONG)*4;
+ minlen -= sizeof(PORT_ULONG)*4;
+ }
+ }
+ }
+ #endif
+
+ /* j is set to the next byte to process by the previous loop. */
+ for (; j < maxlen; j++) {
+ output = (len[0] <= j) ? 0 : src[0][j];
+ if (op == BITOP_NOT) output = ~output;
+ for (i = 1; i < numkeys; i++) {
+ byte = (len[i] <= j) ? 0 : src[i][j];
+ switch(op) {
+ case BITOP_AND: output &= byte; break;
+ case BITOP_OR: output |= byte; break;
+ case BITOP_XOR: output ^= byte; break;
+ }
+ }
+ res[j] = output;
+ }
+ }
+ for (j = 0; j < numkeys; j++) {
+ if (objects[j])
+ decrRefCount(objects[j]);
+ }
+ zfree(src);
+ zfree(len);
+ zfree(objects);
+
+ /* Store the computed value into the target key */
+ if (maxlen) {
+ o = createObject(OBJ_STRING,res);
+ setKey(c->db,targetkey,o);
+ notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id);
+ decrRefCount(o);
+ } else if (dbDelete(c->db,targetkey)) {
+ signalModifiedKey(c->db,targetkey);
+ notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
+ }
+ server.dirty++;
+ addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */
+}
+
+/* BITCOUNT key [start end] */
+void bitcountCommand(client *c) {
+ robj *o;
+ PORT_LONG start, end, strlen;
+ unsigned char *p;
+ char llbuf[LONG_STR_SIZE];
+
+ /* Lookup, check for type, and return 0 for non existing keys. */
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
+ checkType(c,o,OBJ_STRING)) return;
+ p = getObjectReadOnlyString(o,&strlen,llbuf);
+
+ /* Parse start/end range if any. */
+ if (c->argc == 4) {
+ if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK)
+ return;
+ if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK)
+ return;
+ /* Convert negative indexes */
+ if (start < 0 && end < 0 && start > end) {
+ addReply(c,shared.czero);
+ return;
+ }
+ if (start < 0) start = strlen+start;
+ if (end < 0) end = strlen+end;
+ if (start < 0) start = 0;
+ if (end < 0) end = 0;
+ if (end >= strlen) end = strlen-1;
+ } else if (c->argc == 2) {
+ /* The whole string. */
+ start = 0;
+ end = strlen-1;
+ } else {
+ /* Syntax error. */
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+
+ /* Precondition: end >= 0 && end < strlen, so the only condition where
+ * zero can be returned is: start > end. */
+ if (start > end) {
+ addReply(c,shared.czero);
+ } else {
+ PORT_LONG bytes = end-start+1;
+
+ addReplyLongLong(c,redisPopcount(p+start,bytes));
+ }
+}
+
+/* BITPOS key bit [start [end]] */
+void bitposCommand(client *c) {
+ robj *o;
+ PORT_LONG bit, start, end, strlen;
+ unsigned char *p;
+ char llbuf[LONG_STR_SIZE];
+ int end_given = 0;
+
+ /* Parse the bit argument to understand what we are looking for, set
+ * or clear bits. */
+ if (getLongFromObjectOrReply(c,c->argv[2],&bit,NULL) != C_OK)
+ return;
+ if (bit != 0 && bit != 1) {
+ addReplyError(c, "The bit argument must be 1 or 0.");
+ return;
+ }
+
+ /* If the key does not exist, from our point of view it is an infinite
+ * array of 0 bits. If the user is looking for the fist clear bit return 0,
+ * If the user is looking for the first set bit, return -1. */
+ if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
+ addReplyLongLong(c, bit ? -1 : 0);
+ return;
+ }
+ if (checkType(c,o,OBJ_STRING)) return;
+ p = getObjectReadOnlyString(o,&strlen,llbuf);
+
+ /* Parse start/end range if any. */
+ if (c->argc == 4 || c->argc == 5) {
+ if (getLongFromObjectOrReply(c,c->argv[3],&start,NULL) != C_OK)
+ return;
+ if (c->argc == 5) {
+ if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != C_OK)
+ return;
+ end_given = 1;
+ } else {
+ end = strlen-1;
+ }
+ /* Convert negative indexes */
+ if (start < 0) start = strlen+start;
+ if (end < 0) end = strlen+end;
+ if (start < 0) start = 0;
+ if (end < 0) end = 0;
+ if (end >= strlen) end = strlen-1;
+ } else if (c->argc == 3) {
+ /* The whole string. */
+ start = 0;
+ end = strlen-1;
+ } else {
+ /* Syntax error. */
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+
+ /* For empty ranges (start > end) we return -1 as an empty range does
+ * not contain a 0 nor a 1. */
+ if (start > end) {
+ addReplyLongLong(c, -1);
+ } else {
+ PORT_LONG bytes = end-start+1;
+ PORT_LONG pos = redisBitpos(p+start,(PORT_ULONG)bytes,(int)bit); WIN_PORT_FIX /* cast (PORT_ULONG), cast (int) */
+
+ /* If we are looking for clear bits, and the user specified an exact
+ * range with start-end, we can't consider the right of the range as
+ * zero padded (as we do when no explicit end is given).
+ *
+ * So if redisBitpos() returns the first bit outside the range,
+ * we return -1 to the caller, to mean, in the specified range there
+ * is not a single "0" bit. */
+ if (end_given && bit == 0 && pos == bytes*8) {
+ addReplyLongLong(c,-1);
+ return;
+ }
+ if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */
+ addReplyLongLong(c,pos);
+ }
+}
+
+/* BITFIELD key subcommmand-1 arg ... subcommand-2 arg ... subcommand-N ...
+ *
+ * Supported subcommands:
+ *
+ * GET
+ * SET
+ * INCRBY
+ * OVERFLOW [WRAP|SAT|FAIL]
+ */
+
+struct bitfieldOp {
+ uint64_t offset; /* Bitfield offset. */
+ int64_t i64; /* Increment amount (INCRBY) or SET value */
+ int opcode; /* Operation id. */
+ int owtype; /* Overflow type to use. */
+ int bits; /* Integer bitfield bits width. */
+ int sign; /* True if signed, otherwise unsigned op. */
+};
+
+void bitfieldCommand(client *c) {
+ robj *o;
+ size_t bitoffset;
+ int j, numops = 0, changes = 0;
+ struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
+ int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
+ int readonly = 1;
+ PORT_LONG higest_write_offset = 0;
+
+ for (j = 2; j < c->argc; j++) {
+ int remargs = c->argc-j-1; /* Remaining args other than current. */
+ char *subcmd = c->argv[j]->ptr; /* Current command name. */
+ int opcode; /* Current operation code. */
+ PORT_LONGLONG i64 = 0; /* Signed SET value. */
+ int sign = 0; /* Signed or unsigned type? */
+ int bits = 0; /* Bitfield width in bits. */
+
+ if (!strcasecmp(subcmd,"get") && remargs >= 2)
+ opcode = BITFIELDOP_GET;
+ else if (!strcasecmp(subcmd,"set") && remargs >= 3)
+ opcode = BITFIELDOP_SET;
+ else if (!strcasecmp(subcmd,"incrby") && remargs >= 3)
+ opcode = BITFIELDOP_INCRBY;
+ else if (!strcasecmp(subcmd,"overflow") && remargs >= 1) {
+ char *owtypename = c->argv[j+1]->ptr;
+ j++;
+ if (!strcasecmp(owtypename,"wrap"))
+ owtype = BFOVERFLOW_WRAP;
+ else if (!strcasecmp(owtypename,"sat"))
+ owtype = BFOVERFLOW_SAT;
+ else if (!strcasecmp(owtypename,"fail"))
+ owtype = BFOVERFLOW_FAIL;
+ else {
+ addReplyError(c,"Invalid OVERFLOW type specified");
+ zfree(ops);
+ return;
+ }
+ continue;
+ } else {
+ addReply(c,shared.syntaxerr);
+ zfree(ops);
+ return;
+ }
+
+ /* Get the type and offset arguments, common to all the ops. */
+ if (getBitfieldTypeFromArgument(c,c->argv[j+1],&sign,&bits) != C_OK) {
+ zfree(ops);
+ return;
+ }
+
+ if (getBitOffsetFromArgument(c,c->argv[j+2],&bitoffset,1,bits) != C_OK){
+ zfree(ops);
+ return;
+ }
+
+ if (opcode != BITFIELDOP_GET) {
+ readonly = 0;
+ if (higest_write_offset < bitoffset + bits - 1)
+ higest_write_offset = bitoffset + bits - 1;
+ /* INCRBY and SET require another argument. */
+ if (getLongLongFromObjectOrReply(c,c->argv[j+3],&i64,NULL) != C_OK){
+ zfree(ops);
+ return;
+ }
+ }
+
+ /* Populate the array of operations we'll process. */
+ ops = zrealloc(ops,sizeof(*ops)*(numops+1));
+ ops[numops].offset = bitoffset;
+ ops[numops].i64 = i64;
+ ops[numops].opcode = opcode;
+ ops[numops].owtype = owtype;
+ ops[numops].bits = bits;
+ ops[numops].sign = sign;
+ numops++;
+
+ j += 3 - (opcode == BITFIELDOP_GET);
+ }
+
+ if (readonly) {
+ /* Lookup for read is ok if key doesn't exit, but errors
+ * if it's not a string. */
+ o = lookupKeyRead(c->db,c->argv[1]);
+ if (o != NULL && checkType(c,o,OBJ_STRING)) return;
+ } else {
+ /* Lookup by making room up to the farest bit reached by
+ * this operation. */
+ if ((o = lookupStringForBitCommand(c,
+ higest_write_offset)) == NULL) return;
+ }
+
+ addReplyMultiBulkLen(c,numops);
+
+ /* Actually process the operations. */
+ for (j = 0; j < numops; j++) {
+ struct bitfieldOp *thisop = ops+j;
+
+ /* Execute the operation. */
+ if (thisop->opcode == BITFIELDOP_SET ||
+ thisop->opcode == BITFIELDOP_INCRBY)
+ {
+ /* SET and INCRBY: We handle both with the same code path
+ * for simplicity. SET return value is the previous value so
+ * we need fetch & store as well. */
+
+ /* We need two different but very similar code paths for signed
+ * and unsigned operations, since the set of functions to get/set
+ * the integers and the used variables types are different. */
+ if (thisop->sign) {
+ int64_t oldval, newval, wrapped, retval;
+ int overflow;
+
+ oldval = getSignedBitfield(o->ptr,thisop->offset,
+ thisop->bits);
+
+ if (thisop->opcode == BITFIELDOP_INCRBY) {
+ newval = oldval + thisop->i64;
+ overflow = checkSignedBitfieldOverflow(oldval,
+ thisop->i64,thisop->bits,thisop->owtype,&wrapped);
+ if (overflow) newval = wrapped;
+ retval = newval;
+ } else {
+ newval = thisop->i64;
+ overflow = checkSignedBitfieldOverflow(newval,
+ 0,thisop->bits,thisop->owtype,&wrapped);
+ if (overflow) newval = wrapped;
+ retval = oldval;
+ }
+
+ /* On overflow of type is "FAIL", don't write and return
+ * NULL to signal the condition. */
+ if (!(overflow && thisop->owtype == BFOVERFLOW_FAIL)) {
+ addReplyLongLong(c,retval);
+ setSignedBitfield(o->ptr,thisop->offset,
+ thisop->bits,newval);
+ } else {
+ addReply(c,shared.nullbulk);
+ }
+ } else {
+ uint64_t oldval, newval, wrapped, retval;
+ int overflow;
+
+ oldval = getUnsignedBitfield(o->ptr,thisop->offset,
+ thisop->bits);
+
+ if (thisop->opcode == BITFIELDOP_INCRBY) {
+ newval = oldval + thisop->i64;
+ overflow = checkUnsignedBitfieldOverflow(oldval,
+ thisop->i64,thisop->bits,thisop->owtype,&wrapped);
+ if (overflow) newval = wrapped;
+ retval = newval;
+ } else {
+ newval = thisop->i64;
+ overflow = checkUnsignedBitfieldOverflow(newval,
+ 0,thisop->bits,thisop->owtype,&wrapped);
+ if (overflow) newval = wrapped;
+ retval = oldval;
+ }
+ /* On overflow of type is "FAIL", don't write and return
+ * NULL to signal the condition. */
+ if (!(overflow && thisop->owtype == BFOVERFLOW_FAIL)) {
+ addReplyLongLong(c,retval);
+ setUnsignedBitfield(o->ptr,thisop->offset,
+ thisop->bits,newval);
+ } else {
+ addReply(c,shared.nullbulk);
+ }
+ }
+ changes++;
+ } else {
+ /* GET */
+ unsigned char buf[9];
+ PORT_LONG strlen = 0;
+ unsigned char *src = NULL;
+ char llbuf[LONG_STR_SIZE];
+
+ if (o != NULL)
+ src = getObjectReadOnlyString(o,&strlen,llbuf);
+
+ /* For GET we use a trick: before executing the operation
+ * copy up to 9 bytes to a local buffer, so that we can easily
+ * execute up to 64 bit operations that are at actual string
+ * object boundaries. */
+ memset(buf,0,9);
+ int i;
+ size_t byte = thisop->offset >> 3;
+ for (i = 0; i < 9; i++) {
+ if (src == NULL || i+byte >= (size_t)strlen) break;
+ buf[i] = src[i+byte];
+ }
+
+ /* Now operate on the copied buffer which is guaranteed
+ * to be zero-padded. */
+ if (thisop->sign) {
+ int64_t val = getSignedBitfield(buf,thisop->offset-(byte*8),
+ thisop->bits);
+ addReplyLongLong(c,val);
+ } else {
+ uint64_t val = getUnsignedBitfield(buf,thisop->offset-(byte*8),
+ thisop->bits);
+ addReplyLongLong(c,val);
+ }
+ }
+ }
+
+ if (changes) {
+ signalModifiedKey(c->db,c->argv[1]);
+ notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
+ server.dirty += changes;
+ }
+ zfree(ops);
+}
diff --git a/src/cluster.c b/src/cluster.c
index 86bb45fe..ca27c30b 100644
--- a/src/cluster.c
+++ b/src/cluster.c
@@ -118,7 +118,7 @@ int clusterLoadConfig(char *filename) {
}
/* Parse the file. Note that single lines of the cluster config file can
- * be really PORT_LONG as they include all the hash slots of the node.
+ * be really long as they include all the hash slots of the node.
* This means in the worst possible case, half of the Redis slots will be
* present in a single line, possibly in importing or migrating state, so
* together with the node ID of the sender/receiver.
@@ -260,6 +260,7 @@ int clusterLoadConfig(char *filename) {
*p = '\0';
direction = p[1]; /* Either '>' or '<' */
slot = atoi(argv[j] + 1);
+ if (slot < 0 || slot >= CLUSTER_SLOTS) goto fmterr;
p += 3;
cn = clusterLookupNode(p);
if (!cn) {
@@ -282,6 +283,8 @@ int clusterLoadConfig(char *filename) {
else {
start = stop = atoi(argv[j]);
}
+ if (start < 0 || start >= CLUSTER_SLOTS) goto fmterr;
+ if (stop < 0 || stop >= CLUSTER_SLOTS) goto fmterr;
while (start <= stop) clusterAddSlot(n, start++);
}
@@ -438,6 +441,22 @@ int clusterLockConfig(char *filename) {
return C_OK;
}
+/* Some flags (currently just the NOFAILOVER flag) may need to be updated
+ * in the "myself" node based on the current configuration of the node,
+ * that may change at runtime via CONFIG SET. This function changes the
+ * set of flags in myself->flags accordingly. */
+void clusterUpdateMyselfFlags(void) {
+ int oldflags = myself->flags;
+ int nofailover = server.cluster_slave_no_failover ?
+ CLUSTER_NODE_NOFAILOVER : 0;
+ myself->flags &= ~CLUSTER_NODE_NOFAILOVER;
+ myself->flags |= nofailover;
+ if (myself->flags != oldflags) {
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
+ CLUSTER_TODO_UPDATE_STATE);
+ }
+}
+
void clusterInit(void) {
int saveconf = 0;
@@ -531,6 +550,7 @@ void clusterInit(void) {
server.cluster->mf_end = 0;
resetManualFailover();
+ clusterUpdateMyselfFlags();
}
/* Reset a node performing a soft or hard reset:
@@ -1862,6 +1882,18 @@ int clusterProcessPacket(clusterLink *link) {
}
}
+ /* Copy the CLUSTER_NODE_NOFAILOVER flag from what the sender
+ * announced. This is a dynamic flag that we receive from the
+ * sender, and the latest status must be trusted. We need it to
+ * be propagated because the slave ranking used to understand the
+ * delay of each slave in the voting process, needs to know
+ * what are the instances really competing. */
+ if (sender) {
+ int nofailover = flags & CLUSTER_NODE_NOFAILOVER;
+ sender->flags &= ~CLUSTER_NODE_NOFAILOVER;
+ sender->flags |= nofailover;
+ }
+
/* Update the node address if it changed. */
if (sender && type == CLUSTERMSG_TYPE_PING &&
!nodeInHandshake(sender) &&
@@ -2261,7 +2293,7 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
* from event handlers that will do stuff with the same link later. */
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
if (sdslen(link->sndbuf) == 0 && msglen != 0)
- aeCreateFileEvent(server.el, link->fd, AE_WRITABLE,
+ aeCreateFileEvent(server.el,link->fd,AE_WRITABLE|AE_BARRIER,
clusterWriteHandler, link);
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
@@ -2803,6 +2835,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
clusterSendFailoverAuth(node);
server.cluster->lastVoteEpoch = server.cluster->currentEpoch;
node->slaveof->voted_time = mstime();
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
+ clusterSendFailoverAuth(node);
serverLog(LL_WARNING, "Failover auth granted to %.40s for epoch %llu",
node->name, (PORT_ULONGLONG) server.cluster->currentEpoch);
}
@@ -2831,6 +2865,7 @@ int clusterGetSlaveRank(void) {
myoffset = replicationGetSlaveOffset();
for (j = 0; j < master->numslaves; j++)
if (master->slaves[j] != myself &&
+ !nodeCantFailover(master->slaves[j]) &&
master->slaves[j]->repl_offset > myoffset) rank++;
return rank;
}
@@ -2968,7 +3003,9 @@ void clusterHandleSlaveFailover(void) {
* of an automatic or manual failover:
* 1) We are a slave.
* 2) Our master is flagged as FAIL, or this is a manual failover.
- * 3) It is serving slots. */
+ * 3) We don't have the no failover configuration set, and this is
+ * not a manual failover.
+ * 4) It is serving slots. */
if (nodeIsMaster(myself) ||
myself->slaveof == NULL ||
(!nodeFailed(myself->slaveof) && !manual_failover) ||
@@ -3351,6 +3388,9 @@ void clusterCron(void) {
handshake_timeout = server.cluster_node_timeout;
if (handshake_timeout < 1000) handshake_timeout = 1000;
+ /* Update myself flags. */
+ clusterUpdateMyselfFlags();
+
/* Check if we have disconnected nodes and re-establish the connection.
* Also update a few stats while we are here, that can be used to make
* better decisions in other part of the code. */
@@ -3951,7 +3991,8 @@ static struct redisNodeFlags redisNodeFlagsTable[] = {
{CLUSTER_NODE_PFAIL, "fail?,"},
{CLUSTER_NODE_FAIL, "fail,"},
{CLUSTER_NODE_HANDSHAKE, "handshake,"},
- {CLUSTER_NODE_NOADDR, "noaddr,"}
+ {CLUSTER_NODE_NOADDR, "noaddr,"},
+ {CLUSTER_NODE_NOFAILOVER, "nofailover,"}
};
/* Concatenate the comma separated list of node flags to the given SDS
@@ -4560,8 +4601,7 @@ void clusterCommand(client *c) {
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE |
CLUSTER_TODO_SAVE_CONFIG);
addReply(c, shared.ok);
- }
- else if (!strcasecmp(c->argv[1]->ptr, "replicate") && c->argc == 3) {
+ } else if (!strcasecmp(c->argv[1]->ptr,"replicate") && c->argc == 3) {
/* CLUSTER REPLICATE */
clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
@@ -5030,17 +5070,18 @@ void migrateCloseTimedoutSockets(void) {
dictReleaseIterator(di);
}
-/* MIGRATE host port key dbid timeout [COPY | REPLACE]
+/* MIGRATE host port key dbid timeout [COPY | REPLACE | AUTH password]
*
* On in the multiple keys form:
*
- * MIGRATE host port "" dbid timeout [COPY | REPLACE] KEYS key1 key2 ... keyN */
+ * MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password] KEYS key1
+ * key2 ... keyN */
void migrateCommand(client *c) {
migrateCachedSocket *cs;
- int copy, replace, j;
+ int copy=0, replace=0, j;
+ char *password = NULL;
PORT_LONG timeout;
PORT_LONG dbid;
- PORT_LONGLONG ttl, expireat;
robj **ov = NULL; /* Objects to migrate. */
robj **kv = NULL; /* Key names. */
robj **newargv = NULL; /* Used to rewrite the command as DEL ... keys ... */
@@ -5053,19 +5094,21 @@ void migrateCommand(client *c) {
int first_key = 3; /* Argument index of the first key. */
int num_keys = 1; /* By default only migrate the 'key' argument. */
- /* Initialization */
- copy = 0;
- replace = 0;
-
/* Parse additional options */
for (j = 6; j < c->argc; j++) {
+ int moreargs = j < c->argc-1;
if (!strcasecmp(c->argv[j]->ptr, "copy")) {
copy = 1;
- }
- else if (!strcasecmp(c->argv[j]->ptr, "replace")) {
+ } else if (!strcasecmp(c->argv[j]->ptr,"replace")) {
replace = 1;
+ } else if (!strcasecmp(c->argv[j]->ptr,"auth")) {
+ if (!moreargs) {
+ addReply(c,shared.syntaxerr);
+ return;
}
- else if (!strcasecmp(c->argv[j]->ptr, "keys")) {
+ j++;
+ password = c->argv[j]->ptr;
+ } else if (!strcasecmp(c->argv[j]->ptr,"keys")) {
if (sdslen(c->argv[3]->ptr) != 0) {
addReplyError(c,
"When using MIGRATE KEYS option, the key argument"
@@ -5124,6 +5167,14 @@ try_again:
rioInitWithBuffer(&cmd, sdsempty());
+ /* Authentication */
+ if (password) {
+ serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2));
+ serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"AUTH",4));
+ serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,password,
+ sdslen(password)));
+ }
+
/* Send the SELECT command if the current DB is not already selected. */
int select = cs->last_dbid != dbid; /* Should we emit SELECT? */
if (select) {
@@ -5141,7 +5192,9 @@ try_again:
ttl = expireat - mstime();
if (ttl < 1) ttl = 1;
}
- serverAssertWithInfo(c, NULL, rioWriteBulkCount(&cmd, '*', replace ? 5 : 4));
+ serverAssertWithInfo(c,NULL,
+ rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
+
if (server.cluster_enabled)
serverAssertWithInfo(c, NULL,
rioWriteBulkString(&cmd, "RESTORE-ASKING", 14));
@@ -5207,9 +5260,14 @@ try_again:
#endif
}
+ char buf0[1024]; /* Auth reply. */
char buf1[1024]; /* Select reply. */
char buf2[1024]; /* Restore reply. */
+ /* Read the AUTH reply if needed. */
+ if (password && syncReadLine(cs->fd, buf0, sizeof(buf0), timeout) <= 0)
+ goto socket_err;
+
/* Read the SELECT reply if needed. */
if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0)
goto socket_err;
@@ -5226,13 +5284,21 @@ try_again:
socket_error = 1;
break;
}
- if ((select && buf1[0] == '-') || buf2[0] == '-') {
+ if ((password && buf0[0] == '-') ||
+ (select && buf1[0] == '-') ||
+ buf2[0] == '-')
+ {
/* On error assume that last_dbid is no longer valid. */
if (!error_from_target) {
cs->last_dbid = -1;
- addReplyErrorFormat(c, "Target instance replied with error: %s",
- (select && buf1[0] == '-') ? buf1 + 1 : buf2 + 1);
+ char *errbuf;
+ if (password && buf0[0] == '-') errbuf = buf0;
+ else if (select && buf1[0] == '-') errbuf = buf1;
+ else errbuf = buf2;
+
error_from_target = 1;
+ addReplyErrorFormat(c,"Target instance replied with error: %s",
+ errbuf+1);
}
}
else {
@@ -5300,7 +5366,7 @@ try_again:
}
else {
/* On error we already sent it in the for loop above, and set
- * the curretly selected socket to -1 to force SELECT the next time. */
+ * the currently selected socket to -1 to force SELECT the next time. */
}
sdsfree(cmd.io.buffer.ptr);
diff --git a/src/cluster.c.bak b/src/cluster.c.bak
new file mode 100644
index 00000000..0922bf5e
--- /dev/null
+++ b/src/cluster.c.bak
@@ -0,0 +1,5692 @@
+/* Redis Cluster implementation.
+ *
+ * Copyright (c) 2009-2012, Salvatore Sanfilippo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * 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 "server.h"
+#include "cluster.h"
+#include "endianconv.h"
+
+#include
+POSIX_ONLY(#include )
+POSIX_ONLY(#include )
+#include
+POSIX_ONLY(#include )
+POSIX_ONLY(#include )
+#include
+POSIX_ONLY(#include )
+#include
+
+#ifdef _WIN32
+extern int WSIOCP_QueueAccept(int listenfd);
+#include "Win32_Interop/Win32_Error.h"
+#endif
+
+/* A global reference to myself is handy to make code more clear.
+ * Myself always points to server.cluster->myself, that is, the clusterNode
+ * that represents this node. */
+clusterNode *myself = NULL;
+
+clusterNode *createClusterNode(char *nodename, int flags);
+int clusterAddNode(clusterNode *node);
+void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
+void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask);
+void clusterSendPing(clusterLink *link, int type);
+void clusterSendFail(char *nodename);
+void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
+void clusterUpdateState(void);
+int clusterNodeGetSlotBit(clusterNode *n, int slot);
+sds clusterGenNodesDescription(int filter);
+clusterNode *clusterLookupNode(char *name);
+int clusterNodeAddSlave(clusterNode *master, clusterNode *slave);
+int clusterAddSlot(clusterNode *n, int slot);
+int clusterDelSlot(int slot);
+int clusterDelNodeSlots(clusterNode *node);
+int clusterNodeSetSlotBit(clusterNode *n, int slot);
+void clusterSetMaster(clusterNode *n);
+void clusterHandleSlaveFailover(void);
+void clusterHandleSlaveMigration(int max_slaves);
+int bitmapTestBit(unsigned char *bitmap, int pos);
+void clusterDoBeforeSleep(int flags);
+void clusterSendUpdate(clusterLink *link, clusterNode *node);
+void resetManualFailover(void);
+void clusterCloseAllSlots(void);
+void clusterSetNodeAsMaster(clusterNode *n);
+void clusterDelNode(clusterNode *delnode);
+sds representClusterNodeFlags(sds ci, uint16_t flags);
+uint64_t clusterGetMaxEpoch(void);
+int clusterBumpConfigEpochWithoutConsensus(void);
+
+/* -----------------------------------------------------------------------------
+ * Initialization
+ * -------------------------------------------------------------------------- */
+
+ /* Load the cluster config from 'filename'.
+ *
+ * If the file does not exist or is zero-length (this may happen because
+ * when we lock the nodes.conf file, we create a zero-length one for the
+ * sake of locking if it does not already exist), C_ERR is returned.
+ * If the configuration was loaded from the file, C_OK is returned. */
+int clusterLoadConfig(char *filename) {
+ FILE *fp = fopen(filename, IF_WIN32("rb", "r"));
+ struct IF_WIN32(_stat64, stat) sb; // TODO: verify for 32-bit
+ char *line;
+ int maxline, j;
+
+ if (fp == NULL) {
+ if (errno == ENOENT) {
+ return C_ERR;
+ }
+ else {
+ serverLog(LL_WARNING,
+ "Loading the cluster node config from %s: %s",
+ filename, IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ exit(1);
+ }
+ }
+
+ /* Check if the file is zero-length: if so return C_ERR to signal
+ * we have to write the config. */
+ if (fstat(fileno(fp), &sb) != -1 && sb.st_size == 0) {
+ fclose(fp);
+ return C_ERR;
+ }
+
+ /* Parse the file. Note that single lines of the cluster config file can
+ * be really PORT_LONG as they include all the hash slots of the node.
+ * This means in the worst possible case, half of the Redis slots will be
+ * present in a single line, possibly in importing or migrating state, so
+ * together with the node ID of the sender/receiver.
+ *
+ * To simplify we allocate 1024+CLUSTER_SLOTS*128 bytes per line. */
+ maxline = 1024 + CLUSTER_SLOTS * 128;
+ line = zmalloc(maxline);
+ while (fgets(line, maxline, fp) != NULL) {
+ int argc;
+ sds *argv;
+ clusterNode *n, *master;
+ char *p, *s;
+
+ /* Skip blank lines, they can be created either by users manually
+ * editing nodes.conf or by the config writing process if stopped
+ * before the truncate() call. */
+ if (line[0] == '\n' || line[0] == '\0') continue;
+
+ /* Split the line into arguments for processing. */
+ argv = sdssplitargs(line, &argc);
+ if (argv == NULL) goto fmterr;
+
+ /* Handle the special "vars" line. Don't pretend it is the last
+ * line even if it actually is when generated by Redis. */
+ if (strcasecmp(argv[0], "vars") == 0) {
+ for (j = 1; j < argc; j += 2) {
+ if (strcasecmp(argv[j], "currentEpoch") == 0) {
+ server.cluster->currentEpoch =
+ strtoull(argv[j + 1], NULL, 10);
+ }
+ else if (strcasecmp(argv[j], "lastVoteEpoch") == 0) {
+ server.cluster->lastVoteEpoch =
+ strtoull(argv[j + 1], NULL, 10);
+ }
+ else {
+ serverLog(LL_WARNING,
+ "Skipping unknown cluster config variable '%s'",
+ argv[j]);
+ }
+ }
+ sdsfreesplitres(argv, argc);
+ continue;
+ }
+
+ /* Regular config lines have at least eight fields */
+ if (argc < 8) goto fmterr;
+
+ /* Create this node if it does not exist */
+ n = clusterLookupNode(argv[0]);
+ if (!n) {
+ n = createClusterNode(argv[0], 0);
+ clusterAddNode(n);
+ }
+ /* Address and port */
+ if ((p = strrchr(argv[1], ':')) == NULL) goto fmterr;
+ *p = '\0';
+ memcpy(n->ip, argv[1], strlen(argv[1]) + 1);
+ char *port = p + 1;
+ char *busp = strchr(port, '@');
+ if (busp) {
+ *busp = '\0';
+ busp++;
+ }
+ n->port = atoi(port);
+ /* In older versions of nodes.conf the "@busport" part is missing.
+ * In this case we set it to the default offset of 10000 from the
+ * base port. */
+ n->cport = busp ? atoi(busp) : n->port + CLUSTER_PORT_INCR;
+
+ /* Parse flags */
+ p = s = argv[2];
+ while (p) {
+ p = strchr(s, ',');
+ if (p) *p = '\0';
+ if (!strcasecmp(s, "myself")) {
+ serverAssert(server.cluster->myself == NULL);
+ myself = server.cluster->myself = n;
+ n->flags |= CLUSTER_NODE_MYSELF;
+ }
+ else if (!strcasecmp(s, "master")) {
+ n->flags |= CLUSTER_NODE_MASTER;
+ }
+ else if (!strcasecmp(s, "slave")) {
+ n->flags |= CLUSTER_NODE_SLAVE;
+ }
+ else if (!strcasecmp(s, "fail?")) {
+ n->flags |= CLUSTER_NODE_PFAIL;
+ }
+ else if (!strcasecmp(s, "fail")) {
+ n->flags |= CLUSTER_NODE_FAIL;
+ n->fail_time = mstime();
+ }
+ else if (!strcasecmp(s, "handshake")) {
+ n->flags |= CLUSTER_NODE_HANDSHAKE;
+ }
+ else if (!strcasecmp(s, "noaddr")) {
+ n->flags |= CLUSTER_NODE_NOADDR;
+ }
+ else if (!strcasecmp(s, "noflags")) {
+ /* nothing to do */
+ }
+ else {
+ serverPanic("Unknown flag in redis cluster config file");
+ }
+ if (p) s = p + 1;
+ }
+
+ /* Get master if any. Set the master and populate master's
+ * slave list. */
+ if (argv[3][0] != '-') {
+ master = clusterLookupNode(argv[3]);
+ if (!master) {
+ master = createClusterNode(argv[3], 0);
+ clusterAddNode(master);
+ }
+ n->slaveof = master;
+ clusterNodeAddSlave(master, n);
+ }
+
+ /* Set ping sent / pong received timestamps */
+ if (atoi(argv[4])) n->ping_sent = mstime();
+ if (atoi(argv[5])) n->pong_received = mstime();
+
+ /* Set configEpoch for this node. */
+ n->configEpoch = strtoull(argv[6], NULL, 10);
+
+ /* Populate hash slots served by this instance. */
+ for (j = 8; j < argc; j++) {
+ int start, stop;
+
+ if (argv[j][0] == '[') {
+ /* Here we handle migrating / importing slots */
+ int slot;
+ char direction;
+ clusterNode *cn;
+
+ p = strchr(argv[j], '-');
+ serverAssert(p != NULL);
+ *p = '\0';
+ direction = p[1]; /* Either '>' or '<' */
+ slot = atoi(argv[j] + 1);
+ p += 3;
+ cn = clusterLookupNode(p);
+ if (!cn) {
+ cn = createClusterNode(p, 0);
+ clusterAddNode(cn);
+ }
+ if (direction == '>') {
+ server.cluster->migrating_slots_to[slot] = cn;
+ }
+ else {
+ server.cluster->importing_slots_from[slot] = cn;
+ }
+ continue;
+ }
+ else if ((p = strchr(argv[j], '-')) != NULL) {
+ *p = '\0';
+ start = atoi(argv[j]);
+ stop = atoi(p + 1);
+ }
+ else {
+ start = stop = atoi(argv[j]);
+ }
+ while (start <= stop) clusterAddSlot(n, start++);
+ }
+
+ sdsfreesplitres(argv, argc);
+ }
+ /* Config sanity check */
+ if (server.cluster->myself == NULL) goto fmterr;
+
+ zfree(line);
+ fclose(fp);
+
+ serverLog(LL_NOTICE, "Node configuration loaded, I'm %.40s", myself->name);
+
+ /* Something that should never happen: currentEpoch smaller than
+ * the max epoch found in the nodes configuration. However we handle this
+ * as some form of protection against manual editing of critical files. */
+ if (clusterGetMaxEpoch() > server.cluster->currentEpoch) {
+ server.cluster->currentEpoch = clusterGetMaxEpoch();
+ }
+ return C_OK;
+
+fmterr:
+ serverLog(LL_WARNING,
+ "Unrecoverable error: corrupted cluster config file.");
+ zfree(line);
+ if (fp) fclose(fp);
+ exit(1);
+}
+
+/* Cluster node configuration is exactly the same as CLUSTER NODES output.
+ *
+ * This function writes the node config and returns 0, on error -1
+ * is returned.
+ *
+ * Note: we need to write the file in an atomic way from the point of view
+ * of the POSIX filesystem semantics, so that if the server is stopped
+ * or crashes during the write, we'll end with either the old file or the
+ * new one. Since we have the full payload to write available we can use
+ * a single write to write the whole file. If the pre-existing file was
+ * bigger we pad our payload with newlines that are anyway ignored and truncate
+ * the file afterward. */
+int clusterSaveConfig(int do_fsync) {
+ sds ci;
+ size_t content_size;
+ struct IF_WIN32(_stat64, stat) sb; // TODO: verify for 32-bit
+ int fd;
+
+ server.cluster->todo_before_sleep &= ~CLUSTER_TODO_SAVE_CONFIG;
+
+ /* Get the nodes description and concatenate our "vars" directive to
+ * save currentEpoch and lastVoteEpoch. */
+ ci = clusterGenNodesDescription(CLUSTER_NODE_HANDSHAKE);
+ ci = sdscatprintf(ci, "vars currentEpoch %llu lastVoteEpoch %llu\n",
+ (PORT_ULONGLONG) server.cluster->currentEpoch,
+ (PORT_ULONGLONG) server.cluster->lastVoteEpoch);
+ content_size = sdslen(ci);
+
+ if ((fd = open(server.cluster_configfile, O_WRONLY | O_CREAT, 0644))
+ == -1) goto err;
+
+ /* Pad the new payload if the existing file length is greater. */
+ if (fstat(fd, &sb) != -1) {
+ if (sb.st_size > (off_t) content_size) {
+ ci = sdsgrowzero(ci, sb.st_size);
+ memset(ci + content_size, '\n', sb.st_size - content_size);
+ }
+ }
+ if (write(fd, ci, sdslen(ci)) != (ssize_t) sdslen(ci)) goto err;
+ if (do_fsync) {
+ server.cluster->todo_before_sleep &= ~CLUSTER_TODO_FSYNC_CONFIG;
+ fsync(fd);
+ }
+
+ /* Truncate the file if needed to remove the final \n padding that
+ * is just garbage. */
+ if (content_size != sdslen(ci) && ftruncate(fd, content_size) == -1) {
+ /* ftruncate() failing is not a critical error. */
+ }
+ close(fd);
+ sdsfree(ci);
+ return 0;
+
+err:
+ if (fd != -1) close(fd);
+ sdsfree(ci);
+ return -1;
+}
+
+void clusterSaveConfigOrDie(int do_fsync) {
+ if (clusterSaveConfig(do_fsync) == -1) {
+ serverLog(LL_WARNING, "Fatal: can't update cluster config file.");
+ exit(1);
+ }
+}
+
+/* Lock the cluster config using flock(), and leaks the file descritor used to
+ * acquire the lock so that the file will be locked forever.
+ *
+ * This works because we always update nodes.conf with a new version
+ * in-place, reopening the file, and writing to it in place (later adjusting
+ * the length with ftruncate()).
+ *
+ * On success C_OK is returned, otherwise an error is logged and
+ * the function returns C_ERR to signal a lock was not acquired. */
+int clusterLockConfig(char *filename) {
+ /* flock() does not exist on Solaris
+ * and a fcntl-based solution won't help, as we constantly re-open that file,
+ * which will release _all_ locks anyway
+ */
+#if !defined(__sun)
+ /* To lock it, we need to open the file in a way it is created if
+ * it does not exist, otherwise there is a race condition with other
+ * processes. */
+ int fd = open(filename, O_WRONLY | O_CREAT, 0644);
+ if (fd == -1) {
+ serverLog(LL_WARNING,
+ "Can't open %s in order to acquire a lock: %s",
+ filename, IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ return C_ERR;
+ }
+
+#ifndef _WIN32
+ if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
+ if (errno == EWOULDBLOCK) {
+#else
+ HANDLE hFile = (HANDLE) FDAPI_get_osfhandle(fd);
+ OVERLAPPED ovlp;
+ DWORD size_lower, size_upper;
+ // start offset is 0, and also zero the remaining members of the struct
+ memset(&ovlp, 0, sizeof ovlp);
+ // get file size
+ size_lower = GetFileSize(hFile, &size_upper);
+ if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, size_lower, size_upper, &ovlp)) {
+ DWORD err = GetLastError();
+ if (err == ERROR_LOCK_VIOLATION) {
+#endif
+ serverLog(LL_WARNING,
+ "Sorry, the cluster configuration file %s is already used "
+ "by a different Redis Cluster node. Please make sure that "
+ "different nodes use different cluster configuration "
+ "files.", filename);
+ }
+ else {
+ serverLog(LL_WARNING,
+ "Impossible to lock %s: %s", filename, IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ }
+ close(fd);
+ return C_ERR;
+ }
+ /* Lock acquired: leak the 'fd' by not closing it, so that we'll retain the
+ * lock to the file as long as the process exists. */
+#endif /* __sun */
+
+ return C_OK;
+}
+
+void clusterInit(void) {
+ int saveconf = 0;
+
+ server.cluster = zmalloc(sizeof(clusterState));
+ server.cluster->myself = NULL;
+ server.cluster->currentEpoch = 0;
+ server.cluster->state = CLUSTER_FAIL;
+ server.cluster->size = 1;
+ server.cluster->todo_before_sleep = 0;
+ server.cluster->nodes = dictCreate(&clusterNodesDictType, NULL);
+ server.cluster->nodes_black_list =
+ dictCreate(&clusterNodesBlackListDictType, NULL);
+ server.cluster->failover_auth_time = 0;
+ server.cluster->failover_auth_count = 0;
+ server.cluster->failover_auth_rank = 0;
+ server.cluster->failover_auth_epoch = 0;
+ server.cluster->cant_failover_reason = CLUSTER_CANT_FAILOVER_NONE;
+ server.cluster->lastVoteEpoch = 0;
+ for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) {
+ server.cluster->stats_bus_messages_sent[i] = 0;
+ server.cluster->stats_bus_messages_received[i] = 0;
+ }
+ server.cluster->stats_pfail_nodes = 0;
+ memset(server.cluster->slots, 0, sizeof(server.cluster->slots));
+ clusterCloseAllSlots();
+
+#ifndef WIN32 // TODO: review this to verify if we can lock the file on Windows
+ /* Lock the cluster config file to make sure every node uses
+ * its own nodes.conf. */
+ if (clusterLockConfig(server.cluster_configfile) == C_ERR)
+ exit(1);
+#endif
+
+ /* Load or create a new nodes configuration. */
+ if (clusterLoadConfig(server.cluster_configfile) == C_ERR) {
+ /* No configuration found. We will just use the random name provided
+ * by the createClusterNode() function. */
+ myself = server.cluster->myself =
+ createClusterNode(NULL, CLUSTER_NODE_MYSELF | CLUSTER_NODE_MASTER);
+ serverLog(LL_NOTICE, "No cluster configuration found, I'm %.40s",
+ myself->name);
+ clusterAddNode(myself);
+ saveconf = 1;
+ }
+ if (saveconf) clusterSaveConfigOrDie(1);
+
+ /* We need a listening TCP port for our cluster messaging needs. */
+ server.cfd_count = 0;
+
+ /* Port sanity check II
+ * The other handshake port check is triggered too late to stop
+ * us from trying to use a too-high cluster port number. */
+ if (server.port > (65535 - CLUSTER_PORT_INCR)) {
+ serverLog(LL_WARNING, "Redis port number too high. "
+ "Cluster communication port is 10,000 port "
+ "numbers higher than your Redis port. "
+ "Your Redis port number must be "
+ "lower than 55535.");
+ exit(1);
+ }
+
+ if (listenToPort(server.port + CLUSTER_PORT_INCR,
+ server.cfd, &server.cfd_count) == C_ERR)
+ {
+ exit(1);
+ }
+ else {
+ int j;
+
+ for (j = 0; j < server.cfd_count; j++) {
+ if (aeCreateFileEvent(server.el, server.cfd[j], AE_READABLE,
+ clusterAcceptHandler, NULL) == AE_ERR)
+ serverPanic("Unrecoverable error creating Redis Cluster "
+ "file event.");
+ }
+ }
+
+ /* The slots -> keys map is a radix tree. Initialize it here. */
+ server.cluster->slots_to_keys = raxNew();
+ memset(server.cluster->slots_keys_count, 0,
+ sizeof(server.cluster->slots_keys_count));
+
+ /* Set myself->port / cport to my listening ports, we'll just need to
+ * discover the IP address via MEET messages. */
+ myself->port = server.port;
+ myself->cport = server.port + CLUSTER_PORT_INCR;
+ if (server.cluster_announce_port)
+ myself->port = server.cluster_announce_port;
+ if (server.cluster_announce_bus_port)
+ myself->cport = server.cluster_announce_bus_port;
+
+ server.cluster->mf_end = 0;
+ resetManualFailover();
+}
+
+/* Reset a node performing a soft or hard reset:
+ *
+ * 1) All other nodes are forget.
+ * 2) All the assigned / open slots are released.
+ * 3) If the node is a slave, it turns into a master.
+ * 5) Only for hard reset: a new Node ID is generated.
+ * 6) Only for hard reset: currentEpoch and configEpoch are set to 0.
+ * 7) The new configuration is saved and the cluster state updated.
+ * 8) If the node was a slave, the whole data set is flushed away. */
+void clusterReset(int hard) {
+ dictIterator *di;
+ dictEntry *de;
+ int j;
+
+ /* Turn into master. */
+ if (nodeIsSlave(myself)) {
+ clusterSetNodeAsMaster(myself);
+ replicationUnsetMaster();
+ emptyDb(-1, EMPTYDB_NO_FLAGS, NULL);
+ }
+
+ /* Close slots, reset manual failover state. */
+ clusterCloseAllSlots();
+ resetManualFailover();
+
+ /* Unassign all the slots. */
+ for (j = 0; j < CLUSTER_SLOTS; j++) clusterDelSlot(j);
+
+ /* Forget all the nodes, but myself. */
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+
+ if (node == myself) continue;
+ clusterDelNode(node);
+ }
+ dictReleaseIterator(di);
+
+ /* Hard reset only: set epochs to 0, change node ID. */
+ if (hard) {
+ sds oldname;
+
+ server.cluster->currentEpoch = 0;
+ server.cluster->lastVoteEpoch = 0;
+ myself->configEpoch = 0;
+ serverLog(LL_WARNING, "configEpoch set to 0 via CLUSTER RESET HARD");
+
+ /* To change the Node ID we need to remove the old name from the
+ * nodes table, change the ID, and re-add back with new name. */
+ oldname = sdsnewlen(myself->name, CLUSTER_NAMELEN);
+ dictDelete(server.cluster->nodes, oldname);
+ sdsfree(oldname);
+ getRandomHexChars(myself->name, CLUSTER_NAMELEN);
+ clusterAddNode(myself);
+ serverLog(LL_NOTICE, "Node hard reset, now I'm %.40s", myself->name);
+ }
+
+ /* Make sure to persist the new config and update the state. */
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_UPDATE_STATE |
+ CLUSTER_TODO_FSYNC_CONFIG);
+}
+
+/* -----------------------------------------------------------------------------
+ * CLUSTER communication link
+ * -------------------------------------------------------------------------- */
+
+clusterLink *createClusterLink(clusterNode *node) {
+ clusterLink *link = zmalloc(sizeof(*link));
+ link->ctime = mstime();
+ link->sndbuf = sdsempty();
+ link->rcvbuf = sdsempty();
+ link->node = node;
+ link->fd = -1;
+ return link;
+}
+
+/* Free a cluster link, but does not free the associated node of course.
+ * This function will just make sure that the original node associated
+ * with this link will have the 'link' field set to NULL. */
+void freeClusterLink(clusterLink *link) {
+ if (link->fd != -1) {
+ aeDeleteFileEvent(server.el, link->fd, AE_WRITABLE);
+ aeDeleteFileEvent(server.el, link->fd, AE_READABLE);
+ }
+ sdsfree(link->sndbuf);
+ sdsfree(link->rcvbuf);
+ if (link->node)
+ link->node->link = NULL;
+ close(link->fd);
+ zfree(link);
+}
+
+#define MAX_CLUSTER_ACCEPTS_PER_CALL 1000
+void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
+ int cport, cfd;
+ int max = MAX_CLUSTER_ACCEPTS_PER_CALL;
+ char cip[NET_IP_STR_LEN];
+ clusterLink *link;
+ UNUSED(el);
+ UNUSED(mask);
+ UNUSED(privdata);
+
+ /* If the server is starting up, don't accept cluster connections:
+ * UPDATE messages may interact with the database content. */
+ if (server.masterhost == NULL && server.loading) {
+ WIN32_ONLY(WSIOCP_QueueAccept(fd);)
+ return;
+ }
+
+ while (max--) {
+ cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
+ if (cfd == ANET_ERR) {
+ if (errno != EWOULDBLOCK)
+ serverLog(LL_VERBOSE,
+ "Error accepting cluster node: %s", server.neterr);
+#ifdef _WIN32
+ if (WSIOCP_QueueAccept(fd) == -1) {
+ serverLog(LL_WARNING,
+ "acceptTcpHandler: failed to queue another accept.");
+ }
+#endif
+ return;
+ }
+ anetNonBlock(NULL, cfd);
+ anetEnableTcpNoDelay(NULL, cfd);
+
+ /* Use non-blocking I/O for cluster messages. */
+ serverLog(LL_VERBOSE, "Accepted cluster node %s:%d", cip, cport);
+ /* Create a link object we use to handle the connection.
+ * It gets passed to the readable handler when data is available.
+ * Initiallly the link->node pointer is set to NULL as we don't know
+ * which node is, but the right node is references once we know the
+ * node identity. */
+ link = createClusterLink(NULL);
+ link->fd = cfd;
+ aeCreateFileEvent(server.el, cfd, AE_READABLE, clusterReadHandler, link);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Key space handling
+ * -------------------------------------------------------------------------- */
+
+ /* We have 16384 hash slots. The hash slot of a given key is obtained
+ * as the least significant 14 bits of the crc16 of the key.
+ *
+ * However if the key contains the {...} pattern, only the part between
+ * { and } is hashed. This may be useful in the future to force certain
+ * keys to be in the same node (assuming no resharding is in progress). */
+unsigned int keyHashSlot(char *key, int keylen) {
+ int s, e; /* start-end indexes of { and } */
+
+ for (s = 0; s < keylen; s++)
+ if (key[s] == '{') break;
+
+ /* No '{' ? Hash the whole key. This is the base case. */
+ if (s == keylen) return crc16(key, keylen) & 0x3FFF;
+
+ /* '{' found? Check if we have the corresponding '}'. */
+ for (e = s + 1; e < keylen; e++)
+ if (key[e] == '}') break;
+
+ /* No '}' or nothing betweeen {} ? Hash the whole key. */
+ if (e == keylen || e == s + 1) return crc16(key, keylen) & 0x3FFF;
+
+ /* If we are here there is both a { and a } on its right. Hash
+ * what is in the middle between { and }. */
+ return crc16(key + s + 1, e - s - 1) & 0x3FFF;
+}
+
+/* -----------------------------------------------------------------------------
+ * CLUSTER node API
+ * -------------------------------------------------------------------------- */
+
+ /* Create a new cluster node, with the specified flags.
+ * If "nodename" is NULL this is considered a first handshake and a random
+ * node name is assigned to this node (it will be fixed later when we'll
+ * receive the first pong).
+ *
+ * The node is created and returned to the user, but it is not automatically
+ * added to the nodes hash table. */
+clusterNode *createClusterNode(char *nodename, int flags) {
+ clusterNode *node = zmalloc(sizeof(*node));
+
+ if (nodename)
+ memcpy(node->name, nodename, CLUSTER_NAMELEN);
+ else
+ getRandomHexChars(node->name, CLUSTER_NAMELEN);
+ node->ctime = mstime();
+ node->configEpoch = 0;
+ node->flags = flags;
+ memset(node->slots, 0, sizeof(node->slots));
+ node->numslots = 0;
+ node->numslaves = 0;
+ node->slaves = NULL;
+ node->slaveof = NULL;
+ node->ping_sent = node->pong_received = 0;
+ node->fail_time = 0;
+ node->link = NULL;
+ memset(node->ip, 0, sizeof(node->ip));
+ node->port = 0;
+ node->cport = 0;
+ node->fail_reports = listCreate();
+ node->voted_time = 0;
+ node->orphaned_time = 0;
+ node->repl_offset_time = 0;
+ node->repl_offset = 0;
+ listSetFreeMethod(node->fail_reports, zfree);
+ return node;
+}
+
+/* This function is called every time we get a failure report from a node.
+ * The side effect is to populate the fail_reports list (or to update
+ * the timestamp of an existing report).
+ *
+ * 'failing' is the node that is in failure state according to the
+ * 'sender' node.
+ *
+ * The function returns 0 if it just updates a timestamp of an existing
+ * failure report from the same sender. 1 is returned if a new failure
+ * report is created. */
+int clusterNodeAddFailureReport(clusterNode *failing, clusterNode *sender) {
+ list *l = failing->fail_reports;
+ listNode *ln;
+ listIter li;
+ clusterNodeFailReport *fr;
+
+ /* If a failure report from the same sender already exists, just update
+ * the timestamp. */
+ listRewind(l, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ fr = ln->value;
+ if (fr->node == sender) {
+ fr->time = mstime();
+ return 0;
+ }
+ }
+
+ /* Otherwise create a new report. */
+ fr = zmalloc(sizeof(*fr));
+ fr->node = sender;
+ fr->time = mstime();
+ listAddNodeTail(l, fr);
+ return 1;
+}
+
+/* Remove failure reports that are too old, where too old means reasonably
+ * older than the global node timeout. Note that anyway for a node to be
+ * flagged as FAIL we need to have a local PFAIL state that is at least
+ * older than the global node timeout, so we don't just trust the number
+ * of failure reports from other nodes. */
+void clusterNodeCleanupFailureReports(clusterNode *node) {
+ list *l = node->fail_reports;
+ listNode *ln;
+ listIter li;
+ clusterNodeFailReport *fr;
+ mstime_t maxtime = server.cluster_node_timeout *
+ CLUSTER_FAIL_REPORT_VALIDITY_MULT;
+ mstime_t now = mstime();
+
+ listRewind(l, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ fr = ln->value;
+ if (now - fr->time > maxtime) listDelNode(l, ln);
+ }
+}
+
+/* Remove the failing report for 'node' if it was previously considered
+ * failing by 'sender'. This function is called when a node informs us via
+ * gossip that a node is OK from its point of view (no FAIL or PFAIL flags).
+ *
+ * Note that this function is called relatively often as it gets called even
+ * when there are no nodes failing, and is O(N), however when the cluster is
+ * fine the failure reports list is empty so the function runs in constant
+ * time.
+ *
+ * The function returns 1 if the failure report was found and removed.
+ * Otherwise 0 is returned. */
+int clusterNodeDelFailureReport(clusterNode *node, clusterNode *sender) {
+ list *l = node->fail_reports;
+ listNode *ln;
+ listIter li;
+ clusterNodeFailReport *fr;
+
+ /* Search for a failure report from this sender. */
+ listRewind(l, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ fr = ln->value;
+ if (fr->node == sender) break;
+ }
+ if (!ln) return 0; /* No failure report from this sender. */
+
+ /* Remove the failure report. */
+ listDelNode(l, ln);
+ clusterNodeCleanupFailureReports(node);
+ return 1;
+}
+
+/* Return the number of external nodes that believe 'node' is failing,
+ * not including this node, that may have a PFAIL or FAIL state for this
+ * node as well. */
+int clusterNodeFailureReportsCount(clusterNode *node) {
+ clusterNodeCleanupFailureReports(node);
+ return (int) listLength(node->fail_reports); WIN_PORT_FIX /* cast (int) */
+}
+
+int clusterNodeRemoveSlave(clusterNode *master, clusterNode *slave) {
+ int j;
+
+ for (j = 0; j < master->numslaves; j++) {
+ if (master->slaves[j] == slave) {
+ if ((j + 1) < master->numslaves) {
+ int remaining_slaves = (master->numslaves - j) - 1;
+ memmove(master->slaves + j, master->slaves + (j + 1),
+ (sizeof(*master->slaves) * remaining_slaves));
+ }
+ master->numslaves--;
+ if (master->numslaves == 0)
+ master->flags &= ~CLUSTER_NODE_MIGRATE_TO;
+ return C_OK;
+ }
+ }
+ return C_ERR;
+}
+
+int clusterNodeAddSlave(clusterNode *master, clusterNode *slave) {
+ int j;
+
+ /* If it's already a slave, don't add it again. */
+ for (j = 0; j < master->numslaves; j++)
+ if (master->slaves[j] == slave) return C_ERR;
+ master->slaves = zrealloc(master->slaves,
+ sizeof(clusterNode*)*(master->numslaves + 1));
+ master->slaves[master->numslaves] = slave;
+ master->numslaves++;
+ master->flags |= CLUSTER_NODE_MIGRATE_TO;
+ return C_OK;
+}
+
+int clusterCountNonFailingSlaves(clusterNode *n) {
+ int j, okslaves = 0;
+
+ for (j = 0; j < n->numslaves; j++)
+ if (!nodeFailed(n->slaves[j])) okslaves++;
+ return okslaves;
+}
+
+/* Low level cleanup of the node structure. Only called by clusterDelNode(). */
+void freeClusterNode(clusterNode *n) {
+ sds nodename;
+ int j;
+
+ /* If the node has associated slaves, we have to set
+ * all the slaves->slaveof fields to NULL (unknown). */
+ for (j = 0; j < n->numslaves; j++)
+ n->slaves[j]->slaveof = NULL;
+
+ /* Remove this node from the list of slaves of its master. */
+ if (nodeIsSlave(n) && n->slaveof) clusterNodeRemoveSlave(n->slaveof, n);
+
+ /* Unlink from the set of nodes. */
+ nodename = sdsnewlen(n->name, CLUSTER_NAMELEN);
+ serverAssert(dictDelete(server.cluster->nodes, nodename) == DICT_OK);
+ sdsfree(nodename);
+
+ /* Release link and associated data structures. */
+ if (n->link) freeClusterLink(n->link);
+ listRelease(n->fail_reports);
+ zfree(n->slaves);
+ zfree(n);
+}
+
+/* Add a node to the nodes hash table */
+int clusterAddNode(clusterNode *node) {
+ int retval;
+
+ retval = dictAdd(server.cluster->nodes,
+ sdsnewlen(node->name, CLUSTER_NAMELEN), node);
+ return (retval == DICT_OK) ? C_OK : C_ERR;
+}
+
+/* Remove a node from the cluster. The functio performs the high level
+ * cleanup, calling freeClusterNode() for the low level cleanup.
+ * Here we do the following:
+ *
+ * 1) Mark all the slots handled by it as unassigned.
+ * 2) Remove all the failure reports sent by this node and referenced by
+ * other nodes.
+ * 3) Free the node with freeClusterNode() that will in turn remove it
+ * from the hash table and from the list of slaves of its master, if
+ * it is a slave node.
+ */
+void clusterDelNode(clusterNode *delnode) {
+ int j;
+ dictIterator *di;
+ dictEntry *de;
+
+ /* 1) Mark slots as unassigned. */
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ if (server.cluster->importing_slots_from[j] == delnode)
+ server.cluster->importing_slots_from[j] = NULL;
+ if (server.cluster->migrating_slots_to[j] == delnode)
+ server.cluster->migrating_slots_to[j] = NULL;
+ if (server.cluster->slots[j] == delnode)
+ clusterDelSlot(j);
+ }
+
+ /* 2) Remove failure reports. */
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+
+ if (node == delnode) continue;
+ clusterNodeDelFailureReport(node, delnode);
+ }
+ dictReleaseIterator(di);
+
+ /* 3) Free the node, unlinking it from the cluster. */
+ freeClusterNode(delnode);
+}
+
+/* Node lookup by name */
+clusterNode *clusterLookupNode(char *name) {
+ sds s = sdsnewlen(name, CLUSTER_NAMELEN);
+ dictEntry *de;
+
+ de = dictFind(server.cluster->nodes, s);
+ sdsfree(s);
+ if (de == NULL) return NULL;
+ return dictGetVal(de);
+}
+
+/* This is only used after the handshake. When we connect a given IP/PORT
+ * as a result of CLUSTER MEET we don't have the node name yet, so we
+ * pick a random one, and will fix it when we receive the PONG request using
+ * this function. */
+void clusterRenameNode(clusterNode *node, char *newname) {
+ int retval;
+ sds s = sdsnewlen(node->name, CLUSTER_NAMELEN);
+
+ serverLog(LL_DEBUG, "Renaming node %.40s into %.40s",
+ node->name, newname);
+ retval = dictDelete(server.cluster->nodes, s);
+ sdsfree(s);
+ serverAssert(retval == DICT_OK);
+ memcpy(node->name, newname, CLUSTER_NAMELEN);
+ clusterAddNode(node);
+}
+
+/* -----------------------------------------------------------------------------
+ * CLUSTER config epoch handling
+ * -------------------------------------------------------------------------- */
+
+ /* Return the greatest configEpoch found in the cluster, or the current
+ * epoch if greater than any node configEpoch. */
+uint64_t clusterGetMaxEpoch(void) {
+ uint64_t max = 0;
+ dictIterator *di;
+ dictEntry *de;
+
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+ if (node->configEpoch > max) max = node->configEpoch;
+ }
+ dictReleaseIterator(di);
+ if (max < server.cluster->currentEpoch) max = server.cluster->currentEpoch;
+ return max;
+}
+
+/* If this node epoch is zero or is not already the greatest across the
+ * cluster (from the POV of the local configuration), this function will:
+ *
+ * 1) Generate a new config epoch, incrementing the current epoch.
+ * 2) Assign the new epoch to this node, WITHOUT any consensus.
+ * 3) Persist the configuration on disk before sending packets with the
+ * new configuration.
+ *
+ * If the new config epoch is generated and assigend, C_OK is returned,
+ * otherwise C_ERR is returned (since the node has already the greatest
+ * configuration around) and no operation is performed.
+ *
+ * Important note: this function violates the principle that config epochs
+ * should be generated with consensus and should be unique across the cluster.
+ * However Redis Cluster uses this auto-generated new config epochs in two
+ * cases:
+ *
+ * 1) When slots are closed after importing. Otherwise resharding would be
+ * too expensive.
+ * 2) When CLUSTER FAILOVER is called with options that force a slave to
+ * failover its master even if there is not master majority able to
+ * create a new configuration epoch.
+ *
+ * Redis Cluster will not explode using this function, even in the case of
+ * a collision between this node and another node, generating the same
+ * configuration epoch unilaterally, because the config epoch conflict
+ * resolution algorithm will eventually move colliding nodes to different
+ * config epochs. However using this function may violate the "last failover
+ * wins" rule, so should only be used with care. */
+int clusterBumpConfigEpochWithoutConsensus(void) {
+ uint64_t maxEpoch = clusterGetMaxEpoch();
+
+ if (myself->configEpoch == 0 ||
+ myself->configEpoch != maxEpoch)
+ {
+ server.cluster->currentEpoch++;
+ myself->configEpoch = server.cluster->currentEpoch;
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_FSYNC_CONFIG);
+ serverLog(LL_WARNING,
+ "New configEpoch set to %llu",
+ (PORT_ULONGLONG) myself->configEpoch);
+ return C_OK;
+ }
+ else {
+ return C_ERR;
+ }
+}
+
+/* This function is called when this node is a master, and we receive from
+ * another master a configuration epoch that is equal to our configuration
+ * epoch.
+ *
+ * BACKGROUND
+ *
+ * It is not possible that different slaves get the same config
+ * epoch during a failover election, because the slaves need to get voted
+ * by a majority. However when we perform a manual resharding of the cluster
+ * the node will assign a configuration epoch to itself without to ask
+ * for agreement. Usually resharding happens when the cluster is working well
+ * and is supervised by the sysadmin, however it is possible for a failover
+ * to happen exactly while the node we are resharding a slot to assigns itself
+ * a new configuration epoch, but before it is able to propagate it.
+ *
+ * So technically it is possible in this condition that two nodes end with
+ * the same configuration epoch.
+ *
+ * Another possibility is that there are bugs in the implementation causing
+ * this to happen.
+ *
+ * Moreover when a new cluster is created, all the nodes start with the same
+ * configEpoch. This collision resolution code allows nodes to automatically
+ * end with a different configEpoch at startup automatically.
+ *
+ * In all the cases, we want a mechanism that resolves this issue automatically
+ * as a safeguard. The same configuration epoch for masters serving different
+ * set of slots is not harmful, but it is if the nodes end serving the same
+ * slots for some reason (manual errors or software bugs) without a proper
+ * failover procedure.
+ *
+ * In general we want a system that eventually always ends with different
+ * masters having different configuration epochs whatever happened, since
+ * nothign is worse than a split-brain condition in a distributed system.
+ *
+ * BEHAVIOR
+ *
+ * When this function gets called, what happens is that if this node
+ * has the lexicographically smaller Node ID compared to the other node
+ * with the conflicting epoch (the 'sender' node), it will assign itself
+ * the greatest configuration epoch currently detected among nodes plus 1.
+ *
+ * This means that even if there are multiple nodes colliding, the node
+ * with the greatest Node ID never moves forward, so eventually all the nodes
+ * end with a different configuration epoch.
+ */
+void clusterHandleConfigEpochCollision(clusterNode *sender) {
+ /* Prerequisites: nodes have the same configEpoch and are both masters. */
+ if (sender->configEpoch != myself->configEpoch ||
+ !nodeIsMaster(sender) || !nodeIsMaster(myself)) return;
+ /* Don't act if the colliding node has a smaller Node ID. */
+ if (memcmp(sender->name, myself->name, CLUSTER_NAMELEN) <= 0) return;
+ /* Get the next ID available at the best of this node knowledge. */
+ server.cluster->currentEpoch++;
+ myself->configEpoch = server.cluster->currentEpoch;
+ clusterSaveConfigOrDie(1);
+ serverLog(LL_VERBOSE,
+ "WARNING: configEpoch collision with node %.40s."
+ " configEpoch set to %llu",
+ sender->name,
+ (PORT_ULONGLONG) myself->configEpoch);
+}
+
+/* -----------------------------------------------------------------------------
+ * CLUSTER nodes blacklist
+ *
+ * The nodes blacklist is just a way to ensure that a given node with a given
+ * Node ID is not readded before some time elapsed (this time is specified
+ * in seconds in CLUSTER_BLACKLIST_TTL).
+ *
+ * This is useful when we want to remove a node from the cluster completely:
+ * when CLUSTER FORGET is called, it also puts the node into the blacklist so
+ * that even if we receive gossip messages from other nodes that still remember
+ * about the node we want to remove, we don't re-add it before some time.
+ *
+ * Currently the CLUSTER_BLACKLIST_TTL is set to 1 minute, this means
+ * that redis-trib has 60 seconds to send CLUSTER FORGET messages to nodes
+ * in the cluster without dealing with the problem of other nodes re-adding
+ * back the node to nodes we already sent the FORGET command to.
+ *
+ * The data structure used is a hash table with an sds string representing
+ * the node ID as key, and the time when it is ok to re-add the node as
+ * value.
+ * -------------------------------------------------------------------------- */
+
+#define CLUSTER_BLACKLIST_TTL 60 /* 1 minute. */
+
+ /* Before of the addNode() or Exists() operations we always remove expired
+ * entries from the black list. This is an O(N) operation but it is not a
+ * problem since add / exists operations are called very infrequently and
+ * the hash table is supposed to contain very little elements at max.
+ * However without the cleanup during long uptimes and with some automated
+ * node add/removal procedures, entries could accumulate. */
+void clusterBlacklistCleanup(void) {
+ dictIterator *di;
+ dictEntry *de;
+
+ di = dictGetSafeIterator(server.cluster->nodes_black_list);
+ while ((de = dictNext(di)) != NULL) {
+ int64_t expire = dictGetUnsignedIntegerVal(de);
+
+ if (expire < server.unixtime)
+ dictDelete(server.cluster->nodes_black_list, dictGetKey(de));
+ }
+ dictReleaseIterator(di);
+}
+
+/* Cleanup the blacklist and add a new node ID to the black list. */
+void clusterBlacklistAddNode(clusterNode *node) {
+ dictEntry *de;
+ sds id = sdsnewlen(node->name, CLUSTER_NAMELEN);
+
+ clusterBlacklistCleanup();
+ if (dictAdd(server.cluster->nodes_black_list, id, NULL) == DICT_OK) {
+ /* If the key was added, duplicate the sds string representation of
+ * the key for the next lookup. We'll free it at the end. */
+ id = sdsdup(id);
+ }
+ de = dictFind(server.cluster->nodes_black_list, id);
+ dictSetUnsignedIntegerVal(de, time(NULL) + CLUSTER_BLACKLIST_TTL);
+ sdsfree(id);
+}
+
+/* Return non-zero if the specified node ID exists in the blacklist.
+ * You don't need to pass an sds string here, any pointer to 40 bytes
+ * will work. */
+int clusterBlacklistExists(char *nodeid) {
+ sds id = sdsnewlen(nodeid, CLUSTER_NAMELEN);
+ int retval;
+
+ clusterBlacklistCleanup();
+ retval = dictFind(server.cluster->nodes_black_list, id) != NULL;
+ sdsfree(id);
+ return retval;
+}
+
+/* -----------------------------------------------------------------------------
+ * CLUSTER messages exchange - PING/PONG and gossip
+ * -------------------------------------------------------------------------- */
+
+ /* This function checks if a given node should be marked as FAIL.
+ * It happens if the following conditions are met:
+ *
+ * 1) We received enough failure reports from other master nodes via gossip.
+ * Enough means that the majority of the masters signaled the node is
+ * down recently.
+ * 2) We believe this node is in PFAIL state.
+ *
+ * If a failure is detected we also inform the whole cluster about this
+ * event trying to force every other node to set the FAIL flag for the node.
+ *
+ * Note that the form of agreement used here is weak, as we collect the majority
+ * of masters state during some time, and even if we force agreement by
+ * propagating the FAIL message, because of partitions we may not reach every
+ * node. However:
+ *
+ * 1) Either we reach the majority and eventually the FAIL state will propagate
+ * to all the cluster.
+ * 2) Or there is no majority so no slave promotion will be authorized and the
+ * FAIL flag will be cleared after some time.
+ */
+void markNodeAsFailingIfNeeded(clusterNode *node) {
+ int failures;
+ int needed_quorum = (server.cluster->size / 2) + 1;
+
+ if (!nodeTimedOut(node)) return; /* We can reach it. */
+ if (nodeFailed(node)) return; /* Already FAILing. */
+
+ failures = clusterNodeFailureReportsCount(node);
+ /* Also count myself as a voter if I'm a master. */
+ if (nodeIsMaster(myself)) failures++;
+ if (failures < needed_quorum) return; /* No weak agreement from masters. */
+
+ serverLog(LL_NOTICE,
+ "Marking node %.40s as failing (quorum reached).", node->name);
+
+ /* Mark the node as failing. */
+ node->flags &= ~CLUSTER_NODE_PFAIL;
+ node->flags |= CLUSTER_NODE_FAIL;
+ node->fail_time = mstime();
+
+ /* Broadcast the failing node name to everybody, forcing all the other
+ * reachable nodes to flag the node as FAIL. */
+ if (nodeIsMaster(myself)) clusterSendFail(node->name);
+ clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_SAVE_CONFIG);
+}
+
+/* This function is called only if a node is marked as FAIL, but we are able
+ * to reach it again. It checks if there are the conditions to undo the FAIL
+ * state. */
+void clearNodeFailureIfNeeded(clusterNode *node) {
+ mstime_t now = mstime();
+
+ serverAssert(nodeFailed(node));
+
+ /* For slaves we always clear the FAIL flag if we can contact the
+ * node again. */
+ if (nodeIsSlave(node) || node->numslots == 0) {
+ serverLog(LL_NOTICE,
+ "Clear FAIL state for node %.40s: %s is reachable again.",
+ node->name,
+ nodeIsSlave(node) ? "slave" : "master without slots");
+ node->flags &= ~CLUSTER_NODE_FAIL;
+ clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_SAVE_CONFIG);
+ }
+
+ /* If it is a master and...
+ * 1) The FAIL state is old enough.
+ * 2) It is yet serving slots from our point of view (not failed over).
+ * Apparently no one is going to fix these slots, clear the FAIL flag. */
+ if (nodeIsMaster(node) && node->numslots > 0 &&
+ (now - node->fail_time) >
+ (server.cluster_node_timeout * CLUSTER_FAIL_UNDO_TIME_MULT))
+ {
+ serverLog(LL_NOTICE,
+ "Clear FAIL state for node %.40s: is reachable again and nobody is serving its slots after some time.",
+ node->name);
+ node->flags &= ~CLUSTER_NODE_FAIL;
+ clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_SAVE_CONFIG);
+ }
+}
+
+/* Return true if we already have a node in HANDSHAKE state matching the
+ * specified ip address and port number. This function is used in order to
+ * avoid adding a new handshake node for the same address multiple times. */
+int clusterHandshakeInProgress(char *ip, int port, int cport) {
+ dictIterator *di;
+ dictEntry *de;
+
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+
+ if (!nodeInHandshake(node)) continue;
+ if (!strcasecmp(node->ip, ip) &&
+ node->port == port &&
+ node->cport == cport) break;
+ }
+ dictReleaseIterator(di);
+ return de != NULL;
+}
+
+/* Start an handshake with the specified address if there is not one
+ * already in progress. Returns non-zero if the handshake was actually
+ * started. On error zero is returned and errno is set to one of the
+ * following values:
+ *
+ * EAGAIN - There is already an handshake in progress for this address.
+ * EINVAL - IP or port are not valid. */
+int clusterStartHandshake(char *ip, int port, int cport) {
+ clusterNode *n;
+ char norm_ip[NET_IP_STR_LEN];
+ struct sockaddr_storage sa;
+
+ /* IP sanity check */
+ if (inet_pton(AF_INET, ip,
+ &(((struct sockaddr_in *)&sa)->sin_addr)))
+ {
+ sa.ss_family = AF_INET;
+ }
+ else if (inet_pton(AF_INET6, ip,
+ &(((struct sockaddr_in6 *)&sa)->sin6_addr)))
+ {
+ sa.ss_family = AF_INET6;
+ }
+ else {
+ errno = EINVAL;
+ return 0;
+ }
+
+ /* Port sanity check */
+ if (port <= 0 || port > 65535 || cport <= 0 || cport > 65535) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ /* Set norm_ip as the normalized string representation of the node
+ * IP address. */
+ memset(norm_ip, 0, NET_IP_STR_LEN);
+ if (sa.ss_family == AF_INET)
+ inet_ntop(AF_INET,
+ (void*)&(((struct sockaddr_in *)&sa)->sin_addr),
+ norm_ip, NET_IP_STR_LEN);
+ else
+ inet_ntop(AF_INET6,
+ (void*)&(((struct sockaddr_in6 *)&sa)->sin6_addr),
+ norm_ip, NET_IP_STR_LEN);
+
+ if (clusterHandshakeInProgress(norm_ip, port, cport)) {
+ errno = EAGAIN;
+ return 0;
+ }
+
+ /* Add the node with a random address (NULL as first argument to
+ * createClusterNode()). Everything will be fixed during the
+ * handshake. */
+ n = createClusterNode(NULL, CLUSTER_NODE_HANDSHAKE | CLUSTER_NODE_MEET);
+ memcpy(n->ip, norm_ip, sizeof(n->ip));
+ n->port = port;
+ n->cport = cport;
+ clusterAddNode(n);
+ return 1;
+}
+
+/* Process the gossip section of PING or PONG packets.
+ * Note that this function assumes that the packet is already sanity-checked
+ * by the caller, not in the content of the gossip section, but in the
+ * length. */
+void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
+ uint16_t count = ntohs(hdr->count);
+ clusterMsgDataGossip *g = (clusterMsgDataGossip*) hdr->data.ping.gossip;
+ clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender);
+
+ while (count--) {
+ uint16_t flags = ntohs(g->flags);
+ clusterNode *node;
+ sds ci;
+
+ if (server.verbosity == LL_DEBUG) {
+ ci = representClusterNodeFlags(sdsempty(), flags);
+ serverLog(LL_DEBUG, "GOSSIP %.40s %s:%d@%d %s",
+ g->nodename,
+ g->ip,
+ ntohs(g->port),
+ ntohs(g->cport),
+ ci);
+ sdsfree(ci);
+ }
+
+ /* Update our state accordingly to the gossip sections */
+ node = clusterLookupNode(g->nodename);
+ if (node) {
+ /* We already know this node.
+ Handle failure reports, only when the sender is a master. */
+ if (sender && nodeIsMaster(sender) && node != myself) {
+ if (flags & (CLUSTER_NODE_FAIL | CLUSTER_NODE_PFAIL)) {
+ if (clusterNodeAddFailureReport(node, sender)) {
+ serverLog(LL_VERBOSE,
+ "Node %.40s reported node %.40s as not reachable.",
+ sender->name, node->name);
+ }
+ markNodeAsFailingIfNeeded(node);
+ }
+ else {
+ if (clusterNodeDelFailureReport(node, sender)) {
+ serverLog(LL_VERBOSE,
+ "Node %.40s reported node %.40s is back online.",
+ sender->name, node->name);
+ }
+ }
+ }
+
+ /* If from our POV the node is up (no failure flags are set),
+ * we have no pending ping for the node, nor we have failure
+ * reports for this node, update the last pong time with the
+ * one we see from the other nodes. */
+ if (!(flags & (CLUSTER_NODE_FAIL | CLUSTER_NODE_PFAIL)) &&
+ node->ping_sent == 0 &&
+ clusterNodeFailureReportsCount(node) == 0)
+ {
+ mstime_t pongtime = ntohl(g->pong_received);
+ pongtime *= 1000; /* Convert back to milliseconds. */
+
+ /* Replace the pong time with the received one only if
+ * it's greater than our view but is not in the future
+ * (with 500 milliseconds tolerance) from the POV of our
+ * clock. */
+ if (pongtime <= (server.mstime + 500) &&
+ pongtime > node->pong_received)
+ {
+ node->pong_received = pongtime;
+ }
+ }
+
+ /* If we already know this node, but it is not reachable, and
+ * we see a different address in the gossip section of a node that
+ * can talk with this other node, update the address, disconnect
+ * the old link if any, so that we'll attempt to connect with the
+ * new address. */
+ if (node->flags & (CLUSTER_NODE_FAIL | CLUSTER_NODE_PFAIL) &&
+ !(flags & CLUSTER_NODE_NOADDR) &&
+ !(flags & (CLUSTER_NODE_FAIL | CLUSTER_NODE_PFAIL)) &&
+ (strcasecmp(node->ip, g->ip) ||
+ node->port != ntohs(g->port) ||
+ node->cport != ntohs(g->cport)))
+ {
+ if (node->link) freeClusterLink(node->link);
+ memcpy(node->ip, g->ip, NET_IP_STR_LEN);
+ node->port = ntohs(g->port);
+ node->cport = ntohs(g->cport);
+ node->flags &= ~CLUSTER_NODE_NOADDR;
+ }
+ }
+ else {
+ /* If it's not in NOADDR state and we don't have it, we
+ * start a handshake process against this IP/PORT pairs.
+ *
+ * Note that we require that the sender of this gossip message
+ * is a well known node in our cluster, otherwise we risk
+ * joining another cluster. */
+ if (sender &&
+ !(flags & CLUSTER_NODE_NOADDR) &&
+ !clusterBlacklistExists(g->nodename))
+ {
+ clusterStartHandshake(g->ip, ntohs(g->port), ntohs(g->cport));
+ }
+ }
+
+ /* Next node */
+ g++;
+ }
+}
+
+/* IP -> string conversion. 'buf' is supposed to at least be 46 bytes.
+ * If 'announced_ip' length is non-zero, it is used instead of extracting
+ * the IP from the socket peer address. */
+void nodeIp2String(char *buf, clusterLink *link, char *announced_ip) {
+ if (announced_ip[0] != '\0') {
+ memcpy(buf, announced_ip, NET_IP_STR_LEN);
+ buf[NET_IP_STR_LEN - 1] = '\0'; /* We are not sure the input is sane. */
+ }
+ else {
+ anetPeerToString(link->fd, buf, NET_IP_STR_LEN, NULL);
+ }
+}
+
+/* Update the node address to the IP address that can be extracted
+ * from link->fd, or if hdr->myip is non empty, to the address the node
+ * is announcing us. The port is taken from the packet header as well.
+ *
+ * If the address or port changed, disconnect the node link so that we'll
+ * connect again to the new address.
+ *
+ * If the ip/port pair are already correct no operation is performed at
+ * all.
+ *
+ * The function returns 0 if the node address is still the same,
+ * otherwise 1 is returned. */
+int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
+ clusterMsg *hdr)
+{
+ char ip[NET_IP_STR_LEN] = { 0 };
+ int port = ntohs(hdr->port);
+ int cport = ntohs(hdr->cport);
+
+ /* We don't proceed if the link is the same as the sender link, as this
+ * function is designed to see if the node link is consistent with the
+ * symmetric link that is used to receive PINGs from the node.
+ *
+ * As a side effect this function never frees the passed 'link', so
+ * it is safe to call during packet processing. */
+ if (link == node->link) return 0;
+
+ nodeIp2String(ip, link, hdr->myip);
+ if (node->port == port && node->cport == cport &&
+ strcmp(ip, node->ip) == 0) return 0;
+
+ /* IP / port is different, update it. */
+ memcpy(node->ip, ip, sizeof(ip));
+ node->port = port;
+ node->cport = cport;
+ if (node->link) freeClusterLink(node->link);
+ node->flags &= ~CLUSTER_NODE_NOADDR;
+ serverLog(LL_WARNING, "Address updated for node %.40s, now %s:%d",
+ node->name, node->ip, node->port);
+
+ /* Check if this is our master and we have to change the
+ * replication target as well. */
+ if (nodeIsSlave(myself) && myself->slaveof == node)
+ replicationSetMaster(node->ip, node->port);
+ return 1;
+}
+
+/* Reconfigure the specified node 'n' as a master. This function is called when
+ * a node that we believed to be a slave is now acting as master in order to
+ * update the state of the node. */
+void clusterSetNodeAsMaster(clusterNode *n) {
+ if (nodeIsMaster(n)) return;
+
+ if (n->slaveof) {
+ clusterNodeRemoveSlave(n->slaveof, n);
+ if (n != myself) n->flags |= CLUSTER_NODE_MIGRATE_TO;
+ }
+ n->flags &= ~CLUSTER_NODE_SLAVE;
+ n->flags |= CLUSTER_NODE_MASTER;
+ n->slaveof = NULL;
+
+ /* Update config and state. */
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_UPDATE_STATE);
+}
+
+/* This function is called when we receive a master configuration via a
+ * PING, PONG or UPDATE packet. What we receive is a node, a configEpoch of the
+ * node, and the set of slots claimed under this configEpoch.
+ *
+ * What we do is to rebind the slots with newer configuration compared to our
+ * local configuration, and if needed, we turn ourself into a replica of the
+ * node (see the function comments for more info).
+ *
+ * The 'sender' is the node for which we received a configuration update.
+ * Sometimes it is not actually the "Sender" of the information, like in the
+ * case we receive the info via an UPDATE packet. */
+void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoch, unsigned char *slots) {
+ int j;
+ clusterNode *curmaster, *newmaster = NULL;
+ /* The dirty slots list is a list of slots for which we lose the ownership
+ * while having still keys inside. This usually happens after a failover
+ * or after a manual cluster reconfiguration operated by the admin.
+ *
+ * If the update message is not able to demote a master to slave (in this
+ * case we'll resync with the master updating the whole key space), we
+ * need to delete all the keys in the slots we lost ownership. */
+ uint16_t dirty_slots[CLUSTER_SLOTS];
+ int dirty_slots_count = 0;
+
+ /* Here we set curmaster to this node or the node this node
+ * replicates to if it's a slave. In the for loop we are
+ * interested to check if slots are taken away from curmaster. */
+ curmaster = nodeIsMaster(myself) ? myself : myself->slaveof;
+
+ if (sender == myself) {
+ serverLog(LL_WARNING, "Discarding UPDATE message about myself.");
+ return;
+ }
+
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ if (bitmapTestBit(slots, j)) {
+ /* The slot is already bound to the sender of this message. */
+ if (server.cluster->slots[j] == sender) continue;
+
+ /* The slot is in importing state, it should be modified only
+ * manually via redis-trib (example: a resharding is in progress
+ * and the migrating side slot was already closed and is advertising
+ * a new config. We still want the slot to be closed manually). */
+ if (server.cluster->importing_slots_from[j]) continue;
+
+ /* We rebind the slot to the new node claiming it if:
+ * 1) The slot was unassigned or the new node claims it with a
+ * greater configEpoch.
+ * 2) We are not currently importing the slot. */
+ if (server.cluster->slots[j] == NULL ||
+ server.cluster->slots[j]->configEpoch < senderConfigEpoch)
+ {
+ /* Was this slot mine, and still contains keys? Mark it as
+ * a dirty slot. */
+ if (server.cluster->slots[j] == myself &&
+ countKeysInSlot(j) &&
+ sender != myself)
+ {
+ dirty_slots[dirty_slots_count] = j;
+ dirty_slots_count++;
+ }
+
+ if (server.cluster->slots[j] == curmaster)
+ newmaster = sender;
+ clusterDelSlot(j);
+ clusterAddSlot(sender, j);
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_UPDATE_STATE |
+ CLUSTER_TODO_FSYNC_CONFIG);
+ }
+ }
+ }
+
+ /* If at least one slot was reassigned from a node to another node
+ * with a greater configEpoch, it is possible that:
+ * 1) We are a master left without slots. This means that we were
+ * failed over and we should turn into a replica of the new
+ * master.
+ * 2) We are a slave and our master is left without slots. We need
+ * to replicate to the new slots owner. */
+ if (newmaster && curmaster->numslots == 0) {
+ serverLog(LL_WARNING,
+ "Configuration change detected. Reconfiguring myself "
+ "as a replica of %.40s", sender->name);
+ clusterSetMaster(sender);
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_UPDATE_STATE |
+ CLUSTER_TODO_FSYNC_CONFIG);
+ }
+ else if (dirty_slots_count) {
+ /* If we are here, we received an update message which removed
+ * ownership for certain slots we still have keys about, but still
+ * we are serving some slots, so this master node was not demoted to
+ * a slave.
+ *
+ * In order to maintain a consistent state between keys and slots
+ * we need to remove all the keys from the slots we lost. */
+ for (j = 0; j < dirty_slots_count; j++)
+ delKeysInSlot(dirty_slots[j]);
+ }
+}
+
+/* When this function is called, there is a packet to process starting
+ * at node->rcvbuf. Releasing the buffer is up to the caller, so this
+ * function should just handle the higher level stuff of processing the
+ * packet, modifying the cluster state if needed.
+ *
+ * The function returns 1 if the link is still valid after the packet
+ * was processed, otherwise 0 if the link was freed since the packet
+ * processing lead to some inconsistency error (for instance a PONG
+ * received from the wrong sender ID). */
+int clusterProcessPacket(clusterLink *link) {
+ clusterMsg *hdr = (clusterMsg*) link->rcvbuf;
+ uint32_t totlen = ntohl(hdr->totlen);
+ uint16_t type = ntohs(hdr->type);
+
+ if (type < CLUSTERMSG_TYPE_COUNT)
+ server.cluster->stats_bus_messages_received[type]++;
+ serverLog(LL_DEBUG, "--- Processing packet of type %d, %Iu bytes", WIN_PORT_FIX /* %lu -> %Iu */
+ type, (PORT_ULONG) totlen);
+
+ /* Perform sanity checks */
+ if (totlen < 16) return 1; /* At least signature, version, totlen, count. */
+ if (totlen > sdslen(link->rcvbuf)) return 1;
+
+ if (ntohs(hdr->ver) != CLUSTER_PROTO_VER) {
+ /* Can't handle messages of different versions. */
+ return 1;
+ }
+
+ uint16_t flags = ntohs(hdr->flags);
+ uint64_t senderCurrentEpoch = 0, senderConfigEpoch = 0;
+ clusterNode *sender;
+
+ if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG ||
+ type == CLUSTERMSG_TYPE_MEET)
+ {
+ uint16_t count = ntohs(hdr->count);
+ uint32_t explen; /* expected length of this packet */
+
+ explen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+ explen += (sizeof(clusterMsgDataGossip)*count);
+ if (totlen != explen) return 1;
+ }
+ else if (type == CLUSTERMSG_TYPE_FAIL) {
+ uint32_t explen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+
+ explen += sizeof(clusterMsgDataFail);
+ if (totlen != explen) return 1;
+ }
+ else if (type == CLUSTERMSG_TYPE_PUBLISH) {
+ uint32_t explen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+
+ explen += sizeof(clusterMsgDataPublish) -
+ 8 +
+ ntohl(hdr->data.publish.msg.channel_len) +
+ ntohl(hdr->data.publish.msg.message_len);
+ if (totlen != explen) return 1;
+ }
+ else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST ||
+ type == CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK ||
+ type == CLUSTERMSG_TYPE_MFSTART)
+ {
+ uint32_t explen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+
+ if (totlen != explen) return 1;
+ }
+ else if (type == CLUSTERMSG_TYPE_UPDATE) {
+ uint32_t explen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+
+ explen += sizeof(clusterMsgDataUpdate);
+ if (totlen != explen) return 1;
+ }
+
+ /* Check if the sender is a known node. */
+ sender = clusterLookupNode(hdr->sender);
+ if (sender && !nodeInHandshake(sender)) {
+ /* Update our curretEpoch if we see a newer epoch in the cluster. */
+ senderCurrentEpoch = ntohu64(hdr->currentEpoch);
+ senderConfigEpoch = ntohu64(hdr->configEpoch);
+ if (senderCurrentEpoch > server.cluster->currentEpoch)
+ server.cluster->currentEpoch = senderCurrentEpoch;
+ /* Update the sender configEpoch if it is publishing a newer one. */
+ if (senderConfigEpoch > sender->configEpoch) {
+ sender->configEpoch = senderConfigEpoch;
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_FSYNC_CONFIG);
+ }
+ /* Update the replication offset info for this node. */
+ sender->repl_offset = ntohu64(hdr->offset);
+ sender->repl_offset_time = mstime();
+ /* If we are a slave performing a manual failover and our master
+ * sent its offset while already paused, populate the MF state. */
+ if (server.cluster->mf_end &&
+ nodeIsSlave(myself) &&
+ myself->slaveof == sender &&
+ hdr->mflags[0] & CLUSTERMSG_FLAG0_PAUSED &&
+ server.cluster->mf_master_offset == 0)
+ {
+ server.cluster->mf_master_offset = sender->repl_offset;
+ serverLog(LL_WARNING,
+ "Received replication offset for paused "
+ "master manual failover: %lld",
+ server.cluster->mf_master_offset);
+ }
+ }
+
+ /* Initial processing of PING and MEET requests replying with a PONG. */
+ if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_MEET) {
+ serverLog(LL_DEBUG, "Ping packet received: %p", (void*) link->node);
+
+ /* We use incoming MEET messages in order to set the address
+ * for 'myself', since only other cluster nodes will send us
+ * MEET messages on handshakes, when the cluster joins, or
+ * later if we changed address, and those nodes will use our
+ * official address to connect to us. So by obtaining this address
+ * from the socket is a simple way to discover / update our own
+ * address in the cluster without it being hardcoded in the config.
+ *
+ * However if we don't have an address at all, we update the address
+ * even with a normal PING packet. If it's wrong it will be fixed
+ * by MEET later. */
+ if ((type == CLUSTERMSG_TYPE_MEET || myself->ip[0] == '\0') &&
+ server.cluster_announce_ip == NULL)
+ {
+ char ip[NET_IP_STR_LEN];
+
+ if (anetSockName(link->fd, ip, sizeof(ip), NULL) != -1 &&
+ strcmp(ip, myself->ip))
+ {
+ memcpy(myself->ip, ip, NET_IP_STR_LEN);
+ serverLog(LL_WARNING, "IP address for this node updated to %s",
+ myself->ip);
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
+ }
+ }
+
+ /* Add this node if it is new for us and the msg type is MEET.
+ * In this stage we don't try to add the node with the right
+ * flags, slaveof pointer, and so forth, as this details will be
+ * resolved when we'll receive PONGs from the node. */
+ if (!sender && type == CLUSTERMSG_TYPE_MEET) {
+ clusterNode *node;
+
+ node = createClusterNode(NULL, CLUSTER_NODE_HANDSHAKE);
+ nodeIp2String(node->ip, link, hdr->myip);
+ node->port = ntohs(hdr->port);
+ node->cport = ntohs(hdr->cport);
+ clusterAddNode(node);
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
+ }
+
+ /* If this is a MEET packet from an unknown node, we still process
+ * the gossip section here since we have to trust the sender because
+ * of the message type. */
+ if (!sender && type == CLUSTERMSG_TYPE_MEET)
+ clusterProcessGossipSection(hdr, link);
+
+ /* Anyway reply with a PONG */
+ clusterSendPing(link, CLUSTERMSG_TYPE_PONG);
+ }
+
+ /* PING, PONG, MEET: process config information. */
+ if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG ||
+ type == CLUSTERMSG_TYPE_MEET)
+ {
+ serverLog(LL_DEBUG, "%s packet received: %p",
+ type == CLUSTERMSG_TYPE_PING ? "ping" : "pong",
+ (void*) link->node);
+ if (link->node) {
+ if (nodeInHandshake(link->node)) {
+ /* If we already have this node, try to change the
+ * IP/port of the node with the new one. */
+ if (sender) {
+ serverLog(LL_VERBOSE,
+ "Handshake: we already know node %.40s, "
+ "updating the address if needed.", sender->name);
+ if (nodeUpdateAddressIfNeeded(sender, link, hdr))
+ {
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_UPDATE_STATE);
+ }
+ /* Free this node as we already have it. This will
+ * cause the link to be freed as well. */
+ clusterDelNode(link->node);
+ return 0;
+ }
+
+ /* First thing to do is replacing the random name with the
+ * right node name if this was a handshake stage. */
+ clusterRenameNode(link->node, hdr->sender);
+ serverLog(LL_DEBUG, "Handshake with node %.40s completed.",
+ link->node->name);
+ link->node->flags &= ~CLUSTER_NODE_HANDSHAKE;
+ link->node->flags |= flags & (CLUSTER_NODE_MASTER | CLUSTER_NODE_SLAVE);
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
+ }
+ else if (memcmp(link->node->name, hdr->sender,
+ CLUSTER_NAMELEN) != 0)
+ {
+ /* If the reply has a non matching node ID we
+ * disconnect this node and set it as not having an associated
+ * address. */
+ serverLog(LL_DEBUG, "PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d",
+ link->node->name,
+ (int) (mstime() - (link->node->ctime)),
+ link->node->flags);
+ link->node->flags |= CLUSTER_NODE_NOADDR;
+ link->node->ip[0] = '\0';
+ link->node->port = 0;
+ link->node->cport = 0;
+ freeClusterLink(link);
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
+ return 0;
+ }
+ }
+
+ /* Update the node address if it changed. */
+ if (sender && type == CLUSTERMSG_TYPE_PING &&
+ !nodeInHandshake(sender) &&
+ nodeUpdateAddressIfNeeded(sender, link, hdr))
+ {
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_UPDATE_STATE);
+ }
+
+ /* Update our info about the node */
+ if (link->node && type == CLUSTERMSG_TYPE_PONG) {
+ link->node->pong_received = mstime();
+ link->node->ping_sent = 0;
+
+ /* The PFAIL condition can be reversed without external
+ * help if it is momentary (that is, if it does not
+ * turn into a FAIL state).
+ *
+ * The FAIL condition is also reversible under specific
+ * conditions detected by clearNodeFailureIfNeeded(). */
+ if (nodeTimedOut(link->node)) {
+ link->node->flags &= ~CLUSTER_NODE_PFAIL;
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_UPDATE_STATE);
+ }
+ else if (nodeFailed(link->node)) {
+ clearNodeFailureIfNeeded(link->node);
+ }
+ }
+
+ /* Check for role switch: slave -> master or master -> slave. */
+ if (sender) {
+ if (!memcmp(hdr->slaveof, CLUSTER_NODE_NULL_NAME,
+ sizeof(hdr->slaveof)))
+ {
+ /* Node is a master. */
+ clusterSetNodeAsMaster(sender);
+ }
+ else {
+ /* Node is a slave. */
+ clusterNode *master = clusterLookupNode(hdr->slaveof);
+
+ if (nodeIsMaster(sender)) {
+ /* Master turned into a slave! Reconfigure the node. */
+ clusterDelNodeSlots(sender);
+ sender->flags &= ~(CLUSTER_NODE_MASTER |
+ CLUSTER_NODE_MIGRATE_TO);
+ sender->flags |= CLUSTER_NODE_SLAVE;
+
+ /* Update config and state. */
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_UPDATE_STATE);
+ }
+
+ /* Master node changed for this slave? */
+ if (master && sender->slaveof != master) {
+ if (sender->slaveof)
+ clusterNodeRemoveSlave(sender->slaveof, sender);
+ clusterNodeAddSlave(master, sender);
+ sender->slaveof = master;
+
+ /* Update config. */
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
+ }
+ }
+ }
+
+ /* Update our info about served slots.
+ *
+ * Note: this MUST happen after we update the master/slave state
+ * so that CLUSTER_NODE_MASTER flag will be set. */
+
+ /* Many checks are only needed if the set of served slots this
+ * instance claims is different compared to the set of slots we have
+ * for it. Check this ASAP to avoid other computational expansive
+ * checks later. */
+ clusterNode *sender_master = NULL; /* Sender or its master if slave. */
+ int dirty_slots = 0; /* Sender claimed slots don't match my view? */
+
+ if (sender) {
+ sender_master = nodeIsMaster(sender) ? sender : sender->slaveof;
+ if (sender_master) {
+ dirty_slots = memcmp(sender_master->slots,
+ hdr->myslots, sizeof(hdr->myslots)) != 0;
+ }
+ }
+
+ /* 1) If the sender of the message is a master, and we detected that
+ * the set of slots it claims changed, scan the slots to see if we
+ * need to update our configuration. */
+ if (sender && nodeIsMaster(sender) && dirty_slots)
+ clusterUpdateSlotsConfigWith(sender, senderConfigEpoch, hdr->myslots);
+
+ /* 2) We also check for the reverse condition, that is, the sender
+ * claims to serve slots we know are served by a master with a
+ * greater configEpoch. If this happens we inform the sender.
+ *
+ * This is useful because sometimes after a partition heals, a
+ * reappearing master may be the last one to claim a given set of
+ * hash slots, but with a configuration that other instances know to
+ * be deprecated. Example:
+ *
+ * A and B are master and slave for slots 1,2,3.
+ * A is partitioned away, B gets promoted.
+ * B is partitioned away, and A returns available.
+ *
+ * Usually B would PING A publishing its set of served slots and its
+ * configEpoch, but because of the partition B can't inform A of the
+ * new configuration, so other nodes that have an updated table must
+ * do it. In this way A will stop to act as a master (or can try to
+ * failover if there are the conditions to win the election). */
+ if (sender && dirty_slots) {
+ int j;
+
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ if (bitmapTestBit(hdr->myslots, j)) {
+ if (server.cluster->slots[j] == sender ||
+ server.cluster->slots[j] == NULL) continue;
+ if (server.cluster->slots[j]->configEpoch >
+ senderConfigEpoch)
+ {
+ serverLog(LL_VERBOSE,
+ "Node %.40s has old slots configuration, sending "
+ "an UPDATE message about %.40s",
+ sender->name, server.cluster->slots[j]->name);
+ clusterSendUpdate(sender->link,
+ server.cluster->slots[j]);
+
+ /* TODO: instead of exiting the loop send every other
+ * UPDATE packet for other nodes that are the new owner
+ * of sender's slots. */
+ break;
+ }
+ }
+ }
+ }
+
+ /* If our config epoch collides with the sender's try to fix
+ * the problem. */
+ if (sender &&
+ nodeIsMaster(myself) && nodeIsMaster(sender) &&
+ senderConfigEpoch == myself->configEpoch)
+ {
+ clusterHandleConfigEpochCollision(sender);
+ }
+
+ /* Get info from the gossip section */
+ if (sender) clusterProcessGossipSection(hdr, link);
+ }
+ else if (type == CLUSTERMSG_TYPE_FAIL) {
+ clusterNode *failing;
+
+ if (sender) {
+ failing = clusterLookupNode(hdr->data.fail.about.nodename);
+ if (failing &&
+ !(failing->flags & (CLUSTER_NODE_FAIL | CLUSTER_NODE_MYSELF)))
+ {
+ serverLog(LL_NOTICE,
+ "FAIL message received from %.40s about %.40s",
+ hdr->sender, hdr->data.fail.about.nodename);
+ failing->flags |= CLUSTER_NODE_FAIL;
+ failing->fail_time = mstime();
+ failing->flags &= ~CLUSTER_NODE_PFAIL;
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_UPDATE_STATE);
+ }
+ }
+ else {
+ serverLog(LL_NOTICE,
+ "Ignoring FAIL message from unknown node %.40s about %.40s",
+ hdr->sender, hdr->data.fail.about.nodename);
+ }
+ }
+ else if (type == CLUSTERMSG_TYPE_PUBLISH) {
+ robj *channel, *message;
+ uint32_t channel_len, message_len;
+
+ /* Don't bother creating useless objects if there are no
+ * Pub/Sub subscribers. */
+ if (dictSize(server.pubsub_channels) ||
+ listLength(server.pubsub_patterns))
+ {
+ channel_len = ntohl(hdr->data.publish.msg.channel_len);
+ message_len = ntohl(hdr->data.publish.msg.message_len);
+ channel = createStringObject(
+ (char*) hdr->data.publish.msg.bulk_data, channel_len);
+ message = createStringObject(
+ (char*) hdr->data.publish.msg.bulk_data + channel_len,
+ message_len);
+ pubsubPublishMessage(channel, message);
+ decrRefCount(channel);
+ decrRefCount(message);
+ }
+ }
+ else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST) {
+ if (!sender) return 1; /* We don't know that node. */
+ clusterSendFailoverAuthIfNeeded(sender, hdr);
+ }
+ else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK) {
+ if (!sender) return 1; /* We don't know that node. */
+ /* We consider this vote only if the sender is a master serving
+ * a non zero number of slots, and its currentEpoch is greater or
+ * equal to epoch where this node started the election. */
+ if (nodeIsMaster(sender) && sender->numslots > 0 &&
+ senderCurrentEpoch >= server.cluster->failover_auth_epoch)
+ {
+ server.cluster->failover_auth_count++;
+ /* Maybe we reached a quorum here, set a flag to make sure
+ * we check ASAP. */
+ clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER);
+ }
+ }
+ else if (type == CLUSTERMSG_TYPE_MFSTART) {
+ /* This message is acceptable only if I'm a master and the sender
+ * is one of my slaves. */
+ if (!sender || sender->slaveof != myself) return 1;
+ /* Manual failover requested from slaves. Initialize the state
+ * accordingly. */
+ resetManualFailover();
+ server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
+ server.cluster->mf_slave = sender;
+ pauseClients(mstime() + (CLUSTER_MF_TIMEOUT * 2));
+ serverLog(LL_WARNING, "Manual failover requested by slave %.40s.",
+ sender->name);
+ }
+ else if (type == CLUSTERMSG_TYPE_UPDATE) {
+ clusterNode *n; /* The node the update is about. */
+ uint64_t reportedConfigEpoch =
+ ntohu64(hdr->data.update.nodecfg.configEpoch);
+
+ if (!sender) return 1; /* We don't know the sender. */
+ n = clusterLookupNode(hdr->data.update.nodecfg.nodename);
+ if (!n) return 1; /* We don't know the reported node. */
+ if (n->configEpoch >= reportedConfigEpoch) return 1; /* Nothing new. */
+
+ /* If in our current config the node is a slave, set it as a master. */
+ if (nodeIsSlave(n)) clusterSetNodeAsMaster(n);
+
+ /* Update the node's configEpoch. */
+ n->configEpoch = reportedConfigEpoch;
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_FSYNC_CONFIG);
+
+ /* Check the bitmap of served slots and update our
+ * config accordingly. */
+ clusterUpdateSlotsConfigWith(n, reportedConfigEpoch,
+ hdr->data.update.nodecfg.slots);
+ }
+ else {
+ serverLog(LL_WARNING, "Received unknown packet type: %d", type);
+ }
+ return 1;
+}
+
+/* This function is called when we detect the link with this node is lost.
+ We set the node as no longer connected. The Cluster Cron will detect
+ this connection and will try to get it connected again.
+
+ Instead if the node is a temporary node used to accept a query, we
+ completely free the node on error. */
+void handleLinkIOError(clusterLink *link) {
+ freeClusterLink(link);
+}
+
+#ifdef _WIN32
+void clusterWriteDone(aeEventLoop *el, int fd, void *privdata, int written) {
+ WSIOCP_Request *req = (WSIOCP_Request *) privdata;
+ clusterLink *link = (clusterLink *) req->client;
+ UNUSED(el);
+ UNUSED(fd);
+
+ if (sdslen(link->sndbuf) == written) {
+ sdsrange(link->sndbuf, written, -1);
+ aeDeleteFileEvent(server.el, link->fd, AE_WRITABLE);
+ serverLog(LL_DEBUG, "clusterWriteDone written %d fd %d", written, link->fd);
+ }
+}
+
+void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
+ clusterLink *link = (clusterLink*) privdata;
+ UNUSED(el);
+ UNUSED(mask);
+
+ int result = WSIOCP_SocketSend(fd,
+ (char*) link->sndbuf,
+ (int) (sdslen(link->sndbuf)),
+ el,
+ link,
+ NULL,
+ clusterWriteDone);
+ if (errno == WSA_IO_PENDING)
+ serverLog(LL_DEBUG, "WSA_IO_PENDING writing to socket fd %d", link->fd);
+
+ if (result == SOCKET_ERROR && errno != WSA_IO_PENDING) {
+ serverLog(LL_WARNING, "Error writing to socket fd %d", link->fd);
+ handleLinkIOError(link);
+ return;
+ }
+}
+#else
+/* Send data. This is handled using a trivial send buffer that gets
+ * consumed by write(). We don't try to optimize this for speed too much
+ * as this is a very low traffic channel. */
+void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
+ clusterLink *link = (clusterLink*) privdata;
+ ssize_t nwritten;
+ UNUSED(el);
+ UNUSED(mask);
+
+ nwritten = write(fd, link->sndbuf, sdslen(link->sndbuf));
+ if (nwritten <= 0) {
+ serverLog(LL_DEBUG, "I/O error writing to node link: %s",
+ strerror(errno));
+ handleLinkIOError(link);
+ return;
+ }
+ sdsrange(link->sndbuf, nwritten, -1);
+ if (sdslen(link->sndbuf) == 0)
+ aeDeleteFileEvent(server.el, link->fd, AE_WRITABLE);
+}
+#endif
+
+/* Read data. Try to read the first field of the header first to check the
+ * full length of the packet. When a whole packet is in memory this function
+ * will call the function to process the packet. And so forth. */
+void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
+ char buf[sizeof(clusterMsg)];
+ ssize_t nread;
+ clusterMsg *hdr;
+ clusterLink *link = (clusterLink*) privdata;
+ unsigned int readlen, rcvbuflen;
+ UNUSED(el);
+ UNUSED(mask);
+
+ while (1) { /* Read as long as there is data to read. */
+ rcvbuflen = (unsigned int) sdslen(link->rcvbuf); WIN_PORT_FIX /* cast (unsigned int) */
+ if (rcvbuflen < 8) {
+ /* First, obtain the first 8 bytes to get the full message
+ * length. */
+ readlen = 8 - rcvbuflen;
+ }
+ else {
+ /* Finally read the full message. */
+ hdr = (clusterMsg*) link->rcvbuf;
+ if (rcvbuflen == 8) {
+ /* Perform some sanity check on the message signature
+ * and length. */
+ if (memcmp(hdr->sig, "RCmb", 4) != 0 ||
+ ntohl(hdr->totlen) < CLUSTERMSG_MIN_LEN)
+ {
+ serverLog(LL_WARNING,
+ "Bad message length or signature received "
+ "from Cluster bus.");
+ handleLinkIOError(link);
+ return;
+ }
+ }
+ readlen = ntohl(hdr->totlen) - rcvbuflen;
+ if (readlen > sizeof(buf)) readlen = sizeof(buf);
+ }
+
+ nread = read(fd, buf, readlen);
+ if (nread == -1 && errno == EAGAIN) { WIN32_ONLY(WSIOCP_QueueNextRead(fd);) return; } /* No more data ready. */
+
+ if (nread <= 0) {
+ /* I/O error... */
+ serverLog(LL_DEBUG, "I/O error reading from node link: %s",
+ (nread == 0) ? "connection closed" : IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ handleLinkIOError(link);
+ return;
+ }
+ else {
+ /* Read data and recast the pointer to the new buffer. */
+ link->rcvbuf = sdscatlen(link->rcvbuf, buf, nread);
+ hdr = (clusterMsg*) link->rcvbuf;
+ rcvbuflen += (unsigned int) nread; WIN_PORT_FIX /* cast (unsigned int) */
+ }
+
+ /* Total length obtained? Process this packet. */
+ if (rcvbuflen >= 8 && rcvbuflen == ntohl(hdr->totlen)) {
+ if (clusterProcessPacket(link)) {
+ sdsfree(link->rcvbuf);
+ link->rcvbuf = sdsempty();
+ }
+ else {
+ return; /* Link no longer valid. */
+ }
+ }
+ }
+ WIN32_ONLY(WSIOCP_QueueNextRead(fd);)
+}
+
+/* Put stuff into the send buffer.
+ *
+ * It is guaranteed that this function will never have as a side effect
+ * the link to be invalidated, so it is safe to call this function
+ * from event handlers that will do stuff with the same link later. */
+void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
+ if (sdslen(link->sndbuf) == 0 && msglen != 0)
+ aeCreateFileEvent(server.el, link->fd, AE_WRITABLE,
+ clusterWriteHandler, link);
+
+ link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
+
+ /* Populate sent messages stats. */
+ clusterMsg *hdr = (clusterMsg*) msg;
+ uint16_t type = ntohs(hdr->type);
+ if (type < CLUSTERMSG_TYPE_COUNT)
+ server.cluster->stats_bus_messages_sent[type]++;
+}
+
+/* Send a message to all the nodes that are part of the cluster having
+ * a connected link.
+ *
+ * It is guaranteed that this function will never have as a side effect
+ * some node->link to be invalidated, so it is safe to call this function
+ * from event handlers that will do stuff with node links later. */
+void clusterBroadcastMessage(void *buf, size_t len) {
+ dictIterator *di;
+ dictEntry *de;
+
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+
+ if (!node->link) continue;
+ if (node->flags & (CLUSTER_NODE_MYSELF | CLUSTER_NODE_HANDSHAKE))
+ continue;
+ clusterSendMessage(node->link, buf, len);
+ }
+ dictReleaseIterator(di);
+}
+
+/* Build the message header. hdr must point to a buffer at least
+ * sizeof(clusterMsg) in bytes. */
+void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
+ int totlen = 0;
+ uint64_t offset;
+ clusterNode *master;
+
+ /* If this node is a master, we send its slots bitmap and configEpoch.
+ * If this node is a slave we send the master's information instead (the
+ * node is flagged as slave so the receiver knows that it is NOT really
+ * in charge for this slots. */
+ master = (nodeIsSlave(myself) && myself->slaveof) ?
+ myself->slaveof : myself;
+
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->ver = htons(CLUSTER_PROTO_VER);
+ hdr->sig[0] = 'R';
+ hdr->sig[1] = 'C';
+ hdr->sig[2] = 'm';
+ hdr->sig[3] = 'b';
+ hdr->type = htons(type);
+ memcpy(hdr->sender, myself->name, CLUSTER_NAMELEN);
+
+ /* If cluster-announce-ip option is enabled, force the receivers of our
+ * packets to use the specified address for this node. Otherwise if the
+ * first byte is zero, they'll do auto discovery. */
+ memset(hdr->myip, 0, NET_IP_STR_LEN);
+ if (server.cluster_announce_ip) {
+ strncpy(hdr->myip, server.cluster_announce_ip, NET_IP_STR_LEN);
+ hdr->myip[NET_IP_STR_LEN - 1] = '\0';
+ }
+
+ /* Handle cluster-announce-port as well. */
+ int announced_port = server.cluster_announce_port ?
+ server.cluster_announce_port : server.port;
+ int announced_cport = server.cluster_announce_bus_port ?
+ server.cluster_announce_bus_port :
+ (server.port + CLUSTER_PORT_INCR);
+
+ memcpy(hdr->myslots, master->slots, sizeof(hdr->myslots));
+ memset(hdr->slaveof, 0, CLUSTER_NAMELEN);
+ if (myself->slaveof != NULL)
+ memcpy(hdr->slaveof, myself->slaveof->name, CLUSTER_NAMELEN);
+ hdr->port = htons(announced_port);
+ hdr->cport = htons(announced_cport);
+ hdr->flags = htons(myself->flags);
+ hdr->state = server.cluster->state;
+
+ /* Set the currentEpoch and configEpochs. */
+ hdr->currentEpoch = htonu64(server.cluster->currentEpoch);
+ hdr->configEpoch = htonu64(master->configEpoch);
+
+ /* Set the replication offset. */
+ if (nodeIsSlave(myself))
+ offset = replicationGetSlaveOffset();
+ else
+ offset = server.master_repl_offset;
+ hdr->offset = htonu64(offset);
+
+ /* Set the message flags. */
+ if (nodeIsMaster(myself) && server.cluster->mf_end)
+ hdr->mflags[0] |= CLUSTERMSG_FLAG0_PAUSED;
+
+ /* Compute the message length for certain messages. For other messages
+ * this is up to the caller. */
+ if (type == CLUSTERMSG_TYPE_FAIL) {
+ totlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+ totlen += sizeof(clusterMsgDataFail);
+ }
+ else if (type == CLUSTERMSG_TYPE_UPDATE) {
+ totlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+ totlen += sizeof(clusterMsgDataUpdate);
+ }
+ hdr->totlen = htonl(totlen);
+ /* For PING, PONG, and MEET, fixing the totlen field is up to the caller. */
+}
+
+/* Return non zero if the node is already present in the gossip section of the
+ * message pointed by 'hdr' and having 'count' gossip entries. Otherwise
+ * zero is returned. Helper for clusterSendPing(). */
+int clusterNodeIsInGossipSection(clusterMsg *hdr, int count, clusterNode *n) {
+ int j;
+ for (j = 0; j < count; j++) {
+ if (memcmp(hdr->data.ping.gossip[j].nodename, n->name,
+ CLUSTER_NAMELEN) == 0) break;
+ }
+ return j != count;
+}
+
+/* Set the i-th entry of the gossip section in the message pointed by 'hdr'
+ * to the info of the specified node 'n'. */
+void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) {
+ clusterMsgDataGossip *gossip;
+ gossip = &(hdr->data.ping.gossip[i]);
+ memcpy(gossip->nodename, n->name, CLUSTER_NAMELEN);
+ gossip->ping_sent = htonl(n->ping_sent / 1000);
+ gossip->pong_received = htonl(n->pong_received / 1000);
+ memcpy(gossip->ip, n->ip, sizeof(n->ip));
+ gossip->port = htons(n->port);
+ gossip->cport = htons(n->cport);
+ gossip->flags = htons(n->flags);
+ gossip->notused1 = 0;
+}
+
+/* Send a PING or PONG packet to the specified node, making sure to add enough
+ * gossip informations. */
+void clusterSendPing(clusterLink *link, int type) {
+ unsigned char *buf;
+ clusterMsg *hdr;
+ int gossipcount = 0; /* Number of gossip sections added so far. */
+ int wanted; /* Number of gossip sections we want to append if possible. */
+ int totlen; /* Total packet length. */
+ /* freshnodes is the max number of nodes we can hope to append at all:
+ * nodes available minus two (ourself and the node we are sending the
+ * message to). However practically there may be less valid nodes since
+ * nodes in handshake state, disconnected, are not considered. */
+ int freshnodes = (int) dictSize(server.cluster->nodes) - 2; WIN_PORT_FIX /* cast (int) */
+
+ /* How many gossip sections we want to add? 1/10 of the number of nodes
+ * and anyway at least 3. Why 1/10?
+ *
+ * If we have N masters, with N/10 entries, and we consider that in
+ * node_timeout we exchange with each other node at least 4 packets
+ * (we ping in the worst case in node_timeout/2 time, and we also
+ * receive two pings from the host), we have a total of 8 packets
+ * in the node_timeout*2 falure reports validity time. So we have
+ * that, for a single PFAIL node, we can expect to receive the following
+ * number of failure reports (in the specified window of time):
+ *
+ * PROB * GOSSIP_ENTRIES_PER_PACKET * TOTAL_PACKETS:
+ *
+ * PROB = probability of being featured in a single gossip entry,
+ * which is 1 / NUM_OF_NODES.
+ * ENTRIES = 10.
+ * TOTAL_PACKETS = 2 * 4 * NUM_OF_MASTERS.
+ *
+ * If we assume we have just masters (so num of nodes and num of masters
+ * is the same), with 1/10 we always get over the majority, and specifically
+ * 80% of the number of nodes, to account for many masters failing at the
+ * same time.
+ *
+ * Since we have non-voting slaves that lower the probability of an entry
+ * to feature our node, we set the number of entires per packet as
+ * 10% of the total nodes we have. */
+ wanted = floor(dictSize(server.cluster->nodes) / 10);
+ if (wanted < 3) wanted = 3;
+ if (wanted > freshnodes) wanted = freshnodes;
+
+ /* Include all the nodes in PFAIL state, so that failure reports are
+ * faster to propagate to go from PFAIL to FAIL state. */
+ int pfail_wanted = server.cluster->stats_pfail_nodes;
+
+ /* Compute the maxium totlen to allocate our buffer. We'll fix the totlen
+ * later according to the number of gossip sections we really were able
+ * to put inside the packet. */
+ totlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+ totlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted));
+ /* Note: clusterBuildMessageHdr() expects the buffer to be always at least
+ * sizeof(clusterMsg) or more. */
+ if (totlen < (int)sizeof(clusterMsg)) totlen = sizeof(clusterMsg);
+ buf = zcalloc(totlen);
+ hdr = (clusterMsg*) buf;
+
+ /* Populate the header. */
+ if (link->node && type == CLUSTERMSG_TYPE_PING)
+ link->node->ping_sent = mstime();
+ clusterBuildMessageHdr(hdr, type);
+
+ /* Populate the gossip fields */
+ int maxiterations = wanted * 3;
+ while (freshnodes > 0 && gossipcount < wanted && maxiterations--) {
+ dictEntry *de = dictGetRandomKey(server.cluster->nodes);
+ clusterNode *this = dictGetVal(de);
+
+ /* Don't include this node: the whole packet header is about us
+ * already, so we just gossip about other nodes. */
+ if (this == myself) continue;
+
+ /* PFAIL nodes will be added later. */
+ if (this->flags & CLUSTER_NODE_PFAIL) continue;
+
+ /* In the gossip section don't include:
+ * 1) Nodes in HANDSHAKE state.
+ * 3) Nodes with the NOADDR flag set.
+ * 4) Disconnected nodes if they don't have configured slots.
+ */
+ if (this->flags & (CLUSTER_NODE_HANDSHAKE | CLUSTER_NODE_NOADDR) ||
+ (this->link == NULL && this->numslots == 0))
+ {
+ freshnodes--; /* Tecnically not correct, but saves CPU. */
+ continue;
+ }
+
+ /* Do not add a node we already have. */
+ if (clusterNodeIsInGossipSection(hdr, gossipcount, this)) continue;
+
+ /* Add it */
+ clusterSetGossipEntry(hdr, gossipcount, this);
+ freshnodes--;
+ gossipcount++;
+ }
+
+ /* If there are PFAIL nodes, add them at the end. */
+ if (pfail_wanted) {
+ dictIterator *di;
+ dictEntry *de;
+
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL && pfail_wanted > 0) {
+ clusterNode *node = dictGetVal(de);
+ if (node->flags & CLUSTER_NODE_HANDSHAKE) continue;
+ if (node->flags & CLUSTER_NODE_NOADDR) continue;
+ if (!(node->flags & CLUSTER_NODE_PFAIL)) continue;
+ clusterSetGossipEntry(hdr, gossipcount, node);
+ freshnodes--;
+ gossipcount++;
+ /* We take the count of the slots we allocated, since the
+ * PFAIL stats may not match perfectly with the current number
+ * of PFAIL nodes. */
+ pfail_wanted--;
+ }
+ dictReleaseIterator(di);
+ }
+
+ /* Ready to send... fix the totlen fiend and queue the message in the
+ * output buffer. */
+ totlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+ totlen += (sizeof(clusterMsgDataGossip)*gossipcount);
+ hdr->count = htons(gossipcount);
+ hdr->totlen = htonl(totlen);
+ clusterSendMessage(link, buf, totlen);
+ zfree(buf);
+}
+
+/* Send a PONG packet to every connected node that's not in handshake state
+ * and for which we have a valid link.
+ *
+ * In Redis Cluster pongs are not used just for failure detection, but also
+ * to carry important configuration information. So broadcasting a pong is
+ * useful when something changes in the configuration and we want to make
+ * the cluster aware ASAP (for instance after a slave promotion).
+ *
+ * The 'target' argument specifies the receiving instances using the
+ * defines below:
+ *
+ * CLUSTER_BROADCAST_ALL -> All known instances.
+ * CLUSTER_BROADCAST_LOCAL_SLAVES -> All slaves in my master-slaves ring.
+ */
+#define CLUSTER_BROADCAST_ALL 0
+#define CLUSTER_BROADCAST_LOCAL_SLAVES 1
+void clusterBroadcastPong(int target) {
+ dictIterator *di;
+ dictEntry *de;
+
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+
+ if (!node->link) continue;
+ if (node == myself || nodeInHandshake(node)) continue;
+ if (target == CLUSTER_BROADCAST_LOCAL_SLAVES) {
+ int local_slave =
+ nodeIsSlave(node) && node->slaveof &&
+ (node->slaveof == myself || node->slaveof == myself->slaveof);
+ if (!local_slave) continue;
+ }
+ clusterSendPing(node->link, CLUSTERMSG_TYPE_PONG);
+ }
+ dictReleaseIterator(di);
+}
+
+/* Send a PUBLISH message.
+ *
+ * If link is NULL, then the message is broadcasted to the whole cluster. */
+void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
+ unsigned char buf[sizeof(clusterMsg)], *payload;
+ clusterMsg *hdr = (clusterMsg*) buf;
+ uint32_t totlen;
+ uint32_t channel_len, message_len;
+
+ channel = getDecodedObject(channel);
+ message = getDecodedObject(message);
+ channel_len = (uint32_t) sdslen(channel->ptr); WIN_PORT_FIX /* cast (uint32_t) */
+ message_len = (uint32_t) sdslen(message->ptr); WIN_PORT_FIX /* cast (uint32_t) */
+
+ clusterBuildMessageHdr(hdr, CLUSTERMSG_TYPE_PUBLISH);
+ totlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+ totlen += sizeof(clusterMsgDataPublish) - 8 + channel_len + message_len;
+
+ hdr->data.publish.msg.channel_len = htonl(channel_len);
+ hdr->data.publish.msg.message_len = htonl(message_len);
+ hdr->totlen = htonl(totlen);
+
+ /* Try to use the local buffer if possible */
+ if (totlen < sizeof(buf)) {
+ payload = buf;
+ }
+ else {
+ payload = zmalloc(totlen);
+ memcpy(payload, hdr, sizeof(*hdr));
+ hdr = (clusterMsg*) payload;
+ }
+ memcpy(hdr->data.publish.msg.bulk_data, channel->ptr, sdslen(channel->ptr));
+ memcpy(hdr->data.publish.msg.bulk_data + sdslen(channel->ptr),
+ message->ptr, sdslen(message->ptr));
+
+ if (link)
+ clusterSendMessage(link, payload, totlen);
+ else
+ clusterBroadcastMessage(payload, totlen);
+
+ decrRefCount(channel);
+ decrRefCount(message);
+ if (payload != buf) zfree(payload);
+}
+
+/* Send a FAIL message to all the nodes we are able to contact.
+ * The FAIL message is sent when we detect that a node is failing
+ * (CLUSTER_NODE_PFAIL) and we also receive a gossip confirmation of this:
+ * we switch the node state to CLUSTER_NODE_FAIL and ask all the other
+ * nodes to do the same ASAP. */
+void clusterSendFail(char *nodename) {
+ unsigned char buf[sizeof(clusterMsg)];
+ clusterMsg *hdr = (clusterMsg*) buf;
+
+ clusterBuildMessageHdr(hdr, CLUSTERMSG_TYPE_FAIL);
+ memcpy(hdr->data.fail.about.nodename, nodename, CLUSTER_NAMELEN);
+ clusterBroadcastMessage(buf, ntohl(hdr->totlen));
+}
+
+/* Send an UPDATE message to the specified link carrying the specified 'node'
+ * slots configuration. The node name, slots bitmap, and configEpoch info
+ * are included. */
+void clusterSendUpdate(clusterLink *link, clusterNode *node) {
+ unsigned char buf[sizeof(clusterMsg)];
+ clusterMsg *hdr = (clusterMsg*) buf;
+
+ if (link == NULL) return;
+ clusterBuildMessageHdr(hdr, CLUSTERMSG_TYPE_UPDATE);
+ memcpy(hdr->data.update.nodecfg.nodename, node->name, CLUSTER_NAMELEN);
+ hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
+ memcpy(hdr->data.update.nodecfg.slots, node->slots, sizeof(node->slots));
+ clusterSendMessage(link, buf, ntohl(hdr->totlen));
+}
+
+/* -----------------------------------------------------------------------------
+ * CLUSTER Pub/Sub support
+ *
+ * For now we do very little, just propagating PUBLISH messages across the whole
+ * cluster. In the future we'll try to get smarter and avoiding propagating those
+ * messages to hosts without receives for a given channel.
+ * -------------------------------------------------------------------------- */
+void clusterPropagatePublish(robj *channel, robj *message) {
+ clusterSendPublish(NULL, channel, message);
+}
+
+/* -----------------------------------------------------------------------------
+ * SLAVE node specific functions
+ * -------------------------------------------------------------------------- */
+
+ /* This function sends a FAILOVE_AUTH_REQUEST message to every node in order to
+ * see if there is the quorum for this slave instance to failover its failing
+ * master.
+ *
+ * Note that we send the failover request to everybody, master and slave nodes,
+ * but only the masters are supposed to reply to our query. */
+void clusterRequestFailoverAuth(void) {
+ unsigned char buf[sizeof(clusterMsg)];
+ clusterMsg *hdr = (clusterMsg*) buf;
+ uint32_t totlen;
+
+ clusterBuildMessageHdr(hdr, CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST);
+ /* If this is a manual failover, set the CLUSTERMSG_FLAG0_FORCEACK bit
+ * in the header to communicate the nodes receiving the message that
+ * they should authorized the failover even if the master is working. */
+ if (server.cluster->mf_end) hdr->mflags[0] |= CLUSTERMSG_FLAG0_FORCEACK;
+ totlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+ hdr->totlen = htonl(totlen);
+ clusterBroadcastMessage(buf, totlen);
+}
+
+/* Send a FAILOVER_AUTH_ACK message to the specified node. */
+void clusterSendFailoverAuth(clusterNode *node) {
+ unsigned char buf[sizeof(clusterMsg)];
+ clusterMsg *hdr = (clusterMsg*) buf;
+ uint32_t totlen;
+
+ if (!node->link) return;
+ clusterBuildMessageHdr(hdr, CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK);
+ totlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+ hdr->totlen = htonl(totlen);
+ clusterSendMessage(node->link, buf, totlen);
+}
+
+/* Send a MFSTART message to the specified node. */
+void clusterSendMFStart(clusterNode *node) {
+ unsigned char buf[sizeof(clusterMsg)];
+ clusterMsg *hdr = (clusterMsg*) buf;
+ uint32_t totlen;
+
+ if (!node->link) return;
+ clusterBuildMessageHdr(hdr, CLUSTERMSG_TYPE_MFSTART);
+ totlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
+ hdr->totlen = htonl(totlen);
+ clusterSendMessage(node->link, buf, totlen);
+}
+
+/* Vote for the node asking for our vote if there are the conditions. */
+void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
+ clusterNode *master = node->slaveof;
+ uint64_t requestCurrentEpoch = ntohu64(request->currentEpoch);
+ uint64_t requestConfigEpoch = ntohu64(request->configEpoch);
+ unsigned char *claimed_slots = request->myslots;
+ int force_ack = request->mflags[0] & CLUSTERMSG_FLAG0_FORCEACK;
+ int j;
+
+ /* IF we are not a master serving at least 1 slot, we don't have the
+ * right to vote, as the cluster size in Redis Cluster is the number
+ * of masters serving at least one slot, and quorum is the cluster
+ * size + 1 */
+ if (nodeIsSlave(myself) || myself->numslots == 0) return;
+
+ /* Request epoch must be >= our currentEpoch.
+ * Note that it is impossible for it to actually be greater since
+ * our currentEpoch was updated as a side effect of receiving this
+ * request, if the request epoch was greater. */
+ if (requestCurrentEpoch < server.cluster->currentEpoch) {
+ serverLog(LL_WARNING,
+ "Failover auth denied to %.40s: reqEpoch (%llu) < curEpoch(%llu)",
+ node->name,
+ (PORT_ULONGLONG) requestCurrentEpoch,
+ (PORT_ULONGLONG) server.cluster->currentEpoch);
+ return;
+ }
+
+ /* I already voted for this epoch? Return ASAP. */
+ if (server.cluster->lastVoteEpoch == server.cluster->currentEpoch) {
+ serverLog(LL_WARNING,
+ "Failover auth denied to %.40s: already voted for epoch %llu",
+ node->name,
+ (PORT_ULONGLONG) server.cluster->currentEpoch);
+ return;
+ }
+
+ /* Node must be a slave and its master down.
+ * The master can be non failing if the request is flagged
+ * with CLUSTERMSG_FLAG0_FORCEACK (manual failover). */
+ if (nodeIsMaster(node) || master == NULL ||
+ (!nodeFailed(master) && !force_ack))
+ {
+ if (nodeIsMaster(node)) {
+ serverLog(LL_WARNING,
+ "Failover auth denied to %.40s: it is a master node",
+ node->name);
+ }
+ else if (master == NULL) {
+ serverLog(LL_WARNING,
+ "Failover auth denied to %.40s: I don't know its master",
+ node->name);
+ }
+ else if (!nodeFailed(master)) {
+ serverLog(LL_WARNING,
+ "Failover auth denied to %.40s: its master is up",
+ node->name);
+ }
+ return;
+ }
+
+ /* We did not voted for a slave about this master for two
+ * times the node timeout. This is not strictly needed for correctness
+ * of the algorithm but makes the base case more linear. */
+ if (mstime() - node->slaveof->voted_time < server.cluster_node_timeout * 2)
+ {
+ serverLog(LL_WARNING,
+ "Failover auth denied to %.40s: "
+ "can't vote about this master before %lld milliseconds",
+ node->name,
+ (PORT_LONGLONG) ((server.cluster_node_timeout * 2) -
+ (mstime() - node->slaveof->voted_time)));
+ return;
+ }
+
+ /* The slave requesting the vote must have a configEpoch for the claimed
+ * slots that is >= the one of the masters currently serving the same
+ * slots in the current configuration. */
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ if (bitmapTestBit(claimed_slots, j) == 0) continue;
+ if (server.cluster->slots[j] == NULL ||
+ server.cluster->slots[j]->configEpoch <= requestConfigEpoch)
+ {
+ continue;
+ }
+ /* If we reached this point we found a slot that in our current slots
+ * is served by a master with a greater configEpoch than the one claimed
+ * by the slave requesting our vote. Refuse to vote for this slave. */
+ serverLog(LL_WARNING,
+ "Failover auth denied to %.40s: "
+ "slot %d epoch (%llu) > reqEpoch (%llu)",
+ node->name, j,
+ (PORT_ULONGLONG) server.cluster->slots[j]->configEpoch,
+ (PORT_ULONGLONG) requestConfigEpoch);
+ return;
+ }
+
+ /* We can vote for this slave. */
+ clusterSendFailoverAuth(node);
+ server.cluster->lastVoteEpoch = server.cluster->currentEpoch;
+ node->slaveof->voted_time = mstime();
+ serverLog(LL_WARNING, "Failover auth granted to %.40s for epoch %llu",
+ node->name, (PORT_ULONGLONG) server.cluster->currentEpoch);
+}
+
+/* This function returns the "rank" of this instance, a slave, in the context
+ * of its master-slaves ring. The rank of the slave is given by the number of
+ * other slaves for the same master that have a better replication offset
+ * compared to the local one (better means, greater, so they claim more data).
+ *
+ * A slave with rank 0 is the one with the greatest (most up to date)
+ * replication offset, and so forth. Note that because how the rank is computed
+ * multiple slaves may have the same rank, in case they have the same offset.
+ *
+ * The slave rank is used to add a delay to start an election in order to
+ * get voted and replace a failing master. Slaves with better replication
+ * offsets are more likely to win. */
+int clusterGetSlaveRank(void) {
+ PORT_LONGLONG myoffset;
+ int j, rank = 0;
+ clusterNode *master;
+
+ serverAssert(nodeIsSlave(myself));
+ master = myself->slaveof;
+ if (master == NULL) return 0; /* Never called by slaves without master. */
+
+ myoffset = replicationGetSlaveOffset();
+ for (j = 0; j < master->numslaves; j++)
+ if (master->slaves[j] != myself &&
+ master->slaves[j]->repl_offset > myoffset) rank++;
+ return rank;
+}
+
+/* This function is called by clusterHandleSlaveFailover() in order to
+ * let the slave log why it is not able to failover. Sometimes there are
+ * not the conditions, but since the failover function is called again and
+ * again, we can't log the same things continuously.
+ *
+ * This function works by logging only if a given set of conditions are
+ * true:
+ *
+ * 1) The reason for which the failover can't be initiated changed.
+ * The reasons also include a NONE reason we reset the state to
+ * when the slave finds that its master is fine (no FAIL flag).
+ * 2) Also, the log is emitted again if the master is still down and
+ * the reason for not failing over is still the same, but more than
+ * CLUSTER_CANT_FAILOVER_RELOG_PERIOD seconds elapsed.
+ * 3) Finally, the function only logs if the slave is down for more than
+ * five seconds + NODE_TIMEOUT. This way nothing is logged when a
+ * failover starts in a reasonable time.
+ *
+ * The function is called with the reason why the slave can't failover
+ * which is one of the integer macros CLUSTER_CANT_FAILOVER_*.
+ *
+ * The function is guaranteed to be called only if 'myself' is a slave. */
+void clusterLogCantFailover(int reason) {
+ char *msg;
+ static time_t lastlog_time = 0;
+ mstime_t nolog_fail_time = server.cluster_node_timeout + 5000;
+
+ /* Don't log if we have the same reason for some time. */
+ if (reason == server.cluster->cant_failover_reason &&
+ time(NULL) - lastlog_time < CLUSTER_CANT_FAILOVER_RELOG_PERIOD)
+ return;
+
+ server.cluster->cant_failover_reason = reason;
+
+ /* We also don't emit any log if the master failed no long ago, the
+ * goal of this function is to log slaves in a stalled condition for
+ * a long time. */
+ if (myself->slaveof &&
+ nodeFailed(myself->slaveof) &&
+ (mstime() - myself->slaveof->fail_time) < nolog_fail_time) return;
+
+ switch (reason) {
+ case CLUSTER_CANT_FAILOVER_DATA_AGE:
+ msg = "Disconnected from master for longer than allowed. "
+ "Please check the 'cluster-slave-validity-factor' configuration "
+ "option.";
+ break;
+ case CLUSTER_CANT_FAILOVER_WAITING_DELAY:
+ msg = "Waiting the delay before I can start a new failover.";
+ break;
+ case CLUSTER_CANT_FAILOVER_EXPIRED:
+ msg = "Failover attempt expired.";
+ break;
+ case CLUSTER_CANT_FAILOVER_WAITING_VOTES:
+ msg = "Waiting for votes, but majority still not reached.";
+ break;
+ default:
+ msg = "Unknown reason code.";
+ break;
+ }
+ lastlog_time = time(NULL);
+ serverLog(LL_WARNING, "Currently unable to failover: %s", msg);
+}
+
+/* This function implements the final part of automatic and manual failovers,
+ * where the slave grabs its master's hash slots, and propagates the new
+ * configuration.
+ *
+ * Note that it's up to the caller to be sure that the node got a new
+ * configuration epoch already. */
+void clusterFailoverReplaceYourMaster(void) {
+ int j;
+ clusterNode *oldmaster = myself->slaveof;
+
+ if (nodeIsMaster(myself) || oldmaster == NULL) return;
+
+ /* 1) Turn this node into a master. */
+ clusterSetNodeAsMaster(myself);
+ replicationUnsetMaster();
+
+ /* 2) Claim all the slots assigned to our master. */
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ if (clusterNodeGetSlotBit(oldmaster, j)) {
+ clusterDelSlot(j);
+ clusterAddSlot(myself, j);
+ }
+ }
+
+ /* 3) Update state and save config. */
+ clusterUpdateState();
+ clusterSaveConfigOrDie(1);
+
+ /* 4) Pong all the other nodes so that they can update the state
+ * accordingly and detect that we switched to master role. */
+ clusterBroadcastPong(CLUSTER_BROADCAST_ALL);
+
+ /* 5) If there was a manual failover in progress, clear the state. */
+ resetManualFailover();
+}
+
+/* This function is called if we are a slave node and our master serving
+ * a non-zero amount of hash slots is in FAIL state.
+ *
+ * The gaol of this function is:
+ * 1) To check if we are able to perform a failover, is our data updated?
+ * 2) Try to get elected by masters.
+ * 3) Perform the failover informing all the other nodes.
+ */
+void clusterHandleSlaveFailover(void) {
+ mstime_t data_age;
+ mstime_t auth_age = mstime() - server.cluster->failover_auth_time;
+ int needed_quorum = (server.cluster->size / 2) + 1;
+ int manual_failover = server.cluster->mf_end != 0 &&
+ server.cluster->mf_can_start;
+ mstime_t auth_timeout, auth_retry_time;
+
+ server.cluster->todo_before_sleep &= ~CLUSTER_TODO_HANDLE_FAILOVER;
+
+ /* Compute the failover timeout (the max time we have to send votes
+ * and wait for replies), and the failover retry time (the time to wait
+ * before trying to get voted again).
+ *
+ * Timeout is MAX(NODE_TIMEOUT*2,2000) milliseconds.
+ * Retry is two times the Timeout.
+ */
+ auth_timeout = server.cluster_node_timeout * 2;
+ if (auth_timeout < 2000) auth_timeout = 2000;
+ auth_retry_time = auth_timeout * 2;
+
+ /* Pre conditions to run the function, that must be met both in case
+ * of an automatic or manual failover:
+ * 1) We are a slave.
+ * 2) Our master is flagged as FAIL, or this is a manual failover.
+ * 3) It is serving slots. */
+ if (nodeIsMaster(myself) ||
+ myself->slaveof == NULL ||
+ (!nodeFailed(myself->slaveof) && !manual_failover) ||
+ myself->slaveof->numslots == 0)
+ {
+ /* There are no reasons to failover, so we set the reason why we
+ * are returning without failing over to NONE. */
+ server.cluster->cant_failover_reason = CLUSTER_CANT_FAILOVER_NONE;
+ return;
+ }
+
+ /* Set data_age to the number of seconds we are disconnected from
+ * the master. */
+ if (server.repl_state == REPL_STATE_CONNECTED) {
+ data_age = (mstime_t) (server.unixtime - server.master->lastinteraction)
+ * 1000;
+ }
+ else {
+ data_age = (mstime_t) (server.unixtime - server.repl_down_since) * 1000;
+ }
+
+ /* Remove the node timeout from the data age as it is fine that we are
+ * disconnected from our master at least for the time it was down to be
+ * flagged as FAIL, that's the baseline. */
+ if (data_age > server.cluster_node_timeout)
+ data_age -= server.cluster_node_timeout;
+
+ /* Check if our data is recent enough according to the slave validity
+ * factor configured by the user.
+ *
+ * Check bypassed for manual failovers. */
+ if (server.cluster_slave_validity_factor &&
+ data_age >
+ (((mstime_t) server.repl_ping_slave_period * 1000) +
+ (server.cluster_node_timeout * server.cluster_slave_validity_factor)))
+ {
+ if (!manual_failover) {
+ clusterLogCantFailover(CLUSTER_CANT_FAILOVER_DATA_AGE);
+ return;
+ }
+ }
+
+ /* If the previous failover attempt timedout and the retry time has
+ * elapsed, we can setup a new one. */
+ if (auth_age > auth_retry_time) {
+ server.cluster->failover_auth_time = mstime() +
+ 500 + /* Fixed delay of 500 milliseconds, let FAIL msg propagate. */
+ random() % 500; /* Random delay between 0 and 500 milliseconds. */
+ server.cluster->failover_auth_count = 0;
+ server.cluster->failover_auth_sent = 0;
+ server.cluster->failover_auth_rank = clusterGetSlaveRank();
+ /* We add another delay that is proportional to the slave rank.
+ * Specifically 1 second * rank. This way slaves that have a probably
+ * less updated replication offset, are penalized. */
+ server.cluster->failover_auth_time +=
+ server.cluster->failover_auth_rank * 1000;
+ /* However if this is a manual failover, no delay is needed. */
+ if (server.cluster->mf_end) {
+ server.cluster->failover_auth_time = mstime();
+ server.cluster->failover_auth_rank = 0;
+ }
+ serverLog(LL_WARNING,
+ "Start of election delayed for %lld milliseconds "
+ "(rank #%d, offset %lld).",
+ server.cluster->failover_auth_time - mstime(),
+ server.cluster->failover_auth_rank,
+ replicationGetSlaveOffset());
+ /* Now that we have a scheduled election, broadcast our offset
+ * to all the other slaves so that they'll updated their offsets
+ * if our offset is better. */
+ clusterBroadcastPong(CLUSTER_BROADCAST_LOCAL_SLAVES);
+ return;
+ }
+
+ /* It is possible that we received more updated offsets from other
+ * slaves for the same master since we computed our election delay.
+ * Update the delay if our rank changed.
+ *
+ * Not performed if this is a manual failover. */
+ if (server.cluster->failover_auth_sent == 0 &&
+ server.cluster->mf_end == 0)
+ {
+ int newrank = clusterGetSlaveRank();
+ if (newrank > server.cluster->failover_auth_rank) {
+ PORT_LONGLONG added_delay =
+ (newrank - server.cluster->failover_auth_rank) * 1000;
+ server.cluster->failover_auth_time += added_delay;
+ server.cluster->failover_auth_rank = newrank;
+ serverLog(LL_WARNING,
+ "Slave rank updated to #%d, added %lld milliseconds of delay.",
+ newrank, added_delay);
+ }
+ }
+
+ /* Return ASAP if we can't still start the election. */
+ if (mstime() < server.cluster->failover_auth_time) {
+ clusterLogCantFailover(CLUSTER_CANT_FAILOVER_WAITING_DELAY);
+ return;
+ }
+
+ /* Return ASAP if the election is too old to be valid. */
+ if (auth_age > auth_timeout) {
+ clusterLogCantFailover(CLUSTER_CANT_FAILOVER_EXPIRED);
+ return;
+ }
+
+ /* Ask for votes if needed. */
+ if (server.cluster->failover_auth_sent == 0) {
+ server.cluster->currentEpoch++;
+ server.cluster->failover_auth_epoch = server.cluster->currentEpoch;
+ serverLog(LL_WARNING, "Starting a failover election for epoch %llu.",
+ (PORT_ULONGLONG) server.cluster->currentEpoch);
+ clusterRequestFailoverAuth();
+ server.cluster->failover_auth_sent = 1;
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
+ CLUSTER_TODO_UPDATE_STATE |
+ CLUSTER_TODO_FSYNC_CONFIG);
+ return; /* Wait for replies. */
+ }
+
+ /* Check if we reached the quorum. */
+ if (server.cluster->failover_auth_count >= needed_quorum) {
+ /* We have the quorum, we can finally failover the master. */
+
+ serverLog(LL_WARNING,
+ "Failover election won: I'm the new master.");
+
+ /* Update my configEpoch to the epoch of the election. */
+ if (myself->configEpoch < server.cluster->failover_auth_epoch) {
+ myself->configEpoch = server.cluster->failover_auth_epoch;
+ serverLog(LL_WARNING,
+ "configEpoch set to %llu after successful failover",
+ (PORT_ULONGLONG) myself->configEpoch);
+ }
+
+ /* Take responsability for the cluster slots. */
+ clusterFailoverReplaceYourMaster();
+ }
+ else {
+ clusterLogCantFailover(CLUSTER_CANT_FAILOVER_WAITING_VOTES);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * CLUSTER slave migration
+ *
+ * Slave migration is the process that allows a slave of a master that is
+ * already covered by at least another slave, to "migrate" to a master that
+ * is orpaned, that is, left with no working slaves.
+ * ------------------------------------------------------------------------- */
+
+ /* This function is responsible to decide if this replica should be migrated
+ * to a different (orphaned) master. It is called by the clusterCron() function
+ * only if:
+ *
+ * 1) We are a slave node.
+ * 2) It was detected that there is at least one orphaned master in
+ * the cluster.
+ * 3) We are a slave of one of the masters with the greatest number of
+ * slaves.
+ *
+ * This checks are performed by the caller since it requires to iterate
+ * the nodes anyway, so we spend time into clusterHandleSlaveMigration()
+ * if definitely needed.
+ *
+ * The fuction is called with a pre-computed max_slaves, that is the max
+ * number of working (not in FAIL state) slaves for a single master.
+ *
+ * Additional conditions for migration are examined inside the function.
+ */
+void clusterHandleSlaveMigration(int max_slaves) {
+ int j, okslaves = 0;
+ clusterNode *mymaster = myself->slaveof, *target = NULL, *candidate = NULL;
+ dictIterator *di;
+ dictEntry *de;
+
+ /* Step 1: Don't migrate if the cluster state is not ok. */
+ if (server.cluster->state != CLUSTER_OK) return;
+
+ /* Step 2: Don't migrate if my master will not be left with at least
+ * 'migration-barrier' slaves after my migration. */
+ if (mymaster == NULL) return;
+ for (j = 0; j < mymaster->numslaves; j++)
+ if (!nodeFailed(mymaster->slaves[j]) &&
+ !nodeTimedOut(mymaster->slaves[j])) okslaves++;
+ if (okslaves <= server.cluster_migration_barrier) return;
+
+ /* Step 3: Idenitfy a candidate for migration, and check if among the
+ * masters with the greatest number of ok slaves, I'm the one with the
+ * smallest node ID (the "candidate slave").
+ *
+ * Note: this means that eventually a replica migration will occurr
+ * since slaves that are reachable again always have their FAIL flag
+ * cleared, so eventually there must be a candidate. At the same time
+ * this does not mean that there are no race conditions possible (two
+ * slaves migrating at the same time), but this is unlikely to
+ * happen, and harmless when happens. */
+ candidate = myself;
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+ int okslaves = 0, is_orphaned = 1;
+
+ /* We want to migrate only if this master is working, orphaned, and
+ * used to have slaves or if failed over a master that had slaves
+ * (MIGRATE_TO flag). This way we only migrate to instances that were
+ * supposed to have replicas. */
+ if (nodeIsSlave(node) || nodeFailed(node)) is_orphaned = 0;
+ if (!(node->flags & CLUSTER_NODE_MIGRATE_TO)) is_orphaned = 0;
+
+ /* Check number of working slaves. */
+ if (nodeIsMaster(node)) okslaves = clusterCountNonFailingSlaves(node);
+ if (okslaves > 0) is_orphaned = 0;
+
+ if (is_orphaned) {
+ if (!target && node->numslots > 0) target = node;
+
+ /* Track the starting time of the orphaned condition for this
+ * master. */
+ if (!node->orphaned_time) node->orphaned_time = mstime();
+ }
+ else {
+ node->orphaned_time = 0;
+ }
+
+ /* Check if I'm the slave candidate for the migration: attached
+ * to a master with the maximum number of slaves and with the smallest
+ * node ID. */
+ if (okslaves == max_slaves) {
+ for (j = 0; j < node->numslaves; j++) {
+ if (memcmp(node->slaves[j]->name,
+ candidate->name,
+ CLUSTER_NAMELEN) < 0)
+ {
+ candidate = node->slaves[j];
+ }
+ }
+ }
+ }
+ dictReleaseIterator(di);
+
+ /* Step 4: perform the migration if there is a target, and if I'm the
+ * candidate, but only if the master is continuously orphaned for a
+ * couple of seconds, so that during failovers, we give some time to
+ * the natural slaves of this instance to advertise their switch from
+ * the old master to the new one. */
+ if (target && candidate == myself &&
+ (mstime() - target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY)
+ {
+ serverLog(LL_WARNING, "Migrating to orphaned master %.40s",
+ target->name);
+ clusterSetMaster(target);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * CLUSTER manual failover
+ *
+ * This are the important steps performed by slaves during a manual failover:
+ * 1) User send CLUSTER FAILOVER command. The failover state is initialized
+ * setting mf_end to the millisecond unix time at which we'll abort the
+ * attempt.
+ * 2) Slave sends a MFSTART message to the master requesting to pause clients
+ * for two times the manual failover timeout CLUSTER_MF_TIMEOUT.
+ * When master is paused for manual failover, it also starts to flag
+ * packets with CLUSTERMSG_FLAG0_PAUSED.
+ * 3) Slave waits for master to send its replication offset flagged as PAUSED.
+ * 4) If slave received the offset from the master, and its offset matches,
+ * mf_can_start is set to 1, and clusterHandleSlaveFailover() will perform
+ * the failover as usually, with the difference that the vote request
+ * will be modified to force masters to vote for a slave that has a
+ * working master.
+ *
+ * From the point of view of the master things are simpler: when a
+ * PAUSE_CLIENTS packet is received the master sets mf_end as well and
+ * the sender in mf_slave. During the time limit for the manual failover
+ * the master will just send PINGs more often to this slave, flagged with
+ * the PAUSED flag, so that the slave will set mf_master_offset when receiving
+ * a packet from the master with this flag set.
+ *
+ * The gaol of the manual failover is to perform a fast failover without
+ * data loss due to the asynchronous master-slave replication.
+ * -------------------------------------------------------------------------- */
+
+ /* Reset the manual failover state. This works for both masters and slavesa
+ * as all the state about manual failover is cleared.
+ *
+ * The function can be used both to initialize the manual failover state at
+ * startup or to abort a manual failover in progress. */
+void resetManualFailover(void) {
+ if (server.cluster->mf_end && clientsArePaused()) {
+ server.clients_pause_end_time = 0;
+ clientsArePaused(); /* Just use the side effect of the function. */
+ }
+ server.cluster->mf_end = 0; /* No manual failover in progress. */
+ server.cluster->mf_can_start = 0;
+ server.cluster->mf_slave = NULL;
+ server.cluster->mf_master_offset = 0;
+}
+
+/* If a manual failover timed out, abort it. */
+void manualFailoverCheckTimeout(void) {
+ if (server.cluster->mf_end && server.cluster->mf_end < mstime()) {
+ serverLog(LL_WARNING, "Manual failover timed out.");
+ resetManualFailover();
+ }
+}
+
+/* This function is called from the cluster cron function in order to go
+ * forward with a manual failover state machine. */
+void clusterHandleManualFailover(void) {
+ /* Return ASAP if no manual failover is in progress. */
+ if (server.cluster->mf_end == 0) return;
+
+ /* If mf_can_start is non-zero, the failover was already triggered so the
+ * next steps are performed by clusterHandleSlaveFailover(). */
+ if (server.cluster->mf_can_start) return;
+
+ if (server.cluster->mf_master_offset == 0) return; /* Wait for offset... */
+
+ if (server.cluster->mf_master_offset == replicationGetSlaveOffset()) {
+ /* Our replication offset matches the master replication offset
+ * announced after clients were paused. We can start the failover. */
+ server.cluster->mf_can_start = 1;
+ serverLog(LL_WARNING,
+ "All master replication stream processed, "
+ "manual failover can start.");
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * CLUSTER cron job
+ * -------------------------------------------------------------------------- */
+
+ /* This is executed 10 times every second */
+void clusterCron(void) {
+ dictIterator *di;
+ dictEntry *de;
+ int update_state = 0;
+ int orphaned_masters; /* How many masters there are without ok slaves. */
+ int max_slaves; /* Max number of ok slaves for a single master. */
+ int this_slaves; /* Number of ok slaves for our master (if we are slave). */
+ mstime_t min_pong = 0, now = mstime();
+ clusterNode *min_pong_node = NULL;
+ static PORT_ULONGLONG iteration = 0;
+ mstime_t handshake_timeout;
+
+ iteration++; /* Number of times this function was called so far. */
+
+ /* We want to take myself->ip in sync with the cluster-announce-ip option.
+ * The option can be set at runtime via CONFIG SET, so we periodically check
+ * if the option changed to reflect this into myself->ip. */
+ {
+ static char *prev_ip = NULL;
+ char *curr_ip = server.cluster_announce_ip;
+ int changed = 0;
+
+ if (prev_ip == NULL && curr_ip != NULL) changed = 1;
+ if (prev_ip != NULL && curr_ip == NULL) changed = 1;
+ if (prev_ip && curr_ip && strcmp(prev_ip, curr_ip)) changed = 1;
+
+ if (changed) {
+ prev_ip = curr_ip;
+ if (prev_ip) prev_ip = zstrdup(prev_ip);
+
+ if (curr_ip) {
+ strncpy(myself->ip, server.cluster_announce_ip, NET_IP_STR_LEN);
+ myself->ip[NET_IP_STR_LEN - 1] = '\0';
+ }
+ else {
+ myself->ip[0] = '\0'; /* Force autodetection. */
+ }
+ }
+ }
+
+ /* The handshake timeout is the time after which a handshake node that was
+ * not turned into a normal node is removed from the nodes. Usually it is
+ * just the NODE_TIMEOUT value, but when NODE_TIMEOUT is too small we use
+ * the value of 1 second. */
+ handshake_timeout = server.cluster_node_timeout;
+ if (handshake_timeout < 1000) handshake_timeout = 1000;
+
+ /* Check if we have disconnected nodes and re-establish the connection.
+ * Also update a few stats while we are here, that can be used to make
+ * better decisions in other part of the code. */
+ di = dictGetSafeIterator(server.cluster->nodes);
+ server.cluster->stats_pfail_nodes = 0;
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+
+ /* Not interested in reconnecting the link with myself or nodes
+ * for which we have no address. */
+ if (node->flags & (CLUSTER_NODE_MYSELF | CLUSTER_NODE_NOADDR)) continue;
+
+ if (node->flags & CLUSTER_NODE_PFAIL)
+ server.cluster->stats_pfail_nodes++;
+
+ /* A Node in HANDSHAKE state has a limited lifespan equal to the
+ * configured node timeout. */
+ if (nodeInHandshake(node) && now - node->ctime > handshake_timeout) {
+ clusterDelNode(node);
+ continue;
+ }
+
+ if (node->link == NULL) {
+ int fd;
+ mstime_t old_ping_sent;
+ clusterLink *link;
+
+ fd = anetTcpNonBlockBindConnect(server.neterr, node->ip,
+ node->cport, NET_FIRST_BIND_ADDR);
+ if (fd == -1) {
+ /* We got a synchronous error from connect before
+ * clusterSendPing() had a chance to be called.
+ * If node->ping_sent is zero, failure detection can't work,
+ * so we claim we actually sent a ping now (that will
+ * be really sent as soon as the link is obtained). */
+ if (node->ping_sent == 0) node->ping_sent = mstime();
+ serverLog(LL_DEBUG, "Unable to connect to "
+ "Cluster Node [%s]:%d -> %s", node->ip,
+ node->cport, server.neterr);
+ continue;
+ }
+ link = createClusterLink(node);
+ link->fd = fd;
+ node->link = link;
+ aeCreateFileEvent(server.el, link->fd, AE_READABLE,
+ clusterReadHandler, link);
+ /* Queue a PING in the new connection ASAP: this is crucial
+ * to avoid false positives in failure detection.
+ *
+ * If the node is flagged as MEET, we send a MEET message instead
+ * of a PING one, to force the receiver to add us in its node
+ * table. */
+ old_ping_sent = node->ping_sent;
+ clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
+ CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
+ if (old_ping_sent) {
+ /* If there was an active ping before the link was
+ * disconnected, we want to restore the ping time, otherwise
+ * replaced by the clusterSendPing() call. */
+ node->ping_sent = old_ping_sent;
+ }
+ /* We can clear the flag after the first packet is sent.
+ * If we'll never receive a PONG, we'll never send new packets
+ * to this node. Instead after the PONG is received and we
+ * are no longer in meet/handshake status, we want to send
+ * normal PING packets. */
+ node->flags &= ~CLUSTER_NODE_MEET;
+
+ serverLog(LL_DEBUG, "Connecting with Node %.40s at %s:%d",
+ node->name, node->ip, node->cport);
+ }
+ }
+ dictReleaseIterator(di);
+
+ /* Ping some random node 1 time every 10 iterations, so that we usually ping
+ * one random node every second. */
+ if (!(iteration % 10)) {
+ int j;
+
+ /* Check a few random nodes and ping the one with the oldest
+ * pong_received time. */
+ for (j = 0; j < 5; j++) {
+ de = dictGetRandomKey(server.cluster->nodes);
+ clusterNode *this = dictGetVal(de);
+
+ /* Don't ping nodes disconnected or with a ping currently active. */
+ if (this->link == NULL || this->ping_sent != 0) continue;
+ if (this->flags & (CLUSTER_NODE_MYSELF | CLUSTER_NODE_HANDSHAKE))
+ continue;
+ if (min_pong_node == NULL || min_pong > this->pong_received) {
+ min_pong_node = this;
+ min_pong = this->pong_received;
+ }
+ }
+ if (min_pong_node) {
+ serverLog(LL_DEBUG, "Pinging node %.40s", min_pong_node->name);
+ clusterSendPing(min_pong_node->link, CLUSTERMSG_TYPE_PING);
+ }
+ }
+
+ /* Iterate nodes to check if we need to flag something as failing.
+ * This loop is also responsible to:
+ * 1) Check if there are orphaned masters (masters without non failing
+ * slaves).
+ * 2) Count the max number of non failing slaves for a single master.
+ * 3) Count the number of slaves for our master, if we are a slave. */
+ orphaned_masters = 0;
+ max_slaves = 0;
+ this_slaves = 0;
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+ now = mstime(); /* Use an updated time at every iteration. */
+ mstime_t delay;
+
+ if (node->flags &
+ (CLUSTER_NODE_MYSELF | CLUSTER_NODE_NOADDR | CLUSTER_NODE_HANDSHAKE))
+ continue;
+
+ /* Orphaned master check, useful only if the current instance
+ * is a slave that may migrate to another master. */
+ if (nodeIsSlave(myself) && nodeIsMaster(node) && !nodeFailed(node)) {
+ int okslaves = clusterCountNonFailingSlaves(node);
+
+ /* A master is orphaned if it is serving a non-zero number of
+ * slots, have no working slaves, but used to have at least one
+ * slave, or failed over a master that used to have slaves. */
+ if (okslaves == 0 && node->numslots > 0 &&
+ node->flags & CLUSTER_NODE_MIGRATE_TO)
+ {
+ orphaned_masters++;
+ }
+ if (okslaves > max_slaves) max_slaves = okslaves;
+ if (nodeIsSlave(myself) && myself->slaveof == node)
+ this_slaves = okslaves;
+ }
+
+ /* If we are waiting for the PONG more than half the cluster
+ * timeout, reconnect the link: maybe there is a connection
+ * issue even if the node is alive. */
+ if (node->link && /* is connected */
+ now - node->link->ctime >
+ server.cluster_node_timeout && /* was not already reconnected */
+ node->ping_sent && /* we already sent a ping */
+ node->pong_received < node->ping_sent && /* still waiting pong */
+ /* and we are waiting for the pong more than timeout/2 */
+ now - node->ping_sent > server.cluster_node_timeout / 2)
+ {
+ /* Disconnect the link, it will be reconnected automatically. */
+ freeClusterLink(node->link);
+ }
+
+ /* If we have currently no active ping in this instance, and the
+ * received PONG is older than half the cluster timeout, send
+ * a new ping now, to ensure all the nodes are pinged without
+ * a too big delay. */
+ if (node->link &&
+ node->ping_sent == 0 &&
+ (now - node->pong_received) > server.cluster_node_timeout / 2)
+ {
+ clusterSendPing(node->link, CLUSTERMSG_TYPE_PING);
+ continue;
+ }
+
+ /* If we are a master and one of the slaves requested a manual
+ * failover, ping it continuously. */
+ if (server.cluster->mf_end &&
+ nodeIsMaster(myself) &&
+ server.cluster->mf_slave == node &&
+ node->link)
+ {
+ clusterSendPing(node->link, CLUSTERMSG_TYPE_PING);
+ continue;
+ }
+
+ /* Check only if we have an active ping for this instance. */
+ if (node->ping_sent == 0) continue;
+
+ /* Compute the delay of the PONG. Note that if we already received
+ * the PONG, then node->ping_sent is zero, so can't reach this
+ * code at all. */
+ delay = now - node->ping_sent;
+
+ if (delay > server.cluster_node_timeout) {
+ /* Timeout reached. Set the node as possibly failing if it is
+ * not already in this state. */
+ if (!(node->flags & (CLUSTER_NODE_PFAIL | CLUSTER_NODE_FAIL))) {
+ serverLog(LL_DEBUG, "*** NODE %.40s possibly failing",
+ node->name);
+ node->flags |= CLUSTER_NODE_PFAIL;
+ update_state = 1;
+ }
+ }
+ }
+ dictReleaseIterator(di);
+
+ /* If we are a slave node but the replication is still turned off,
+ * enable it if we know the address of our master and it appears to
+ * be up. */
+ if (nodeIsSlave(myself) &&
+ server.masterhost == NULL &&
+ myself->slaveof &&
+ nodeHasAddr(myself->slaveof))
+ {
+ replicationSetMaster(myself->slaveof->ip, myself->slaveof->port);
+ }
+
+ /* Abourt a manual failover if the timeout is reached. */
+ manualFailoverCheckTimeout();
+
+ if (nodeIsSlave(myself)) {
+ clusterHandleManualFailover();
+ clusterHandleSlaveFailover();
+ /* If there are orphaned slaves, and we are a slave among the masters
+ * with the max number of non-failing slaves, consider migrating to
+ * the orphaned masters. Note that it does not make sense to try
+ * a migration if there is no master with at least *two* working
+ * slaves. */
+ if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves)
+ clusterHandleSlaveMigration(max_slaves);
+ }
+
+ if (update_state || server.cluster->state == CLUSTER_FAIL)
+ clusterUpdateState();
+}
+
+/* This function is called before the event handler returns to sleep for
+ * events. It is useful to perform operations that must be done ASAP in
+ * reaction to events fired but that are not safe to perform inside event
+ * handlers, or to perform potentially expansive tasks that we need to do
+ * a single time before replying to clients. */
+void clusterBeforeSleep(void) {
+ /* Handle failover, this is needed when it is likely that there is already
+ * the quorum from masters in order to react fast. */
+ if (server.cluster->todo_before_sleep & CLUSTER_TODO_HANDLE_FAILOVER)
+ clusterHandleSlaveFailover();
+
+ /* Update the cluster state. */
+ if (server.cluster->todo_before_sleep & CLUSTER_TODO_UPDATE_STATE)
+ clusterUpdateState();
+
+ /* Save the config, possibly using fsync. */
+ if (server.cluster->todo_before_sleep & CLUSTER_TODO_SAVE_CONFIG) {
+ int fsync = server.cluster->todo_before_sleep &
+ CLUSTER_TODO_FSYNC_CONFIG;
+ clusterSaveConfigOrDie(fsync);
+ }
+
+ /* Reset our flags (not strictly needed since every single function
+ * called for flags set should be able to clear its flag). */
+ server.cluster->todo_before_sleep = 0;
+}
+
+void clusterDoBeforeSleep(int flags) {
+ server.cluster->todo_before_sleep |= flags;
+}
+
+/* -----------------------------------------------------------------------------
+ * Slots management
+ * -------------------------------------------------------------------------- */
+
+ /* Test bit 'pos' in a generic bitmap. Return 1 if the bit is set,
+ * otherwise 0. */
+int bitmapTestBit(unsigned char *bitmap, int pos) {
+ off_t byte = pos / 8;
+ int bit = pos & 7;
+ return (bitmap[byte] & (1 << bit)) != 0;
+}
+
+/* Set the bit at position 'pos' in a bitmap. */
+void bitmapSetBit(unsigned char *bitmap, int pos) {
+ off_t byte = pos / 8;
+ int bit = pos & 7;
+ bitmap[byte] |= 1 << bit;
+}
+
+/* Clear the bit at position 'pos' in a bitmap. */
+void bitmapClearBit(unsigned char *bitmap, int pos) {
+ off_t byte = pos / 8;
+ int bit = pos & 7;
+ bitmap[byte] &= ~(1 << bit);
+}
+
+/* Return non-zero if there is at least one master with slaves in the cluster.
+ * Otherwise zero is returned. Used by clusterNodeSetSlotBit() to set the
+ * MIGRATE_TO flag the when a master gets the first slot. */
+int clusterMastersHaveSlaves(void) {
+ dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
+ dictEntry *de;
+ int slaves = 0;
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+
+ if (nodeIsSlave(node)) continue;
+ slaves += node->numslaves;
+ }
+ dictReleaseIterator(di);
+ return slaves != 0;
+}
+
+/* Set the slot bit and return the old value. */
+int clusterNodeSetSlotBit(clusterNode *n, int slot) {
+ int old = bitmapTestBit(n->slots, slot);
+ bitmapSetBit(n->slots, slot);
+ if (!old) {
+ n->numslots++;
+ /* When a master gets its first slot, even if it has no slaves,
+ * it gets flagged with MIGRATE_TO, that is, the master is a valid
+ * target for replicas migration, if and only if at least one of
+ * the other masters has slaves right now.
+ *
+ * Normally masters are valid targerts of replica migration if:
+ * 1. The used to have slaves (but no longer have).
+ * 2. They are slaves failing over a master that used to have slaves.
+ *
+ * However new masters with slots assigned are considered valid
+ * migration tagets if the rest of the cluster is not a slave-less.
+ *
+ * See https://github.com/antirez/redis/issues/3043 for more info. */
+ if (n->numslots == 1 && clusterMastersHaveSlaves())
+ n->flags |= CLUSTER_NODE_MIGRATE_TO;
+ }
+ return old;
+}
+
+/* Clear the slot bit and return the old value. */
+int clusterNodeClearSlotBit(clusterNode *n, int slot) {
+ int old = bitmapTestBit(n->slots, slot);
+ bitmapClearBit(n->slots, slot);
+ if (old) n->numslots--;
+ return old;
+}
+
+/* Return the slot bit from the cluster node structure. */
+int clusterNodeGetSlotBit(clusterNode *n, int slot) {
+ return bitmapTestBit(n->slots, slot);
+}
+
+/* Add the specified slot to the list of slots that node 'n' will
+ * serve. Return C_OK if the operation ended with success.
+ * If the slot is already assigned to another instance this is considered
+ * an error and C_ERR is returned. */
+int clusterAddSlot(clusterNode *n, int slot) {
+ if (server.cluster->slots[slot]) return C_ERR;
+ clusterNodeSetSlotBit(n, slot);
+ server.cluster->slots[slot] = n;
+ return C_OK;
+}
+
+/* Delete the specified slot marking it as unassigned.
+ * Returns C_OK if the slot was assigned, otherwise if the slot was
+ * already unassigned C_ERR is returned. */
+int clusterDelSlot(int slot) {
+ clusterNode *n = server.cluster->slots[slot];
+
+ if (!n) return C_ERR;
+ serverAssert(clusterNodeClearSlotBit(n, slot) == 1);
+ server.cluster->slots[slot] = NULL;
+ return C_OK;
+}
+
+/* Delete all the slots associated with the specified node.
+ * The number of deleted slots is returned. */
+int clusterDelNodeSlots(clusterNode *node) {
+ int deleted = 0, j;
+
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ if (clusterNodeGetSlotBit(node, j)) {
+ clusterDelSlot(j);
+ deleted++;
+ }
+ }
+ return deleted;
+}
+
+/* Clear the migrating / importing state for all the slots.
+ * This is useful at initialization and when turning a master into slave. */
+void clusterCloseAllSlots(void) {
+ memset(server.cluster->migrating_slots_to, 0,
+ sizeof(server.cluster->migrating_slots_to));
+ memset(server.cluster->importing_slots_from, 0,
+ sizeof(server.cluster->importing_slots_from));
+}
+
+/* -----------------------------------------------------------------------------
+ * Cluster state evaluation function
+ * -------------------------------------------------------------------------- */
+
+ /* The following are defines that are only used in the evaluation function
+ * and are based on heuristics. Actaully the main point about the rejoin and
+ * writable delay is that they should be a few orders of magnitude larger
+ * than the network latency. */
+#define CLUSTER_MAX_REJOIN_DELAY 5000
+#define CLUSTER_MIN_REJOIN_DELAY 500
+#define CLUSTER_WRITABLE_DELAY 2000
+
+void clusterUpdateState(void) {
+ int j, new_state;
+ int reachable_masters = 0;
+ static mstime_t among_minority_time;
+ static mstime_t first_call_time = 0;
+
+ server.cluster->todo_before_sleep &= ~CLUSTER_TODO_UPDATE_STATE;
+
+ /* If this is a master node, wait some time before turning the state
+ * into OK, since it is not a good idea to rejoin the cluster as a writable
+ * master, after a reboot, without giving the cluster a chance to
+ * reconfigure this node. Note that the delay is calculated starting from
+ * the first call to this function and not since the server start, in order
+ * to don't count the DB loading time. */
+ if (first_call_time == 0) first_call_time = mstime();
+ if (nodeIsMaster(myself) &&
+ server.cluster->state == CLUSTER_FAIL &&
+ mstime() - first_call_time < CLUSTER_WRITABLE_DELAY) return;
+
+ /* Start assuming the state is OK. We'll turn it into FAIL if there
+ * are the right conditions. */
+ new_state = CLUSTER_OK;
+
+ /* Check if all the slots are covered. */
+ if (server.cluster_require_full_coverage) {
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ if (server.cluster->slots[j] == NULL ||
+ server.cluster->slots[j]->flags & (CLUSTER_NODE_FAIL))
+ {
+ new_state = CLUSTER_FAIL;
+ break;
+ }
+ }
+ }
+
+ /* Compute the cluster size, that is the number of master nodes
+ * serving at least a single slot.
+ *
+ * At the same time count the number of reachable masters having
+ * at least one slot. */
+ {
+ dictIterator *di;
+ dictEntry *de;
+
+ server.cluster->size = 0;
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+
+ if (nodeIsMaster(node) && node->numslots) {
+ server.cluster->size++;
+ if ((node->flags & (CLUSTER_NODE_FAIL | CLUSTER_NODE_PFAIL)) == 0)
+ reachable_masters++;
+ }
+ }
+ dictReleaseIterator(di);
+ }
+
+ /* If we are in a minority partition, change the cluster state
+ * to FAIL. */
+ {
+ int needed_quorum = (server.cluster->size / 2) + 1;
+
+ if (reachable_masters < needed_quorum) {
+ new_state = CLUSTER_FAIL;
+ among_minority_time = mstime();
+ }
+ }
+
+ /* Log a state change */
+ if (new_state != server.cluster->state) {
+ mstime_t rejoin_delay = server.cluster_node_timeout;
+
+ /* If the instance is a master and was partitioned away with the
+ * minority, don't let it accept queries for some time after the
+ * partition heals, to make sure there is enough time to receive
+ * a configuration update. */
+ if (rejoin_delay > CLUSTER_MAX_REJOIN_DELAY)
+ rejoin_delay = CLUSTER_MAX_REJOIN_DELAY;
+ if (rejoin_delay < CLUSTER_MIN_REJOIN_DELAY)
+ rejoin_delay = CLUSTER_MIN_REJOIN_DELAY;
+
+ if (new_state == CLUSTER_OK &&
+ nodeIsMaster(myself) &&
+ mstime() - among_minority_time < rejoin_delay)
+ {
+ return;
+ }
+
+ /* Change the state and log the event. */
+ serverLog(LL_WARNING, "Cluster state changed: %s",
+ new_state == CLUSTER_OK ? "ok" : "fail");
+ server.cluster->state = new_state;
+ }
+}
+
+/* This function is called after the node startup in order to verify that data
+ * loaded from disk is in agreement with the cluster configuration:
+ *
+ * 1) If we find keys about hash slots we have no responsibility for, the
+ * following happens:
+ * A) If no other node is in charge according to the current cluster
+ * configuration, we add these slots to our node.
+ * B) If according to our config other nodes are already in charge for
+ * this lots, we set the slots as IMPORTING from our point of view
+ * in order to justify we have those slots, and in order to make
+ * redis-trib aware of the issue, so that it can try to fix it.
+ * 2) If we find data in a DB different than DB0 we return C_ERR to
+ * signal the caller it should quit the server with an error message
+ * or take other actions.
+ *
+ * The function always returns C_OK even if it will try to correct
+ * the error described in "1". However if data is found in DB different
+ * from DB0, C_ERR is returned.
+ *
+ * The function also uses the logging facility in order to warn the user
+ * about desynchronizations between the data we have in memory and the
+ * cluster configuration. */
+int verifyClusterConfigWithData(void) {
+ int j;
+ int update_config = 0;
+
+ /* If this node is a slave, don't perform the check at all as we
+ * completely depend on the replication stream. */
+ if (nodeIsSlave(myself)) return C_OK;
+
+ /* Make sure we only have keys in DB0. */
+ for (j = 1; j < server.dbnum; j++) {
+ if (dictSize(server.db[j].dict)) return C_ERR;
+ }
+
+ /* Check that all the slots we see populated memory have a corresponding
+ * entry in the cluster table. Otherwise fix the table. */
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ if (!countKeysInSlot(j)) continue; /* No keys in this slot. */
+ /* Check if we are assigned to this slot or if we are importing it.
+ * In both cases check the next slot as the configuration makes
+ * sense. */
+ if (server.cluster->slots[j] == myself ||
+ server.cluster->importing_slots_from[j] != NULL) continue;
+
+ /* If we are here data and cluster config don't agree, and we have
+ * slot 'j' populated even if we are not importing it, nor we are
+ * assigned to this slot. Fix this condition. */
+
+ update_config++;
+ /* Case A: slot is unassigned. Take responsibility for it. */
+ if (server.cluster->slots[j] == NULL) {
+ serverLog(LL_WARNING, "I have keys for unassigned slot %d. "
+ "Taking responsibility for it.", j);
+ clusterAddSlot(myself, j);
+ }
+ else {
+ serverLog(LL_WARNING, "I have keys for slot %d, but the slot is "
+ "assigned to another node. "
+ "Setting it to importing state.", j);
+ server.cluster->importing_slots_from[j] = server.cluster->slots[j];
+ }
+ }
+ if (update_config) clusterSaveConfigOrDie(1);
+ return C_OK;
+}
+
+/* -----------------------------------------------------------------------------
+ * SLAVE nodes handling
+ * -------------------------------------------------------------------------- */
+
+ /* Set the specified node 'n' as master for this node.
+ * If this node is currently a master, it is turned into a slave. */
+void clusterSetMaster(clusterNode *n) {
+ serverAssert(n != myself);
+ serverAssert(myself->numslots == 0);
+
+ if (nodeIsMaster(myself)) {
+ myself->flags &= ~(CLUSTER_NODE_MASTER | CLUSTER_NODE_MIGRATE_TO);
+ myself->flags |= CLUSTER_NODE_SLAVE;
+ clusterCloseAllSlots();
+ }
+ else {
+ if (myself->slaveof)
+ clusterNodeRemoveSlave(myself->slaveof, myself);
+ }
+ myself->slaveof = n;
+ clusterNodeAddSlave(n, myself);
+ replicationSetMaster(n->ip, n->port);
+ resetManualFailover();
+}
+
+/* -----------------------------------------------------------------------------
+ * Nodes to string representation functions.
+ * -------------------------------------------------------------------------- */
+
+struct redisNodeFlags {
+ uint16_t flag;
+ char *name;
+};
+
+static struct redisNodeFlags redisNodeFlagsTable[] = {
+ {CLUSTER_NODE_MYSELF, "myself,"},
+ {CLUSTER_NODE_MASTER, "master,"},
+ {CLUSTER_NODE_SLAVE, "slave,"},
+ {CLUSTER_NODE_PFAIL, "fail?,"},
+ {CLUSTER_NODE_FAIL, "fail,"},
+ {CLUSTER_NODE_HANDSHAKE, "handshake,"},
+ {CLUSTER_NODE_NOADDR, "noaddr,"}
+};
+
+/* Concatenate the comma separated list of node flags to the given SDS
+ * string 'ci'. */
+sds representClusterNodeFlags(sds ci, uint16_t flags) {
+ size_t orig_len = sdslen(ci);
+ int i, size = sizeof(redisNodeFlagsTable) / sizeof(struct redisNodeFlags);
+ for (i = 0; i < size; i++) {
+ struct redisNodeFlags *nodeflag = redisNodeFlagsTable + i;
+ if (flags & nodeflag->flag) ci = sdscat(ci, nodeflag->name);
+ }
+ /* If no flag was added, add the "noflags" special flag. */
+ if (sdslen(ci) == orig_len) ci = sdscat(ci, "noflags,");
+ sdsIncrLen(ci, -1); /* Remove trailing comma. */
+ return ci;
+}
+
+/* Generate a csv-alike representation of the specified cluster node.
+ * See clusterGenNodesDescription() top comment for more information.
+ *
+ * The function returns the string representation as an SDS string. */
+sds clusterGenNodeDescription(clusterNode *node) {
+ int j, start;
+ sds ci;
+
+ /* Node coordinates */
+ ci = sdscatprintf(sdsempty(), "%.40s %s:%d@%d ",
+ node->name,
+ node->ip,
+ node->port,
+ node->cport);
+
+ /* Flags */
+ ci = representClusterNodeFlags(ci, node->flags);
+
+ /* Slave of... or just "-" */
+ if (node->slaveof)
+ ci = sdscatprintf(ci, " %.40s ", node->slaveof->name);
+ else
+ ci = sdscatlen(ci, " - ", 3);
+
+ /* Latency from the POV of this node, config epoch, link status */
+ ci = sdscatprintf(ci, "%lld %lld %llu %s",
+ (PORT_LONGLONG) node->ping_sent,
+ (PORT_LONGLONG) node->pong_received,
+ (PORT_ULONGLONG) node->configEpoch,
+ (node->link || node->flags & CLUSTER_NODE_MYSELF) ?
+ "connected" : "disconnected");
+
+ /* Slots served by this instance */
+ start = -1;
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ int bit;
+
+ if ((bit = clusterNodeGetSlotBit(node, j)) != 0) {
+ if (start == -1) start = j;
+ }
+ if (start != -1 && (!bit || j == CLUSTER_SLOTS - 1)) {
+ if (bit && j == CLUSTER_SLOTS - 1) j++;
+
+ if (start == j - 1) {
+ ci = sdscatprintf(ci, " %d", start);
+ }
+ else {
+ ci = sdscatprintf(ci, " %d-%d", start, j - 1);
+ }
+ start = -1;
+ }
+ }
+
+ /* Just for MYSELF node we also dump info about slots that
+ * we are migrating to other instances or importing from other
+ * instances. */
+ if (node->flags & CLUSTER_NODE_MYSELF) {
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ if (server.cluster->migrating_slots_to[j]) {
+ ci = sdscatprintf(ci, " [%d->-%.40s]", j,
+ server.cluster->migrating_slots_to[j]->name);
+ }
+ else if (server.cluster->importing_slots_from[j]) {
+ ci = sdscatprintf(ci, " [%d-<-%.40s]", j,
+ server.cluster->importing_slots_from[j]->name);
+ }
+ }
+ }
+ return ci;
+}
+
+/* Generate a csv-alike representation of the nodes we are aware of,
+ * including the "myself" node, and return an SDS string containing the
+ * representation (it is up to the caller to free it).
+ *
+ * All the nodes matching at least one of the node flags specified in
+ * "filter" are excluded from the output, so using zero as a filter will
+ * include all the known nodes in the representation, including nodes in
+ * the HANDSHAKE state.
+ *
+ * The representation obtained using this function is used for the output
+ * of the CLUSTER NODES function, and as format for the cluster
+ * configuration file (nodes.conf) for a given node. */
+sds clusterGenNodesDescription(int filter) {
+ sds ci = sdsempty(), ni;
+ dictIterator *di;
+ dictEntry *de;
+
+ di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+
+ if (node->flags & filter) continue;
+ ni = clusterGenNodeDescription(node);
+ ci = sdscatsds(ci, ni);
+ sdsfree(ni);
+ ci = sdscatlen(ci, "\n", 1);
+ }
+ dictReleaseIterator(di);
+ return ci;
+}
+
+/* -----------------------------------------------------------------------------
+ * CLUSTER command
+ * -------------------------------------------------------------------------- */
+
+const char *clusterGetMessageTypeString(int type) {
+ switch (type) {
+ case CLUSTERMSG_TYPE_PING: return "ping";
+ case CLUSTERMSG_TYPE_PONG: return "pong";
+ case CLUSTERMSG_TYPE_MEET: return "meet";
+ case CLUSTERMSG_TYPE_FAIL: return "fail";
+ case CLUSTERMSG_TYPE_PUBLISH: return "publish";
+ case CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST: return "auth-req";
+ case CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK: return "auth-ack";
+ case CLUSTERMSG_TYPE_UPDATE: return "update";
+ case CLUSTERMSG_TYPE_MFSTART: return "mfstart";
+ }
+ return "unknown";
+}
+
+int getSlotOrReply(client *c, robj *o) {
+ PORT_LONGLONG slot;
+
+ if (getLongLongFromObject(o, &slot) != C_OK ||
+ slot < 0 || slot >= CLUSTER_SLOTS)
+ {
+ addReplyError(c, "Invalid or out of range slot");
+ return -1;
+ }
+ return (int) slot;
+}
+
+void clusterReplyMultiBulkSlots(client *c) {
+ /* Format: 1) 1) start slot
+ * 2) end slot
+ * 3) 1) master IP
+ * 2) master port
+ * 3) node ID
+ * 4) 1) replica IP
+ * 2) replica port
+ * 3) node ID
+ * ... continued until done
+ */
+
+ int num_masters = 0;
+ void *slot_replylen = addDeferredMultiBulkLength(c);
+
+ dictEntry *de;
+ dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
+ while ((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+ int j = 0, start = -1;
+
+ /* Skip slaves (that are iterated when producing the output of their
+ * master) and masters not serving any slot. */
+ if (!nodeIsMaster(node) || node->numslots == 0) continue;
+
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ int bit, i;
+
+ if ((bit = clusterNodeGetSlotBit(node, j)) != 0) {
+ if (start == -1) start = j;
+ }
+ if (start != -1 && (!bit || j == CLUSTER_SLOTS - 1)) {
+ int nested_elements = 3; /* slots (2) + master addr (1). */
+ void *nested_replylen = addDeferredMultiBulkLength(c);
+
+ if (bit && j == CLUSTER_SLOTS - 1) j++;
+
+ /* If slot exists in output map, add to it's list.
+ * else, create a new output map for this slot */
+ if (start == j - 1) {
+ addReplyLongLong(c, start); /* only one slot; low==high */
+ addReplyLongLong(c, start);
+ }
+ else {
+ addReplyLongLong(c, start); /* low */
+ addReplyLongLong(c, j - 1); /* high */
+ }
+ start = -1;
+
+ /* First node reply position is always the master */
+ addReplyMultiBulkLen(c, 3);
+ addReplyBulkCString(c, node->ip);
+ addReplyLongLong(c, node->port);
+ addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
+
+ /* Remaining nodes in reply are replicas for slot range */
+ for (i = 0; i < node->numslaves; i++) {
+ /* This loop is copy/pasted from clusterGenNodeDescription()
+ * with modifications for per-slot node aggregation */
+ if (nodeFailed(node->slaves[i])) continue;
+ addReplyMultiBulkLen(c, 3);
+ addReplyBulkCString(c, node->slaves[i]->ip);
+ addReplyLongLong(c, node->slaves[i]->port);
+ addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
+ nested_elements++;
+ }
+ setDeferredMultiBulkLength(c, nested_replylen, nested_elements);
+ num_masters++;
+ }
+ }
+ }
+ dictReleaseIterator(di);
+ setDeferredMultiBulkLength(c, slot_replylen, num_masters);
+}
+
+void clusterCommand(client *c) {
+ if (server.cluster_enabled == 0) {
+ addReplyError(c, "This instance has cluster support disabled");
+ return;
+ }
+
+ if (!strcasecmp(c->argv[1]->ptr, "meet") && (c->argc == 4 || c->argc == 5)) {
+ /* CLUSTER MEET [cport] */
+ PORT_LONGLONG port, cport;
+
+ if (getLongLongFromObject(c->argv[3], &port) != C_OK) {
+ addReplyErrorFormat(c, "Invalid TCP base port specified: %s",
+ (char*) c->argv[3]->ptr);
+ return;
+ }
+
+ if (c->argc == 5) {
+ if (getLongLongFromObject(c->argv[4], &cport) != C_OK) {
+ addReplyErrorFormat(c, "Invalid TCP bus port specified: %s",
+ (char*) c->argv[4]->ptr);
+ return;
+ }
+ }
+ else {
+ cport = port + CLUSTER_PORT_INCR;
+ }
+
+ if (clusterStartHandshake(c->argv[2]->ptr, (int) port, (int) cport) == 0 && WIN_PORT_FIX /* cast (int) */
+ errno == EINVAL)
+ {
+ addReplyErrorFormat(c, "Invalid node address specified: %s:%s",
+ (char*) c->argv[2]->ptr, (char*) c->argv[3]->ptr);
+ }
+ else {
+ addReply(c, shared.ok);
+ }
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "nodes") && c->argc == 2) {
+ /* CLUSTER NODES */
+ robj *o;
+ sds ci = clusterGenNodesDescription(0);
+
+ o = createObject(OBJ_STRING, ci);
+ addReplyBulk(c, o);
+ decrRefCount(o);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "myid") && c->argc == 2) {
+ /* CLUSTER MYID */
+ addReplyBulkCBuffer(c, myself->name, CLUSTER_NAMELEN);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "slots") && c->argc == 2) {
+ /* CLUSTER SLOTS */
+ clusterReplyMultiBulkSlots(c);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "flushslots") && c->argc == 2) {
+ /* CLUSTER FLUSHSLOTS */
+ if (dictSize(server.db[0].dict) != 0) {
+ addReplyError(c, "DB must be empty to perform CLUSTER FLUSHSLOTS.");
+ return;
+ }
+ clusterDelNodeSlots(myself);
+ clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_SAVE_CONFIG);
+ addReply(c, shared.ok);
+ }
+ else if ((!strcasecmp(c->argv[1]->ptr, "addslots") ||
+ !strcasecmp(c->argv[1]->ptr, "delslots")) && c->argc >= 3)
+ {
+ /* CLUSTER ADDSLOTS [slot] ... */
+ /* CLUSTER DELSLOTS [slot] ... */
+ int j, slot;
+ unsigned char *slots = zmalloc(CLUSTER_SLOTS);
+ int del = !strcasecmp(c->argv[1]->ptr, "delslots");
+
+ memset(slots, 0, CLUSTER_SLOTS);
+ /* Check that all the arguments are parseable and that all the
+ * slots are not already busy. */
+ for (j = 2; j < c->argc; j++) {
+ if ((slot = getSlotOrReply(c, c->argv[j])) == -1) {
+ zfree(slots);
+ return;
+ }
+ if (del && server.cluster->slots[slot] == NULL) {
+ addReplyErrorFormat(c, "Slot %d is already unassigned", slot);
+ zfree(slots);
+ return;
+ }
+ else if (!del && server.cluster->slots[slot]) {
+ addReplyErrorFormat(c, "Slot %d is already busy", slot);
+ zfree(slots);
+ return;
+ }
+ if (slots[slot]++ == 1) {
+ addReplyErrorFormat(c, "Slot %d specified multiple times",
+ (int) slot);
+ zfree(slots);
+ return;
+ }
+ }
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ if (slots[j]) {
+ int retval;
+
+ /* If this slot was set as importing we can clear this
+ * state as now we are the real owner of the slot. */
+ if (server.cluster->importing_slots_from[j])
+ server.cluster->importing_slots_from[j] = NULL;
+
+ retval = del ? clusterDelSlot(j) :
+ clusterAddSlot(myself, j);
+ serverAssertWithInfo(c, NULL, retval == C_OK);
+ }
+ }
+ zfree(slots);
+ clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_SAVE_CONFIG);
+ addReply(c, shared.ok);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "setslot") && c->argc >= 4) {
+ /* SETSLOT 10 MIGRATING */
+ /* SETSLOT 10 IMPORTING */
+ /* SETSLOT 10 STABLE */
+ /* SETSLOT 10 NODE */
+ int slot;
+ clusterNode *n;
+
+ if (nodeIsSlave(myself)) {
+ addReplyError(c, "Please use SETSLOT only with masters.");
+ return;
+ }
+
+ if ((slot = getSlotOrReply(c, c->argv[2])) == -1) return;
+
+ if (!strcasecmp(c->argv[3]->ptr, "migrating") && c->argc == 5) {
+ if (server.cluster->slots[slot] != myself) {
+ addReplyErrorFormat(c, "I'm not the owner of hash slot %u", slot);
+ return;
+ }
+ if ((n = clusterLookupNode(c->argv[4]->ptr)) == NULL) {
+ addReplyErrorFormat(c, "I don't know about node %s",
+ (char*) c->argv[4]->ptr);
+ return;
+ }
+ server.cluster->migrating_slots_to[slot] = n;
+ }
+ else if (!strcasecmp(c->argv[3]->ptr, "importing") && c->argc == 5) {
+ if (server.cluster->slots[slot] == myself) {
+ addReplyErrorFormat(c,
+ "I'm already the owner of hash slot %u", slot);
+ return;
+ }
+ if ((n = clusterLookupNode(c->argv[4]->ptr)) == NULL) {
+ addReplyErrorFormat(c, "I don't know about node %s",
+ (char*) c->argv[4]->ptr);
+ return;
+ }
+ server.cluster->importing_slots_from[slot] = n;
+ }
+ else if (!strcasecmp(c->argv[3]->ptr, "stable") && c->argc == 4) {
+ /* CLUSTER SETSLOT STABLE */
+ server.cluster->importing_slots_from[slot] = NULL;
+ server.cluster->migrating_slots_to[slot] = NULL;
+ }
+ else if (!strcasecmp(c->argv[3]->ptr, "node") && c->argc == 5) {
+ /* CLUSTER SETSLOT NODE */
+ clusterNode *n = clusterLookupNode(c->argv[4]->ptr);
+
+ if (!n) {
+ addReplyErrorFormat(c, "Unknown node %s",
+ (char*) c->argv[4]->ptr);
+ return;
+ }
+ /* If this hash slot was served by 'myself' before to switch
+ * make sure there are no longer local keys for this hash slot. */
+ if (server.cluster->slots[slot] == myself && n != myself) {
+ if (countKeysInSlot(slot) != 0) {
+ addReplyErrorFormat(c,
+ "Can't assign hashslot %d to a different node "
+ "while I still hold keys for this hash slot.", slot);
+ return;
+ }
+ }
+ /* If this slot is in migrating status but we have no keys
+ * for it assigning the slot to another node will clear
+ * the migratig status. */
+ if (countKeysInSlot(slot) == 0 &&
+ server.cluster->migrating_slots_to[slot])
+ server.cluster->migrating_slots_to[slot] = NULL;
+
+ /* If this node was importing this slot, assigning the slot to
+ * itself also clears the importing status. */
+ if (n == myself &&
+ server.cluster->importing_slots_from[slot])
+ {
+ /* This slot was manually migrated, set this node configEpoch
+ * to a new epoch so that the new version can be propagated
+ * by the cluster.
+ *
+ * Note that if this ever results in a collision with another
+ * node getting the same configEpoch, for example because a
+ * failover happens at the same time we close the slot, the
+ * configEpoch collision resolution will fix it assigning
+ * a different epoch to each node. */
+ if (clusterBumpConfigEpochWithoutConsensus() == C_OK) {
+ serverLog(LL_WARNING,
+ "configEpoch updated after importing slot %d", slot);
+ }
+ server.cluster->importing_slots_from[slot] = NULL;
+ }
+ clusterDelSlot(slot);
+ clusterAddSlot(n, slot);
+ }
+ else {
+ addReplyError(c,
+ "Invalid CLUSTER SETSLOT action or number of arguments");
+ return;
+ }
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_UPDATE_STATE);
+ addReply(c, shared.ok);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "bumpepoch") && c->argc == 2) {
+ /* CLUSTER BUMPEPOCH */
+ int retval = clusterBumpConfigEpochWithoutConsensus();
+ sds reply = sdscatprintf(sdsempty(), "+%s %llu\r\n",
+ (retval == C_OK) ? "BUMPED" : "STILL",
+ (PORT_ULONGLONG) myself->configEpoch);
+ addReplySds(c, reply);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "info") && c->argc == 2) {
+ /* CLUSTER INFO */
+ char *statestr[] = { "ok","fail","needhelp" };
+ int slots_assigned = 0, slots_ok = 0, slots_pfail = 0, slots_fail = 0;
+ uint64_t myepoch;
+ int j;
+
+ for (j = 0; j < CLUSTER_SLOTS; j++) {
+ clusterNode *n = server.cluster->slots[j];
+
+ if (n == NULL) continue;
+ slots_assigned++;
+ if (nodeFailed(n)) {
+ slots_fail++;
+ }
+ else if (nodeTimedOut(n)) {
+ slots_pfail++;
+ }
+ else {
+ slots_ok++;
+ }
+ }
+
+ myepoch = (nodeIsSlave(myself) && myself->slaveof) ?
+ myself->slaveof->configEpoch : myself->configEpoch;
+
+ sds info = sdscatprintf(sdsempty(),
+ "cluster_state:%s\r\n"
+ "cluster_slots_assigned:%d\r\n"
+ "cluster_slots_ok:%d\r\n"
+ "cluster_slots_pfail:%d\r\n"
+ "cluster_slots_fail:%d\r\n"
+ "cluster_known_nodes:%Iu\r\n" WIN_PORT_FIX /* %lu -> %Iu */
+ "cluster_size:%d\r\n"
+ "cluster_current_epoch:%llu\r\n"
+ "cluster_my_epoch:%llu\r\n"
+ , statestr[server.cluster->state],
+ slots_assigned,
+ slots_ok,
+ slots_pfail,
+ slots_fail,
+ dictSize(server.cluster->nodes),
+ server.cluster->size,
+ (PORT_ULONGLONG) server.cluster->currentEpoch,
+ (PORT_ULONGLONG) myepoch
+ );
+
+ /* Show stats about messages sent and received. */
+ PORT_LONGLONG tot_msg_sent = 0;
+ PORT_LONGLONG tot_msg_received = 0;
+
+ for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) {
+ if (server.cluster->stats_bus_messages_sent[i] == 0) continue;
+ tot_msg_sent += server.cluster->stats_bus_messages_sent[i];
+ info = sdscatprintf(info,
+ "cluster_stats_messages_%s_sent:%lld\r\n",
+ clusterGetMessageTypeString(i),
+ server.cluster->stats_bus_messages_sent[i]);
+ }
+ info = sdscatprintf(info,
+ "cluster_stats_messages_sent:%lld\r\n", tot_msg_sent);
+
+ for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) {
+ if (server.cluster->stats_bus_messages_received[i] == 0) continue;
+ tot_msg_received += server.cluster->stats_bus_messages_received[i];
+ info = sdscatprintf(info,
+ "cluster_stats_messages_%s_received:%lld\r\n",
+ clusterGetMessageTypeString(i),
+ server.cluster->stats_bus_messages_received[i]);
+ }
+ info = sdscatprintf(info,
+ "cluster_stats_messages_received:%lld\r\n", tot_msg_received);
+
+ /* Produce the reply protocol. */
+ addReplySds(c, sdscatprintf(sdsempty(), "$%Iu\r\n", WIN_PORT_FIX /* %lu -> %Iu */
+ (PORT_ULONG)sdslen(info)));
+ addReplySds(c, info);
+ addReply(c, shared.crlf);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "saveconfig") && c->argc == 2) {
+ int retval = clusterSaveConfig(1);
+
+ if (retval == 0)
+ addReply(c, shared.ok);
+ else
+ addReplyErrorFormat(c, "error saving the cluster node config: %s",
+ IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "keyslot") && c->argc == 3) {
+ /* CLUSTER KEYSLOT */
+ sds key = c->argv[2]->ptr;
+
+ addReplyLongLong(c, keyHashSlot(key, (int) sdslen(key))); WIN_PORT_FIX /* cast (int) */
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "countkeysinslot") && c->argc == 3) {
+ /* CLUSTER COUNTKEYSINSLOT */
+ PORT_LONGLONG slot;
+
+ if (getLongLongFromObjectOrReply(c, c->argv[2], &slot, NULL) != C_OK)
+ return;
+ if (slot < 0 || slot >= CLUSTER_SLOTS) {
+ addReplyError(c, "Invalid slot");
+ return;
+ }
+ addReplyLongLong(c, countKeysInSlot((unsigned int) slot)); WIN_PORT_FIX /* cast (unsigned int) */
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "getkeysinslot") && c->argc == 4) {
+ /* CLUSTER GETKEYSINSLOT */
+ PORT_LONGLONG maxkeys, slot;
+ unsigned int numkeys, j;
+ robj **keys;
+
+ if (getLongLongFromObjectOrReply(c, c->argv[2], &slot, NULL) != C_OK)
+ return;
+ if (getLongLongFromObjectOrReply(c, c->argv[3], &maxkeys, NULL)
+ != C_OK)
+ return;
+ if (slot < 0 || slot >= CLUSTER_SLOTS || maxkeys < 0) {
+ addReplyError(c, "Invalid slot or number of keys");
+ return;
+ }
+
+ /* Avoid allocating more than needed in case of large COUNT argument
+ * and smaller actual number of keys. */
+ unsigned int keys_in_slot = countKeysInSlot(slot);
+ if (maxkeys > keys_in_slot) maxkeys = keys_in_slot;
+
+ keys = zmalloc(sizeof(robj*)*maxkeys);
+ numkeys = getKeysInSlot((unsigned int) slot, keys, (unsigned int) maxkeys); WIN_PORT_FIX /* cast (unsigned int) */
+ addReplyMultiBulkLen(c, numkeys);
+ for (j = 0; j < numkeys; j++) {
+ addReplyBulk(c, keys[j]);
+ decrRefCount(keys[j]);
+ }
+ zfree(keys);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "forget") && c->argc == 3) {
+ /* CLUSTER FORGET */
+ clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
+
+ if (!n) {
+ addReplyErrorFormat(c, "Unknown node %s", (char*) c->argv[2]->ptr);
+ return;
+ }
+ else if (n == myself) {
+ addReplyError(c, "I tried hard but I can't forget myself...");
+ return;
+ }
+ else if (nodeIsSlave(myself) && myself->slaveof == n) {
+ addReplyError(c, "Can't forget my master!");
+ return;
+ }
+ clusterBlacklistAddNode(n);
+ clusterDelNode(n);
+ clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE |
+ CLUSTER_TODO_SAVE_CONFIG);
+ addReply(c, shared.ok);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "replicate") && c->argc == 3) {
+ /* CLUSTER REPLICATE */
+ clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
+
+ /* Lookup the specified node in our table. */
+ if (!n) {
+ addReplyErrorFormat(c, "Unknown node %s", (char*) c->argv[2]->ptr);
+ return;
+ }
+
+ /* I can't replicate myself. */
+ if (n == myself) {
+ addReplyError(c, "Can't replicate myself");
+ return;
+ }
+
+ /* Can't replicate a slave. */
+ if (nodeIsSlave(n)) {
+ addReplyError(c, "I can only replicate a master, not a slave.");
+ return;
+ }
+
+ /* If the instance is currently a master, it should have no assigned
+ * slots nor keys to accept to replicate some other node.
+ * Slaves can switch to another master without issues. */
+ if (nodeIsMaster(myself) &&
+ (myself->numslots != 0 || dictSize(server.db[0].dict) != 0)) {
+ addReplyError(c,
+ "To set a master the node must be empty and "
+ "without assigned slots.");
+ return;
+ }
+
+ /* Set the master. */
+ clusterSetMaster(n);
+ clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_SAVE_CONFIG);
+ addReply(c, shared.ok);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "slaves") && c->argc == 3) {
+ /* CLUSTER SLAVES */
+ clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
+ int j;
+
+ /* Lookup the specified node in our table. */
+ if (!n) {
+ addReplyErrorFormat(c, "Unknown node %s", (char*) c->argv[2]->ptr);
+ return;
+ }
+
+ if (nodeIsSlave(n)) {
+ addReplyError(c, "The specified node is not a master");
+ return;
+ }
+
+ addReplyMultiBulkLen(c, n->numslaves);
+ for (j = 0; j < n->numslaves; j++) {
+ sds ni = clusterGenNodeDescription(n->slaves[j]);
+ addReplyBulkCString(c, ni);
+ sdsfree(ni);
+ }
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "count-failure-reports") &&
+ c->argc == 3)
+ {
+ /* CLUSTER COUNT-FAILURE-REPORTS */
+ clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
+
+ if (!n) {
+ addReplyErrorFormat(c, "Unknown node %s", (char*) c->argv[2]->ptr);
+ return;
+ }
+ else {
+ addReplyLongLong(c, clusterNodeFailureReportsCount(n));
+ }
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "failover") &&
+ (c->argc == 2 || c->argc == 3))
+ {
+ /* CLUSTER FAILOVER [FORCE|TAKEOVER] */
+ int force = 0, takeover = 0;
+
+ if (c->argc == 3) {
+ if (!strcasecmp(c->argv[2]->ptr, "force")) {
+ force = 1;
+ }
+ else if (!strcasecmp(c->argv[2]->ptr, "takeover")) {
+ takeover = 1;
+ force = 1; /* Takeover also implies force. */
+ }
+ else {
+ addReply(c, shared.syntaxerr);
+ return;
+ }
+ }
+
+ /* Check preconditions. */
+ if (nodeIsMaster(myself)) {
+ addReplyError(c, "You should send CLUSTER FAILOVER to a slave");
+ return;
+ }
+ else if (myself->slaveof == NULL) {
+ addReplyError(c, "I'm a slave but my master is unknown to me");
+ return;
+ }
+ else if (!force &&
+ (nodeFailed(myself->slaveof) ||
+ myself->slaveof->link == NULL))
+ {
+ addReplyError(c, "Master is down or failed, "
+ "please use CLUSTER FAILOVER FORCE");
+ return;
+ }
+ resetManualFailover();
+ server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
+
+ if (takeover) {
+ /* A takeover does not perform any initial check. It just
+ * generates a new configuration epoch for this node without
+ * consensus, claims the master's slots, and broadcast the new
+ * configuration. */
+ serverLog(LL_WARNING, "Taking over the master (user request).");
+ clusterBumpConfigEpochWithoutConsensus();
+ clusterFailoverReplaceYourMaster();
+ }
+ else if (force) {
+ /* If this is a forced failover, we don't need to talk with our
+ * master to agree about the offset. We just failover taking over
+ * it without coordination. */
+ serverLog(LL_WARNING, "Forced failover user request accepted.");
+ server.cluster->mf_can_start = 1;
+ }
+ else {
+ serverLog(LL_WARNING, "Manual failover user request accepted.");
+ clusterSendMFStart(myself->slaveof);
+ }
+ addReply(c, shared.ok);
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "set-config-epoch") && c->argc == 3)
+ {
+ /* CLUSTER SET-CONFIG-EPOCH
+ *
+ * The user is allowed to set the config epoch only when a node is
+ * totally fresh: no config epoch, no other known node, and so forth.
+ * This happens at cluster creation time to start with a cluster where
+ * every node has a different node ID, without to rely on the conflicts
+ * resolution system which is too slow when a big cluster is created. */
+ PORT_LONGLONG epoch;
+
+ if (getLongLongFromObjectOrReply(c, c->argv[2], &epoch, NULL) != C_OK)
+ return;
+
+ if (epoch < 0) {
+ addReplyErrorFormat(c, "Invalid config epoch specified: %lld", epoch);
+ }
+ else if (dictSize(server.cluster->nodes) > 1) {
+ addReplyError(c, "The user can assign a config epoch only when the "
+ "node does not know any other node.");
+ }
+ else if (myself->configEpoch != 0) {
+ addReplyError(c, "Node config epoch is already non-zero");
+ }
+ else {
+ myself->configEpoch = epoch;
+ serverLog(LL_WARNING,
+ "configEpoch set to %llu via CLUSTER SET-CONFIG-EPOCH",
+ (PORT_ULONGLONG) myself->configEpoch);
+
+ if (server.cluster->currentEpoch < (uint64_t) epoch)
+ server.cluster->currentEpoch = epoch;
+ /* No need to fsync the config here since in the unlucky event
+ * of a failure to persist the config, the conflict resolution code
+ * will assign an unique config to this node. */
+ clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE |
+ CLUSTER_TODO_SAVE_CONFIG);
+ addReply(c, shared.ok);
+ }
+ }
+ else if (!strcasecmp(c->argv[1]->ptr, "reset") &&
+ (c->argc == 2 || c->argc == 3))
+ {
+ /* CLUSTER RESET [SOFT|HARD] */
+ int hard = 0;
+
+ /* Parse soft/hard argument. Default is soft. */
+ if (c->argc == 3) {
+ if (!strcasecmp(c->argv[2]->ptr, "hard")) {
+ hard = 1;
+ }
+ else if (!strcasecmp(c->argv[2]->ptr, "soft")) {
+ hard = 0;
+ }
+ else {
+ addReply(c, shared.syntaxerr);
+ return;
+ }
+ }
+
+ /* Slaves can be reset while containing data, but not master nodes
+ * that must be empty. */
+ if (nodeIsMaster(myself) && dictSize(c->db->dict) != 0) {
+ addReplyError(c, "CLUSTER RESET can't be called with "
+ "master nodes containing keys");
+ return;
+ }
+ clusterReset(hard);
+ addReply(c, shared.ok);
+ }
+ else {
+ addReplyError(c, "Wrong CLUSTER subcommand or number of arguments");
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * DUMP, RESTORE and MIGRATE commands
+ * -------------------------------------------------------------------------- */
+
+ /* Generates a DUMP-format representation of the object 'o', adding it to the
+ * io stream pointed by 'rio'. This function can't fail. */
+void createDumpPayload(rio *payload, robj *o) {
+ unsigned char buf[2];
+ uint64_t crc;
+
+ /* Serialize the object in a RDB-like format. It consist of an object type
+ * byte followed by the serialized object. This is understood by RESTORE. */
+ rioInitWithBuffer(payload, sdsempty());
+ serverAssert(rdbSaveObjectType(payload, o));
+ serverAssert(rdbSaveObject(payload, o));
+
+ /* Write the footer, this is how it looks like:
+ * ----------------+---------------------+---------------+
+ * ... RDB payload | 2 bytes RDB version | 8 bytes CRC64 |
+ * ----------------+---------------------+---------------+
+ * RDB version and CRC are both in little endian.
+ */
+
+ /* RDB version */
+ buf[0] = RDB_VERSION & 0xff;
+ buf[1] = (RDB_VERSION >> 8) & 0xff;
+ payload->io.buffer.ptr = sdscatlen(payload->io.buffer.ptr, buf, 2);
+
+ /* CRC64 */
+ crc = crc64(0, (unsigned char*) payload->io.buffer.ptr,
+ sdslen(payload->io.buffer.ptr));
+ memrev64ifbe(&crc);
+ payload->io.buffer.ptr = sdscatlen(payload->io.buffer.ptr, &crc, 8);
+}
+
+/* Verify that the RDB version of the dump payload matches the one of this Redis
+ * instance and that the checksum is ok.
+ * If the DUMP payload looks valid C_OK is returned, otherwise C_ERR
+ * is returned. */
+int verifyDumpPayload(unsigned char *p, size_t len) {
+ unsigned char *footer;
+ uint16_t rdbver;
+ uint64_t crc;
+
+ /* At least 2 bytes of RDB version and 8 of CRC64 should be present. */
+ if (len < 10) return C_ERR;
+ footer = p + (len - 10);
+
+ /* Verify RDB version */
+ rdbver = (footer[1] << 8) | footer[0];
+ if (rdbver > RDB_VERSION) return C_ERR;
+
+ /* Verify CRC64 */
+ crc = crc64(0, p, len - 8);
+ memrev64ifbe(&crc);
+ return (memcmp(&crc, footer + 2, 8) == 0) ? C_OK : C_ERR;
+}
+
+/* DUMP keyname
+ * DUMP is actually not used by Redis Cluster but it is the obvious
+ * complement of RESTORE and can be useful for different applications. */
+void dumpCommand(client *c) {
+ robj *o, *dumpobj;
+ rio payload;
+
+ /* Check if the key is here. */
+ if ((o = lookupKeyRead(c->db, c->argv[1])) == NULL) {
+ addReply(c, shared.nullbulk);
+ return;
+ }
+
+ /* Create the DUMP encoded representation. */
+ createDumpPayload(&payload, o);
+
+ /* Transfer to the client */
+ dumpobj = createObject(OBJ_STRING, payload.io.buffer.ptr);
+ addReplyBulk(c, dumpobj);
+ decrRefCount(dumpobj);
+ return;
+}
+
+/* RESTORE key ttl serialized-value [REPLACE] */
+void restoreCommand(client *c) {
+ PORT_LONGLONG ttl;
+ rio payload;
+ int j, type, replace = 0;
+ robj *obj;
+
+ /* Parse additional options */
+ for (j = 4; j < c->argc; j++) {
+ if (!strcasecmp(c->argv[j]->ptr, "replace")) {
+ replace = 1;
+ }
+ else {
+ addReply(c, shared.syntaxerr);
+ return;
+ }
+ }
+
+ /* Make sure this key does not already exist here... */
+ if (!replace && lookupKeyWrite(c->db, c->argv[1]) != NULL) {
+ addReply(c, shared.busykeyerr);
+ return;
+ }
+
+ /* Check if the TTL value makes sense */
+ if (getLongLongFromObjectOrReply(c, c->argv[2], &ttl, NULL) != C_OK) {
+ return;
+ }
+ else if (ttl < 0) {
+ addReplyError(c, "Invalid TTL value, must be >= 0");
+ return;
+ }
+
+ /* Verify RDB version and data checksum. */
+ if (verifyDumpPayload(c->argv[3]->ptr, sdslen(c->argv[3]->ptr)) == C_ERR)
+ {
+ addReplyError(c, "DUMP payload version or checksum are wrong");
+ return;
+ }
+
+ rioInitWithBuffer(&payload, c->argv[3]->ptr);
+ if (((type = rdbLoadObjectType(&payload)) == -1) ||
+ ((obj = rdbLoadObject(type, &payload)) == NULL))
+ {
+ addReplyError(c, "Bad data format");
+ return;
+ }
+
+ /* Remove the old key if needed. */
+ if (replace) dbDelete(c->db, c->argv[1]);
+
+ /* Create the key and set the TTL if any */
+ dbAdd(c->db, c->argv[1], obj);
+ if (ttl) setExpire(c, c->db, c->argv[1], mstime() + ttl);
+ signalModifiedKey(c->db, c->argv[1]);
+ addReply(c, shared.ok);
+ server.dirty++;
+}
+
+/* MIGRATE socket cache implementation.
+ *
+ * We take a map between host:ip and a TCP socket that we used to connect
+ * to this instance in recent time.
+ * This sockets are closed when the max number we cache is reached, and also
+ * in serverCron() when they are around for more than a few seconds. */
+#define MIGRATE_SOCKET_CACHE_ITEMS 64 /* max num of items in the cache. */
+#define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */
+
+typedef struct migrateCachedSocket {
+ int fd;
+ PORT_LONG last_dbid;
+ time_t last_use_time;
+} migrateCachedSocket;
+
+/* Return a migrateCachedSocket containing a TCP socket connected with the
+ * target instance, possibly returning a cached one.
+ *
+ * This function is responsible of sending errors to the client if a
+ * connection can't be established. In this case -1 is returned.
+ * Otherwise on success the socket is returned, and the caller should not
+ * attempt to free it after usage.
+ *
+ * If the caller detects an error while using the socket, migrateCloseSocket()
+ * should be called so that the connection will be created from scratch
+ * the next time. */
+migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, PORT_LONG timeout) {
+ int fd;
+ sds name = sdsempty();
+ migrateCachedSocket *cs;
+
+ /* Check if we have an already cached socket for this ip:port pair. */
+ name = sdscatlen(name, host->ptr, sdslen(host->ptr));
+ name = sdscatlen(name, ":", 1);
+ name = sdscatlen(name, port->ptr, sdslen(port->ptr));
+ cs = dictFetchValue(server.migrate_cached_sockets, name);
+ if (cs) {
+ sdsfree(name);
+ cs->last_use_time = server.unixtime;
+ return cs;
+ }
+
+ /* No cached socket, create one. */
+ if (dictSize(server.migrate_cached_sockets) == MIGRATE_SOCKET_CACHE_ITEMS) {
+ /* Too many items, drop one at random. */
+ dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets);
+ cs = dictGetVal(de);
+ close(cs->fd);
+ zfree(cs);
+ dictDelete(server.migrate_cached_sockets, dictGetKey(de));
+ }
+
+ /* Create the socket */
+ fd = anetTcpNonBlockConnect(server.neterr, c->argv[1]->ptr,
+ atoi(c->argv[2]->ptr));
+ if (fd == -1) {
+ sdsfree(name);
+ addReplyErrorFormat(c, "Can't connect to target node: %s",
+ server.neterr);
+ return NULL;
+ }
+ anetEnableTcpNoDelay(server.neterr, fd);
+
+ /* Check if it connects within the specified timeout. */
+ if ((aeWait(fd, AE_WRITABLE, timeout) & AE_WRITABLE) == 0) {
+ sdsfree(name);
+ addReplySds(c,
+ sdsnew("-IOERR error or timeout connecting to the client\r\n"));
+ close(fd);
+ return NULL;
+ }
+
+ /* Add to the cache and return it to the caller. */
+ cs = zmalloc(sizeof(*cs));
+ cs->fd = fd;
+ cs->last_dbid = -1;
+ cs->last_use_time = server.unixtime;
+ dictAdd(server.migrate_cached_sockets, name, cs);
+ return cs;
+}
+
+/* Free a migrate cached connection. */
+void migrateCloseSocket(robj *host, robj *port) {
+ sds name = sdsempty();
+ migrateCachedSocket *cs;
+
+ name = sdscatlen(name, host->ptr, sdslen(host->ptr));
+ name = sdscatlen(name, ":", 1);
+ name = sdscatlen(name, port->ptr, sdslen(port->ptr));
+ cs = dictFetchValue(server.migrate_cached_sockets, name);
+ if (!cs) {
+ sdsfree(name);
+ return;
+ }
+
+ close(cs->fd);
+ zfree(cs);
+ dictDelete(server.migrate_cached_sockets, name);
+ sdsfree(name);
+}
+
+void migrateCloseTimedoutSockets(void) {
+ dictIterator *di = dictGetSafeIterator(server.migrate_cached_sockets);
+ dictEntry *de;
+
+ while ((de = dictNext(di)) != NULL) {
+ migrateCachedSocket *cs = dictGetVal(de);
+
+ if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) {
+ close(cs->fd);
+ zfree(cs);
+ dictDelete(server.migrate_cached_sockets, dictGetKey(de));
+ }
+ }
+ dictReleaseIterator(di);
+}
+
+/* MIGRATE host port key dbid timeout [COPY | REPLACE | AUTH password]
+ *
+ * On in the multiple keys form:
+ *
+ * MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password] KEYS key1
+ * key2 ... keyN */
+void migrateCommand(client *c) {
+ migrateCachedSocket *cs;
+ int copy=0, replace=0, j;
+ char *password = NULL;
+ PORT_LONG timeout;
+ PORT_LONG dbid;
+ robj **ov = NULL; /* Objects to migrate. */
+ robj **kv = NULL; /* Key names. */
+ robj **newargv = NULL; /* Used to rewrite the command as DEL ... keys ... */
+ rio cmd, payload;
+ int may_retry = 1;
+ int write_error = 0;
+ int argv_rewritten = 0;
+
+ /* To support the KEYS option we need the following additional state. */
+ int first_key = 3; /* Argument index of the first key. */
+ int num_keys = 1; /* By default only migrate the 'key' argument. */
+
+ /* Parse additional options */
+ for (j = 6; j < c->argc; j++) {
+ int moreargs = j < c->argc-1;
+ if (!strcasecmp(c->argv[j]->ptr, "copy")) {
+ copy = 1;
+ } else if (!strcasecmp(c->argv[j]->ptr,"replace")) {
+ replace = 1;
+ } else if (!strcasecmp(c->argv[j]->ptr,"auth")) {
+ if (!moreargs) {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+ j++;
+ password = c->argv[j]->ptr;
+ } else if (!strcasecmp(c->argv[j]->ptr,"keys")) {
+ if (sdslen(c->argv[3]->ptr) != 0) {
+ addReplyError(c,
+ "When using MIGRATE KEYS option, the key argument"
+ " must be set to the empty string");
+ return;
+ }
+ first_key = j + 1;
+ num_keys = c->argc - j - 1;
+ break; /* All the remaining args are keys. */
+ }
+ else {
+ addReply(c, shared.syntaxerr);
+ return;
+ }
+ }
+
+ /* Sanity check */
+ if (getLongFromObjectOrReply(c, c->argv[5], &timeout, NULL) != C_OK ||
+ getLongFromObjectOrReply(c, c->argv[4], &dbid, NULL) != C_OK)
+ {
+ return;
+ }
+ if (timeout <= 0) timeout = 1000;
+
+ /* Check if the keys are here. If at least one key is to migrate, do it
+ * otherwise if all the keys are missing reply with "NOKEY" to signal
+ * the caller there was nothing to migrate. We don't return an error in
+ * this case, since often this is due to a normal condition like the key
+ * expiring in the meantime. */
+ ov = zrealloc(ov, sizeof(robj*)*num_keys);
+ kv = zrealloc(kv, sizeof(robj*)*num_keys);
+ int oi = 0;
+
+ for (j = 0; j < num_keys; j++) {
+ if ((ov[oi] = lookupKeyRead(c->db, c->argv[first_key + j])) != NULL) {
+ kv[oi] = c->argv[first_key + j];
+ oi++;
+ }
+ }
+ num_keys = oi;
+ if (num_keys == 0) {
+ zfree(ov); zfree(kv);
+ addReplySds(c, sdsnew("+NOKEY\r\n"));
+ return;
+ }
+
+try_again:
+ write_error = 0;
+
+ /* Connect */
+ cs = migrateGetSocket(c, c->argv[1], c->argv[2], timeout);
+ if (cs == NULL) {
+ zfree(ov); zfree(kv);
+ return; /* error sent to the client by migrateGetSocket() */
+ }
+
+ rioInitWithBuffer(&cmd, sdsempty());
+
+ /* Authentication */
+ if (password) {
+ serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2));
+ serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"AUTH",4));
+ serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,password,
+ sdslen(password)));
+ }
+
+ /* Send the SELECT command if the current DB is not already selected. */
+ int select = cs->last_dbid != dbid; /* Should we emit SELECT? */
+ if (select) {
+ serverAssertWithInfo(c, NULL, rioWriteBulkCount(&cmd, '*', 2));
+ serverAssertWithInfo(c, NULL, rioWriteBulkString(&cmd, "SELECT", 6));
+ serverAssertWithInfo(c, NULL, rioWriteBulkLongLong(&cmd, dbid));
+ }
+
+ /* Create RESTORE payload and generate the protocol to call the command. */
+ for (j = 0; j < num_keys; j++) {
+ PORT_LONGLONG ttl = 0;
+ PORT_LONGLONG expireat = getExpire(c->db, kv[j]);
+
+ if (expireat != -1) {
+ ttl = expireat - mstime();
+ if (ttl < 1) ttl = 1;
+ }
+ serverAssertWithInfo(c,NULL,
+ rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
+
+ if (server.cluster_enabled)
+ serverAssertWithInfo(c, NULL,
+ rioWriteBulkString(&cmd, "RESTORE-ASKING", 14));
+ else
+ serverAssertWithInfo(c, NULL, rioWriteBulkString(&cmd, "RESTORE", 7));
+ serverAssertWithInfo(c, NULL, sdsEncodedObject(kv[j]));
+ serverAssertWithInfo(c, NULL, rioWriteBulkString(&cmd, kv[j]->ptr,
+ sdslen(kv[j]->ptr)));
+ serverAssertWithInfo(c, NULL, rioWriteBulkLongLong(&cmd, ttl));
+
+ /* Emit the payload argument, that is the serialized object using
+ * the DUMP format. */
+ createDumpPayload(&payload, ov[j]);
+ serverAssertWithInfo(c, NULL,
+ rioWriteBulkString(&cmd, payload.io.buffer.ptr,
+ sdslen(payload.io.buffer.ptr)));
+ sdsfree(payload.io.buffer.ptr);
+
+ /* Add the REPLACE option to the RESTORE command if it was specified
+ * as a MIGRATE option. */
+ if (replace)
+ serverAssertWithInfo(c, NULL, rioWriteBulkString(&cmd, "REPLACE", 7));
+ }
+
+ /* Transfer the query to the other node in 64K chunks. */
+ errno = 0;
+ {
+ sds buf = cmd.io.buffer.ptr;
+ size_t pos = 0, towrite;
+ int nwritten = 0;
+
+#ifdef _WIN32
+ while ((towrite = sdslen(buf) - pos) > 0) {
+ towrite = (towrite > (64 * 1024) ? (64 * 1024) : towrite);
+ while (nwritten != (signed) towrite) {
+ nwritten = (int) syncWrite(cs->fd, buf + pos, (ssize_t) towrite, timeout);
+ if (nwritten != (signed) towrite) {
+ DWORD err = GetLastError();
+ if (err == WSAEWOULDBLOCK) {
+ // Likely send buffer is full. A short delay or two is sufficient to allow this to work.
+ serverLog(LL_VERBOSE, "In migrate. WSAEWOULDBLOCK with synchronous socket: sleeping for 0.1s");
+ Sleep(100);
+ }
+ else {
+ serverLog(LL_WARNING, "SyncWrite failure toWrite=%d written=%d err=%d timeout=%d ", towrite, nwritten, GetLastError(), timeout);
+ goto socket_err;
+ }
+ }
+ }
+ pos += nwritten;
+ nwritten = 0;
+ }
+#else
+ while ((towrite = sdslen(buf) - pos) > 0) {
+ towrite = (towrite > (64 * 1024) ? (64 * 1024) : towrite);
+ nwritten = syncWrite(cs->fd, buf + pos, towrite, timeout);
+ if (nwritten != (signed) towrite) {
+ write_error = 1;
+ goto socket_err;
+ }
+ pos += nwritten;
+ }
+#endif
+ }
+
+ char buf0[1024]; /* Auth reply. */
+ char buf1[1024]; /* Select reply. */
+ char buf2[1024]; /* Restore reply. */
+
+ /* Read the AUTH reply if needed. */
+ if (password && syncReadLine(cs->fd, buf0, sizeof(buf0), timeout) <= 0)
+ goto socket_err;
+
+ /* Read the SELECT reply if needed. */
+ if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0)
+ goto socket_err;
+
+ /* Read the RESTORE replies. */
+ int error_from_target = 0;
+ int socket_error = 0;
+ int del_idx = 1; /* Index of the key argument for the replicated DEL op. */
+
+ if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys + 1));
+
+ for (j = 0; j < num_keys; j++) {
+ if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) {
+ socket_error = 1;
+ break;
+ }
+ if ((password && buf0[0] == '-') ||
+ (select && buf1[0] == '-') ||
+ buf2[0] == '-')
+ {
+ /* On error assume that last_dbid is no longer valid. */
+ if (!error_from_target) {
+ cs->last_dbid = -1;
+ char *errbuf;
+ if (password && buf0[0] == '-') errbuf = buf0;
+ else if (select && buf1[0] == '-') errbuf = buf1;
+ else errbuf = buf2;
+
+ error_from_target = 1;
+ addReplyErrorFormat(c,"Target instance replied with error: %s",
+ errbuf+1);
+ }
+ }
+ else {
+ if (!copy) {
+ /* No COPY option: remove the local key, signal the change. */
+ dbDelete(c->db, kv[j]);
+ signalModifiedKey(c->db, kv[j]);
+ server.dirty++;
+
+ /* Populate the argument vector to replace the old one. */
+ newargv[del_idx++] = kv[j];
+ incrRefCount(kv[j]);
+ }
+ }
+ }
+
+ /* On socket error, if we want to retry, do it now before rewriting the
+ * command vector. We only retry if we are sure nothing was processed
+ * and we failed to read the first reply (j == 0 test). */
+ if (!error_from_target && socket_error && j == 0 && may_retry &&
+ errno != ETIMEDOUT)
+ {
+ goto socket_err; /* A retry is guaranteed because of tested conditions.*/
+ }
+
+ /* On socket errors, close the migration socket now that we still have
+ * the original host/port in the ARGV. Later the original command may be
+ * rewritten to DEL and will be too later. */
+ if (socket_error) migrateCloseSocket(c->argv[1], c->argv[2]);
+
+ if (!copy) {
+ /* Translate MIGRATE as DEL for replication/AOF. Note that we do
+ * this only for the keys for which we received an acknowledgement
+ * from the receiving Redis server, by using the del_idx index. */
+ if (del_idx > 1) {
+ newargv[0] = createStringObject("DEL", 3);
+ /* Note that the following call takes ownership of newargv. */
+ replaceClientCommandVector(c, del_idx, newargv);
+ argv_rewritten = 1;
+ }
+ else {
+ /* No key transfer acknowledged, no need to rewrite as DEL. */
+ zfree(newargv);
+ }
+ newargv = NULL; /* Make it safe to call zfree() on it in the future. */
+ }
+
+ /* If we are here and a socket error happened, we don't want to retry.
+ * Just signal the problem to the client, but only do it if we did not
+ * already queue a different error reported by the destination server. */
+ if (!error_from_target && socket_error) {
+ may_retry = 0;
+ goto socket_err;
+ }
+
+ if (!error_from_target) {
+ /* Success! Update the last_dbid in migrateCachedSocket, so that we can
+ * avoid SELECT the next time if the target DB is the same. Reply +OK.
+ *
+ * Note: If we reached this point, even if socket_error is true
+ * still the SELECT command succeeded (otherwise the code jumps to
+ * socket_err label. */
+ cs->last_dbid = dbid;
+ addReply(c, shared.ok);
+ }
+ else {
+ /* On error we already sent it in the for loop above, and set
+ * the currently selected socket to -1 to force SELECT the next time. */
+ }
+
+ sdsfree(cmd.io.buffer.ptr);
+ zfree(ov); zfree(kv); zfree(newargv);
+ return;
+
+ /* On socket errors we try to close the cached socket and try again.
+ * It is very common for the cached socket to get closed, if just reopening
+ * it works it's a shame to notify the error to the caller. */
+socket_err:
+#ifdef _WIN32
+ serverLog(LL_WARNING, "syncReadLine failure err=%d timeout=%d ", GetLastError(), timeout);
+#endif
+ /* Cleanup we want to perform in both the retry and no retry case.
+ * Note: Closing the migrate socket will also force SELECT next time. */
+ sdsfree(cmd.io.buffer.ptr);
+
+ /* If the command was rewritten as DEL and there was a socket error,
+ * we already closed the socket earlier. While migrateCloseSocket()
+ * is idempotent, the host/port arguments are now gone, so don't do it
+ * again. */
+ if (!argv_rewritten) migrateCloseSocket(c->argv[1], c->argv[2]);
+ zfree(newargv);
+ newargv = NULL; /* This will get reallocated on retry. */
+
+ /* Retry only if it's not a timeout and we never attempted a retry
+ * (or the code jumping here did not set may_retry to zero). */
+ if (errno != ETIMEDOUT && may_retry) {
+ may_retry = 0;
+ goto try_again;
+ }
+
+ /* Cleanup we want to do if no retry is attempted. */
+ zfree(ov); zfree(kv);
+ addReplySds(c,
+ sdscatprintf(sdsempty(),
+ "-IOERR error or timeout %s to target instance\r\n",
+ write_error ? "writing" : "reading"));
+ return;
+}
+
+/* -----------------------------------------------------------------------------
+ * Cluster functions related to serving / redirecting clients
+ * -------------------------------------------------------------------------- */
+
+ /* The ASKING command is required after a -ASK redirection.
+ * The client should issue ASKING before to actually send the command to
+ * the target instance. See the Redis Cluster specification for more
+ * information. */
+void askingCommand(client *c) {
+ if (server.cluster_enabled == 0) {
+ addReplyError(c, "This instance has cluster support disabled");
+ return;
+ }
+ c->flags |= CLIENT_ASKING;
+ addReply(c, shared.ok);
+}
+
+/* The READONLY command is used by clients to enter the read-only mode.
+ * In this mode slaves will not redirect clients as long as clients access
+ * with read-only commands to keys that are served by the slave's master. */
+void readonlyCommand(client *c) {
+ if (server.cluster_enabled == 0) {
+ addReplyError(c, "This instance has cluster support disabled");
+ return;
+ }
+ c->flags |= CLIENT_READONLY;
+ addReply(c, shared.ok);
+}
+
+/* The READWRITE command just clears the READONLY command state. */
+void readwriteCommand(client *c) {
+ c->flags &= ~CLIENT_READONLY;
+ addReply(c, shared.ok);
+}
+
+/* Return the pointer to the cluster node that is able to serve the command.
+ * For the function to succeed the command should only target either:
+ *
+ * 1) A single key (even multiple times like LPOPRPUSH mylist mylist).
+ * 2) Multiple keys in the same hash slot, while the slot is stable (no
+ * resharding in progress).
+ *
+ * On success the function returns the node that is able to serve the request.
+ * If the node is not 'myself' a redirection must be perfomed. The kind of
+ * redirection is specified setting the integer passed by reference
+ * 'error_code', which will be set to CLUSTER_REDIR_ASK or
+ * CLUSTER_REDIR_MOVED.
+ *
+ * When the node is 'myself' 'error_code' is set to CLUSTER_REDIR_NONE.
+ *
+ * If the command fails NULL is returned, and the reason of the failure is
+ * provided via 'error_code', which will be set to:
+ *
+ * CLUSTER_REDIR_CROSS_SLOT if the request contains multiple keys that
+ * don't belong to the same hash slot.
+ *
+ * CLUSTER_REDIR_UNSTABLE if the request contains multiple keys
+ * belonging to the same slot, but the slot is not stable (in migration or
+ * importing state, likely because a resharding is in progress).
+ *
+ * CLUSTER_REDIR_DOWN_UNBOUND if the request addresses a slot which is
+ * not bound to any node. In this case the cluster global state should be
+ * already "down" but it is fragile to rely on the update of the global state,
+ * so we also handle it here.
+ *
+ * CLUSTER_REDIR_DOWN_STATE if the cluster is down but the user attempts to
+ * execute a command that addresses one or more keys. */
+clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *error_code) {
+ clusterNode *n = NULL;
+ robj *firstkey = NULL;
+ int multiple_keys = 0;
+ multiState *ms, _ms;
+ multiCmd mc;
+ int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0;
+
+ /* Set error code optimistically for the base case. */
+ if (error_code) *error_code = CLUSTER_REDIR_NONE;
+
+ /* We handle all the cases as if they were EXEC commands, so we have
+ * a common code path for everything */
+ if (cmd->proc == execCommand) {
+ /* If CLIENT_MULTI flag is not set EXEC is just going to return an
+ * error. */
+ if (!(c->flags & CLIENT_MULTI)) return myself;
+ ms = &c->mstate;
+ }
+ else {
+ /* In order to have a single codepath create a fake Multi State
+ * structure if the client is not in MULTI/EXEC state, this way
+ * we have a single codepath below. */
+ ms = &_ms;
+ _ms.commands = &mc;
+ _ms.count = 1;
+ mc.argv = argv;
+ mc.argc = argc;
+ mc.cmd = cmd;
+ }
+
+ /* Check that all the keys are in the same hash slot, and obtain this
+ * slot and the node associated. */
+ for (i = 0; i < ms->count; i++) {
+ struct redisCommand *mcmd;
+ robj **margv;
+ int margc, *keyindex, numkeys, j;
+
+ mcmd = ms->commands[i].cmd;
+ margc = ms->commands[i].argc;
+ margv = ms->commands[i].argv;
+
+ keyindex = getKeysFromCommand(mcmd, margv, margc, &numkeys);
+ for (j = 0; j < numkeys; j++) {
+ robj *thiskey = margv[keyindex[j]];
+ int thisslot = keyHashSlot((char*) thiskey->ptr,
+ (int) sdslen(thiskey->ptr)); WIN_PORT_FIX /* cast (int) */
+
+ if (firstkey == NULL) {
+ /* This is the first key we see. Check what is the slot
+ * and node. */
+ firstkey = thiskey;
+ slot = thisslot;
+ n = server.cluster->slots[slot];
+
+ /* Error: If a slot is not served, we are in "cluster down"
+ * state. However the state is yet to be updated, so this was
+ * not trapped earlier in processCommand(). Report the same
+ * error to the client. */
+ if (n == NULL) {
+ getKeysFreeResult(keyindex);
+ if (error_code)
+ *error_code = CLUSTER_REDIR_DOWN_UNBOUND;
+ return NULL;
+ }
+
+ /* If we are migrating or importing this slot, we need to check
+ * if we have all the keys in the request (the only way we
+ * can safely serve the request, otherwise we return a TRYAGAIN
+ * error). To do so we set the importing/migrating state and
+ * increment a counter for every missing key. */
+ if (n == myself &&
+ server.cluster->migrating_slots_to[slot] != NULL)
+ {
+ migrating_slot = 1;
+ }
+ else if (server.cluster->importing_slots_from[slot] != NULL) {
+ importing_slot = 1;
+ }
+ }
+ else {
+ /* If it is not the first key, make sure it is exactly
+ * the same key as the first we saw. */
+ if (!equalStringObjects(firstkey, thiskey)) {
+ if (slot != thisslot) {
+ /* Error: multiple keys from different slots. */
+ getKeysFreeResult(keyindex);
+ if (error_code)
+ *error_code = CLUSTER_REDIR_CROSS_SLOT;
+ return NULL;
+ }
+ else {
+ /* Flag this request as one with multiple different
+ * keys. */
+ multiple_keys = 1;
+ }
+ }
+ }
+
+ /* Migarting / Improrting slot? Count keys we don't have. */
+ if ((migrating_slot || importing_slot) &&
+ lookupKeyRead(&server.db[0], thiskey) == NULL)
+ {
+ missing_keys++;
+ }
+ }
+ getKeysFreeResult(keyindex);
+ }
+
+ /* No key at all in command? then we can serve the request
+ * without redirections or errors in all the cases. */
+ if (n == NULL) return myself;
+
+ /* Cluster is globally down but we got keys? We can't serve the request. */
+ if (server.cluster->state != CLUSTER_OK) {
+ if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
+ return NULL;
+ }
+
+ /* Return the hashslot by reference. */
+ if (hashslot) *hashslot = slot;
+
+ /* MIGRATE always works in the context of the local node if the slot
+ * is open (migrating or importing state). We need to be able to freely
+ * move keys among instances in this case. */
+ if ((migrating_slot || importing_slot) && cmd->proc == migrateCommand)
+ return myself;
+
+ /* If we don't have all the keys and we are migrating the slot, send
+ * an ASK redirection. */
+ if (migrating_slot && missing_keys) {
+ if (error_code) *error_code = CLUSTER_REDIR_ASK;
+ return server.cluster->migrating_slots_to[slot];
+ }
+
+ /* If we are receiving the slot, and the client correctly flagged the
+ * request as "ASKING", we can serve the request. However if the request
+ * involves multiple keys and we don't have them all, the only option is
+ * to send a TRYAGAIN error. */
+ if (importing_slot &&
+ (c->flags & CLIENT_ASKING || cmd->flags & CMD_ASKING))
+ {
+ if (multiple_keys && missing_keys) {
+ if (error_code) *error_code = CLUSTER_REDIR_UNSTABLE;
+ return NULL;
+ }
+ else {
+ return myself;
+ }
+ }
+
+ /* Handle the read-only client case reading from a slave: if this
+ * node is a slave and the request is about an hash slot our master
+ * is serving, we can reply without redirection. */
+ if (c->flags & CLIENT_READONLY &&
+ cmd->flags & CMD_READONLY &&
+ nodeIsSlave(myself) &&
+ myself->slaveof == n)
+ {
+ return myself;
+ }
+
+ /* Base case: just return the right node. However if this node is not
+ * myself, set error_code to MOVED since we need to issue a rediretion. */
+ if (n != myself && error_code) *error_code = CLUSTER_REDIR_MOVED;
+ return n;
+}
+
+/* Send the client the right redirection code, according to error_code
+ * that should be set to one of CLUSTER_REDIR_* macros.
+ *
+ * If CLUSTER_REDIR_ASK or CLUSTER_REDIR_MOVED error codes
+ * are used, then the node 'n' should not be NULL, but should be the
+ * node we want to mention in the redirection. Moreover hashslot should
+ * be set to the hash slot that caused the redirection. */
+void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code) {
+ if (error_code == CLUSTER_REDIR_CROSS_SLOT) {
+ addReplySds(c, sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
+ }
+ else if (error_code == CLUSTER_REDIR_UNSTABLE) {
+ /* The request spawns mutliple keys in the same slot,
+ * but the slot is not "stable" currently as there is
+ * a migration or import in progress. */
+ addReplySds(c, sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
+ }
+ else if (error_code == CLUSTER_REDIR_DOWN_STATE) {
+ addReplySds(c, sdsnew("-CLUSTERDOWN The cluster is down\r\n"));
+ }
+ else if (error_code == CLUSTER_REDIR_DOWN_UNBOUND) {
+ addReplySds(c, sdsnew("-CLUSTERDOWN Hash slot not served\r\n"));
+ }
+ else if (error_code == CLUSTER_REDIR_MOVED ||
+ error_code == CLUSTER_REDIR_ASK)
+ {
+ addReplySds(c, sdscatprintf(sdsempty(),
+ "-%s %d %s:%d\r\n",
+ (error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
+ hashslot, n->ip, n->port));
+ }
+ else {
+ serverPanic("getNodeByQuery() unknown error.");
+ }
+}
+
+/* This function is called by the function processing clients incrementally
+ * to detect timeouts, in order to handle the following case:
+ *
+ * 1) A client blocks with BLPOP or similar blocking operation.
+ * 2) The master migrates the hash slot elsewhere or turns into a slave.
+ * 3) The client may remain blocked forever (or up to the max timeout time)
+ * waiting for a key change that will never happen.
+ *
+ * If the client is found to be blocked into an hash slot this node no
+ * longer handles, the client is sent a redirection error, and the function
+ * returns 1. Otherwise 0 is returned and no operation is performed. */
+int clusterRedirectBlockedClientIfNeeded(client *c) {
+ if (c->flags & CLIENT_BLOCKED && c->btype == BLOCKED_LIST) {
+ dictEntry *de;
+ dictIterator *di;
+
+ /* If the cluster is down, unblock the client with the right error. */
+ if (server.cluster->state == CLUSTER_FAIL) {
+ clusterRedirectClient(c, NULL, 0, CLUSTER_REDIR_DOWN_STATE);
+ return 1;
+ }
+
+ /* All keys must belong to the same slot, so check first key only. */
+ di = dictGetIterator(c->bpop.keys);
+ if ((de = dictNext(di)) != NULL) {
+ robj *key = dictGetKey(de);
+ int slot = keyHashSlot((char*) key->ptr, (int) sdslen(key->ptr)); WIN_PORT_FIX /* cast (int) */
+ clusterNode *node = server.cluster->slots[slot];
+
+ /* We send an error and unblock the client if:
+ * 1) The slot is unassigned, emitting a cluster down error.
+ * 2) The slot is not handled by this node, nor being imported. */
+ if (node != myself &&
+ server.cluster->importing_slots_from[slot] == NULL)
+ {
+ if (node == NULL) {
+ clusterRedirectClient(c, NULL, 0,
+ CLUSTER_REDIR_DOWN_UNBOUND);
+ }
+ else {
+ clusterRedirectClient(c, node, slot,
+ CLUSTER_REDIR_MOVED);
+ }
+ dictReleaseIterator(di);
+ return 1;
+ }
+ }
+ dictReleaseIterator(di);
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/src/cluster.h b/src/cluster.h
index 9ad47d21..ff8dc561 100644
--- a/src/cluster.h
+++ b/src/cluster.h
@@ -16,6 +16,7 @@
#define CLUSTER_DEFAULT_NODE_TIMEOUT 15000
#define CLUSTER_DEFAULT_SLAVE_VALIDITY 10 /* Slave max data age factor. */
#define CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE 1
+#define CLUSTER_DEFAULT_SLAVE_NO_FAILOVER 0 /* Failover by default. */
#define CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
#define CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */
#define CLUSTER_FAIL_UNDO_TIME_ADD 10 /* Some additional time. */
@@ -55,6 +56,7 @@ typedef struct clusterLink {
#define CLUSTER_NODE_NOADDR 64 /* We don't know the address of this node */
#define CLUSTER_NODE_MEET 128 /* Send a MEET message to this node */
#define CLUSTER_NODE_MIGRATE_TO 256 /* Master elegible for replica migration. */
+#define CLUSTER_NODE_NOFAILOVER 512 /* Slave will not try to failver. */
#define CLUSTER_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
#define nodeIsMaster(n) ((n)->flags & CLUSTER_NODE_MASTER)
@@ -64,6 +66,7 @@ typedef struct clusterLink {
#define nodeWithoutAddr(n) ((n)->flags & CLUSTER_NODE_NOADDR)
#define nodeTimedOut(n) ((n)->flags & CLUSTER_NODE_PFAIL)
#define nodeFailed(n) ((n)->flags & CLUSTER_NODE_FAIL)
+#define nodeCantFailover(n) ((n)->flags & CLUSTER_NODE_NOFAILOVER)
/* Reasons why a slave is not able to failover. */
#define CLUSTER_CANT_FAILOVER_NONE 0
diff --git a/src/cluster.h.bak b/src/cluster.h.bak
new file mode 100644
index 00000000..9ad47d21
--- /dev/null
+++ b/src/cluster.h.bak
@@ -0,0 +1,270 @@
+#ifndef __CLUSTER_H
+#define __CLUSTER_H
+
+/*-----------------------------------------------------------------------------
+ * Redis cluster data structures, defines, exported API.
+ *----------------------------------------------------------------------------*/
+
+#define CLUSTER_SLOTS 16384
+#define CLUSTER_OK 0 /* Everything looks ok */
+#define CLUSTER_FAIL 1 /* The cluster can't work */
+#define CLUSTER_NAMELEN 40 /* sha1 hex length */
+#define CLUSTER_PORT_INCR 10000 /* Cluster port = baseport + PORT_INCR */
+
+/* The following defines are amount of time, sometimes expressed as
+ * multiplicators of the node timeout value (when ending with MULT). */
+#define CLUSTER_DEFAULT_NODE_TIMEOUT 15000
+#define CLUSTER_DEFAULT_SLAVE_VALIDITY 10 /* Slave max data age factor. */
+#define CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE 1
+#define CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
+#define CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */
+#define CLUSTER_FAIL_UNDO_TIME_ADD 10 /* Some additional time. */
+#define CLUSTER_FAILOVER_DELAY 5 /* Seconds */
+#define CLUSTER_DEFAULT_MIGRATION_BARRIER 1
+#define CLUSTER_MF_TIMEOUT 5000 /* Milliseconds to do a manual failover. */
+#define CLUSTER_MF_PAUSE_MULT 2 /* Master pause manual failover mult. */
+#define CLUSTER_SLAVE_MIGRATION_DELAY 5000 /* Delay for slave migration. */
+
+/* Redirection errors returned by getNodeByQuery(). */
+#define CLUSTER_REDIR_NONE 0 /* Node can serve the request. */
+#define CLUSTER_REDIR_CROSS_SLOT 1 /* -CROSSSLOT request. */
+#define CLUSTER_REDIR_UNSTABLE 2 /* -TRYAGAIN redirection required */
+#define CLUSTER_REDIR_ASK 3 /* -ASK redirection required. */
+#define CLUSTER_REDIR_MOVED 4 /* -MOVED redirection required. */
+#define CLUSTER_REDIR_DOWN_STATE 5 /* -CLUSTERDOWN, global state. */
+#define CLUSTER_REDIR_DOWN_UNBOUND 6 /* -CLUSTERDOWN, unbound slot. */
+
+struct clusterNode;
+
+/* clusterLink encapsulates everything needed to talk with a remote node. */
+typedef struct clusterLink {
+ mstime_t ctime; /* Link creation time */
+ int fd; /* TCP socket file descriptor */
+ sds sndbuf; /* Packet send buffer */
+ sds rcvbuf; /* Packet reception buffer */
+ struct clusterNode *node; /* Node related to this link if any, or NULL */
+} clusterLink;
+
+/* Cluster node flags and macros. */
+#define CLUSTER_NODE_MASTER 1 /* The node is a master */
+#define CLUSTER_NODE_SLAVE 2 /* The node is a slave */
+#define CLUSTER_NODE_PFAIL 4 /* Failure? Need acknowledge */
+#define CLUSTER_NODE_FAIL 8 /* The node is believed to be malfunctioning */
+#define CLUSTER_NODE_MYSELF 16 /* This node is myself */
+#define CLUSTER_NODE_HANDSHAKE 32 /* We have still to exchange the first ping */
+#define CLUSTER_NODE_NOADDR 64 /* We don't know the address of this node */
+#define CLUSTER_NODE_MEET 128 /* Send a MEET message to this node */
+#define CLUSTER_NODE_MIGRATE_TO 256 /* Master elegible for replica migration. */
+#define CLUSTER_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+
+#define nodeIsMaster(n) ((n)->flags & CLUSTER_NODE_MASTER)
+#define nodeIsSlave(n) ((n)->flags & CLUSTER_NODE_SLAVE)
+#define nodeInHandshake(n) ((n)->flags & CLUSTER_NODE_HANDSHAKE)
+#define nodeHasAddr(n) (!((n)->flags & CLUSTER_NODE_NOADDR))
+#define nodeWithoutAddr(n) ((n)->flags & CLUSTER_NODE_NOADDR)
+#define nodeTimedOut(n) ((n)->flags & CLUSTER_NODE_PFAIL)
+#define nodeFailed(n) ((n)->flags & CLUSTER_NODE_FAIL)
+
+/* Reasons why a slave is not able to failover. */
+#define CLUSTER_CANT_FAILOVER_NONE 0
+#define CLUSTER_CANT_FAILOVER_DATA_AGE 1
+#define CLUSTER_CANT_FAILOVER_WAITING_DELAY 2
+#define CLUSTER_CANT_FAILOVER_EXPIRED 3
+#define CLUSTER_CANT_FAILOVER_WAITING_VOTES 4
+#define CLUSTER_CANT_FAILOVER_RELOG_PERIOD (60*5) /* seconds. */
+
+/* clusterState todo_before_sleep flags. */
+#define CLUSTER_TODO_HANDLE_FAILOVER (1<<0)
+#define CLUSTER_TODO_UPDATE_STATE (1<<1)
+#define CLUSTER_TODO_SAVE_CONFIG (1<<2)
+#define CLUSTER_TODO_FSYNC_CONFIG (1<<3)
+
+/* Message types.
+ *
+ * Note that the PING, PONG and MEET messages are actually the same exact
+ * kind of packet. PONG is the reply to ping, in the exact format as a PING,
+ * while MEET is a special PING that forces the receiver to add the sender
+ * as a node (if it is not already in the list). */
+#define CLUSTERMSG_TYPE_PING 0 /* Ping */
+#define CLUSTERMSG_TYPE_PONG 1 /* Pong (reply to Ping) */
+#define CLUSTERMSG_TYPE_MEET 2 /* Meet "let's join" message */
+#define CLUSTERMSG_TYPE_FAIL 3 /* Mark node xxx as failing */
+#define CLUSTERMSG_TYPE_PUBLISH 4 /* Pub/Sub Publish propagation */
+#define CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 5 /* May I failover? */
+#define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6 /* Yes, you have my vote */
+#define CLUSTERMSG_TYPE_UPDATE 7 /* Another node slots configuration */
+#define CLUSTERMSG_TYPE_MFSTART 8 /* Pause clients for manual failover */
+#define CLUSTERMSG_TYPE_COUNT 9 /* Total number of message types. */
+
+/* This structure represent elements of node->fail_reports. */
+typedef struct clusterNodeFailReport {
+ struct clusterNode *node; /* Node reporting the failure condition. */
+ mstime_t time; /* Time of the last report from this node. */
+} clusterNodeFailReport;
+
+typedef struct clusterNode {
+ mstime_t ctime; /* Node object creation time. */
+ char name[CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
+ int flags; /* CLUSTER_NODE_... */
+ uint64_t configEpoch; /* Last configEpoch observed for this node */
+ unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
+ int numslots; /* Number of slots handled by this node */
+ int numslaves; /* Number of slave nodes, if this is a master */
+ struct clusterNode **slaves; /* pointers to slave nodes */
+ struct clusterNode *slaveof; /* pointer to the master node. Note that it
+ may be NULL even if the node is a slave
+ if we don't have the master node in our
+ tables. */
+ mstime_t ping_sent; /* Unix time we sent latest ping */
+ mstime_t pong_received; /* Unix time we received the pong */
+ mstime_t fail_time; /* Unix time when FAIL flag was set */
+ mstime_t voted_time; /* Last time we voted for a slave of this master */
+ mstime_t repl_offset_time; /* Unix time we received offset for this node */
+ mstime_t orphaned_time; /* Starting time of orphaned master condition */
+ PORT_LONGLONG repl_offset; /* Last known repl offset for this node. */
+ char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
+ int port; /* Latest known clients port of this node */
+ int cport; /* Latest known cluster port of this node. */
+ clusterLink *link; /* TCP/IP link with this node */
+ list *fail_reports; /* List of nodes signaling this as failing */
+} clusterNode;
+
+typedef struct clusterState {
+ clusterNode *myself; /* This node */
+ uint64_t currentEpoch;
+ int state; /* CLUSTER_OK, CLUSTER_FAIL, ... */
+ int size; /* Num of master nodes with at least one slot */
+ dict *nodes; /* Hash table of name -> clusterNode structures */
+ dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */
+ clusterNode *migrating_slots_to[CLUSTER_SLOTS];
+ clusterNode *importing_slots_from[CLUSTER_SLOTS];
+ clusterNode *slots[CLUSTER_SLOTS];
+ uint64_t slots_keys_count[CLUSTER_SLOTS];
+ rax *slots_to_keys;
+ /* The following fields are used to take the slave state on elections. */
+ mstime_t failover_auth_time; /* Time of previous or next election. */
+ int failover_auth_count; /* Number of votes received so far. */
+ int failover_auth_sent; /* True if we already asked for votes. */
+ int failover_auth_rank; /* This slave rank for current auth request. */
+ uint64_t failover_auth_epoch; /* Epoch of the current election. */
+ int cant_failover_reason; /* Why a slave is currently not able to
+ failover. See the CANT_FAILOVER_* macros. */
+ /* Manual failover state in common. */
+ mstime_t mf_end; /* Manual failover time limit (ms unixtime).
+ It is zero if there is no MF in progress. */
+ /* Manual failover state of master. */
+ clusterNode *mf_slave; /* Slave performing the manual failover. */
+ /* Manual failover state of slave. */
+ PORT_LONGLONG mf_master_offset; /* Master offset the slave needs to start MF
+ or zero if stil not received. */
+ int mf_can_start; /* If non-zero signal that the manual failover
+ can start requesting masters vote. */
+ /* The followign fields are used by masters to take state on elections. */
+ uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */
+ int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */
+ /* Messages received and sent by type. */
+ PORT_LONGLONG stats_bus_messages_sent[CLUSTERMSG_TYPE_COUNT];
+ PORT_LONGLONG stats_bus_messages_received[CLUSTERMSG_TYPE_COUNT];
+ PORT_LONGLONG stats_pfail_nodes; /* Number of nodes in PFAIL status,
+ excluding nodes without address. */
+} clusterState;
+
+/* Redis cluster messages header */
+
+/* Initially we don't know our "name", but we'll find it once we connect
+ * to the first node, using the getsockname() function. Then we'll use this
+ * address for all the next messages. */
+typedef struct {
+ char nodename[CLUSTER_NAMELEN];
+ uint32_t ping_sent;
+ uint32_t pong_received;
+ char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */
+ uint16_t port; /* base port last time it was seen */
+ uint16_t cport; /* cluster port last time it was seen */
+ uint16_t flags; /* node->flags copy */
+ uint32_t notused1;
+} clusterMsgDataGossip;
+
+typedef struct {
+ char nodename[CLUSTER_NAMELEN];
+} clusterMsgDataFail;
+
+typedef struct {
+ uint32_t channel_len;
+ uint32_t message_len;
+ /* We can't reclare bulk_data as bulk_data[] since this structure is
+ * nested. The 8 bytes are removed from the count during the message
+ * length computation. */
+ unsigned char bulk_data[8];
+} clusterMsgDataPublish;
+
+typedef struct {
+ uint64_t configEpoch; /* Config epoch of the specified instance. */
+ char nodename[CLUSTER_NAMELEN]; /* Name of the slots owner. */
+ unsigned char slots[CLUSTER_SLOTS/8]; /* Slots bitmap. */
+} clusterMsgDataUpdate;
+
+union clusterMsgData {
+ /* PING, MEET and PONG */
+ struct {
+ /* Array of N clusterMsgDataGossip structures */
+ clusterMsgDataGossip gossip[1];
+ } ping;
+
+ /* FAIL */
+ struct {
+ clusterMsgDataFail about;
+ } fail;
+
+ /* PUBLISH */
+ struct {
+ clusterMsgDataPublish msg;
+ } publish;
+
+ /* UPDATE */
+ struct {
+ clusterMsgDataUpdate nodecfg;
+ } update;
+};
+
+#define CLUSTER_PROTO_VER 1 /* Cluster bus protocol version. */
+
+typedef struct {
+ char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
+ uint32_t totlen; /* Total length of this message */
+ uint16_t ver; /* Protocol version, currently set to 1. */
+ uint16_t port; /* TCP base port number. */
+ uint16_t type; /* Message type */
+ uint16_t count; /* Only used for some kind of messages. */
+ uint64_t currentEpoch; /* The epoch accordingly to the sending node. */
+ uint64_t configEpoch; /* The config epoch if it's a master, or the last
+ epoch advertised by its master if it is a
+ slave. */
+ uint64_t offset; /* Master replication offset if node is a master or
+ processed replication offset if node is a slave. */
+ char sender[CLUSTER_NAMELEN]; /* Name of the sender node */
+ unsigned char myslots[CLUSTER_SLOTS/8];
+ char slaveof[CLUSTER_NAMELEN];
+ char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
+ char notused1[34]; /* 34 bytes reserved for future usage. */
+ uint16_t cport; /* Sender TCP cluster bus port */
+ uint16_t flags; /* Sender node flags */
+ unsigned char state; /* Cluster state from the POV of the sender */
+ unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */
+ union clusterMsgData data;
+} clusterMsg;
+
+#define CLUSTERMSG_MIN_LEN (sizeof(clusterMsg)-sizeof(union clusterMsgData))
+
+/* Message flags better specify the packet content or are used to
+ * provide some information about the node state. */
+#define CLUSTERMSG_FLAG0_PAUSED (1<<0) /* Master paused for manual failover. */
+#define CLUSTERMSG_FLAG0_FORCEACK (1<<1) /* Give ACK to AUTH_REQUEST even if
+ master is up. */
+
+/* ---------------------- API exported outside cluster.c -------------------- */
+clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask);
+int clusterRedirectBlockedClientIfNeeded(client *c);
+void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code);
+
+#endif /* __CLUSTER_H */
diff --git a/src/config.c b/src/config.c
index 6ed57992..cb273649 100644
--- a/src/config.c
+++ b/src/config.c
@@ -372,15 +372,19 @@ void loadServerConfigFromString(char *config) {
err = "maxmemory-samples must be 1 or greater";
goto loaderr;
}
+ } else if ((!strcasecmp(argv[0],"proto-max-bulk-len")) && argc == 2) {
+ server.proto_max_bulk_len = memtoll(argv[1],NULL);
+ } else if ((!strcasecmp(argv[0],"client-query-buffer-limit")) && argc == 2) {
+ server.client_max_querybuf_len = memtoll(argv[1],NULL);
} else if (!strcasecmp(argv[0],"lfu-log-factor") && argc == 2) {
server.lfu_log_factor = atoi(argv[1]);
- if (server.maxmemory_samples < 0) {
+ if (server.lfu_log_factor < 0) {
err = "lfu-log-factor must be 0 or greater";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"lfu-decay-time") && argc == 2) {
server.lfu_decay_time = atoi(argv[1]);
- if (server.maxmemory_samples < 1) {
+ if (server.lfu_decay_time < 0) {
err = "lfu-decay-time must be 0 or greater";
goto loaderr;
}
@@ -675,6 +679,14 @@ void loadServerConfigFromString(char *config) {
err = "cluster slave validity factor must be zero or positive";
goto loaderr;
}
+ } else if (!strcasecmp(argv[0],"cluster-slave-no-failover") &&
+ argc == 2)
+ {
+ server.cluster_slave_no_failover = yesnotoi(argv[1]);
+ if (server.cluster_slave_no_failover == -1) {
+ err = "argument must be 'yes' or 'no'";
+ goto loaderr;
+ }
} else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
server.lua_time_limit = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
@@ -1082,6 +1094,8 @@ void configSetCommand(client *c) {
"repl-diskless-sync",server.repl_diskless_sync) {
} config_set_bool_field(
"cluster-require-full-coverage",server.cluster_require_full_coverage) {
+ } config_set_bool_field(
+ "cluster-slave-no-failover",server.cluster_slave_no_failover) {
} config_set_bool_field(
"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) {
} config_set_bool_field(
@@ -1312,7 +1326,11 @@ void configGetCommand(client *c) {
/* Numerical values */
config_get_numerical_field("maxmemory",server.maxmemory);
+ config_get_numerical_field("proto-max-bulk-len",server.proto_max_bulk_len);
+ config_get_numerical_field("client-query-buffer-limit",server.client_max_querybuf_len);
config_get_numerical_field("maxmemory-samples",server.maxmemory_samples);
+ config_get_numerical_field("lfu-log-factor",server.lfu_log_factor);
+ config_get_numerical_field("lfu-decay-time",server.lfu_decay_time);
config_get_numerical_field("timeout",server.maxidletime);
config_get_numerical_field("active-defrag-threshold-lower",server.active_defrag_threshold_lower);
config_get_numerical_field("active-defrag-threshold-upper",server.active_defrag_threshold_upper);
@@ -1371,6 +1389,8 @@ void configGetCommand(client *c) {
/* Bool (yes/no) values */
config_get_bool_field("cluster-require-full-coverage",
server.cluster_require_full_coverage);
+ config_get_bool_field("cluster-slave-no-failover",
+ server.cluster_slave_no_failover);
config_get_bool_field("no-appendfsync-on-rewrite",
server.aof_no_fsync_on_rewrite);
config_get_bool_field("slave-serve-stale-data",
@@ -2088,8 +2108,12 @@ int rewriteConfig(char *path) {
rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,CONFIG_DEFAULT_MAX_CLIENTS);
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,CONFIG_DEFAULT_MAXMEMORY);
+ rewriteConfigBytesOption(state,"proto-max-bulk-len",server.proto_max_bulk_len,CONFIG_DEFAULT_PROTO_MAX_BULK_LEN);
+ rewriteConfigBytesOption(state,"client-query-buffer-limit",server.client_max_querybuf_len,PROTO_MAX_QUERYBUF_LEN);
rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum,CONFIG_DEFAULT_MAXMEMORY_POLICY);
rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,CONFIG_DEFAULT_MAXMEMORY_SAMPLES);
+ rewriteConfigNumericalOption(state,"lfu-log-factor",server.lfu_log_factor,CONFIG_DEFAULT_LFU_LOG_FACTOR);
+ rewriteConfigNumericalOption(state,"lfu-decay-time",server.lfu_decay_time,CONFIG_DEFAULT_LFU_DECAY_TIME);
rewriteConfigNumericalOption(state,"active-defrag-threshold-lower",server.active_defrag_threshold_lower,CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER);
rewriteConfigNumericalOption(state,"active-defrag-threshold-upper",server.active_defrag_threshold_upper,CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER);
rewriteConfigBytesOption(state,"active-defrag-ignore-bytes",server.active_defrag_ignore_bytes,CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES);
@@ -2105,6 +2129,7 @@ int rewriteConfig(char *path) {
rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0);
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
rewriteConfigYesNoOption(state,"cluster-require-full-coverage",server.cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE);
+ rewriteConfigYesNoOption(state,"cluster-slave-no-failover",server.cluster_slave_no_failover,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER);
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,CLUSTER_DEFAULT_NODE_TIMEOUT);
rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,CLUSTER_DEFAULT_MIGRATION_BARRIER);
rewriteConfigNumericalOption(state,"cluster-slave-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY);
diff --git a/src/config.c.bak b/src/config.c.bak
new file mode 100644
index 00000000..6ed57992
--- /dev/null
+++ b/src/config.c.bak
@@ -0,0 +1,2199 @@
+/* Configuration file parsing and CONFIG GET/SET commands implementation.
+ *
+ * Copyright (c) 2009-2012, Salvatore Sanfilippo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * 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.
+ */
+
+#ifdef _WIN32
+#include "Win32_Interop/win32_types.h"
+#include "Win32_Interop/Win32_EventLog.h"
+#include "Win32_Interop/Win32_Error.h"
+#include
+#endif
+
+#include "server.h"
+#include "cluster.h"
+
+#include
+#include
+
+/*-----------------------------------------------------------------------------
+ * Config file name-value maps.
+ *----------------------------------------------------------------------------*/
+
+typedef struct configEnum {
+ const char *name;
+ const int val;
+} configEnum;
+
+configEnum maxmemory_policy_enum[] = {
+ {"volatile-lru", MAXMEMORY_VOLATILE_LRU},
+ {"volatile-lfu", MAXMEMORY_VOLATILE_LFU},
+ {"volatile-random",MAXMEMORY_VOLATILE_RANDOM},
+ {"volatile-ttl",MAXMEMORY_VOLATILE_TTL},
+ {"allkeys-lru",MAXMEMORY_ALLKEYS_LRU},
+ {"allkeys-lfu",MAXMEMORY_ALLKEYS_LFU},
+ {"allkeys-random",MAXMEMORY_ALLKEYS_RANDOM},
+ {"noeviction",MAXMEMORY_NO_EVICTION},
+ {NULL, 0}
+};
+
+#ifndef _WIN32
+configEnum syslog_facility_enum[] = {
+ {"user", LOG_USER},
+ {"local0", LOG_LOCAL0},
+ {"local1", LOG_LOCAL1},
+ {"local2", LOG_LOCAL2},
+ {"local3", LOG_LOCAL3},
+ {"local4", LOG_LOCAL4},
+ {"local5", LOG_LOCAL5},
+ {"local6", LOG_LOCAL6},
+ {"local7", LOG_LOCAL7},
+ {NULL, 0}
+};
+#endif
+
+configEnum loglevel_enum[] = {
+ {"debug", LL_DEBUG},
+ {"verbose", LL_VERBOSE},
+ {"notice", LL_NOTICE},
+ {"warning", LL_WARNING},
+ {NULL,0}
+};
+
+configEnum supervised_mode_enum[] = {
+ {"upstart", SUPERVISED_UPSTART},
+ {"systemd", SUPERVISED_SYSTEMD},
+ {"auto", SUPERVISED_AUTODETECT},
+ {"no", SUPERVISED_NONE},
+ {NULL, 0}
+};
+
+configEnum aof_fsync_enum[] = {
+ {"everysec", AOF_FSYNC_EVERYSEC},
+ {"always", AOF_FSYNC_ALWAYS},
+ {"no", AOF_FSYNC_NO},
+ {NULL, 0}
+};
+
+/* Output buffer limits presets. */
+clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
+ {0, 0, 0}, /* normal */
+ {1024*1024*256, 1024*1024*64, 60}, /* slave */
+ {1024*1024*32, 1024*1024*8, 60} /* pubsub */
+};
+
+/*-----------------------------------------------------------------------------
+ * Enum access functions
+ *----------------------------------------------------------------------------*/
+
+/* Get enum value from name. If there is no match INT_MIN is returned. */
+int configEnumGetValue(configEnum *ce, char *name) {
+ while(ce->name != NULL) {
+ if (!strcasecmp(ce->name,name)) return ce->val;
+ ce++;
+ }
+ return INT_MIN;
+}
+
+/* Get enum name from value. If no match is found NULL is returned. */
+const char *configEnumGetName(configEnum *ce, int val) {
+ while(ce->name != NULL) {
+ if (ce->val == val) return ce->name;
+ ce++;
+ }
+ return NULL;
+}
+
+/* Wrapper for configEnumGetName() returning "unknown" insetad of NULL if
+ * there is no match. */
+const char *configEnumGetNameOrUnknown(configEnum *ce, int val) {
+ const char *name = configEnumGetName(ce,val);
+ return name ? name : "unknown";
+}
+
+/* Used for INFO generation. */
+const char *evictPolicyToString(void) {
+ return configEnumGetNameOrUnknown(maxmemory_policy_enum,server.maxmemory_policy);
+}
+
+/*-----------------------------------------------------------------------------
+ * Config file parsing
+ *----------------------------------------------------------------------------*/
+
+int yesnotoi(char *s) {
+ if (!strcasecmp(s,"yes")) return 1;
+ else if (!strcasecmp(s,"no")) return 0;
+ else return -1;
+}
+
+void appendServerSaveParams(time_t seconds, int changes) {
+ server.saveparams = zrealloc(server.saveparams,sizeof(struct saveparam)*(server.saveparamslen+1));
+ server.saveparams[server.saveparamslen].seconds = seconds;
+ server.saveparams[server.saveparamslen].changes = changes;
+ server.saveparamslen++;
+}
+
+void resetServerSaveParams(void) {
+ zfree(server.saveparams);
+ server.saveparams = NULL;
+ server.saveparamslen = 0;
+}
+
+void queueLoadModule(sds path, sds *argv, int argc) {
+ int i;
+ struct moduleLoadQueueEntry *loadmod;
+
+ loadmod = zmalloc(sizeof(struct moduleLoadQueueEntry));
+ loadmod->argv = zmalloc(sizeof(robj*)*argc);
+ loadmod->path = sdsnew(path);
+ loadmod->argc = argc;
+ for (i = 0; i < argc; i++) {
+ loadmod->argv[i] = createRawStringObject(argv[i],sdslen(argv[i]));
+ }
+ listAddNodeTail(server.loadmodule_queue,loadmod);
+}
+
+void loadServerConfigFromString(char *config) {
+ char *err = NULL;
+ int linenum = 0, totlines, i;
+ int slaveof_linenum = 0;
+ sds *lines;
+
+ lines = sdssplitlen(config,(int)strlen(config),"\n",1,&totlines); WIN_PORT_FIX /* cast (int) */
+
+ for (i = 0; i < totlines; i++) {
+ sds *argv;
+ int argc;
+
+ linenum = i+1;
+ lines[i] = sdstrim(lines[i]," \t\r\n");
+
+ /* Skip comments and blank lines */
+ if (lines[i][0] == '#' || lines[i][0] == '\0') continue;
+
+ /* Split into arguments */
+ argv = sdssplitargs(lines[i],&argc);
+ if (argv == NULL) {
+ err = "Unbalanced quotes in configuration line";
+ goto loaderr;
+ }
+
+ /* Skip this line if the resulting command vector is empty. */
+ if (argc == 0) {
+ sdsfreesplitres(argv,argc);
+ continue;
+ }
+ sdstolower(argv[0]);
+
+ /* Execute config directives */
+ if (!strcasecmp(argv[0],"timeout") && argc == 2) {
+ server.maxidletime = atoi(argv[1]);
+ if (server.maxidletime < 0) {
+ err = "Invalid timeout value"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"tcp-keepalive") && argc == 2) {
+ server.tcpkeepalive = atoi(argv[1]);
+ if (server.tcpkeepalive < 0) {
+ err = "Invalid tcp-keepalive value"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"protected-mode") && argc == 2) {
+ if ((server.protected_mode = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"port") && argc == 2) {
+ server.port = atoi(argv[1]);
+ if (server.port < 0 || server.port > 65535) {
+ err = "Invalid port"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"tcp-backlog") && argc == 2) {
+ server.tcp_backlog = atoi(argv[1]);
+ if (server.tcp_backlog < 0) {
+ err = "Invalid backlog value"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"bind") && argc >= 2) {
+ int j, addresses = argc-1;
+
+ if (addresses > CONFIG_BINDADDR_MAX) {
+ err = "Too many bind addresses specified"; goto loaderr;
+ }
+ for (j = 0; j < addresses; j++)
+ server.bindaddr[j] = zstrdup(argv[j+1]);
+ server.bindaddr_count = addresses;
+ } else if (!strcasecmp(argv[0],"unixsocket") && argc == 2) {
+ server.unixsocket = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"unixsocketperm") && argc == 2) {
+ errno = 0;
+ server.unixsocketperm = (mode_t)strtol(argv[1], NULL, 8);
+ if (errno || server.unixsocketperm > 0777) {
+ err = "Invalid socket file permissions"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"save")) {
+ if (argc == 3) {
+ int seconds = atoi(argv[1]);
+ int changes = atoi(argv[2]);
+ if (seconds < 1 || changes < 0) {
+ err = "Invalid save parameters"; goto loaderr;
+ }
+ appendServerSaveParams(seconds,changes);
+ } else if (argc == 2 && !strcasecmp(argv[1],"")) {
+ resetServerSaveParams();
+ }
+ } else if (!strcasecmp(argv[0],"dir") && argc == 2) {
+ if (chdir(argv[1]) == -1) {
+ serverLog(LL_WARNING,"Can't chdir to '%s': %s",
+ argv[1], IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ exit(1);
+ }
+ } else if (!strcasecmp(argv[0],"loglevel") && argc == 2) {
+ server.verbosity = configEnumGetValue(loglevel_enum,argv[1]);
+ if (server.verbosity == INT_MIN) {
+ err = "Invalid log level. "
+ "Must be one of debug, verbose, notice, warning";
+ goto loaderr;
+ }
+#ifdef _WIN32
+ setLogVerbosityLevel(server.verbosity);
+#endif
+ } else if (!strcasecmp(argv[0],"logfile") && argc == 2) {
+ FILE *logfp;
+
+ zfree(server.logfile);
+#ifdef _WIN32
+ int length = (int)sdslen(argv[1]);
+ if ((argv[1][0] == '\'' && argv[1][length-1] == '\'') ||
+ (argv[1][0] == '\"' && argv[1][length-1] == '\"')) {
+ if (length == 2) {
+ server.logfile = zstrdup("\0");
+ } else {
+ size_t l = length - 2 + 1;
+ char *p = zmalloc(l);
+ memcpy(p, argv[1]+1, l);
+ server.logfile = p;
+ }
+ } else {
+ server.logfile = zstrdup(argv[1]);
+ }
+#else
+ server.logfile = zstrdup(argv[1]);
+#endif
+ if (server.logfile[0] != '\0') {
+ /* Test if we are able to open the file. The server will not
+ * be able to abort just for this problem later... */
+ logfp = fopen(server.logfile,"a");
+ if (logfp == NULL) {
+ err = sdscatprintf(sdsempty(),
+ "Can't open the log file: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ goto loaderr;
+#ifdef _WIN32
+ } else {
+ setLogFile(server.logfile);
+#endif
+ }
+
+ fclose(logfp);
+ }
+ } else if (!strcasecmp(argv[0],"always-show-logo") && argc == 2) {
+ if ((server.always_show_logo = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"syslog-enabled") && argc == 2) {
+ if ((server.syslog_enabled = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+#ifdef _WIN32
+ setSyslogEnabled(server.syslog_enabled);
+#endif
+ } else if (!strcasecmp(argv[0],"syslog-ident") && argc == 2) {
+ if (server.syslog_ident) zfree(server.syslog_ident);
+ server.syslog_ident = zstrdup(argv[1]);
+#ifdef _WIN32
+ setSyslogIdent(server.syslog_ident);
+#endif
+ } else if (!strcasecmp(argv[0],"syslog-facility") && argc == 2) {
+#ifdef _WIN32
+ // Skip error - just ignore syslog-facility
+#else
+ server.syslog_facility =
+ configEnumGetValue(syslog_facility_enum,argv[1]);
+ if (server.syslog_facility == INT_MIN) {
+ err = "Invalid log facility. Must be one of USER or between LOCAL0-LOCAL7";
+ goto loaderr;
+ }
+#endif
+ } else if (!strcasecmp(argv[0],"databases") && argc == 2) {
+ server.dbnum = atoi(argv[1]);
+ if (server.dbnum < 1) {
+ err = "Invalid number of databases"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"include") && argc == 2) {
+ loadServerConfig(argv[1],NULL);
+ } else if (!strcasecmp(argv[0],"maxclients") && argc == 2) {
+ server.maxclients = atoi(argv[1]);
+ if (server.maxclients < 1) {
+ err = "Invalid max clients limit"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) {
+ server.maxmemory = memtoll(argv[1],NULL);
+ } else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) {
+ server.maxmemory_policy =
+ configEnumGetValue(maxmemory_policy_enum,argv[1]);
+ if (server.maxmemory_policy == INT_MIN) {
+ err = "Invalid maxmemory policy";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"maxmemory-samples") && argc == 2) {
+ server.maxmemory_samples = atoi(argv[1]);
+ if (server.maxmemory_samples <= 0) {
+ err = "maxmemory-samples must be 1 or greater";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"lfu-log-factor") && argc == 2) {
+ server.lfu_log_factor = atoi(argv[1]);
+ if (server.maxmemory_samples < 0) {
+ err = "lfu-log-factor must be 0 or greater";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"lfu-decay-time") && argc == 2) {
+ server.lfu_decay_time = atoi(argv[1]);
+ if (server.maxmemory_samples < 1) {
+ err = "lfu-decay-time must be 0 or greater";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"slaveof") && argc == 3) {
+ slaveof_linenum = linenum;
+ server.masterhost = sdsnew(argv[1]);
+ server.masterport = atoi(argv[2]);
+ server.repl_state = REPL_STATE_CONNECT;
+ } else if (!strcasecmp(argv[0],"repl-ping-slave-period") && argc == 2) {
+ server.repl_ping_slave_period = atoi(argv[1]);
+ if (server.repl_ping_slave_period <= 0) {
+ err = "repl-ping-slave-period must be 1 or greater";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"repl-timeout") && argc == 2) {
+ server.repl_timeout = atoi(argv[1]);
+ if (server.repl_timeout <= 0) {
+ err = "repl-timeout must be 1 or greater";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"repl-disable-tcp-nodelay") && argc==2) {
+ if ((server.repl_disable_tcp_nodelay = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"repl-diskless-sync") && argc==2) {
+ if ((server.repl_diskless_sync = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
+ server.repl_diskless_sync_delay = atoi(argv[1]);
+ if (server.repl_diskless_sync_delay < 0) {
+ err = "repl-diskless-sync-delay can't be negative";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"repl-backlog-size") && argc == 2) {
+ PORT_LONGLONG size = memtoll(argv[1],NULL);
+ if (size <= 0) {
+ err = "repl-backlog-size must be 1 or greater.";
+ goto loaderr;
+ }
+ resizeReplicationBacklog(size);
+ } else if (!strcasecmp(argv[0],"repl-backlog-ttl") && argc == 2) {
+ server.repl_backlog_time_limit = atoi(argv[1]);
+ if (server.repl_backlog_time_limit < 0) {
+ err = "repl-backlog-ttl can't be negative ";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
+ zfree(server.masterauth);
+ server.masterauth = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
+ if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"slave-read-only") && argc == 2) {
+ if ((server.repl_slave_ro = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
+ if ((server.rdb_compression = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"rdbchecksum") && argc == 2) {
+ if ((server.rdb_checksum = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"activerehashing") && argc == 2) {
+ if ((server.activerehashing = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"lazyfree-lazy-eviction") && argc == 2) {
+ if ((server.lazyfree_lazy_eviction = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"lazyfree-lazy-expire") && argc == 2) {
+ if ((server.lazyfree_lazy_expire = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"lazyfree-lazy-server-del") && argc == 2){
+ if ((server.lazyfree_lazy_server_del = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"slave-lazy-flush") && argc == 2) {
+ if ((server.repl_slave_lazy_flush = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"activedefrag") && argc == 2) {
+ if ((server.active_defrag_enabled = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"daemonize") && argc == 2) {
+ if ((server.daemonize = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"hz") && argc == 2) {
+ server.hz = atoi(argv[1]);
+ if (server.hz < CONFIG_MIN_HZ) server.hz = CONFIG_MIN_HZ;
+ if (server.hz > CONFIG_MAX_HZ) server.hz = CONFIG_MAX_HZ;
+ } else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
+ int yes;
+
+ if ((yes = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ server.aof_state = yes ? AOF_ON : AOF_OFF;
+ } else if (!strcasecmp(argv[0],"appendfilename") && argc == 2) {
+ if (!pathIsBaseName(argv[1])) {
+ err = "appendfilename can't be a path, just a filename";
+ goto loaderr;
+ }
+ zfree(server.aof_filename);
+ server.aof_filename = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"no-appendfsync-on-rewrite")
+ && argc == 2) {
+ if ((server.aof_no_fsync_on_rewrite= yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) {
+ server.aof_fsync = configEnumGetValue(aof_fsync_enum,argv[1]);
+ if (server.aof_fsync == INT_MIN) {
+ err = "argument must be 'no', 'always' or 'everysec'";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"auto-aof-rewrite-percentage") &&
+ argc == 2)
+ {
+ server.aof_rewrite_perc = atoi(argv[1]);
+ if (server.aof_rewrite_perc < 0) {
+ err = "Invalid negative percentage for AOF auto rewrite";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"auto-aof-rewrite-min-size") &&
+ argc == 2)
+ {
+ server.aof_rewrite_min_size = memtoll(argv[1],NULL);
+ } else if (!strcasecmp(argv[0],"aof-rewrite-incremental-fsync") &&
+ argc == 2)
+ {
+ if ((server.aof_rewrite_incremental_fsync =
+ yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"aof-load-truncated") && argc == 2) {
+ if ((server.aof_load_truncated = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"aof-use-rdb-preamble") && argc == 2) {
+ if ((server.aof_use_rdb_preamble = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
+ if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
+ err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
+ goto loaderr;
+ }
+ server.requirepass = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"pidfile") && argc == 2) {
+ zfree(server.pidfile);
+ server.pidfile = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"dbfilename") && argc == 2) {
+ if (!pathIsBaseName(argv[1])) {
+ err = "dbfilename can't be a path, just a filename";
+ goto loaderr;
+ }
+ zfree(server.rdb_filename);
+ server.rdb_filename = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"active-defrag-threshold-lower") && argc == 2) {
+ server.active_defrag_threshold_lower = atoi(argv[1]);
+ if (server.active_defrag_threshold_lower < 0) {
+ err = "active-defrag-threshold-lower must be 0 or greater";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"active-defrag-threshold-upper") && argc == 2) {
+ server.active_defrag_threshold_upper = atoi(argv[1]);
+ if (server.active_defrag_threshold_upper < 0) {
+ err = "active-defrag-threshold-upper must be 0 or greater";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"active-defrag-ignore-bytes") && argc == 2) {
+ server.active_defrag_ignore_bytes = memtoll(argv[1], NULL);
+ if (server.active_defrag_ignore_bytes <= 0) {
+ err = "active-defrag-ignore-bytes must above 0";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"active-defrag-cycle-min") && argc == 2) {
+ server.active_defrag_cycle_min = atoi(argv[1]);
+ if (server.active_defrag_cycle_min < 1 || server.active_defrag_cycle_min > 99) {
+ err = "active-defrag-cycle-min must be between 1 and 99";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"active-defrag-cycle-max") && argc == 2) {
+ server.active_defrag_cycle_max = atoi(argv[1]);
+ if (server.active_defrag_cycle_max < 1 || server.active_defrag_cycle_max > 99) {
+ err = "active-defrag-cycle-max must be between 1 and 99";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"hash-max-ziplist-entries") && argc == 2) {
+ server.hash_max_ziplist_entries = memtoll(argv[1], NULL);
+ } else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
+ server.hash_max_ziplist_value = memtoll(argv[1], NULL);
+ } else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
+ /* DEAD OPTION */
+ } else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
+ /* DEAD OPTION */
+ } else if (!strcasecmp(argv[0],"list-max-ziplist-size") && argc == 2) {
+ server.list_max_ziplist_size = atoi(argv[1]);
+ } else if (!strcasecmp(argv[0],"list-compress-depth") && argc == 2) {
+ server.list_compress_depth = atoi(argv[1]);
+ } else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2) {
+ server.set_max_intset_entries = memtoll(argv[1], NULL);
+ } else if (!strcasecmp(argv[0],"zset-max-ziplist-entries") && argc == 2) {
+ server.zset_max_ziplist_entries = memtoll(argv[1], NULL);
+ } else if (!strcasecmp(argv[0],"zset-max-ziplist-value") && argc == 2) {
+ server.zset_max_ziplist_value = memtoll(argv[1], NULL);
+ } else if (!strcasecmp(argv[0],"hll-sparse-max-bytes") && argc == 2) {
+ server.hll_sparse_max_bytes = memtoll(argv[1], NULL);
+ } else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
+ struct redisCommand *cmd = lookupCommand(argv[1]);
+ int retval;
+
+ if (!cmd) {
+ err = "No such command in rename-command";
+ goto loaderr;
+ }
+
+ /* If the target command name is the empty string we just
+ * remove it from the command table. */
+ retval = dictDelete(server.commands, argv[1]);
+ serverAssert(retval == DICT_OK);
+
+ /* Otherwise we re-add the command under a different name. */
+ if (sdslen(argv[2]) != 0) {
+ sds copy = sdsdup(argv[2]);
+
+ retval = dictAdd(server.commands, copy, cmd);
+ if (retval != DICT_OK) {
+ sdsfree(copy);
+ err = "Target command name already exists"; goto loaderr;
+ }
+ }
+ } else if (!strcasecmp(argv[0],"cluster-enabled") && argc == 2) {
+ if ((server.cluster_enabled = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"cluster-config-file") && argc == 2) {
+ zfree(server.cluster_configfile);
+ server.cluster_configfile = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"cluster-announce-ip") && argc == 2) {
+ zfree(server.cluster_announce_ip);
+ server.cluster_announce_ip = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"cluster-announce-port") && argc == 2) {
+ server.cluster_announce_port = atoi(argv[1]);
+ if (server.cluster_announce_port < 0 ||
+ server.cluster_announce_port > 65535)
+ {
+ err = "Invalid port"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"cluster-announce-bus-port") &&
+ argc == 2)
+ {
+ server.cluster_announce_bus_port = atoi(argv[1]);
+ if (server.cluster_announce_bus_port < 0 ||
+ server.cluster_announce_bus_port > 65535)
+ {
+ err = "Invalid port"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"cluster-require-full-coverage") &&
+ argc == 2)
+ {
+ if ((server.cluster_require_full_coverage = yesnotoi(argv[1])) == -1)
+ {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"cluster-node-timeout") && argc == 2) {
+ server.cluster_node_timeout = strtoll(argv[1],NULL,10);
+ if (server.cluster_node_timeout <= 0) {
+ err = "cluster node timeout must be 1 or greater"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"cluster-migration-barrier")
+ && argc == 2)
+ {
+ server.cluster_migration_barrier = atoi(argv[1]);
+ if (server.cluster_migration_barrier < 0) {
+ err = "cluster migration barrier must zero or positive";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"cluster-slave-validity-factor")
+ && argc == 2)
+ {
+ server.cluster_slave_validity_factor = atoi(argv[1]);
+ if (server.cluster_slave_validity_factor < 0) {
+ err = "cluster slave validity factor must be zero or positive";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
+ server.lua_time_limit = strtoll(argv[1],NULL,10);
+ } else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
+ argc == 2)
+ {
+ server.slowlog_log_slower_than = strtoll(argv[1],NULL,10);
+ } else if (!strcasecmp(argv[0],"latency-monitor-threshold") &&
+ argc == 2)
+ {
+ server.latency_monitor_threshold = strtoll(argv[1],NULL,10);
+ if (server.latency_monitor_threshold < 0) {
+ err = "The latency threshold can't be negative";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"slowlog-max-len") && argc == 2) {
+ server.slowlog_max_len = (PORT_ULONG)(strtol(argv[1],NULL,10)); WIN_PORT_FIX /* cast (PORT_ULONG) */
+ } else if (!strcasecmp(argv[0],"client-output-buffer-limit") &&
+ argc == 5)
+ {
+ int class = getClientTypeByName(argv[1]);
+ PORT_ULONGLONG hard, soft;
+ int soft_seconds;
+
+ if (class == -1 || class == CLIENT_TYPE_MASTER) {
+ err = "Unrecognized client limit class: the user specified "
+ "an invalid one, or 'master' which has no buffer limits.";
+ goto loaderr;
+ }
+ hard = memtoll(argv[2],NULL);
+ soft = memtoll(argv[3],NULL);
+ soft_seconds = atoi(argv[4]);
+ if (soft_seconds < 0) {
+ err = "Negative number of seconds in soft limit is invalid";
+ goto loaderr;
+ }
+ server.client_obuf_limits[class].hard_limit_bytes = hard;
+ server.client_obuf_limits[class].soft_limit_bytes = soft;
+ server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
+ } else if (!strcasecmp(argv[0],"stop-writes-on-bgsave-error") &&
+ argc == 2) {
+ if ((server.stop_writes_on_bgsave_err = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"slave-priority") && argc == 2) {
+ server.slave_priority = atoi(argv[1]);
+ } else if (!strcasecmp(argv[0],"slave-announce-ip") && argc == 2) {
+ zfree(server.slave_announce_ip);
+ server.slave_announce_ip = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"slave-announce-port") && argc == 2) {
+ server.slave_announce_port = atoi(argv[1]);
+ if (server.slave_announce_port < 0 ||
+ server.slave_announce_port > 65535)
+ {
+ err = "Invalid port"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"min-slaves-to-write") && argc == 2) {
+ server.repl_min_slaves_to_write = atoi(argv[1]);
+ if (server.repl_min_slaves_to_write < 0) {
+ err = "Invalid value for min-slaves-to-write."; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"min-slaves-max-lag") && argc == 2) {
+ server.repl_min_slaves_max_lag = atoi(argv[1]);
+ if (server.repl_min_slaves_max_lag < 0) {
+ err = "Invalid value for min-slaves-max-lag."; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) {
+ int flags = keyspaceEventsStringToFlags(argv[1]);
+
+ if (flags == -1) {
+ err = "Invalid event class character. Use 'g$lshzxeA'.";
+ goto loaderr;
+ }
+ server.notify_keyspace_events = flags;
+ } else if (!strcasecmp(argv[0],"supervised") && argc == 2) {
+ server.supervised_mode =
+ configEnumGetValue(supervised_mode_enum,argv[1]);
+
+ if (server.supervised_mode == INT_MIN) {
+ err = "Invalid option for 'supervised'. "
+ "Allowed values: 'upstart', 'systemd', 'auto', or 'no'";
+ goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) {
+ queueLoadModule(argv[1],&argv[2],argc-2);
+ } else if (!strcasecmp(argv[0],"sentinel")) {
+ /* argc == 1 is handled by main() as we need to enter the sentinel
+ * mode ASAP. */
+ if (argc != 1) {
+ if (!server.sentinel_mode) {
+ err = "sentinel directive while not in sentinel mode";
+ goto loaderr;
+ }
+ err = sentinelHandleConfiguration(argv+1,argc-1);
+ if (err) goto loaderr;
+ }
+#ifdef _WIN32
+ } else if (!strcasecmp(argv[0], "service-name")) {
+ // ignore. This is taken care of in the win32_service code.
+ } else if (!strcasecmp(argv[0], "persistence-available")) {
+ if (strcasecmp(argv[1], "no") == 0) {
+ // remove BGSAVE, BGREWRITEAOF and replication commands
+ // when persistence is disabled
+ int retval;
+ sds bgsave;
+ sds bgrewriteaof;
+ sds replconf;
+ sds psync;
+ sds sync;
+
+ bgsave = sdsnew("bgsave");
+ bgrewriteaof = sdsnew("bgrewriteaof");
+ replconf = sdsnew("replconf");
+ psync = sdsnew("psync");
+ sync = sdsnew("sync");
+
+ retval = dictDelete(server.commands, bgsave);
+ serverAssert(retval == DICT_OK);
+ retval = dictDelete(server.commands, bgrewriteaof);
+ serverAssert(retval == DICT_OK);
+ retval = dictDelete(server.commands, replconf);
+ serverAssert(retval == DICT_OK);
+ retval = dictDelete(server.commands, psync);
+ serverAssert(retval == DICT_OK);
+ retval = dictDelete(server.commands, sync);
+ serverAssert(retval == DICT_OK);
+
+ sdsfree(bgsave);
+ sdsfree(bgrewriteaof);
+ sdsfree(replconf);
+ sdsfree(psync);
+ sdsfree(sync);
+ }
+#endif
+ } else {
+ err = "Bad directive or wrong number of arguments"; goto loaderr;
+ }
+ sdsfreesplitres(argv,argc);
+ }
+
+ /* Sanity checks. */
+ if (server.cluster_enabled && server.masterhost) {
+ linenum = slaveof_linenum;
+ i = linenum-1;
+ err = "slaveof directive not allowed in cluster mode";
+ goto loaderr;
+ }
+
+ sdsfreesplitres(lines,totlines);
+ return;
+
+loaderr:
+#ifdef _WIN32
+ serverLog(LL_WARNING, "\n*** FATAL CONFIG FILE ERROR ***\n");
+ serverLog(LL_WARNING, "Reading the configuration file, at line %d\n", linenum);
+ serverLog(LL_WARNING, ">>> '%s'\n", lines[i]);
+ serverLog(LL_WARNING, "%s\n", err);
+#else
+ fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR ***\n");
+ fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
+ fprintf(stderr, ">>> '%s'\n", lines[i]);
+ fprintf(stderr, "%s\n", err);
+#endif
+ exit(1);
+}
+
+/* Load the server configuration from the specified filename.
+ * The function appends the additional configuration directives stored
+ * in the 'options' string to the config file before loading.
+ *
+ * Both filename and options can be NULL, in such a case are considered
+ * empty. This way loadServerConfig can be used to just load a file or
+ * just load a string. */
+void loadServerConfig(char *filename, char *options) {
+ sds config = sdsempty();
+ char buf[CONFIG_MAX_LINE+1];
+
+ /* Load the file content */
+ if (filename) {
+ FILE *fp;
+
+ if (filename[0] == '-' && filename[1] == '\0') {
+ fp = stdin;
+ } else {
+ if ((fp = fopen(filename,IF_WIN32("rb","r"))) == NULL) {
+ serverLog(LL_WARNING,
+ "Fatal error, can't open config file '%s'", filename);
+ exit(1);
+ }
+ }
+ while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
+ config = sdscat(config,buf);
+ if (fp != stdin) fclose(fp);
+ }
+ /* Append the additional options */
+ if (options) {
+ config = sdscat(config,"\n");
+ config = sdscat(config,options);
+ }
+ loadServerConfigFromString(config);
+ sdsfree(config);
+}
+
+/*-----------------------------------------------------------------------------
+ * CONFIG SET implementation
+ *----------------------------------------------------------------------------*/
+
+#define config_set_bool_field(_name,_var) \
+ } else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
+ int yn = yesnotoi(o->ptr); \
+ if (yn == -1) goto badfmt; \
+ _var = yn;
+
+#define config_set_numerical_field(_name,_var,min,max) \
+ } else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
+ if (getLongLongFromObject(o,&ll) == C_ERR) goto badfmt; \
+ if (min != LLONG_MIN && ll < min) goto badfmt; \
+ if (max != LLONG_MAX && ll > max) goto badfmt; \
+ _var = ll;
+
+#define config_set_memory_field(_name,_var) \
+ } else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
+ ll = memtoll(o->ptr,&err); \
+ if (err || ll < 0) goto badfmt; \
+ _var = ll;
+
+#define config_set_enum_field(_name,_var,_enumvar) \
+ } else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
+ int enumval = configEnumGetValue(_enumvar,o->ptr); \
+ if (enumval == INT_MIN) goto badfmt; \
+ _var = enumval;
+
+#define config_set_special_field(_name) \
+ } else if (!strcasecmp(c->argv[2]->ptr,_name)) {
+
+#define config_set_else } else
+
+void configSetCommand(client *c) {
+ robj *o;
+ PORT_LONGLONG ll;
+ int err;
+ serverAssertWithInfo(c,c->argv[2],sdsEncodedObject(c->argv[2]));
+ serverAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
+ o = c->argv[3];
+
+ if (0) { /* this starts the config_set macros else-if chain. */
+
+ /* Special fields that can't be handled with general macros. */
+ config_set_special_field("dbfilename") {
+ if (!pathIsBaseName(o->ptr)) {
+ addReplyError(c, "dbfilename can't be a path, just a filename");
+ return;
+ }
+ zfree(server.rdb_filename);
+ server.rdb_filename = zstrdup(o->ptr);
+ } config_set_special_field("requirepass") {
+ if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
+ zfree(server.requirepass);
+ server.requirepass = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
+ } config_set_special_field("masterauth") {
+ zfree(server.masterauth);
+ server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
+ } config_set_special_field("cluster-announce-ip") {
+ zfree(server.cluster_announce_ip);
+ server.cluster_announce_ip = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
+ } config_set_special_field("maxclients") {
+ int orig_value = server.maxclients;
+
+ if (getLongLongFromObject(o,&ll) == C_ERR || ll < 1) goto badfmt;
+
+ /* Try to check if the OS is capable of supporting so many FDs. */
+ server.maxclients = (int)ll; WIN_PORT_FIX /* cast (int) */
+ if (ll > orig_value) {
+ adjustOpenFilesLimit();
+ if (server.maxclients != ll) {
+ addReplyErrorFormat(c,"The operating system is not able to handle the specified number of clients, try with %d", server.maxclients);
+ server.maxclients = orig_value;
+ return;
+ }
+ if ((unsigned int) aeGetSetSize(server.el) <
+ server.maxclients + CONFIG_FDSET_INCR)
+ {
+ if (aeResizeSetSize(server.el,
+ server.maxclients + CONFIG_FDSET_INCR) == AE_ERR)
+ {
+ addReplyError(c,"The event loop API used by Redis is not able to handle the specified number of clients");
+ server.maxclients = orig_value;
+ return;
+ }
+ }
+ }
+ } config_set_special_field("appendonly") {
+ int enable = yesnotoi(o->ptr);
+
+ if (enable == -1) goto badfmt;
+ if (enable == 0 && server.aof_state != AOF_OFF) {
+ stopAppendOnly();
+ } else if (enable && server.aof_state == AOF_OFF) {
+ if (startAppendOnly() == C_ERR) {
+ addReplyError(c,
+ "Unable to turn on AOF. Check server logs.");
+ return;
+ }
+ }
+ } config_set_special_field("save") {
+ int vlen, j;
+ sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
+
+ /* Perform sanity check before setting the new config:
+ * - Even number of args
+ * - Seconds >= 1, changes >= 0 */
+ if (vlen & 1) {
+ sdsfreesplitres(v,vlen);
+ goto badfmt;
+ }
+ for (j = 0; j < vlen; j++) {
+ char *eptr;
+ PORT_LONG val;
+
+ val = strtoll(v[j], &eptr, 10);
+ if (eptr[0] != '\0' ||
+ ((j & 1) == 0 && val < 1) ||
+ ((j & 1) == 1 && val < 0)) {
+ sdsfreesplitres(v,vlen);
+ goto badfmt;
+ }
+ }
+ /* Finally set the new config */
+ resetServerSaveParams();
+ for (j = 0; j < vlen; j += 2) {
+ time_t seconds;
+ int changes;
+
+ seconds = strtoll(v[j],NULL,10);
+ changes = strtoll(v[j+1],NULL,10);
+ appendServerSaveParams(seconds, changes);
+ }
+ sdsfreesplitres(v,vlen);
+ } config_set_special_field("dir") {
+ if (chdir((char*)o->ptr) == -1) {
+ addReplyErrorFormat(c,"Changing directory: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ return;
+ }
+ } config_set_special_field("client-output-buffer-limit") {
+ int vlen, j;
+ sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
+
+ /* We need a multiple of 4: */
+ if (vlen % 4) {
+ sdsfreesplitres(v,vlen);
+ goto badfmt;
+ }
+
+ /* Sanity check of single arguments, so that we either refuse the
+ * whole configuration string or accept it all, even if a single
+ * error in a single client class is present. */
+ for (j = 0; j < vlen; j++) {
+ PORT_LONG val;
+
+ if ((j % 4) == 0) {
+ int class = getClientTypeByName(v[j]);
+ if (class == -1 || class == CLIENT_TYPE_MASTER) {
+ sdsfreesplitres(v,vlen);
+ goto badfmt;
+ }
+ } else {
+ val = memtoll(v[j], &err);
+ if (err || val < 0) {
+ sdsfreesplitres(v,vlen);
+ goto badfmt;
+ }
+ }
+ }
+ /* Finally set the new config */
+ for (j = 0; j < vlen; j += 4) {
+ int class;
+ PORT_ULONGLONG hard, soft;
+ int soft_seconds;
+
+ class = getClientTypeByName(v[j]);
+ hard = strtoll(v[j+1],NULL,10);
+ soft = strtoll(v[j+2],NULL,10);
+ soft_seconds = strtoll(v[j+3],NULL,10);
+
+ server.client_obuf_limits[class].hard_limit_bytes = hard;
+ server.client_obuf_limits[class].soft_limit_bytes = soft;
+ server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
+ }
+ sdsfreesplitres(v,vlen);
+ } config_set_special_field("notify-keyspace-events") {
+ int flags = keyspaceEventsStringToFlags(o->ptr);
+
+ if (flags == -1) goto badfmt;
+ server.notify_keyspace_events = flags;
+ } config_set_special_field("slave-announce-ip") {
+ zfree(server.slave_announce_ip);
+ server.slave_announce_ip = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
+
+ /* Boolean fields.
+ * config_set_bool_field(name,var). */
+ } config_set_bool_field(
+ "rdbcompression", server.rdb_compression) {
+ } config_set_bool_field(
+ "repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay) {
+ } config_set_bool_field(
+ "repl-diskless-sync",server.repl_diskless_sync) {
+ } config_set_bool_field(
+ "cluster-require-full-coverage",server.cluster_require_full_coverage) {
+ } config_set_bool_field(
+ "aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) {
+ } config_set_bool_field(
+ "aof-load-truncated",server.aof_load_truncated) {
+ } config_set_bool_field(
+ "aof-use-rdb-preamble",server.aof_use_rdb_preamble) {
+ } config_set_bool_field(
+ "slave-serve-stale-data",server.repl_serve_stale_data) {
+ } config_set_bool_field(
+ "slave-read-only",server.repl_slave_ro) {
+ } config_set_bool_field(
+ "activerehashing",server.activerehashing) {
+ } config_set_bool_field(
+ "activedefrag",server.active_defrag_enabled) {
+#ifndef HAVE_DEFRAG
+ if (server.active_defrag_enabled) {
+ server.active_defrag_enabled = 0;
+ addReplyError(c,
+ "Active defragmentation cannot be enabled: it requires a "
+ "Redis server compiled with a modified Jemalloc like the "
+ "one shipped by default with the Redis source distribution");
+ return;
+ }
+#endif
+ } config_set_bool_field(
+ "protected-mode",server.protected_mode) {
+ } config_set_bool_field(
+ "stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err) {
+ } config_set_bool_field(
+ "lazyfree-lazy-eviction",server.lazyfree_lazy_eviction) {
+ } config_set_bool_field(
+ "lazyfree-lazy-expire",server.lazyfree_lazy_expire) {
+ } config_set_bool_field(
+ "lazyfree-lazy-server-del",server.lazyfree_lazy_server_del) {
+ } config_set_bool_field(
+ "slave-lazy-flush",server.repl_slave_lazy_flush) {
+ } config_set_bool_field(
+ "no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite) {
+
+ /* Numerical fields.
+ * config_set_numerical_field(name,var,min,max) */
+ } config_set_numerical_field(
+ "tcp-keepalive",server.tcpkeepalive,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "maxmemory-samples",server.maxmemory_samples,1,LLONG_MAX) {
+ } config_set_numerical_field(
+ "lfu-log-factor",server.lfu_log_factor,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "lfu-decay-time",server.lfu_decay_time,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "timeout",server.maxidletime,0,LONG_MAX) {
+ } config_set_numerical_field(
+ "active-defrag-threshold-lower",server.active_defrag_threshold_lower,0,1000) {
+ } config_set_numerical_field(
+ "active-defrag-threshold-upper",server.active_defrag_threshold_upper,0,1000) {
+ } config_set_memory_field(
+ "active-defrag-ignore-bytes",server.active_defrag_ignore_bytes) {
+ } config_set_numerical_field(
+ "active-defrag-cycle-min",server.active_defrag_cycle_min,1,99) {
+ } config_set_numerical_field(
+ "active-defrag-cycle-max",server.active_defrag_cycle_max,1,99) {
+ } config_set_numerical_field(
+ "auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,LLONG_MAX){
+ } config_set_numerical_field(
+ "hash-max-ziplist-entries",server.hash_max_ziplist_entries,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "hash-max-ziplist-value",server.hash_max_ziplist_value,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "list-max-ziplist-size",server.list_max_ziplist_size,INT_MIN,INT_MAX) {
+ } config_set_numerical_field(
+ "list-compress-depth",server.list_compress_depth,0,INT_MAX) {
+ } config_set_numerical_field(
+ "set-max-intset-entries",server.set_max_intset_entries,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "zset-max-ziplist-entries",server.zset_max_ziplist_entries,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "zset-max-ziplist-value",server.zset_max_ziplist_value,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "hll-sparse-max-bytes",server.hll_sparse_max_bytes,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "lua-time-limit",server.lua_time_limit,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "slowlog-log-slower-than",server.slowlog_log_slower_than,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "slowlog-max-len",ll,0,LLONG_MAX) {
+ /* Cast to unsigned. */
+ server.slowlog_max_len = (unsigned)ll;
+ } config_set_numerical_field(
+ "latency-monitor-threshold",server.latency_monitor_threshold,0,LLONG_MAX){
+ } config_set_numerical_field(
+ "repl-ping-slave-period",server.repl_ping_slave_period,1,LLONG_MAX) {
+ } config_set_numerical_field(
+ "repl-timeout",server.repl_timeout,1,LLONG_MAX) {
+ } config_set_numerical_field(
+ "repl-backlog-ttl",server.repl_backlog_time_limit,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "slave-priority",server.slave_priority,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "slave-announce-port",server.slave_announce_port,0,65535) {
+ } config_set_numerical_field(
+ "min-slaves-to-write",server.repl_min_slaves_to_write,0,LLONG_MAX) {
+ refreshGoodSlavesCount();
+ } config_set_numerical_field(
+ "min-slaves-max-lag",server.repl_min_slaves_max_lag,0,LLONG_MAX) {
+ refreshGoodSlavesCount();
+ } config_set_numerical_field(
+ "cluster-node-timeout",server.cluster_node_timeout,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "cluster-announce-port",server.cluster_announce_port,0,65535) {
+ } config_set_numerical_field(
+ "cluster-announce-bus-port",server.cluster_announce_bus_port,0,65535) {
+ } config_set_numerical_field(
+ "cluster-migration-barrier",server.cluster_migration_barrier,0,LLONG_MAX){
+ } config_set_numerical_field(
+ "cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "hz",server.hz,0,LLONG_MAX) {
+ /* Hz is more an hint from the user, so we accept values out of range
+ * but cap them to reasonable values. */
+ if (server.hz < CONFIG_MIN_HZ) server.hz = CONFIG_MIN_HZ;
+ if (server.hz > CONFIG_MAX_HZ) server.hz = CONFIG_MAX_HZ;
+ } config_set_numerical_field(
+ "watchdog-period",ll,0,LLONG_MAX) {
+ if (ll)
+ enableWatchdog(ll);
+ else
+ disableWatchdog();
+
+ /* Memory fields.
+ * config_set_memory_field(name,var) */
+ } config_set_memory_field("maxmemory",server.maxmemory) {
+ if (server.maxmemory) {
+ if (server.maxmemory < zmalloc_used_memory()) {
+ serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in keys eviction and/or inability to accept new write commands depending on the maxmemory-policy.");
+ }
+ freeMemoryIfNeeded();
+ }
+ } config_set_memory_field("repl-backlog-size",ll) {
+ resizeReplicationBacklog(ll);
+ } config_set_memory_field("auto-aof-rewrite-min-size",ll) {
+ server.aof_rewrite_min_size = ll;
+
+ /* Enumeration fields.
+ * config_set_enum_field(name,var,enum_var) */
+ } config_set_enum_field(
+ "loglevel",server.verbosity,loglevel_enum) {
+#ifdef _WIN32
+ setLogVerbosityLevel(server.verbosity);
+#endif
+ } config_set_enum_field(
+ "maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum) {
+ } config_set_enum_field(
+ "appendfsync",server.aof_fsync,aof_fsync_enum) {
+
+ /* Everyhing else is an error... */
+ } config_set_else {
+ addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
+ (char*)c->argv[2]->ptr);
+ return;
+ }
+
+ /* On success we just return a generic OK for all the options. */
+ addReply(c,shared.ok);
+ return;
+
+badfmt: /* Bad format errors */
+ addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
+ (char*)o->ptr,
+ (char*)c->argv[2]->ptr);
+}
+
+/*-----------------------------------------------------------------------------
+ * CONFIG GET implementation
+ *----------------------------------------------------------------------------*/
+
+#define config_get_string_field(_name,_var) do { \
+ if (stringmatch(pattern,_name,1)) { \
+ addReplyBulkCString(c,_name); \
+ addReplyBulkCString(c,_var ? _var : ""); \
+ matches++; \
+ } \
+} while(0);
+
+#define config_get_bool_field(_name,_var) do { \
+ if (stringmatch(pattern,_name,1)) { \
+ addReplyBulkCString(c,_name); \
+ addReplyBulkCString(c,_var ? "yes" : "no"); \
+ matches++; \
+ } \
+} while(0);
+
+#define config_get_numerical_field(_name,_var) do { \
+ if (stringmatch(pattern,_name,1)) { \
+ ll2string(buf,sizeof(buf),_var); \
+ addReplyBulkCString(c,_name); \
+ addReplyBulkCString(c,buf); \
+ matches++; \
+ } \
+} while(0);
+
+#define config_get_enum_field(_name,_var,_enumvar) do { \
+ if (stringmatch(pattern,_name,1)) { \
+ addReplyBulkCString(c,_name); \
+ addReplyBulkCString(c,configEnumGetNameOrUnknown(_enumvar,_var)); \
+ matches++; \
+ } \
+} while(0);
+
+void configGetCommand(client *c) {
+ robj *o = c->argv[2];
+ void *replylen = addDeferredMultiBulkLength(c);
+ char *pattern = o->ptr;
+ char buf[128];
+ int matches = 0;
+ serverAssertWithInfo(c,o,sdsEncodedObject(o));
+
+ /* String values */
+ config_get_string_field("dbfilename",server.rdb_filename);
+ config_get_string_field("requirepass",server.requirepass);
+ config_get_string_field("masterauth",server.masterauth);
+ config_get_string_field("cluster-announce-ip",server.cluster_announce_ip);
+ config_get_string_field("unixsocket",server.unixsocket);
+ config_get_string_field("logfile",server.logfile);
+ config_get_string_field("pidfile",server.pidfile);
+ config_get_string_field("slave-announce-ip",server.slave_announce_ip);
+
+ /* Numerical values */
+ config_get_numerical_field("maxmemory",server.maxmemory);
+ config_get_numerical_field("maxmemory-samples",server.maxmemory_samples);
+ config_get_numerical_field("timeout",server.maxidletime);
+ config_get_numerical_field("active-defrag-threshold-lower",server.active_defrag_threshold_lower);
+ config_get_numerical_field("active-defrag-threshold-upper",server.active_defrag_threshold_upper);
+ config_get_numerical_field("active-defrag-ignore-bytes",server.active_defrag_ignore_bytes);
+ config_get_numerical_field("active-defrag-cycle-min",server.active_defrag_cycle_min);
+ config_get_numerical_field("active-defrag-cycle-max",server.active_defrag_cycle_max);
+ config_get_numerical_field("auto-aof-rewrite-percentage",
+ server.aof_rewrite_perc);
+ config_get_numerical_field("auto-aof-rewrite-min-size",
+ server.aof_rewrite_min_size);
+ config_get_numerical_field("hash-max-ziplist-entries",
+ server.hash_max_ziplist_entries);
+ config_get_numerical_field("hash-max-ziplist-value",
+ server.hash_max_ziplist_value);
+ config_get_numerical_field("list-max-ziplist-size",
+ server.list_max_ziplist_size);
+ config_get_numerical_field("list-compress-depth",
+ server.list_compress_depth);
+ config_get_numerical_field("set-max-intset-entries",
+ server.set_max_intset_entries);
+ config_get_numerical_field("zset-max-ziplist-entries",
+ server.zset_max_ziplist_entries);
+ config_get_numerical_field("zset-max-ziplist-value",
+ server.zset_max_ziplist_value);
+ config_get_numerical_field("hll-sparse-max-bytes",
+ server.hll_sparse_max_bytes);
+ config_get_numerical_field("lua-time-limit",server.lua_time_limit);
+ config_get_numerical_field("slowlog-log-slower-than",
+ server.slowlog_log_slower_than);
+ config_get_numerical_field("latency-monitor-threshold",
+ server.latency_monitor_threshold);
+ config_get_numerical_field("slowlog-max-len",
+ server.slowlog_max_len);
+ config_get_numerical_field("port",server.port);
+ config_get_numerical_field("cluster-announce-port",server.cluster_announce_port);
+ config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port);
+ config_get_numerical_field("tcp-backlog",server.tcp_backlog);
+ config_get_numerical_field("databases",server.dbnum);
+ config_get_numerical_field("repl-ping-slave-period",server.repl_ping_slave_period);
+ config_get_numerical_field("repl-timeout",server.repl_timeout);
+ config_get_numerical_field("repl-backlog-size",server.repl_backlog_size);
+ config_get_numerical_field("repl-backlog-ttl",server.repl_backlog_time_limit);
+ config_get_numerical_field("maxclients",server.maxclients);
+ config_get_numerical_field("watchdog-period",server.watchdog_period);
+ config_get_numerical_field("slave-priority",server.slave_priority);
+ config_get_numerical_field("slave-announce-port",server.slave_announce_port);
+ config_get_numerical_field("min-slaves-to-write",server.repl_min_slaves_to_write);
+ config_get_numerical_field("min-slaves-max-lag",server.repl_min_slaves_max_lag);
+ config_get_numerical_field("hz",server.hz);
+ config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
+ config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier);
+ config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
+ config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay);
+ config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
+
+ /* Bool (yes/no) values */
+ config_get_bool_field("cluster-require-full-coverage",
+ server.cluster_require_full_coverage);
+ config_get_bool_field("no-appendfsync-on-rewrite",
+ server.aof_no_fsync_on_rewrite);
+ config_get_bool_field("slave-serve-stale-data",
+ server.repl_serve_stale_data);
+ config_get_bool_field("slave-read-only",
+ server.repl_slave_ro);
+ config_get_bool_field("stop-writes-on-bgsave-error",
+ server.stop_writes_on_bgsave_err);
+ config_get_bool_field("daemonize", server.daemonize);
+ config_get_bool_field("rdbcompression", server.rdb_compression);
+ config_get_bool_field("rdbchecksum", server.rdb_checksum);
+ config_get_bool_field("activerehashing", server.activerehashing);
+ config_get_bool_field("activedefrag", server.active_defrag_enabled);
+ config_get_bool_field("protected-mode", server.protected_mode);
+ config_get_bool_field("repl-disable-tcp-nodelay",
+ server.repl_disable_tcp_nodelay);
+ config_get_bool_field("repl-diskless-sync",
+ server.repl_diskless_sync);
+ config_get_bool_field("aof-rewrite-incremental-fsync",
+ server.aof_rewrite_incremental_fsync);
+ config_get_bool_field("aof-load-truncated",
+ server.aof_load_truncated);
+ config_get_bool_field("aof-use-rdb-preamble",
+ server.aof_use_rdb_preamble);
+ config_get_bool_field("lazyfree-lazy-eviction",
+ server.lazyfree_lazy_eviction);
+ config_get_bool_field("lazyfree-lazy-expire",
+ server.lazyfree_lazy_expire);
+ config_get_bool_field("lazyfree-lazy-server-del",
+ server.lazyfree_lazy_server_del);
+ config_get_bool_field("slave-lazy-flush",
+ server.repl_slave_lazy_flush);
+
+ /* Enum values */
+ config_get_enum_field("maxmemory-policy",
+ server.maxmemory_policy,maxmemory_policy_enum);
+ config_get_enum_field("loglevel",
+ server.verbosity,loglevel_enum);
+ config_get_enum_field("supervised",
+ server.supervised_mode,supervised_mode_enum);
+ config_get_enum_field("appendfsync",
+ server.aof_fsync,aof_fsync_enum);
+#ifndef _WIN32
+ config_get_enum_field("syslog-facility",
+ server.syslog_facility,syslog_facility_enum);
+#endif
+
+ /* Everything we can't handle with macros follows. */
+
+ if (stringmatch(pattern,"appendonly",1)) {
+ addReplyBulkCString(c,"appendonly");
+ addReplyBulkCString(c,server.aof_state == AOF_OFF ? "no" : "yes");
+ matches++;
+ }
+ if (stringmatch(pattern,"dir",1)) {
+ char buf[1024];
+
+ if (getcwd(buf,sizeof(buf)) == NULL)
+ buf[0] = '\0';
+
+ addReplyBulkCString(c,"dir");
+ addReplyBulkCString(c,buf);
+ matches++;
+ }
+ if (stringmatch(pattern,"save",1)) {
+ sds buf = sdsempty();
+ int j;
+
+ for (j = 0; j < server.saveparamslen; j++) {
+ buf = sdscatprintf(buf,"%jd %d",
+ (intmax_t)server.saveparams[j].seconds,
+ server.saveparams[j].changes);
+ if (j != server.saveparamslen-1)
+ buf = sdscatlen(buf," ",1);
+ }
+ addReplyBulkCString(c,"save");
+ addReplyBulkCString(c,buf);
+ sdsfree(buf);
+ matches++;
+ }
+ if (stringmatch(pattern,"client-output-buffer-limit",1)) {
+ sds buf = sdsempty();
+ int j;
+
+ for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) {
+ buf = sdscatprintf(buf,"%s %llu %llu %Id", WIN_PORT_FIX /* %ld -> %Id */
+ getClientTypeName(j),
+ server.client_obuf_limits[j].hard_limit_bytes,
+ server.client_obuf_limits[j].soft_limit_bytes,
+ (PORT_LONG) server.client_obuf_limits[j].soft_limit_seconds);
+ if (j != CLIENT_TYPE_OBUF_COUNT-1)
+ buf = sdscatlen(buf," ",1);
+ }
+ addReplyBulkCString(c,"client-output-buffer-limit");
+ addReplyBulkCString(c,buf);
+ sdsfree(buf);
+ matches++;
+ }
+ if (stringmatch(pattern,"unixsocketperm",1)) {
+ char buf[32];
+ snprintf(buf,sizeof(buf),"%o",server.unixsocketperm);
+ addReplyBulkCString(c,"unixsocketperm");
+ addReplyBulkCString(c,buf);
+ matches++;
+ }
+ if (stringmatch(pattern,"slaveof",1)) {
+ char buf[256];
+
+ addReplyBulkCString(c,"slaveof");
+ if (server.masterhost)
+ snprintf(buf,sizeof(buf),"%s %d",
+ server.masterhost, server.masterport);
+ else
+ buf[0] = '\0';
+ addReplyBulkCString(c,buf);
+ matches++;
+ }
+ if (stringmatch(pattern,"notify-keyspace-events",1)) {
+ robj *flagsobj = createObject(OBJ_STRING,
+ keyspaceEventsFlagsToString(server.notify_keyspace_events));
+
+ addReplyBulkCString(c,"notify-keyspace-events");
+ addReplyBulk(c,flagsobj);
+ decrRefCount(flagsobj);
+ matches++;
+ }
+ if (stringmatch(pattern,"bind",1)) {
+ sds aux = sdsjoin(server.bindaddr,server.bindaddr_count," ");
+
+ addReplyBulkCString(c,"bind");
+ addReplyBulkCString(c,aux);
+ sdsfree(aux);
+ matches++;
+ }
+ setDeferredMultiBulkLength(c,replylen,matches*2);
+}
+
+/*-----------------------------------------------------------------------------
+ * CONFIG REWRITE implementation
+ *----------------------------------------------------------------------------*/
+
+#define REDIS_CONFIG_REWRITE_SIGNATURE "# Generated by CONFIG REWRITE"
+
+/* We use the following dictionary type to store where a configuration
+ * option is mentioned in the old configuration file, so it's
+ * like "maxmemory" -> list of line numbers (first line is zero). */
+uint64_t dictSdsCaseHash(const void *key);
+int dictSdsKeyCaseCompare(void *privdata, const void *key1, const void *key2);
+void dictSdsDestructor(void *privdata, void *val);
+void dictListDestructor(void *privdata, void *val);
+
+/* Sentinel config rewriting is implemented inside sentinel.c by
+ * rewriteConfigSentinelOption(). */
+void rewriteConfigSentinelOption(struct rewriteConfigState *state);
+
+dictType optionToLineDictType = {
+ dictSdsCaseHash, /* hash function */
+ NULL, /* key dup */
+ NULL, /* val dup */
+ dictSdsKeyCaseCompare, /* key compare */
+ dictSdsDestructor, /* key destructor */
+ dictListDestructor /* val destructor */
+};
+
+dictType optionSetDictType = {
+ dictSdsCaseHash, /* hash function */
+ NULL, /* key dup */
+ NULL, /* val dup */
+ dictSdsKeyCaseCompare, /* key compare */
+ dictSdsDestructor, /* key destructor */
+ NULL /* val destructor */
+};
+
+/* The config rewrite state. */
+struct rewriteConfigState {
+ dict *option_to_line; /* Option -> list of config file lines map */
+ dict *rewritten; /* Dictionary of already processed options */
+ int numlines; /* Number of lines in current config */
+ sds *lines; /* Current lines as an array of sds strings */
+ int has_tail; /* True if we already added directives that were
+ not present in the original config file. */
+};
+
+/* Append the new line to the current configuration state. */
+void rewriteConfigAppendLine(struct rewriteConfigState *state, sds line) {
+ state->lines = zrealloc(state->lines, sizeof(char*) * (state->numlines+1));
+ state->lines[state->numlines++] = line;
+}
+
+/* Populate the option -> list of line numbers map. */
+void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds option, int linenum) {
+ list *l = dictFetchValue(state->option_to_line,option);
+
+ if (l == NULL) {
+ l = listCreate();
+ dictAdd(state->option_to_line,sdsdup(option),l);
+ }
+ listAddNodeTail(l,(void*)(PORT_LONG)linenum);
+}
+
+/* Add the specified option to the set of processed options.
+ * This is useful as only unused lines of processed options will be blanked
+ * in the config file, while options the rewrite process does not understand
+ * remain untouched. */
+void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *option) {
+ sds opt = sdsnew(option);
+
+ if (dictAdd(state->rewritten,opt,NULL) != DICT_OK) sdsfree(opt);
+}
+
+/* Read the old file, split it into lines to populate a newly created
+ * config rewrite state, and return it to the caller.
+ *
+ * If it is impossible to read the old file, NULL is returned.
+ * If the old file does not exist at all, an empty state is returned. */
+struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
+ FILE *fp = fopen(path,"r");
+ struct rewriteConfigState *state = zmalloc(sizeof(*state));
+ char buf[CONFIG_MAX_LINE+1];
+ int linenum = -1;
+
+ if (fp == NULL && errno != ENOENT) return NULL;
+
+ state->option_to_line = dictCreate(&optionToLineDictType,NULL);
+ state->rewritten = dictCreate(&optionSetDictType,NULL);
+ state->numlines = 0;
+ state->lines = NULL;
+ state->has_tail = 0;
+ if (fp == NULL) return state;
+
+ /* Read the old file line by line, populate the state. */
+ while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL) {
+ int argc;
+ sds *argv;
+ sds line = sdstrim(sdsnew(buf),"\r\n\t ");
+
+ linenum++; /* Zero based, so we init at -1 */
+
+ /* Handle comments and empty lines. */
+ if (line[0] == '#' || line[0] == '\0') {
+ if (!state->has_tail && !strcmp(line,REDIS_CONFIG_REWRITE_SIGNATURE))
+ state->has_tail = 1;
+ rewriteConfigAppendLine(state,line);
+ continue;
+ }
+
+ /* Not a comment, split into arguments. */
+ argv = sdssplitargs(line,&argc);
+ if (argv == NULL) {
+ /* Apparently the line is unparsable for some reason, for
+ * instance it may have unbalanced quotes. Load it as a
+ * comment. */
+ sds aux = sdsnew("# ??? ");
+ aux = sdscatsds(aux,line);
+ sdsfree(line);
+ rewriteConfigAppendLine(state,aux);
+ continue;
+ }
+
+ sdstolower(argv[0]); /* We only want lowercase config directives. */
+
+ /* Now we populate the state according to the content of this line.
+ * Append the line and populate the option -> line numbers map. */
+ rewriteConfigAppendLine(state,line);
+ rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
+
+ sdsfreesplitres(argv,argc);
+ }
+ fclose(fp);
+ return state;
+}
+
+/* Rewrite the specified configuration option with the new "line".
+ * It progressively uses lines of the file that were already used for the same
+ * configuration option in the old version of the file, removing that line from
+ * the map of options -> line numbers.
+ *
+ * If there are lines associated with a given configuration option and
+ * "force" is non-zero, the line is appended to the configuration file.
+ * Usually "force" is true when an option has not its default value, so it
+ * must be rewritten even if not present previously.
+ *
+ * The first time a line is appended into a configuration file, a comment
+ * is added to show that starting from that point the config file was generated
+ * by CONFIG REWRITE.
+ *
+ * "line" is either used, or freed, so the caller does not need to free it
+ * in any way. */
+void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force) {
+ sds o = sdsnew(option);
+ list *l = dictFetchValue(state->option_to_line,o);
+
+ rewriteConfigMarkAsProcessed(state,option);
+
+ if (!l && !force) {
+ /* Option not used previously, and we are not forced to use it. */
+ sdsfree(line);
+ sdsfree(o);
+ return;
+ }
+
+ if (l) {
+ listNode *ln = listFirst(l);
+ int linenum = (int)((PORT_LONG) ln->value); WIN_PORT_FIX /* cast (int) */
+
+ /* There are still lines in the old configuration file we can reuse
+ * for this option. Replace the line with the new one. */
+ listDelNode(l,ln);
+ if (listLength(l) == 0) dictDelete(state->option_to_line,o);
+ sdsfree(state->lines[linenum]);
+ state->lines[linenum] = line;
+ } else {
+ /* Append a new line. */
+ if (!state->has_tail) {
+ rewriteConfigAppendLine(state,
+ sdsnew(REDIS_CONFIG_REWRITE_SIGNATURE));
+ state->has_tail = 1;
+ }
+ rewriteConfigAppendLine(state,line);
+ }
+ sdsfree(o);
+}
+
+/* Write the PORT_LONGLONG 'bytes' value as a string in a way that is parsable
+ * inside redis.conf. If possible uses the GB, MB, KB notation. */
+int rewriteConfigFormatMemory(char *buf, size_t len, PORT_LONGLONG bytes) {
+ int gb = 1024*1024*1024;
+ int mb = 1024*1024;
+ int kb = 1024;
+
+ if (bytes && (bytes % gb) == 0) {
+ return snprintf(buf,len,"%lldgb",bytes/gb);
+ } else if (bytes && (bytes % mb) == 0) {
+ return snprintf(buf,len,"%lldmb",bytes/mb);
+ } else if (bytes && (bytes % kb) == 0) {
+ return snprintf(buf,len,"%lldkb",bytes/kb);
+ } else {
+ return snprintf(buf,len,"%lld",bytes);
+ }
+}
+
+/* Rewrite a simple "option-name " configuration option. */
+void rewriteConfigBytesOption(struct rewriteConfigState *state, char *option, PORT_LONGLONG value, PORT_LONGLONG defvalue) {
+ char buf[64];
+ int force = value != defvalue;
+ sds line;
+
+ rewriteConfigFormatMemory(buf,sizeof(buf),value);
+ line = sdscatprintf(sdsempty(),"%s %s",option,buf);
+ rewriteConfigRewriteLine(state,option,line,force);
+}
+
+/* Rewrite a yes/no option. */
+void rewriteConfigYesNoOption(struct rewriteConfigState *state, char *option, int value, int defvalue) {
+ int force = value != defvalue;
+ sds line = sdscatprintf(sdsempty(),"%s %s",option,
+ value ? "yes" : "no");
+
+ rewriteConfigRewriteLine(state,option,line,force);
+}
+
+/* Rewrite a string option. */
+void rewriteConfigStringOption(struct rewriteConfigState *state, char *option, char *value, char *defvalue) {
+ int force = 1;
+ sds line;
+
+ /* String options set to NULL need to be not present at all in the
+ * configuration file to be set to NULL again at the next reboot. */
+ if (value == NULL) {
+ rewriteConfigMarkAsProcessed(state,option);
+ return;
+ }
+
+ /* Set force to zero if the value is set to its default. */
+ if (defvalue && strcmp(value,defvalue) == 0) force = 0;
+
+ line = sdsnew(option);
+ line = sdscatlen(line, " ", 1);
+ line = sdscatrepr(line, value, strlen(value));
+
+ rewriteConfigRewriteLine(state,option,line,force);
+}
+
+/* Rewrite a numerical (PORT_LONGLONG range) option. */
+void rewriteConfigNumericalOption(struct rewriteConfigState *state, char *option, PORT_LONGLONG value, PORT_LONGLONG defvalue) {
+ int force = value != defvalue;
+ sds line = sdscatprintf(sdsempty(),"%s %lld",option,value);
+
+ rewriteConfigRewriteLine(state,option,line,force);
+}
+
+/* Rewrite a octal option. */
+void rewriteConfigOctalOption(struct rewriteConfigState *state, char *option, int value, int defvalue) {
+ int force = value != defvalue;
+ sds line = sdscatprintf(sdsempty(),"%s %o",option,value);
+
+ rewriteConfigRewriteLine(state,option,line,force);
+}
+
+/* Rewrite an enumeration option. It takes as usually state and option name,
+ * and in addition the enumeration array and the default value for the
+ * option. */
+void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int value, configEnum *ce, int defval) {
+ sds line;
+ const char *name = configEnumGetNameOrUnknown(ce,value);
+ int force = value != defval;
+
+ line = sdscatprintf(sdsempty(),"%s %s",option,name);
+ rewriteConfigRewriteLine(state,option,line,force);
+}
+
+#ifndef _WIN32
+/* Rewrite the syslog-facility option. */
+void rewriteConfigSyslogfacilityOption(struct rewriteConfigState *state) {
+ int value = server.syslog_facility;
+ int force = value != LOG_LOCAL0;
+ const char *name = NULL, *option = "syslog-facility";
+ sds line;
+
+ name = configEnumGetNameOrUnknown(syslog_facility_enum,value);
+ line = sdscatprintf(sdsempty(),"%s %s",option,name);
+ rewriteConfigRewriteLine(state,option,line,force);
+}
+#endif
+
+/* Rewrite the save option. */
+void rewriteConfigSaveOption(struct rewriteConfigState *state) {
+ int j;
+ sds line;
+
+ /* Note that if there are no save parameters at all, all the current
+ * config line with "save" will be detected as orphaned and deleted,
+ * resulting into no RDB persistence as expected. */
+ for (j = 0; j < server.saveparamslen; j++) {
+ line = sdscatprintf(sdsempty(),"save %Id %d", WIN_PORT_FIX /* %ld -> %Id */
+ (PORT_LONG) server.saveparams[j].seconds, server.saveparams[j].changes);
+ rewriteConfigRewriteLine(state,"save",line,1);
+ }
+ /* Mark "save" as processed in case server.saveparamslen is zero. */
+ rewriteConfigMarkAsProcessed(state,"save");
+}
+
+/* Rewrite the dir option, always using absolute paths.*/
+void rewriteConfigDirOption(struct rewriteConfigState *state) {
+ char cwd[1024];
+
+ if (getcwd(cwd,sizeof(cwd)) == NULL) {
+ rewriteConfigMarkAsProcessed(state,"dir");
+ return; /* no rewrite on error. */
+ }
+ rewriteConfigStringOption(state,"dir",cwd,NULL);
+}
+
+/* Rewrite the slaveof option. */
+void rewriteConfigSlaveofOption(struct rewriteConfigState *state) {
+ char *option = "slaveof";
+ sds line;
+
+ /* If this is a master, we want all the slaveof config options
+ * in the file to be removed. Note that if this is a cluster instance
+ * we don't want a slaveof directive inside redis.conf. */
+ if (server.cluster_enabled || server.masterhost == NULL) {
+ rewriteConfigMarkAsProcessed(state,"slaveof");
+ return;
+ }
+ line = sdscatprintf(sdsempty(),"%s %s %d", option,
+ server.masterhost, server.masterport);
+ rewriteConfigRewriteLine(state,option,line,1);
+}
+
+/* Rewrite the notify-keyspace-events option. */
+void rewriteConfigNotifykeyspaceeventsOption(struct rewriteConfigState *state) {
+ int force = server.notify_keyspace_events != 0;
+ char *option = "notify-keyspace-events";
+ sds line, flags;
+
+ flags = keyspaceEventsFlagsToString(server.notify_keyspace_events);
+ line = sdsnew(option);
+ line = sdscatlen(line, " ", 1);
+ line = sdscatrepr(line, flags, sdslen(flags));
+ sdsfree(flags);
+ rewriteConfigRewriteLine(state,option,line,force);
+}
+
+/* Rewrite the client-output-buffer-limit option. */
+void rewriteConfigClientoutputbufferlimitOption(struct rewriteConfigState *state) {
+ int j;
+ char *option = "client-output-buffer-limit";
+
+ for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) {
+ int force = (server.client_obuf_limits[j].hard_limit_bytes !=
+ clientBufferLimitsDefaults[j].hard_limit_bytes) ||
+ (server.client_obuf_limits[j].soft_limit_bytes !=
+ clientBufferLimitsDefaults[j].soft_limit_bytes) ||
+ (server.client_obuf_limits[j].soft_limit_seconds !=
+ clientBufferLimitsDefaults[j].soft_limit_seconds);
+ sds line;
+ char hard[64], soft[64];
+
+ rewriteConfigFormatMemory(hard,sizeof(hard),
+ server.client_obuf_limits[j].hard_limit_bytes);
+ rewriteConfigFormatMemory(soft,sizeof(soft),
+ server.client_obuf_limits[j].soft_limit_bytes);
+
+ line = sdscatprintf(sdsempty(),"%s %s %s %s %Id", WIN_PORT_FIX /* %ld -> %Id */
+ option, getClientTypeName(j), hard, soft,
+ (PORT_LONG) server.client_obuf_limits[j].soft_limit_seconds);
+ rewriteConfigRewriteLine(state,option,line,force);
+ }
+}
+
+/* Rewrite the bind option. */
+void rewriteConfigBindOption(struct rewriteConfigState *state) {
+ int force = 1;
+ sds line, addresses;
+ char *option = "bind";
+
+ /* Nothing to rewrite if we don't have bind addresses. */
+ if (server.bindaddr_count == 0) {
+ rewriteConfigMarkAsProcessed(state,option);
+ return;
+ }
+
+ /* Rewrite as bind ... */
+ addresses = sdsjoin(server.bindaddr,server.bindaddr_count," ");
+ line = sdsnew(option);
+ line = sdscatlen(line, " ", 1);
+ line = sdscatsds(line, addresses);
+ sdsfree(addresses);
+
+ rewriteConfigRewriteLine(state,option,line,force);
+}
+
+/* Glue together the configuration lines in the current configuration
+ * rewrite state into a single string, stripping multiple empty lines. */
+sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
+ sds content = sdsempty();
+ int j, was_empty = 0;
+
+ for (j = 0; j < state->numlines; j++) {
+ /* Every cluster of empty lines is turned into a single empty line. */
+ if (sdslen(state->lines[j]) == 0) {
+ if (was_empty) continue;
+ was_empty = 1;
+ } else {
+ was_empty = 0;
+ }
+ content = sdscatsds(content,state->lines[j]);
+ content = sdscatlen(content,"\n",1);
+ }
+ return content;
+}
+
+/* Free the configuration rewrite state. */
+void rewriteConfigReleaseState(struct rewriteConfigState *state) {
+ sdsfreesplitres(state->lines,state->numlines);
+ dictRelease(state->option_to_line);
+ dictRelease(state->rewritten);
+ zfree(state);
+}
+
+/* At the end of the rewrite process the state contains the remaining
+ * map between "option name" => "lines in the original config file".
+ * Lines used by the rewrite process were removed by the function
+ * rewriteConfigRewriteLine(), all the other lines are "orphaned" and
+ * should be replaced by empty lines.
+ *
+ * This function does just this, iterating all the option names and
+ * blanking all the lines still associated. */
+void rewriteConfigRemoveOrphaned(struct rewriteConfigState *state) {
+ dictIterator *di = dictGetIterator(state->option_to_line);
+ dictEntry *de;
+
+ while((de = dictNext(di)) != NULL) {
+ list *l = dictGetVal(de);
+ sds option = dictGetKey(de);
+
+ /* Don't blank lines about options the rewrite process
+ * don't understand. */
+ if (dictFind(state->rewritten,option) == NULL) {
+ serverLog(LL_DEBUG,"Not rewritten option: %s", option);
+ continue;
+ }
+
+ while(listLength(l)) {
+ listNode *ln = listFirst(l);
+ int linenum = (int)((PORT_LONG) ln->value); WIN_PORT_FIX /* cast (int) */
+
+ sdsfree(state->lines[linenum]);
+ state->lines[linenum] = sdsempty();
+ listDelNode(l,ln);
+ }
+ }
+ dictReleaseIterator(di);
+}
+
+/* This function overwrites the old configuration file with the new content.
+ *
+ * 1) The old file length is obtained.
+ * 2) If the new content is smaller, padding is added.
+ * 3) A single write(2) call is used to replace the content of the file.
+ * 4) Later the file is truncated to the length of the new content.
+ *
+ * This way we are sure the file is left in a consistent state even if the
+ * process is stopped between any of the four operations.
+ *
+ * The function returns 0 on success, otherwise -1 is returned and errno
+ * set accordingly. */
+int rewriteConfigOverwriteFile(char *configfile, sds content) {
+ int retval = 0;
+ int fd = open(configfile,O_RDWR|O_CREAT,0644);
+ int content_size = (int)sdslen(content), padding = 0; WIN_PORT_FIX /* cast (int) */
+ struct IF_WIN32(_stat64,stat) sb; // TODO: verify for 32-bit
+ sds content_padded;
+
+ /* 1) Open the old file (or create a new one if it does not
+ * exist), get the size. */
+ if (fd == -1) return -1; /* errno set by open(). */
+ if (fstat(fd,&sb) == -1) {
+ close(fd);
+ return -1; /* errno set by fstat(). */
+ }
+
+ /* 2) Pad the content at least match the old file size. */
+ content_padded = sdsdup(content);
+ if (content_size < sb.st_size) {
+ /* If the old file was bigger, pad the content with
+ * a newline plus as many "#" chars as required. */
+ padding = (int)(sb.st_size - content_size); WIN_PORT_FIX /* cast (int) */
+ content_padded = sdsgrowzero(content_padded,sb.st_size);
+ content_padded[content_size] = '\n';
+ memset(content_padded+content_size+1,'#',padding-1);
+ }
+
+ /* 3) Write the new content using a single write(2). */
+ if (write(fd,content_padded,strlen(content_padded)) == -1) {
+ retval = -1;
+ goto cleanup;
+ }
+
+ /* 4) Truncate the file to the right length if we used padding. */
+ if (padding) {
+ if (ftruncate(fd,content_size) == -1) {
+ /* Non critical error... */
+ }
+ }
+
+cleanup:
+ sdsfree(content_padded);
+ close(fd);
+ return retval;
+}
+
+/* Rewrite the configuration file at "path".
+ * If the configuration file already exists, we try at best to retain comments
+ * and overall structure.
+ *
+ * Configuration parameters that are at their default value, unless already
+ * explicitly included in the old configuration file, are not rewritten.
+ *
+ * On error -1 is returned and errno is set accordingly, otherwise 0. */
+int rewriteConfig(char *path) {
+ struct rewriteConfigState *state;
+ sds newcontent;
+ int retval;
+
+ /* Step 1: read the old config into our rewrite state. */
+ if ((state = rewriteConfigReadOldFile(path)) == NULL) return -1;
+
+ /* Step 2: rewrite every single option, replacing or appending it inside
+ * the rewrite state. */
+
+ rewriteConfigYesNoOption(state,"daemonize",server.daemonize,0);
+ rewriteConfigStringOption(state,"pidfile",server.pidfile,CONFIG_DEFAULT_PID_FILE);
+ rewriteConfigNumericalOption(state,"port",server.port,CONFIG_DEFAULT_SERVER_PORT);
+ rewriteConfigNumericalOption(state,"cluster-announce-port",server.cluster_announce_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT);
+ rewriteConfigNumericalOption(state,"cluster-announce-bus-port",server.cluster_announce_bus_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT);
+ rewriteConfigNumericalOption(state,"tcp-backlog",server.tcp_backlog,CONFIG_DEFAULT_TCP_BACKLOG);
+ rewriteConfigBindOption(state);
+ rewriteConfigStringOption(state,"unixsocket",server.unixsocket,NULL);
+ rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,CONFIG_DEFAULT_UNIX_SOCKET_PERM);
+ rewriteConfigNumericalOption(state,"timeout",server.maxidletime,CONFIG_DEFAULT_CLIENT_TIMEOUT);
+ rewriteConfigNumericalOption(state,"tcp-keepalive",server.tcpkeepalive,CONFIG_DEFAULT_TCP_KEEPALIVE);
+ rewriteConfigNumericalOption(state,"slave-announce-port",server.slave_announce_port,CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT);
+ rewriteConfigEnumOption(state,"loglevel",server.verbosity,loglevel_enum,CONFIG_DEFAULT_VERBOSITY);
+ rewriteConfigStringOption(state,"logfile",server.logfile,CONFIG_DEFAULT_LOGFILE);
+ rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,CONFIG_DEFAULT_SYSLOG_ENABLED);
+ rewriteConfigStringOption(state,"syslog-ident",server.syslog_ident,CONFIG_DEFAULT_SYSLOG_IDENT);
+#ifndef _WIN32
+ rewriteConfigSyslogfacilityOption(state);
+#endif
+ rewriteConfigSaveOption(state);
+ rewriteConfigNumericalOption(state,"databases",server.dbnum,CONFIG_DEFAULT_DBNUM);
+ rewriteConfigYesNoOption(state,"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err,CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR);
+ rewriteConfigYesNoOption(state,"rdbcompression",server.rdb_compression,CONFIG_DEFAULT_RDB_COMPRESSION);
+ rewriteConfigYesNoOption(state,"rdbchecksum",server.rdb_checksum,CONFIG_DEFAULT_RDB_CHECKSUM);
+ rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,CONFIG_DEFAULT_RDB_FILENAME);
+ rewriteConfigDirOption(state);
+ rewriteConfigSlaveofOption(state);
+ rewriteConfigStringOption(state,"slave-announce-ip",server.slave_announce_ip,CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP);
+ rewriteConfigStringOption(state,"masterauth",server.masterauth,NULL);
+ rewriteConfigStringOption(state,"cluster-announce-ip",server.cluster_announce_ip,NULL);
+ rewriteConfigYesNoOption(state,"slave-serve-stale-data",server.repl_serve_stale_data,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA);
+ rewriteConfigYesNoOption(state,"slave-read-only",server.repl_slave_ro,CONFIG_DEFAULT_SLAVE_READ_ONLY);
+ rewriteConfigNumericalOption(state,"repl-ping-slave-period",server.repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD);
+ rewriteConfigNumericalOption(state,"repl-timeout",server.repl_timeout,CONFIG_DEFAULT_REPL_TIMEOUT);
+ rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,CONFIG_DEFAULT_REPL_BACKLOG_SIZE);
+ rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT);
+ rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY);
+ rewriteConfigYesNoOption(state,"repl-diskless-sync",server.repl_diskless_sync,CONFIG_DEFAULT_REPL_DISKLESS_SYNC);
+ rewriteConfigNumericalOption(state,"repl-diskless-sync-delay",server.repl_diskless_sync_delay,CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY);
+ rewriteConfigNumericalOption(state,"slave-priority",server.slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY);
+ rewriteConfigNumericalOption(state,"min-slaves-to-write",server.repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE);
+ rewriteConfigNumericalOption(state,"min-slaves-max-lag",server.repl_min_slaves_max_lag,CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG);
+ rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
+ rewriteConfigNumericalOption(state,"maxclients",server.maxclients,CONFIG_DEFAULT_MAX_CLIENTS);
+ rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,CONFIG_DEFAULT_MAXMEMORY);
+ rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum,CONFIG_DEFAULT_MAXMEMORY_POLICY);
+ rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,CONFIG_DEFAULT_MAXMEMORY_SAMPLES);
+ rewriteConfigNumericalOption(state,"active-defrag-threshold-lower",server.active_defrag_threshold_lower,CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER);
+ rewriteConfigNumericalOption(state,"active-defrag-threshold-upper",server.active_defrag_threshold_upper,CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER);
+ rewriteConfigBytesOption(state,"active-defrag-ignore-bytes",server.active_defrag_ignore_bytes,CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES);
+ rewriteConfigNumericalOption(state,"active-defrag-cycle-min",server.active_defrag_cycle_min,CONFIG_DEFAULT_DEFRAG_CYCLE_MIN);
+ rewriteConfigNumericalOption(state,"active-defrag-cycle-max",server.active_defrag_cycle_max,CONFIG_DEFAULT_DEFRAG_CYCLE_MAX);
+ rewriteConfigYesNoOption(state,"appendonly",server.aof_state != AOF_OFF,0);
+ rewriteConfigStringOption(state,"appendfilename",server.aof_filename,CONFIG_DEFAULT_AOF_FILENAME);
+ rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,aof_fsync_enum,CONFIG_DEFAULT_AOF_FSYNC);
+ rewriteConfigYesNoOption(state,"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite,CONFIG_DEFAULT_AOF_NO_FSYNC_ON_REWRITE);
+ rewriteConfigNumericalOption(state,"auto-aof-rewrite-percentage",server.aof_rewrite_perc,AOF_REWRITE_PERC);
+ rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,AOF_REWRITE_MIN_SIZE);
+ rewriteConfigNumericalOption(state,"lua-time-limit",server.lua_time_limit,LUA_SCRIPT_TIME_LIMIT);
+ rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0);
+ rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
+ rewriteConfigYesNoOption(state,"cluster-require-full-coverage",server.cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE);
+ rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,CLUSTER_DEFAULT_NODE_TIMEOUT);
+ rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,CLUSTER_DEFAULT_MIGRATION_BARRIER);
+ rewriteConfigNumericalOption(state,"cluster-slave-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY);
+ rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN);
+ rewriteConfigNumericalOption(state,"latency-monitor-threshold",server.latency_monitor_threshold,CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD);
+ rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,CONFIG_DEFAULT_SLOWLOG_MAX_LEN);
+ rewriteConfigNotifykeyspaceeventsOption(state);
+ rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,OBJ_HASH_MAX_ZIPLIST_ENTRIES);
+ rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,OBJ_HASH_MAX_ZIPLIST_VALUE);
+ rewriteConfigNumericalOption(state,"list-max-ziplist-size",server.list_max_ziplist_size,OBJ_LIST_MAX_ZIPLIST_SIZE);
+ rewriteConfigNumericalOption(state,"list-compress-depth",server.list_compress_depth,OBJ_LIST_COMPRESS_DEPTH);
+ rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,OBJ_SET_MAX_INTSET_ENTRIES);
+ rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,OBJ_ZSET_MAX_ZIPLIST_ENTRIES);
+ rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,OBJ_ZSET_MAX_ZIPLIST_VALUE);
+ rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES);
+ rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,CONFIG_DEFAULT_ACTIVE_REHASHING);
+ rewriteConfigYesNoOption(state,"activedefrag",server.active_defrag_enabled,CONFIG_DEFAULT_ACTIVE_DEFRAG);
+ rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
+ rewriteConfigClientoutputbufferlimitOption(state);
+ rewriteConfigNumericalOption(state,"hz",server.hz,CONFIG_DEFAULT_HZ);
+ rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
+ rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED);
+ rewriteConfigYesNoOption(state,"aof-use-rdb-preamble",server.aof_use_rdb_preamble,CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE);
+ rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE);
+ rewriteConfigYesNoOption(state,"lazyfree-lazy-eviction",server.lazyfree_lazy_eviction,CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION);
+ rewriteConfigYesNoOption(state,"lazyfree-lazy-expire",server.lazyfree_lazy_expire,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE);
+ rewriteConfigYesNoOption(state,"lazyfree-lazy-server-del",server.lazyfree_lazy_server_del,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL);
+ rewriteConfigYesNoOption(state,"slave-lazy-flush",server.repl_slave_lazy_flush,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH);
+
+ /* Rewrite Sentinel config if in Sentinel mode. */
+ if (server.sentinel_mode) rewriteConfigSentinelOption(state);
+
+ /* Step 3: remove all the orphaned lines in the old file, that is, lines
+ * that were used by a config option and are no longer used, like in case
+ * of multiple "save" options or duplicated options. */
+ rewriteConfigRemoveOrphaned(state);
+
+ /* Step 4: generate a new configuration file from the modified state
+ * and write it into the original file. */
+ newcontent = rewriteConfigGetContentFromState(state);
+ retval = rewriteConfigOverwriteFile(server.configfile,newcontent);
+
+ sdsfree(newcontent);
+ rewriteConfigReleaseState(state);
+ return retval;
+}
+
+/*-----------------------------------------------------------------------------
+ * CONFIG command entry point
+ *----------------------------------------------------------------------------*/
+
+void configCommand(client *c) {
+ /* Only allow CONFIG GET while loading. */
+ if (server.loading && strcasecmp(c->argv[1]->ptr,"get")) {
+ addReplyError(c,"Only CONFIG GET is allowed during loading");
+ return;
+ }
+
+ if (!strcasecmp(c->argv[1]->ptr,"set")) {
+ if (c->argc != 4) goto badarity;
+ configSetCommand(c);
+ } else if (!strcasecmp(c->argv[1]->ptr,"get")) {
+ if (c->argc != 3) goto badarity;
+ configGetCommand(c);
+ } else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) {
+ if (c->argc != 2) goto badarity;
+ resetServerStats();
+ resetCommandTableStats();
+ addReply(c,shared.ok);
+ } else if (!strcasecmp(c->argv[1]->ptr,"rewrite")) {
+ if (c->argc != 2) goto badarity;
+ if (server.configfile == NULL) {
+ addReplyError(c,"The server is running without a config file");
+ return;
+ }
+ if (rewriteConfig(server.configfile) == -1) {
+ serverLog(LL_WARNING,"CONFIG REWRITE failed: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ addReplyErrorFormat(c,"Rewriting config file: %s", IF_WIN32(wsa_strerror(errno), strerror(errno)));
+ } else {
+ serverLog(LL_WARNING,"CONFIG REWRITE executed with success.");
+ addReply(c,shared.ok);
+ }
+ } else {
+ addReplyError(c,
+ "CONFIG subcommand must be one of GET, SET, RESETSTAT, REWRITE");
+ }
+ return;
+
+badarity:
+ addReplyErrorFormat(c,"Wrong number of arguments for CONFIG %s",
+ (char*) c->argv[1]->ptr);
+}
diff --git a/src/db.c b/src/db.c
index 8f2ec8e3..e2b63ffc 100644
--- a/src/db.c
+++ b/src/db.c
@@ -41,6 +41,15 @@
* C-level DB API
*----------------------------------------------------------------------------*/
+/* Update LFU when an object is accessed.
+ * Firstly, decrement the counter if the decrement time is reached.
+ * Then logarithmically increment the counter, and update the access time. */
+void updateLFU(robj *val) {
+ unsigned long counter = LFUDecrAndReturn(val);
+ counter = LFULogIncr(counter);
+ val->lru = (LFUGetTimeInMinutes()<<8) | counter;
+}
+
/* Low level key lookup API, not actually called directly from commands
* implementations that should instead rely on lookupKeyRead(),
* lookupKeyWrite() and lookupKeyReadWithFlags(). */
@@ -57,9 +66,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
!(flags & LOOKUP_NOTOUCH))
{
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
- PORT_LONG ldt = val->lru >> 8;
- PORT_LONG counter = LFULogIncr(val->lru & 255);
- val->lru = (ldt << 8) | counter;
+ updateLFU(val);
} else {
val->lru = LRU_CLOCK();
}
@@ -183,6 +190,9 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
int saved_lru = old->lru;
dictReplace(db->dict, key->ptr, val);
val->lru = saved_lru;
+ /* LFU should be not only copied but also updated
+ * when a key is overwritten. */
+ updateLFU(val);
} else {
dictReplace(db->dict, key->ptr, val);
}
@@ -1091,6 +1101,25 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
decrRefCount(argv[1]);
}
+/* This function is called when we are going to perform some operation
+ * in a given key, but such key may be already logically expired even if
+ * it still exists in the database. The main way this function is called
+ * is via lookupKey*() family of functions.
+ *
+ * The behavior of the function depends on the replication role of the
+ * instance, because slave instances do not expire keys, they wait
+ * for DELs from the master for consistency matters. However even
+ * slaves will try to have a coherent return value for the function,
+ * so that read commands executed in the slave side will be able to
+ * behave like if the key is expired even if still present (because the
+ * master has yet to propagate the DEL).
+ *
+ * In masters as a side effect of finding a key which is expired, such
+ * key will be evicted from the database. Also this may trigger the
+ * propagation of a DEL/UNLINK command in AOF / replication stream.
+ *
+ * The return value of the function is 0 if the key is still valid,
+ * otherwise the function returns 1 if the key is expired. */
int expireIfNeeded(redisDb *db, robj *key) {
mstime_t when = getExpire(db,key);
mstime_t now;
@@ -1100,7 +1129,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
/* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0;
- /* If we are in the context of a Lua script, we claim that time is
+ /* If we are in the context of a Lua script, we pretend that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
@@ -1148,11 +1177,13 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1));
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
if (j >= argc) {
- /* Modules command do not have dispatch time arity checks, so
- * we need to handle the case where the user passed an invalid
- * number of arguments here. In this case we return no keys
- * and expect the module command to report an arity error. */
- if (cmd->flags & CMD_MODULE) {
+ /* Modules commands, and standard commands with a not fixed number
+ * of arugments (negative arity parameter) do not have dispatch
+ * time arity checks, so we need to handle the case where the user
+ * passed an invalid number of arguments here. In this case we
+ * return no keys and expect the command implementation to report
+ * an arity or syntax error. */
+ if (cmd->flags & CMD_MODULE || cmd->arity < 0) {
zfree(keys);
*numkeys = 0;
return NULL;
diff --git a/src/db.c.bak b/src/db.c.bak
new file mode 100644
index 00000000..273e06ae
--- /dev/null
+++ b/src/db.c.bak
@@ -0,0 +1,1455 @@
+/*
+ * Copyright (c) 2009-2012, Salvatore Sanfilippo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * 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 "server.h"
+#ifdef _WIN32
+#include "Win32_Interop/Win32_QFork.h"
+#endif
+#include "cluster.h"
+#include "atomicvar.h"
+
+#include
+#include
+
+/*-----------------------------------------------------------------------------
+ * C-level DB API
+ *----------------------------------------------------------------------------*/
+
+/* Update LFU when an object is accessed.
+ * Firstly, decrement the counter if the decrement time is reached.
+ * Then logarithmically increment the counter, and update the access time. */
+void updateLFU(robj *val) {
+ unsigned long counter = LFUDecrAndReturn(val);
+ counter = LFULogIncr(counter);
+ val->lru = (LFUGetTimeInMinutes()<<8) | counter;
+}
+
+/* Low level key lookup API, not actually called directly from commands
+ * implementations that should instead rely on lookupKeyRead(),
+ * lookupKeyWrite() and lookupKeyReadWithFlags(). */
+robj *lookupKey(redisDb *db, robj *key, int flags) {
+ dictEntry *de = dictFind(db->dict,key->ptr);
+ if (de) {
+ robj *val = dictGetVal(de);
+
+ /* Update the access time for the ageing algorithm.
+ * Don't do it if we have a saving child, as this will trigger
+ * a copy on write madness. */
+ if (server.rdb_child_pid == -1 &&
+ server.aof_child_pid == -1 &&
+ !(flags & LOOKUP_NOTOUCH))
+ {
+ if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
+ updateLFU(val);
+ } else {
+ val->lru = LRU_CLOCK();
+ }
+ }
+ return val;
+ } else {
+ return NULL;
+ }
+}
+
+/* Lookup a key for read operations, or return NULL if the key is not found
+ * in the specified DB.
+ *
+ * As a side effect of calling this function:
+ * 1. A key gets expired if it reached it's TTL.
+ * 2. The key last access time is updated.
+ * 3. The global keys hits/misses stats are updated (reported in INFO).
+ *
+ * This API should not be used when we write to the key after obtaining
+ * the object linked to the key, but only for read only operations.
+ *
+ * Flags change the behavior of this command:
+ *
+ * LOOKUP_NONE (or zero): no special flags are passed.
+ * LOOKUP_NOTOUCH: don't alter the last access time of the key.
+ *
+ * Note: this function also returns NULL is the key is logically expired
+ * but still existing, in case this is a slave, since this API is called only
+ * for read operations. Even if the key expiry is master-driven, we can
+ * correctly report a key is expired on slaves even if the master is lagging
+ * expiring our key via DELs in the replication link. */
+robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
+ robj *val;
+
+ if (expireIfNeeded(db,key) == 1) {
+ /* Key expired. If we are in the context of a master, expireIfNeeded()
+ * returns 0 only when the key does not exist at all, so it's safe
+ * to return NULL ASAP. */
+ if (server.masterhost == NULL) return NULL;
+
+ /* However if we are in the context of a slave, expireIfNeeded() will
+ * not really try to expire the key, it only returns information
+ * about the "logical" status of the key: key expiring is up to the
+ * master in order to have a consistent view of master's data set.
+ *
+ * However, if the command caller is not the master, and as additional
+ * safety measure, the command invoked is a read-only command, we can
+ * safely return NULL here, and provide a more consistent behavior
+ * to clients accessign expired values in a read-only fashion, that
+ * will say the key as non exisitng.
+ *
+ * Notably this covers GETs when slaves are used to scale reads. */
+ if (server.current_client &&
+ server.current_client != server.master &&
+ server.current_client->cmd &&
+ server.current_client->cmd->flags & CMD_READONLY)
+ {
+ return NULL;
+ }
+ }
+ val = lookupKey(db,key,flags);
+ if (val == NULL)
+ server.stat_keyspace_misses++;
+ else
+ server.stat_keyspace_hits++;
+ return val;
+}
+
+/* Like lookupKeyReadWithFlags(), but does not use any flag, which is the
+ * common case. */
+robj *lookupKeyRead(redisDb *db, robj *key) {
+ return lookupKeyReadWithFlags(db,key,LOOKUP_NONE);
+}
+
+/* Lookup a key for write operations, and as a side effect, if needed, expires
+ * the key if its TTL is reached.
+ *
+ * Returns the linked value object if the key exists or NULL if the key
+ * does not exist in the specified DB. */
+robj *lookupKeyWrite(redisDb *db, robj *key) {
+ expireIfNeeded(db,key);
+ return lookupKey(db,key,LOOKUP_NONE);
+}
+
+robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
+ robj *o = lookupKeyRead(c->db, key);
+ if (!o) addReply(c,reply);
+ return o;
+}
+
+robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) {
+ robj *o = lookupKeyWrite(c->db, key);
+ if (!o) addReply(c,reply);
+ return o;
+}
+
+/* Add the key to the DB. It's up to the caller to increment the reference
+ * counter of the value if needed.
+ *
+ * The program is aborted if the key already exists. */
+void dbAdd(redisDb *db, robj *key, robj *val) {
+ sds copy = sdsdup(key->ptr);
+ int retval = dictAdd(db->dict, copy, val);
+
+ serverAssertWithInfo(NULL,key,retval == DICT_OK);
+ if (val->type == OBJ_LIST) signalListAsReady(db, key);
+ if (server.cluster_enabled) slotToKeyAdd(key);
+ }
+
+/* Overwrite an existing key with a new value. Incrementing the reference
+ * count of the new value is up to the caller.
+ * This function does not modify the expire time of the existing key.
+ *
+ * The program is aborted if the key was not already present. */
+void dbOverwrite(redisDb *db, robj *key, robj *val) {
+ dictEntry *de = dictFind(db->dict,key->ptr);
+
+ serverAssertWithInfo(NULL,key,de != NULL);
+ if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
+ robj *old = dictGetVal(de);
+ int saved_lru = old->lru;
+ dictReplace(db->dict, key->ptr, val);
+ val->lru = saved_lru;
+ /* LFU should be not only copied but also updated
+ * when a key is overwritten. */
+ updateLFU(val);
+ } else {
+ dictReplace(db->dict, key->ptr, val);
+ }
+}
+
+/* High level Set operation. This function can be used in order to set
+ * a key, whatever it was existing or not, to a new object.
+ *
+ * 1) The ref count of the value object is incremented.
+ * 2) clients WATCHing for the destination key notified.
+ * 3) The expire time of the key is reset (the key is made persistent).
+ *
+ * All the new keys in the database should be craeted via this interface. */
+void setKey(redisDb *db, robj *key, robj *val) {
+ if (lookupKeyWrite(db,key) == NULL) {
+ dbAdd(db,key,val);
+ } else {
+ dbOverwrite(db,key,val);
+ }
+ incrRefCount(val);
+ removeExpire(db,key);
+ signalModifiedKey(db,key);
+}
+
+int dbExists(redisDb *db, robj *key) {
+ return dictFind(db->dict,key->ptr) != NULL;
+}
+
+/* Return a random key, in form of a Redis object.
+ * If there are no keys, NULL is returned.
+ *
+ * The function makes sure to return keys not already expired. */
+robj *dbRandomKey(redisDb *db) {
+ dictEntry *de;
+
+ while(1) {
+ sds key;
+ robj *keyobj;
+
+ de = dictGetRandomKey(db->dict);
+ if (de == NULL) return NULL;
+
+ key = dictGetKey(de);
+ keyobj = createStringObject(key,sdslen(key));
+ if (dictFind(db->expires,key)) {
+ if (expireIfNeeded(db,keyobj)) {
+ decrRefCount(keyobj);
+ continue; /* search for another key. This expired. */
+ }
+ }
+ return keyobj;
+ }
+}
+
+/* Delete a key, value, and associated expiration entry if any, from the DB */
+int dbSyncDelete(redisDb *db, robj *key) {
+ /* Deleting an entry from the expires dict will not free the sds of
+ * the key, because it is shared with the main dictionary. */
+ if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
+ if (dictDelete(db->dict,key->ptr) == DICT_OK) {
+ if (server.cluster_enabled) slotToKeyDel(key);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/* This is a wrapper whose behavior depends on the Redis lazy free
+ * configuration. Deletes the key synchronously or asynchronously. */
+int dbDelete(redisDb *db, robj *key) {
+ return server.lazyfree_lazy_server_del ? dbAsyncDelete(db,key) :
+ dbSyncDelete(db,key);
+}
+
+/* Prepare the string object stored at 'key' to be modified destructively
+ * to implement commands like SETBIT or APPEND.
+ *
+ * An object is usually ready to be modified unless one of the two conditions
+ * are true:
+ *
+ * 1) The object 'o' is shared (refcount > 1), we don't want to affect
+ * other users.
+ * 2) The object encoding is not "RAW".
+ *
+ * If the object is found in one of the above conditions (or both) by the
+ * function, an unshared / not-encoded copy of the string object is stored
+ * at 'key' in the specified 'db'. Otherwise the object 'o' itself is
+ * returned.
+ *
+ * USAGE:
+ *
+ * The object 'o' is what the caller already obtained by looking up 'key'
+ * in 'db', the usage pattern looks like this:
+ *
+ * o = lookupKeyWrite(db,key);
+ * if (checkType(c,o,OBJ_STRING)) return;
+ * o = dbUnshareStringValue(db,key,o);
+ *
+ * At this point the caller is ready to modify the object, for example
+ * using an sdscat() call to append some data, or anything else.
+ */
+robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
+ serverAssert(o->type == OBJ_STRING);
+ if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) {
+ robj *decoded = getDecodedObject(o);
+ o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
+ decrRefCount(decoded);
+ dbOverwrite(db,key,o);
+ }
+ return o;
+}
+
+/* Remove all keys from all the databases in a Redis server.
+ * If callback is given the function is called from time to time to
+ * signal that work is in progress.
+ *
+ * The dbnum can be -1 if all teh DBs should be flushed, or the specified
+ * DB number if we want to flush only a single Redis database number.
+ *
+ * Flags are be EMPTYDB_NO_FLAGS if no special flags are specified or
+ * EMPTYDB_ASYNC if we want the memory to be freed in a different thread
+ * and the function to return ASAP.
+ *
+ * On success the fuction returns the number of keys removed from the
+ * database(s). Otherwise -1 is returned in the specific case the
+ * DB number is out of range, and errno is set to EINVAL. */
+PORT_LONGLONG emptyDb(int dbnum, int flags, void(callback)(void*)) {
+ int j, async = (flags & EMPTYDB_ASYNC);
+ PORT_LONGLONG removed = 0;
+
+ if (dbnum < -1 || dbnum >= server.dbnum) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (j = 0; j < server.dbnum; j++) {
+ if (dbnum != -1 && dbnum != j) continue;
+ removed += dictSize(server.db[j].dict);
+ if (async) {
+ emptyDbAsync(&server.db[j]);
+ } else {
+ dictEmpty(server.db[j].dict,callback);
+ dictEmpty(server.db[j].expires,callback);
+ }
+ }
+ if (server.cluster_enabled) {
+ if (async) {
+ slotToKeyFlushAsync();
+ } else {
+ slotToKeyFlush();
+ }
+ }
+ if (dbnum == -1) flushSlaveKeysWithExpireList();
+ return removed;
+}
+
+int selectDb(client *c, int id) {
+ if (id < 0 || id >= server.dbnum)
+ return C_ERR;
+ c->db = &server.db[id];
+ return C_OK;
+}
+
+/*-----------------------------------------------------------------------------
+ * Hooks for key space changes.
+ *
+ * Every time a key in the database is modified the function
+ * signalModifiedKey() is called.
+ *
+ * Every time a DB is flushed the function signalFlushDb() is called.
+ *----------------------------------------------------------------------------*/
+
+void signalModifiedKey(redisDb *db, robj *key) {
+ touchWatchedKey(db,key);
+}
+
+void signalFlushedDb(int dbid) {
+ touchWatchedKeysOnFlush(dbid);
+}
+
+/*-----------------------------------------------------------------------------
+ * Type agnostic commands operating on the key space
+ *----------------------------------------------------------------------------*/
+
+/* Return the set of flags to use for the emptyDb() call for FLUSHALL
+ * and FLUSHDB commands.
+ *
+ * Currently the command just attempts to parse the "ASYNC" option. It
+ * also checks if the command arity is wrong.
+ *
+ * On success C_OK is returned and the flags are stored in *flags, otherwise
+ * C_ERR is returned and the function sends an error to the client. */
+int getFlushCommandFlags(client *c, int *flags) {
+ /* Parse the optional ASYNC option. */
+ if (c->argc > 1) {
+ if (c->argc > 2 || strcasecmp(c->argv[1]->ptr,"async")) {
+ addReply(c,shared.syntaxerr);
+ return C_ERR;
+ }
+ *flags = EMPTYDB_ASYNC;
+ } else {
+ *flags = EMPTYDB_NO_FLAGS;
+ }
+ return C_OK;
+}
+
+/* FLUSHDB [ASYNC]
+ *
+ * Flushes the currently SELECTed Redis DB. */
+void flushdbCommand(client *c) {
+ int flags;
+
+ if (getFlushCommandFlags(c,&flags) == C_ERR) return;
+ signalFlushedDb(c->db->id);
+ server.dirty += emptyDb(c->db->id,flags,NULL);
+ addReply(c,shared.ok);
+}
+
+/* FLUSHALL [ASYNC]
+ *
+ * Flushes the whole server data set. */
+void flushallCommand(client *c) {
+ int flags;
+
+ if (getFlushCommandFlags(c,&flags) == C_ERR) return;
+ signalFlushedDb(-1);
+ server.dirty += emptyDb(-1,flags,NULL);
+ addReply(c,shared.ok);
+ if (server.rdb_child_pid != -1) {
+#ifdef _WIN32
+ AbortForkOperation();
+#else
+ kill(server.rdb_child_pid,SIGUSR1);
+#endif
+ rdbRemoveTempFile(server.rdb_child_pid);
+ }
+ if (server.saveparamslen > 0) {
+ /* Normally rdbSave() will reset dirty, but we don't want this here
+ * as otherwise FLUSHALL will not be replicated nor put into the AOF. */
+ int saved_dirty = server.dirty;
+ rdbSaveInfo rsi, *rsiptr;
+ rsiptr = rdbPopulateSaveInfo(&rsi);
+ rdbSave(server.rdb_filename,rsiptr);
+ server.dirty = saved_dirty;
+ }
+ server.dirty++;
+}
+
+/* This command implements DEL and LAZYDEL. */
+void delGenericCommand(client *c, int lazy) {
+ int numdel = 0, j;
+
+ for (j = 1; j < c->argc; j++) {
+ expireIfNeeded(c->db,c->argv[j]);
+ int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) :
+ dbSyncDelete(c->db,c->argv[j]);
+ if (deleted) {
+ signalModifiedKey(c->db,c->argv[j]);
+ notifyKeyspaceEvent(NOTIFY_GENERIC,
+ "del",c->argv[j],c->db->id);
+ server.dirty++;
+ numdel++;
+ }
+ }
+ addReplyLongLong(c,numdel);
+}
+
+void delCommand(client *c) {
+ delGenericCommand(c,0);
+}
+
+void unlinkCommand(client *c) {
+ delGenericCommand(c,1);
+}
+
+/* EXISTS key1 key2 ... key_N.
+ * Return value is the number of keys existing. */
+void existsCommand(client *c) {
+ PORT_LONGLONG count = 0;
+ int j;
+
+ for (j = 1; j < c->argc; j++) {
+ expireIfNeeded(c->db,c->argv[j]);
+ if (dbExists(c->db,c->argv[j])) count++;
+ }
+ addReplyLongLong(c,count);
+}
+
+void selectCommand(client *c) {
+ PORT_LONG id;
+
+ if (getLongFromObjectOrReply(c, c->argv[1], &id,
+ "invalid DB index") != C_OK)
+ return;
+
+ if (server.cluster_enabled && id != 0) {
+ addReplyError(c,"SELECT is not allowed in cluster mode");
+ return;
+ }
+ if (selectDb(c,(int)id) == C_ERR) { WIN_PORT_FIX /* cast (int) */
+ addReplyError(c,"DB index is out of range");
+ } else {
+ addReply(c,shared.ok);
+ }
+}
+
+void randomkeyCommand(client *c) {
+ robj *key;
+
+ if ((key = dbRandomKey(c->db)) == NULL) {
+ addReply(c,shared.nullbulk);
+ return;
+ }
+
+ addReplyBulk(c,key);
+ decrRefCount(key);
+}
+
+void keysCommand(client *c) {
+ dictIterator *di;
+ dictEntry *de;
+ sds pattern = c->argv[1]->ptr;
+ int plen = (int)sdslen(pattern), allkeys;
+ PORT_ULONG numkeys = 0;
+ void *replylen = addDeferredMultiBulkLength(c);
+
+ di = dictGetSafeIterator(c->db->dict);
+ allkeys = (pattern[0] == '*' && pattern[1] == '\0');
+ while((de = dictNext(di)) != NULL) {
+ sds key = dictGetKey(de);
+ robj *keyobj;
+
+ if (allkeys || stringmatchlen(pattern,plen,key,(int)sdslen(key),0)) {
+ keyobj = createStringObject(key,sdslen(key));
+ if (expireIfNeeded(c->db,keyobj) == 0) {
+ addReplyBulk(c,keyobj);
+ numkeys++;
+ }
+ decrRefCount(keyobj);
+ }
+ }
+ dictReleaseIterator(di);
+ setDeferredMultiBulkLength(c,replylen,numkeys);
+}
+
+/* This callback is used by scanGenericCommand in order to collect elements
+ * returned by the dictionary iterator into a list. */
+void scanCallback(void *privdata, const dictEntry *de) {
+ void **pd = (void**) privdata;
+ list *keys = pd[0];
+ robj *o = pd[1];
+ robj *key, *val = NULL;
+
+ if (o == NULL) {
+ sds sdskey = dictGetKey(de);
+ key = createStringObject(sdskey, sdslen(sdskey));
+ } else if (o->type == OBJ_SET) {
+ sds keysds = dictGetKey(de);
+ key = createStringObject(keysds,sdslen(keysds));
+ } else if (o->type == OBJ_HASH) {
+ sds sdskey = dictGetKey(de);
+ sds sdsval = dictGetVal(de);
+ key = createStringObject(sdskey,sdslen(sdskey));
+ val = createStringObject(sdsval,sdslen(sdsval));
+ } else if (o->type == OBJ_ZSET) {
+ sds sdskey = dictGetKey(de);
+ key = createStringObject(sdskey,sdslen(sdskey));
+ val = createStringObjectFromLongDouble(*(double*)dictGetVal(de),0);
+ } else {
+ serverPanic("Type not handled in SCAN callback.");
+ }
+
+ listAddNodeTail(keys, key);
+ if (val) listAddNodeTail(keys, val);
+}
+
+/* Try to parse a SCAN cursor stored at object 'o':
+ * if the cursor is valid, store it as unsigned integer into *cursor and
+ * returns C_OK. Otherwise return C_ERR and send an error to the
+ * client. */
+int parseScanCursorOrReply(client *c, robj *o, PORT_ULONG *cursor) {
+ char *eptr;
+
+ /* Use strtoul() because we need an *unsigned* PORT_LONG, so
+ * getLongLongFromObject() does not cover the whole cursor space. */
+ errno = 0;
+ *cursor = strtoul(o->ptr, &eptr, 10);
+ if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' || errno == ERANGE)
+ {
+ addReplyError(c, "invalid cursor");
+ return C_ERR;
+ }
+ return C_OK;
+}
+
+/* This command implements SCAN, HSCAN and SSCAN commands.
+ * If object 'o' is passed, then it must be a Hash or Set object, otherwise
+ * if 'o' is NULL the command will operate on the dictionary associated with
+ * the current database.
+ *
+ * When 'o' is not NULL the function assumes that the first argument in
+ * the client arguments vector is a key so it skips it before iterating
+ * in order to parse options.
+ *
+ * In the case of a Hash object the function returns both the field and value
+ * of every element on the Hash. */
+void scanGenericCommand(client *c, robj *o, PORT_ULONG cursor) {
+ int i, j;
+ list *keys = listCreate();
+ listNode *node, *nextnode;
+ PORT_LONG count = 10;
+ sds pat = NULL;
+ int patlen = 0, use_pattern = 0;
+ dict *ht;
+
+ /* Object must be NULL (to iterate keys names), or the type of the object
+ * must be Set, Sorted Set, or Hash. */
+ serverAssert(o == NULL || o->type == OBJ_SET || o->type == OBJ_HASH ||
+ o->type == OBJ_ZSET);
+
+ /* Set i to the first option argument. The previous one is the cursor. */
+ i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */
+
+ /* Step 1: Parse options. */
+ while (i < c->argc) {
+ j = c->argc - i;
+ if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {
+ if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL)
+ != C_OK)
+ {
+ goto cleanup;
+ }
+
+ if (count < 1) {
+ addReply(c,shared.syntaxerr);
+ goto cleanup;
+ }
+
+ i += 2;
+ } else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {
+ pat = c->argv[i+1]->ptr;
+ patlen = (int)sdslen(pat); WIN_PORT_FIX /* cast (int) */
+
+ /* The pattern always matches if it is exactly "*", so it is
+ * equivalent to disabling it. */
+ use_pattern = !(pat[0] == '*' && patlen == 1);
+
+ i += 2;
+ } else {
+ addReply(c,shared.syntaxerr);
+ goto cleanup;
+ }
+ }
+
+ /* Step 2: Iterate the collection.
+ *
+ * Note that if the object is encoded with a ziplist, intset, or any other
+ * representation that is not a hash table, we are sure that it is also
+ * composed of a small number of elements. So to avoid taking state we
+ * just return everything inside the object in a single call, setting the
+ * cursor to zero to signal the end of the iteration. */
+
+ /* Handle the case of a hash table. */
+ ht = NULL;
+ if (o == NULL) {
+ ht = c->db->dict;
+ } else if (o->type == OBJ_SET && o->encoding == OBJ_ENCODING_HT) {
+ ht = o->ptr;
+ } else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_HT) {
+ ht = o->ptr;
+ count *= 2; /* We return key / value for this type. */
+ } else if (o->type == OBJ_ZSET && o->encoding == OBJ_ENCODING_SKIPLIST) {
+ zset *zs = o->ptr;
+ ht = zs->dict;
+ count *= 2; /* We return key / value for this type. */
+ }
+
+ if (ht) {
+ void *privdata[2];
+ /* We set the max number of iterations to ten times the specified
+ * COUNT, so if the hash table is in a pathological state (very
+ * sparsely populated) we avoid to block too much time at the cost
+ * of returning no or very few elements. */
+ PORT_LONG maxiterations = count*10;
+
+ /* We pass two pointers to the callback: the list to which it will
+ * add new elements, and the object containing the dictionary so that
+ * it is possible to fetch more data in a type-dependent way. */
+ privdata[0] = keys;
+ privdata[1] = o;
+ do {
+ cursor = dictScan(ht, cursor, scanCallback, NULL, privdata);
+ } while (cursor &&
+ maxiterations-- &&
+ listLength(keys) < (PORT_ULONG)count);
+ } else if (o->type == OBJ_SET) {
+ int pos = 0;
+ int64_t ll;
+
+ while(intsetGet(o->ptr,pos++,&ll))
+ listAddNodeTail(keys,createStringObjectFromLongLong(ll));
+ cursor = 0;
+ } else if (o->type == OBJ_HASH || o->type == OBJ_ZSET) {
+ unsigned char *p = ziplistIndex(o->ptr,0);
+ unsigned char *vstr;
+ unsigned int vlen;
+ PORT_LONGLONG vll;
+
+ while(p) {
+ ziplistGet(p,&vstr,&vlen,&vll);
+ listAddNodeTail(keys,
+ (vstr != NULL) ? createStringObject((char*)vstr,vlen) :
+ createStringObjectFromLongLong(vll));
+ p = ziplistNext(o->ptr,p);
+ }
+ cursor = 0;
+ } else {
+ serverPanic("Not handled encoding in SCAN.");
+ }
+
+ /* Step 3: Filter elements. */
+ node = listFirst(keys);
+ while (node) {
+ robj *kobj = listNodeValue(node);
+ nextnode = listNextNode(node);
+ int filter = 0;
+
+ /* Filter element if it does not match the pattern. */
+ if (!filter && use_pattern) {
+ if (sdsEncodedObject(kobj)) {
+ if (!stringmatchlen(pat, patlen, kobj->ptr, (int)sdslen(kobj->ptr), 0)) WIN_PORT_FIX /* cast (int) */
+ filter = 1;
+ } else {
+ char buf[LONG_STR_SIZE];
+ int len;
+
+ serverAssert(kobj->encoding == OBJ_ENCODING_INT);
+ len = ll2string(buf,sizeof(buf),(PORT_LONG)kobj->ptr);
+ if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;
+ }
+ }
+
+ /* Filter element if it is an expired key. */
+ if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
+
+ /* Remove the element and its associted value if needed. */
+ if (filter) {
+ decrRefCount(kobj);
+ listDelNode(keys, node);
+ }
+
+ /* If this is a hash or a sorted set, we have a flat list of
+ * key-value elements, so if this element was filtered, remove the
+ * value, or skip it if it was not filtered: we only match keys. */
+ if (o && (o->type == OBJ_ZSET || o->type == OBJ_HASH)) {
+ node = nextnode;
+ nextnode = listNextNode(node);
+ if (filter) {
+ kobj = listNodeValue(node);
+ decrRefCount(kobj);
+ listDelNode(keys, node);
+ }
+ }
+ node = nextnode;
+ }
+
+ /* Step 4: Reply to the client. */
+ addReplyMultiBulkLen(c, 2);
+ addReplyBulkLongLong(c,cursor);
+
+ addReplyMultiBulkLen(c, listLength(keys));
+ while ((node = listFirst(keys)) != NULL) {
+ robj *kobj = listNodeValue(node);
+ addReplyBulk(c, kobj);
+ decrRefCount(kobj);
+ listDelNode(keys, node);
+ }
+
+cleanup:
+ listSetFreeMethod(keys,decrRefCountVoid);
+ listRelease(keys);
+}
+
+/* The SCAN command completely relies on scanGenericCommand. */
+void scanCommand(client *c) {
+ PORT_ULONG cursor;
+ if (parseScanCursorOrReply(c,c->argv[1],&cursor) == C_ERR) return;
+ scanGenericCommand(c,NULL,cursor);
+}
+
+void dbsizeCommand(client *c) {
+ addReplyLongLong(c,dictSize(c->db->dict));
+}
+
+void lastsaveCommand(client *c) {
+ addReplyLongLong(c,server.lastsave);
+}
+
+void typeCommand(client *c) {
+ robj *o;
+ char *type;
+
+ o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
+ if (o == NULL) {
+ type = "none";
+ } else {
+ switch(o->type) {
+ case OBJ_STRING: type = "string"; break;
+ case OBJ_LIST: type = "list"; break;
+ case OBJ_SET: type = "set"; break;
+ case OBJ_ZSET: type = "zset"; break;
+ case OBJ_HASH: type = "hash"; break;
+ case OBJ_MODULE: {
+ moduleValue *mv = o->ptr;
+ type = mv->type->name;
+ }; break;
+ default: type = "unknown"; break;
+ }
+ }
+ addReplyStatus(c,type);
+}
+
+void shutdownCommand(client *c) {
+ int flags = 0;
+
+ if (c->argc > 2) {
+ addReply(c,shared.syntaxerr);
+ return;
+ } else if (c->argc == 2) {
+ if (!strcasecmp(c->argv[1]->ptr,"nosave")) {
+ flags |= SHUTDOWN_NOSAVE;
+ } else if (!strcasecmp(c->argv[1]->ptr,"save")) {
+ flags |= SHUTDOWN_SAVE;
+ } else {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+ }
+ /* When SHUTDOWN is called while the server is loading a dataset in
+ * memory we need to make sure no attempt is performed to save
+ * the dataset on shutdown (otherwise it could overwrite the current DB
+ * with half-read data).
+ *
+ * Also when in Sentinel mode clear the SAVE flag and force NOSAVE. */
+ if (server.loading || server.sentinel_mode)
+ flags = (flags & ~SHUTDOWN_SAVE) | SHUTDOWN_NOSAVE;
+ if (prepareForShutdown(flags) == C_OK) exit(0);
+ addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
+}
+
+void renameGenericCommand(client *c, int nx) {
+ robj *o;
+ PORT_LONGLONG expire;
+ int samekey = 0;
+
+ /* When source and dest key is the same, no operation is performed,
+ * if the key exists, however we still return an error on unexisting key. */
+ if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) samekey = 1;
+
+ if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL)
+ return;
+
+ if (samekey) {
+ addReply(c,nx ? shared.czero : shared.ok);
+ return;
+ }
+
+ incrRefCount(o);
+ expire = getExpire(c->db,c->argv[1]);
+ if (lookupKeyWrite(c->db,c->argv[2]) != NULL) {
+ if (nx) {
+ decrRefCount(o);
+ addReply(c,shared.czero);
+ return;
+ }
+ /* Overwrite: delete the old key before creating the new one
+ * with the same name. */
+ dbDelete(c->db,c->argv[2]);
+ }
+ dbAdd(c->db,c->argv[2],o);
+ if (expire != -1) setExpire(c,c->db,c->argv[2],expire);
+ dbDelete(c->db,c->argv[1]);
+ signalModifiedKey(c->db,c->argv[1]);
+ signalModifiedKey(c->db,c->argv[2]);
+ notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_from",
+ c->argv[1],c->db->id);
+ notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_to",
+ c->argv[2],c->db->id);
+ server.dirty++;
+ addReply(c,nx ? shared.cone : shared.ok);
+}
+
+void renameCommand(client *c) {
+ renameGenericCommand(c,0);
+}
+
+void renamenxCommand(client *c) {
+ renameGenericCommand(c,1);
+}
+
+void moveCommand(client *c) {
+ robj *o;
+ redisDb *src, *dst;
+ int srcid;
+ PORT_LONGLONG dbid, expire;
+
+ if (server.cluster_enabled) {
+ addReplyError(c,"MOVE is not allowed in cluster mode");
+ return;
+ }
+
+ /* Obtain source and target DB pointers */
+ src = c->db;
+ srcid = c->db->id;
+
+ if (getLongLongFromObject(c->argv[2],&dbid) == C_ERR ||
+ dbid < INT_MIN || dbid > INT_MAX ||
+ selectDb(c,(int)dbid) == C_ERR) WIN_PORT_FIX /* cast (int) */
+ {
+ addReply(c,shared.outofrangeerr);
+ return;
+ }
+ dst = c->db;
+ selectDb(c,srcid); /* Back to the source DB */
+
+ /* If the user is moving using as target the same
+ * DB as the source DB it is probably an error. */
+ if (src == dst) {
+ addReply(c,shared.sameobjecterr);
+ return;
+ }
+
+ /* Check if the element exists and get a reference */
+ o = lookupKeyWrite(c->db,c->argv[1]);
+ if (!o) {
+ addReply(c,shared.czero);
+ return;
+ }
+ expire = getExpire(c->db,c->argv[1]);
+
+ /* Return zero if the key already exists in the target DB */
+ if (lookupKeyWrite(dst,c->argv[1]) != NULL) {
+ addReply(c,shared.czero);
+ return;
+ }
+ dbAdd(dst,c->argv[1],o);
+ if (expire != -1) setExpire(c,dst,c->argv[1],expire);
+ incrRefCount(o);
+
+ /* OK! key moved, free the entry in the source DB */
+ dbDelete(src,c->argv[1]);
+ server.dirty++;
+ addReply(c,shared.cone);
+}
+
+/* Helper function for dbSwapDatabases(): scans the list of keys that have
+ * one or more blocked clients for B[LR]POP or other list blocking commands
+ * and signal the keys are ready if they are lists. See the comment where
+ * the function is used for more info. */
+void scanDatabaseForReadyLists(redisDb *db) {
+ dictEntry *de;
+ dictIterator *di = dictGetSafeIterator(db->blocking_keys);
+ while((de = dictNext(di)) != NULL) {
+ robj *key = dictGetKey(de);
+ robj *value = lookupKey(db,key,LOOKUP_NOTOUCH);
+ if (value && value->type == OBJ_LIST)
+ signalListAsReady(db, key);
+ }
+ dictReleaseIterator(di);
+}
+
+/* Swap two databases at runtime so that all clients will magically see
+ * the new database even if already connected. Note that the client
+ * structure c->db points to a given DB, so we need to be smarter and
+ * swap the underlying referenced structures, otherwise we would need
+ * to fix all the references to the Redis DB structure.
+ *
+ * Returns C_ERR if at least one of the DB ids are out of range, otherwise
+ * C_OK is returned. */
+int dbSwapDatabases(int id1, int id2) {
+ if (id1 < 0 || id1 >= server.dbnum ||
+ id2 < 0 || id2 >= server.dbnum) return C_ERR;
+ if (id1 == id2) return C_OK;
+ redisDb aux = server.db[id1];
+ redisDb *db1 = &server.db[id1], *db2 = &server.db[id2];
+
+ /* Swap hash tables. Note that we don't swap blocking_keys,
+ * ready_keys and watched_keys, since we want clients to
+ * remain in the same DB they were. */
+ db1->dict = db2->dict;
+ db1->expires = db2->expires;
+ db1->avg_ttl = db2->avg_ttl;
+
+ db2->dict = aux.dict;
+ db2->expires = aux.expires;
+ db2->avg_ttl = aux.avg_ttl;
+
+ /* Now we need to handle clients blocked on lists: as an effect
+ * of swapping the two DBs, a client that was waiting for list
+ * X in a given DB, may now actually be unblocked if X happens
+ * to exist in the new version of the DB, after the swap.
+ *
+ * However normally we only do this check for efficiency reasons
+ * in dbAdd() when a list is created. So here we need to rescan
+ * the list of clients blocked on lists and signal lists as ready
+ * if needed. */
+ scanDatabaseForReadyLists(db1);
+ scanDatabaseForReadyLists(db2);
+ return C_OK;
+}
+
+/* SWAPDB db1 db2 */
+void swapdbCommand(client *c) {
+ PORT_LONG id1, id2;
+
+ /* Not allowed in cluster mode: we have just DB 0 there. */
+ if (server.cluster_enabled) {
+ addReplyError(c,"SWAPDB is not allowed in cluster mode");
+ return;
+ }
+
+ /* Get the two DBs indexes. */
+ if (getLongFromObjectOrReply(c, c->argv[1], &id1,
+ "invalid first DB index") != C_OK)
+ return;
+
+ if (getLongFromObjectOrReply(c, c->argv[2], &id2,
+ "invalid second DB index") != C_OK)
+ return;
+
+ /* Swap... */
+ if (dbSwapDatabases(id1,id2) == C_ERR) {
+ addReplyError(c,"DB index is out of range");
+ return;
+ } else {
+ server.dirty++;
+ addReply(c,shared.ok);
+ }
+}
+
+/*-----------------------------------------------------------------------------
+ * Expires API
+ *----------------------------------------------------------------------------*/
+
+int removeExpire(redisDb *db, robj *key) {
+ /* An expire may only be removed if there is a corresponding entry in the
+ * main dict. Otherwise, the key will never be freed. */
+ serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
+ return dictDelete(db->expires,key->ptr) == DICT_OK;
+}
+
+/* Set an expire to the specified key. If the expire is set in the context
+ * of an user calling a command 'c' is the client, otherwise 'c' is set
+ * to NULL. The 'when' parameter is the absolute unix time in milliseconds
+ * after which the key will no longer be considered valid. */
+void setExpire(client *c, redisDb *db, robj *key, PORT_LONGLONG when) {
+ dictEntry *kde, *de;
+
+ /* Reuse the sds from the main dict in the expire dict */
+ kde = dictFind(db->dict,key->ptr);
+ serverAssertWithInfo(NULL,key,kde != NULL);
+ de = dictAddOrFind(db->expires,dictGetKey(kde));
+ dictSetSignedIntegerVal(de,when);
+
+ int writable_slave = server.masterhost && server.repl_slave_ro == 0;
+ if (c && writable_slave && !(c->flags & CLIENT_MASTER))
+ rememberSlaveKeyWithExpire(db,key);
+}
+
+/* Return the expire time of the specified key, or -1 if no expire
+ * is associated with this key (i.e. the key is non volatile) */
+PORT_LONGLONG getExpire(redisDb *db, robj *key) {
+ dictEntry *de;
+
+ /* No expire? return ASAP */
+ if (dictSize(db->expires) == 0 ||
+ (de = dictFind(db->expires,key->ptr)) == NULL) return -1;
+
+ /* The entry was found in the expire dict, this means it should also
+ * be present in the main dict (safety check). */
+ serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
+ return dictGetSignedIntegerVal(de);
+}
+
+/* Propagate expires into slaves and the AOF file.
+ * When a key expires in the master, a DEL operation for this key is sent
+ * to all the slaves and the AOF file if enabled.
+ *
+ * This way the key expiry is centralized in one place, and since both
+ * AOF and the master->slave link guarantee operation ordering, everything
+ * will be consistent even if we allow write operations against expiring
+ * keys. */
+void propagateExpire(redisDb *db, robj *key, int lazy) {
+ robj *argv[2];
+
+ argv[0] = lazy ? shared.unlink : shared.del;
+ argv[1] = key;
+ incrRefCount(argv[0]);
+ incrRefCount(argv[1]);
+
+ if (server.aof_state != AOF_OFF)
+ feedAppendOnlyFile(server.delCommand,db->id,argv,2);
+ replicationFeedSlaves(server.slaves,db->id,argv,2);
+
+ decrRefCount(argv[0]);
+ decrRefCount(argv[1]);
+}
+
+int expireIfNeeded(redisDb *db, robj *key) {
+ mstime_t when = getExpire(db,key);
+ mstime_t now;
+
+ if (when < 0) return 0; /* No expire for this key */
+
+ /* Don't expire anything while loading. It will be done later. */
+ if (server.loading) return 0;
+
+ /* If we are in the context of a Lua script, we claim that time is
+ * blocked to when the Lua script started. This way a key can expire
+ * only the first time it is accessed and not in the middle of the
+ * script execution, making propagation to slaves / AOF consistent.
+ * See issue #1525 on Github for more information. */
+ now = server.lua_caller ? server.lua_time_start : mstime();
+
+ /* If we are running in the context of a slave, return ASAP:
+ * the slave key expiration is controlled by the master that will
+ * send us synthesized DEL operations for expired keys.
+ *
+ * Still we try to return the right information to the caller,
+ * that is, 0 if we think the key should be still valid, 1 if
+ * we think the key is expired at this time. */
+ if (server.masterhost != NULL) return now > when;
+
+ /* Return when this key has not expired */
+ if (now <= when) return 0;
+
+ /* Delete the key */
+ server.stat_expiredkeys++;
+ propagateExpire(db,key,server.lazyfree_lazy_expire);
+ notifyKeyspaceEvent(NOTIFY_EXPIRED,
+ "expired",key,db->id);
+ return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
+ dbSyncDelete(db,key);
+}
+
+/* -----------------------------------------------------------------------------
+ * API to get key arguments from commands
+ * ---------------------------------------------------------------------------*/
+
+/* The base case is to use the keys position as given in the command table
+ * (firstkey, lastkey, step). */
+int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) {
+ int j, i = 0, last, *keys;
+ UNUSED(argv);
+
+ if (cmd->firstkey == 0) {
+ *numkeys = 0;
+ return NULL;
+ }
+
+ last = cmd->lastkey;
+ if (last < 0) last = argc+last;
+ keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1));
+ for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
+ if (j >= argc) {
+ /* Modules command do not have dispatch time arity checks, so
+ * we need to handle the case where the user passed an invalid
+ * number of arguments here. In this case we return no keys
+ * and expect the module command to report an arity error. */
+ if (cmd->flags & CMD_MODULE) {
+ zfree(keys);
+ *numkeys = 0;
+ return NULL;
+ } else {
+ serverPanic("Redis built-in command declared keys positions not matching the arity requirements.");
+ }
+ }
+ keys[i++] = j;
+ }
+ *numkeys = i;
+ return keys;
+}
+
+/* Return all the arguments that are keys in the command passed via argc / argv.
+ *
+ * The command returns the positions of all the key arguments inside the array,
+ * so the actual return value is an heap allocated array of integers. The
+ * length of the array is returned by reference into *numkeys.
+ *
+ * 'cmd' must be point to the corresponding entry into the redisCommand
+ * table, according to the command name in argv[0].
+ *
+ * This function uses the command table if a command-specific helper function
+ * is not required, otherwise it calls the command-specific function. */
+int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
+ if (cmd->flags & CMD_MODULE_GETKEYS) {
+ return moduleGetCommandKeysViaAPI(cmd,argv,argc,numkeys);
+ } else if (!(cmd->flags & CMD_MODULE) && cmd->getkeys_proc) {
+ return cmd->getkeys_proc(cmd,argv,argc,numkeys);
+ } else {
+ return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
+ }
+}
+
+/* Free the result of getKeysFromCommand. */
+void getKeysFreeResult(int *result) {
+ zfree(result);
+}
+
+/* Helper function to extract keys from following commands:
+ * ZUNIONSTORE ...
+ * ZINTERSTORE ... */
+int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
+ int i, num, *keys;
+ UNUSED(cmd);
+
+ num = atoi(argv[2]->ptr);
+ /* Sanity check. Don't return any key if the command is going to
+ * reply with syntax error. */
+ if (num > (argc-3)) {
+ *numkeys = 0;
+ return NULL;
+ }
+
+ /* Keys in z{union,inter}store come from two places:
+ * argv[1] = storage key,
+ * argv[3...n] = keys to intersect */
+ keys = zmalloc(sizeof(int)*(num+1));
+
+ /* Add all key positions for argv[3...n] to keys[] */
+ for (i = 0; i < num; i++) keys[i] = 3+i;
+
+ /* Finally add the argv[1] key position (the storage key target). */
+ keys[num] = 1;
+ *numkeys = num+1; /* Total keys = {union,inter} keys + storage key */
+ return keys;
+}
+
+/* Helper function to extract keys from the following commands:
+ * EVAL