Ported redis-server 4.0.10 commits
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
<ProjectGuid>{8B897E33-6428-4254-8335-4911D179BAD1}</ProjectGuid>
|
||||
<RootNamespace>Jemalloc_vc2008</RootNamespace>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<ProjectGuid>{B00D4BB5-44DE-405E-839C-D16F547006CF}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>RedisBenchmark</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{392BBB91-3934-4A56-AF42-65C5728311E8}</ProjectGuid>
|
||||
<RootNamespace>RedisCli</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
|
||||
Binary file not shown.
@@ -21,7 +21,7 @@
|
||||
<PropertyGroup Label="Globals">
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{46842776-68A5-EC98-6A09-1859BBFC73AA}</ProjectGuid>
|
||||
<WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<ProjectGuid>{2B888C28-52CF-4E94-993C-B43D77E0B91A}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>geohash</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<ProjectGuid>{13E85053-54B3-487B-8DDB-3430B1C1B3BF}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>hiredis</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<Keyword>ManagedCProj</Keyword>
|
||||
<RootNamespace>lua</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
|
||||
@@ -108,7 +108,7 @@ link.exe -dll -noentry resources/EventLog.res -out:$(TargetDir)EventLog.dll
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>Win32_Redis</RootNamespace>
|
||||
<ProjectName>Win32_Interop</ProjectName>
|
||||
<WindowsTargetPlatformVersion>10.0.14393.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
|
||||
+1
-1
@@ -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. */
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
/* adlist.c - A generic doubly linked list implementation
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#include "Win32_Interop/Win32_Portability.h"
|
||||
#include "Win32_Interop/win32_types.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#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,<direction>);
|
||||
* 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;
|
||||
}
|
||||
@@ -51,6 +51,7 @@
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
+515
@@ -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 <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Win32_Interop\win32_types_hiredis.h"
|
||||
#endif
|
||||
#include "ae.h"
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef _WIN32
|
||||
#include <sys/types.h>
|
||||
#include <sys/timeb.h>
|
||||
#include "../src/Win32_Interop/Win32_FDAPI.h"
|
||||
#include "../src/Win32_Interop/Win32_Service.h"
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
+131
@@ -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 <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __AE_H__
|
||||
#define __AE_H__
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1814
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -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. */
|
||||
|
||||
+1125
File diff suppressed because it is too large
Load Diff
+88
-22
@@ -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 <NODE ID> */
|
||||
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);
|
||||
|
||||
+5692
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
+27
-2
@@ -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);
|
||||
|
||||
+2199
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
+1455
File diff suppressed because it is too large
Load Diff
+9
-2
@@ -313,6 +313,8 @@ void debugCommand(client *c) {
|
||||
"structsize -- Return the size of different Redis core C structures.");
|
||||
blen++; addReplyStatus(c,
|
||||
"htstats <dbid> -- Return hash table statistics of the specified Redis database.");
|
||||
blen++; addReplyStatus(c,
|
||||
"change-repl-id -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.");
|
||||
setDeferredMultiBulkLength(c,blenp,blen);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
|
||||
*((char*)-1) = 'x';
|
||||
@@ -375,13 +377,13 @@ void debugCommand(client *c) {
|
||||
val = dictGetVal(de);
|
||||
strenc = strEncoding(val->encoding);
|
||||
|
||||
char extra[128] = {0};
|
||||
char extra[138] = {0};
|
||||
if (val->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
char *nextra = extra;
|
||||
int remaining = sizeof(extra);
|
||||
quicklist *ql = val->ptr;
|
||||
/* Add number of quicklist nodes */
|
||||
int used = snprintf(nextra, remaining, " ql_nodes:%u", ql->len);
|
||||
int used = snprintf(nextra, remaining, " ql_nodes:%Iu", ql->len); WIN_PORT_FIX /* %lu -> %Iu */
|
||||
nextra += used;
|
||||
remaining -= used;
|
||||
/* Add average quicklist fill factor */
|
||||
@@ -558,6 +560,11 @@ void debugCommand(client *c) {
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
addReplyBulkSds(c,stats);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
|
||||
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'",
|
||||
(char*)c->argv[1]->ptr);
|
||||
|
||||
+1240
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -289,7 +289,7 @@ int defragKey(redisDb *db, dictEntry *de) {
|
||||
/* Dirty code:
|
||||
* I can't search in db->expires for that key after i already released
|
||||
* the pointer it holds it won't be able to do the string compare */
|
||||
unsigned int hash = dictGetHash(db->dict, de->key);
|
||||
uint64_t hash = dictGetHash(db->dict, de->key);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(db->expires, keysds, newsds, hash, &defragged);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,579 @@
|
||||
/*
|
||||
* Active memory defragmentation
|
||||
* Try to find key / value allocations that need to be re-allocated in order
|
||||
* to reduce external fragmentation.
|
||||
* We do that by scanning the keyspace and for each pointer we have, we can try to
|
||||
* ask the allocator if moving it to a new address will help reduce fragmentation.
|
||||
*
|
||||
* Copyright (c) 2017, Oran Agra
|
||||
* Copyright (c) 2017, Redis Labs, Inc
|
||||
* 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 <time.h>
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef HAVE_DEFRAG
|
||||
|
||||
/* this method was added to jemalloc in order to help us understand which
|
||||
* pointers are worthwhile moving and which aren't */
|
||||
int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
|
||||
|
||||
/* Defrag helper for generic allocations.
|
||||
*
|
||||
* returns NULL in case the allocatoin wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
void* activeDefragAlloc(void *ptr) {
|
||||
int bin_util, run_util;
|
||||
size_t size;
|
||||
void *newptr;
|
||||
if(!je_get_defrag_hint(ptr, &bin_util, &run_util)) {
|
||||
server.stat_active_defrag_misses++;
|
||||
return NULL;
|
||||
}
|
||||
/* if this run is more utilized than the average utilization in this bin
|
||||
* (or it is full), skip it. This will eventually move all the allocations
|
||||
* from relatively empty runs into relatively full runs. */
|
||||
if (run_util > bin_util || run_util == 1<<16) {
|
||||
server.stat_active_defrag_misses++;
|
||||
return NULL;
|
||||
}
|
||||
/* move this allocation to a new allocation.
|
||||
* make sure not to use the thread cache. so that we don't get back the same
|
||||
* pointers we try to free */
|
||||
size = zmalloc_size(ptr);
|
||||
newptr = zmalloc_no_tcache(size);
|
||||
memcpy(newptr, ptr, size);
|
||||
zfree_no_tcache(ptr);
|
||||
return newptr;
|
||||
}
|
||||
|
||||
/*Defrag helper for sds strings
|
||||
*
|
||||
* returns NULL in case the allocatoin wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
sds activeDefragSds(sds sdsptr) {
|
||||
void* ptr = sdsAllocPtr(sdsptr);
|
||||
void* newptr = activeDefragAlloc(ptr);
|
||||
if (newptr) {
|
||||
size_t offset = sdsptr - (char*)ptr;
|
||||
sdsptr = (char*)newptr + offset;
|
||||
return sdsptr;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Defrag helper for robj and/or string objects
|
||||
*
|
||||
* returns NULL in case the allocatoin wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
robj *activeDefragStringOb(robj* ob, int *defragged) {
|
||||
robj *ret = NULL;
|
||||
if (ob->refcount!=1)
|
||||
return NULL;
|
||||
|
||||
/* try to defrag robj (only if not an EMBSTR type (handled below). */
|
||||
if (ob->type!=OBJ_STRING || ob->encoding!=OBJ_ENCODING_EMBSTR) {
|
||||
if ((ret = activeDefragAlloc(ob))) {
|
||||
ob = ret;
|
||||
(*defragged)++;
|
||||
}
|
||||
}
|
||||
|
||||
/* try to defrag string object */
|
||||
if (ob->type == OBJ_STRING) {
|
||||
if(ob->encoding==OBJ_ENCODING_RAW) {
|
||||
sds newsds = activeDefragSds((sds)ob->ptr);
|
||||
if (newsds) {
|
||||
ob->ptr = newsds;
|
||||
(*defragged)++;
|
||||
}
|
||||
} else if (ob->encoding==OBJ_ENCODING_EMBSTR) {
|
||||
/* The sds is embedded in the object allocation, calculate the
|
||||
* offset and update the pointer in the new allocation. */
|
||||
PORT_LONG ofs = (intptr_t)ob->ptr - (intptr_t)ob;
|
||||
if ((ret = activeDefragAlloc(ob))) {
|
||||
ret->ptr = (void*)((intptr_t)ret + ofs);
|
||||
(*defragged)++;
|
||||
}
|
||||
} else if (ob->encoding!=OBJ_ENCODING_INT) {
|
||||
serverPanic("Unknown string encoding");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Defrag helper for dictEntries to be used during dict iteration (called on
|
||||
* each step). Teturns a stat of how many pointers were moved. */
|
||||
int dictIterDefragEntry(dictIterator *iter) {
|
||||
/* This function is a little bit dirty since it messes with the internals
|
||||
* of the dict and it's iterator, but the benefit is that it is very easy
|
||||
* to use, and require no other chagnes in the dict. */
|
||||
int defragged = 0;
|
||||
dictht *ht;
|
||||
/* Handle the next entry (if there is one), and update the pointer in the
|
||||
* current entry. */
|
||||
if (iter->nextEntry) {
|
||||
dictEntry *newde = activeDefragAlloc(iter->nextEntry);
|
||||
if (newde) {
|
||||
defragged++;
|
||||
iter->nextEntry = newde;
|
||||
iter->entry->next = newde;
|
||||
}
|
||||
}
|
||||
/* handle the case of the first entry in the hash bucket. */
|
||||
ht = &iter->d->ht[iter->table];
|
||||
if (ht->table[iter->index] == iter->entry) {
|
||||
dictEntry *newde = activeDefragAlloc(iter->entry);
|
||||
if (newde) {
|
||||
iter->entry = newde;
|
||||
ht->table[iter->index] = newde;
|
||||
defragged++;
|
||||
}
|
||||
}
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* Defrag helper for dict main allocations (dict struct, and hash tables).
|
||||
* receives a pointer to the dict* and implicitly updates it when the dict
|
||||
* struct itself was moved. Returns a stat of how many pointers were moved. */
|
||||
int dictDefragTables(dict** dictRef) {
|
||||
dict *d = *dictRef;
|
||||
dictEntry **newtable;
|
||||
int defragged = 0;
|
||||
/* handle the dict struct */
|
||||
dict *newd = activeDefragAlloc(d);
|
||||
if (newd)
|
||||
defragged++, *dictRef = d = newd;
|
||||
/* handle the first hash table */
|
||||
newtable = activeDefragAlloc(d->ht[0].table);
|
||||
if (newtable)
|
||||
defragged++, d->ht[0].table = newtable;
|
||||
/* handle the second hash table */
|
||||
if (d->ht[1].table) {
|
||||
newtable = activeDefragAlloc(d->ht[1].table);
|
||||
if (newtable)
|
||||
defragged++, d->ht[1].table = newtable;
|
||||
}
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* Internal function used by zslDefrag */
|
||||
void zslUpdateNode(zskiplist *zsl, zskiplistNode *oldnode, zskiplistNode *newnode, zskiplistNode **update) {
|
||||
int i;
|
||||
for (i = 0; i < zsl->level; i++) {
|
||||
if (update[i]->level[i].forward == oldnode)
|
||||
update[i]->level[i].forward = newnode;
|
||||
}
|
||||
serverAssert(zsl->header!=oldnode);
|
||||
if (newnode->level[0].forward) {
|
||||
serverAssert(newnode->level[0].forward->backward==oldnode);
|
||||
newnode->level[0].forward->backward = newnode;
|
||||
} else {
|
||||
serverAssert(zsl->tail==oldnode);
|
||||
zsl->tail = newnode;
|
||||
}
|
||||
}
|
||||
|
||||
/* Defrag helper for sorted set.
|
||||
* Update the robj pointer, defrag the skiplist struct and return the new score
|
||||
* reference. We may not access oldele pointer (not even the pointer stored in
|
||||
* the skiplist), as it was already freed. Newele may be null, in which case we
|
||||
* only need to defrag the skiplist, but not update the obj pointer.
|
||||
* When return value is non-NULL, it is the score reference that must be updated
|
||||
* in the dict record. */
|
||||
double *zslDefrag(zskiplist *zsl, double score, sds oldele, sds newele) {
|
||||
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x, *newx;
|
||||
int i;
|
||||
sds ele = newele? newele: oldele;
|
||||
|
||||
/* find the skiplist node referring to the object that was moved,
|
||||
* and all pointers that need to be updated if we'll end up moving the skiplist node. */
|
||||
x = zsl->header;
|
||||
for (i = zsl->level-1; i >= 0; i--) {
|
||||
while (x->level[i].forward &&
|
||||
x->level[i].forward->ele != oldele && /* make sure not to access the
|
||||
->obj pointer if it matches
|
||||
oldele */
|
||||
(x->level[i].forward->score < score ||
|
||||
(x->level[i].forward->score == score &&
|
||||
sdscmp(x->level[i].forward->ele,ele) < 0)))
|
||||
x = x->level[i].forward;
|
||||
update[i] = x;
|
||||
}
|
||||
|
||||
/* update the robj pointer inside the skip list record. */
|
||||
x = x->level[0].forward;
|
||||
serverAssert(x && score == x->score && x->ele==oldele);
|
||||
if (newele)
|
||||
x->ele = newele;
|
||||
|
||||
/* try to defrag the skiplist record itself */
|
||||
newx = activeDefragAlloc(x);
|
||||
if (newx) {
|
||||
zslUpdateNode(zsl, x, newx, update);
|
||||
return &newx->score;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Utility function that replaces an old key pointer in the dictionary with a
|
||||
* new pointer. Additionally, we try to defrag the dictEntry in that dict.
|
||||
* Oldkey mey be a dead pointer and should not be accessed (we get a
|
||||
* pre-calculated hash value). Newkey may be null if the key pointer wasn't
|
||||
* moved. Return value is the the dictEntry if found, or NULL if not found.
|
||||
* NOTE: this is very ugly code, but it let's us avoid the complication of
|
||||
* doing a scan on another dict. */
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, unsigned int hash, int *defragged) {
|
||||
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, oldkey, hash);
|
||||
if (deref) {
|
||||
dictEntry *de = *deref;
|
||||
dictEntry *newde = activeDefragAlloc(de);
|
||||
if (newde) {
|
||||
de = *deref = newde;
|
||||
(*defragged)++;
|
||||
}
|
||||
if (newkey)
|
||||
de->key = newkey;
|
||||
return de;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* for each key we scan in the main dict, this function will attempt to defrag
|
||||
* all the various pointers it has. Returns a stat of how many pointers were
|
||||
* moved. */
|
||||
int defragKey(redisDb *db, dictEntry *de) {
|
||||
sds keysds = dictGetKey(de);
|
||||
robj *newob, *ob;
|
||||
unsigned char *newzl;
|
||||
dict *d;
|
||||
dictIterator *di;
|
||||
int defragged = 0;
|
||||
sds newsds;
|
||||
|
||||
/* Try to defrag the key name. */
|
||||
newsds = activeDefragSds(keysds);
|
||||
if (newsds)
|
||||
defragged++, de->key = newsds;
|
||||
if (dictSize(db->expires)) {
|
||||
/* Dirty code:
|
||||
* I can't search in db->expires for that key after i already released
|
||||
* the pointer it holds it won't be able to do the string compare */
|
||||
unsigned int hash = dictGetHash(db->dict, de->key);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(db->expires, keysds, newsds, hash, &defragged);
|
||||
}
|
||||
|
||||
/* Try to defrag robj and / or string value. */
|
||||
ob = dictGetVal(de);
|
||||
if ((newob = activeDefragStringOb(ob, &defragged))) {
|
||||
de->v.val = newob;
|
||||
ob = newob;
|
||||
}
|
||||
|
||||
if (ob->type == OBJ_STRING) {
|
||||
/* Already handled in activeDefragStringOb. */
|
||||
} else if (ob->type == OBJ_LIST) {
|
||||
if (ob->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
quicklist *ql = ob->ptr, *newql;
|
||||
quicklistNode *node = ql->head, *newnode;
|
||||
if ((newql = activeDefragAlloc(ql)))
|
||||
defragged++, ob->ptr = ql = newql;
|
||||
while (node) {
|
||||
if ((newnode = activeDefragAlloc(node))) {
|
||||
if (newnode->prev)
|
||||
newnode->prev->next = newnode;
|
||||
else
|
||||
ql->head = newnode;
|
||||
if (newnode->next)
|
||||
newnode->next->prev = newnode;
|
||||
else
|
||||
ql->tail = newnode;
|
||||
node = newnode;
|
||||
defragged++;
|
||||
}
|
||||
if ((newzl = activeDefragAlloc(node->zl)))
|
||||
defragged++, node->zl = newzl;
|
||||
node = node->next;
|
||||
}
|
||||
} else if (ob->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
if ((newzl = activeDefragAlloc(ob->ptr)))
|
||||
defragged++, ob->ptr = newzl;
|
||||
} else {
|
||||
serverPanic("Unknown list encoding");
|
||||
}
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
if (ob->encoding == OBJ_ENCODING_HT) {
|
||||
d = ob->ptr;
|
||||
di = dictGetIterator(d);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds sdsele = dictGetKey(de);
|
||||
if ((newsds = activeDefragSds(sdsele)))
|
||||
defragged++, de->key = newsds;
|
||||
defragged += dictIterDefragEntry(di);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
dictDefragTables((dict**)&ob->ptr);
|
||||
} else if (ob->encoding == OBJ_ENCODING_INTSET) {
|
||||
intset *is = ob->ptr;
|
||||
intset *newis = activeDefragAlloc(is);
|
||||
if (newis)
|
||||
defragged++, ob->ptr = newis;
|
||||
} else {
|
||||
serverPanic("Unknown set encoding");
|
||||
}
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
if (ob->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
if ((newzl = activeDefragAlloc(ob->ptr)))
|
||||
defragged++, ob->ptr = newzl;
|
||||
} else if (ob->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = (zset*)ob->ptr;
|
||||
zset *newzs;
|
||||
zskiplist *newzsl;
|
||||
struct zskiplistNode *newheader;
|
||||
if ((newzs = activeDefragAlloc(zs)))
|
||||
defragged++, ob->ptr = zs = newzs;
|
||||
if ((newzsl = activeDefragAlloc(zs->zsl)))
|
||||
defragged++, zs->zsl = newzsl;
|
||||
if ((newheader = activeDefragAlloc(zs->zsl->header)))
|
||||
defragged++, zs->zsl->header = newheader;
|
||||
d = zs->dict;
|
||||
di = dictGetIterator(d);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
double* newscore;
|
||||
sds sdsele = dictGetKey(de);
|
||||
if ((newsds = activeDefragSds(sdsele)))
|
||||
defragged++, de->key = newsds;
|
||||
newscore = zslDefrag(zs->zsl, *(double*)dictGetVal(de), sdsele, newsds);
|
||||
if (newscore) {
|
||||
dictSetVal(d, de, newscore);
|
||||
defragged++;
|
||||
}
|
||||
defragged += dictIterDefragEntry(di);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
dictDefragTables(&zs->dict);
|
||||
} else {
|
||||
serverPanic("Unknown sorted set encoding");
|
||||
}
|
||||
} else if (ob->type == OBJ_HASH) {
|
||||
if (ob->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
if ((newzl = activeDefragAlloc(ob->ptr)))
|
||||
defragged++, ob->ptr = newzl;
|
||||
} else if (ob->encoding == OBJ_ENCODING_HT) {
|
||||
d = ob->ptr;
|
||||
di = dictGetIterator(d);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds sdsele = dictGetKey(de);
|
||||
if ((newsds = activeDefragSds(sdsele)))
|
||||
defragged++, de->key = newsds;
|
||||
sdsele = dictGetVal(de);
|
||||
if ((newsds = activeDefragSds(sdsele)))
|
||||
defragged++, de->v.val = newsds;
|
||||
defragged += dictIterDefragEntry(di);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
dictDefragTables((dict**)&ob->ptr);
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
} else if (ob->type == OBJ_MODULE) {
|
||||
/* Currently defragmenting modules private data types
|
||||
* is not supported. */
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for the main db dictionary. */
|
||||
void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
int defragged = defragKey((redisDb*)privdata, (dictEntry*)de);
|
||||
server.stat_active_defrag_hits += defragged;
|
||||
if(defragged)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
else
|
||||
server.stat_active_defrag_key_misses++;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for for each hash table bicket,
|
||||
* used in order to defrag the dictEntry allocations. */
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
|
||||
UNUSED(privdata);
|
||||
while(*bucketref) {
|
||||
dictEntry *de = *bucketref, *newde;
|
||||
if ((newde = activeDefragAlloc(de))) {
|
||||
*bucketref = newde;
|
||||
}
|
||||
bucketref = &(*bucketref)->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Utility function to get the fragmentation ratio from jemalloc.
|
||||
* It is critical to do that by comparing only heap maps that belown to
|
||||
* jemalloc, and skip ones the jemalloc keeps as spare. Since we use this
|
||||
* fragmentation ratio in order to decide if a defrag action should be taken
|
||||
* or not, a false detection can cause the defragmenter to waste a lot of CPU
|
||||
* without the possibility of getting any results. */
|
||||
float getAllocatorFragmentation(size_t *out_frag_bytes) {
|
||||
size_t epoch = 1, allocated = 0, resident = 0, active = 0, sz = sizeof(size_t);
|
||||
/* Update the statistics cached by mallctl. */
|
||||
je_mallctl("epoch", &epoch, &sz, &epoch, sz);
|
||||
/* Unlike RSS, this does not include RSS from shared libraries and other non
|
||||
* heap mappings. */
|
||||
je_mallctl("stats.resident", &resident, &sz, NULL, 0);
|
||||
/* Unlike resident, this doesn't not include the pages jemalloc reserves
|
||||
* for re-use (purge will clean that). */
|
||||
je_mallctl("stats.active", &active, &sz, NULL, 0);
|
||||
/* Unlike zmalloc_used_memory, this matches the stats.resident by taking
|
||||
* into account all allocations done by this process (not only zmalloc). */
|
||||
je_mallctl("stats.allocated", &allocated, &sz, NULL, 0);
|
||||
float frag_pct = ((float)active / allocated)*100 - 100;
|
||||
size_t frag_bytes = active - allocated;
|
||||
float rss_pct = ((float)resident / allocated)*100 - 100;
|
||||
size_t rss_bytes = resident - allocated;
|
||||
if(out_frag_bytes)
|
||||
*out_frag_bytes = frag_bytes;
|
||||
serverLog(LL_DEBUG,
|
||||
"allocated=%zu, active=%zu, resident=%zu, frag=%.0f%% (%.0f%% rss), frag_bytes=%zu (%zu%% rss)",
|
||||
allocated, active, resident, frag_pct, rss_pct, frag_bytes, rss_bytes);
|
||||
return frag_pct;
|
||||
}
|
||||
|
||||
#define INTERPOLATE(x, x1, x2, y1, y2) ( (y1) + ((x)-(x1)) * ((y2)-(y1)) / ((x2)-(x1)) )
|
||||
#define LIMIT(y, min, max) ((y)<(min)? min: ((y)>(max)? max: (y)))
|
||||
|
||||
/* Perform incremental defragmentation work from the serverCron.
|
||||
* This works in a similar way to activeExpireCycle, in the sense that
|
||||
* we do incremental work across calls. */
|
||||
void activeDefragCycle(void) {
|
||||
static int current_db = -1;
|
||||
static PORT_ULONG cursor = 0;
|
||||
static redisDb *db = NULL;
|
||||
static PORT_LONGLONG start_scan, start_stat;
|
||||
unsigned int iterations = 0;
|
||||
PORT_ULONGLONG defragged = server.stat_active_defrag_hits;
|
||||
PORT_LONGLONG start, timelimit;
|
||||
|
||||
if (server.aof_child_pid!=-1 || server.rdb_child_pid!=-1)
|
||||
return; /* Defragging memory while there's a fork will just do damage. */
|
||||
|
||||
/* Once a second, check if we the fragmentation justfies starting a scan
|
||||
* or making it more aggressive. */
|
||||
run_with_period(1000) {
|
||||
size_t frag_bytes;
|
||||
float frag_pct = getAllocatorFragmentation(&frag_bytes);
|
||||
/* If we're not already running, and below the threshold, exit. */
|
||||
if (!server.active_defrag_running) {
|
||||
if(frag_pct < server.active_defrag_threshold_lower || frag_bytes < server.active_defrag_ignore_bytes)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Calculate the adaptive aggressiveness of the defrag */
|
||||
int cpu_pct = INTERPOLATE(frag_pct,
|
||||
server.active_defrag_threshold_lower,
|
||||
server.active_defrag_threshold_upper,
|
||||
server.active_defrag_cycle_min,
|
||||
server.active_defrag_cycle_max);
|
||||
cpu_pct = LIMIT(cpu_pct,
|
||||
server.active_defrag_cycle_min,
|
||||
server.active_defrag_cycle_max);
|
||||
/* We allow increasing the aggressiveness during a scan, but don't
|
||||
* reduce it. */
|
||||
if (!server.active_defrag_running ||
|
||||
cpu_pct > server.active_defrag_running)
|
||||
{
|
||||
server.active_defrag_running = cpu_pct;
|
||||
serverLog(LL_VERBOSE,
|
||||
"Starting active defrag, frag=%.0f%%, frag_bytes=%zu, cpu=%d%%",
|
||||
frag_pct, frag_bytes, cpu_pct);
|
||||
}
|
||||
}
|
||||
if (!server.active_defrag_running)
|
||||
return;
|
||||
|
||||
/* See activeExpireCycle for how timelimit is handled. */
|
||||
start = ustime();
|
||||
timelimit = 1000000*server.active_defrag_running/server.hz/100;
|
||||
if (timelimit <= 0) timelimit = 1;
|
||||
|
||||
do {
|
||||
if (!cursor) {
|
||||
/* Move on to next database, and stop if we reached the last one. */
|
||||
if (++current_db >= server.dbnum) {
|
||||
PORT_LONGLONG now = ustime();
|
||||
size_t frag_bytes;
|
||||
float frag_pct = getAllocatorFragmentation(&frag_bytes);
|
||||
serverLog(LL_VERBOSE,
|
||||
"Active defrag done in %dms, reallocated=%d, frag=%.0f%%, frag_bytes=%zu",
|
||||
(int)((now - start_scan)/1000), (int)(server.stat_active_defrag_hits - start_stat), frag_pct, frag_bytes);
|
||||
|
||||
start_scan = now;
|
||||
current_db = -1;
|
||||
cursor = 0;
|
||||
db = NULL;
|
||||
server.active_defrag_running = 0;
|
||||
return;
|
||||
}
|
||||
else if (current_db==0) {
|
||||
/* Start a scan from the first database. */
|
||||
start_scan = ustime();
|
||||
start_stat = server.stat_active_defrag_hits;
|
||||
}
|
||||
|
||||
db = &server.db[current_db];
|
||||
cursor = 0;
|
||||
}
|
||||
|
||||
do {
|
||||
cursor = dictScan(db->dict, cursor, defragScanCallback, defragDictBucketCallback, db);
|
||||
/* Once in 16 scan iterations, or 1000 pointer reallocations
|
||||
* (if we have a lot of pointers in one hash bucket), check if we
|
||||
* reached the tiem limit. */
|
||||
if (cursor && (++iterations > 16 || server.stat_active_defrag_hits - defragged > 1000)) {
|
||||
if ((ustime() - start) > timelimit) {
|
||||
return;
|
||||
}
|
||||
iterations = 0;
|
||||
defragged = server.stat_active_defrag_hits;
|
||||
}
|
||||
} while(cursor);
|
||||
} while(1);
|
||||
}
|
||||
|
||||
#else /* HAVE_DEFRAG */
|
||||
|
||||
void activeDefragCycle(void) {
|
||||
/* Not implemented yet. */
|
||||
}
|
||||
|
||||
#endif
|
||||
+26
-23
@@ -74,7 +74,7 @@ static unsigned int dict_force_resize_ratio = 5;
|
||||
|
||||
static int _dictExpandIfNeeded(dict *ht);
|
||||
static PORT_ULONG _dictNextPower(PORT_ULONG size);
|
||||
static int _dictKeyIndex(dict *ht, const void *key, unsigned int hash, dictEntry **existing);
|
||||
static PORT_LONG _dictKeyIndex(dict *ht, const void *key, uint64_t hash, dictEntry **existing);
|
||||
static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
|
||||
|
||||
/* -------------------------- hash functions -------------------------------- */
|
||||
@@ -192,7 +192,7 @@ int dictExpand(dict *d, PORT_ULONG size)
|
||||
* since part of the hash table may be composed of empty spaces, it is not
|
||||
* guaranteed that this function will rehash even a single bucket, since it
|
||||
* will visit at max N*10 empty buckets in total, otherwise the amount of
|
||||
* work it does would be unbound and the function may block for a PORT_LONG time. */
|
||||
* work it does would be unbound and the function may block for a long time. */
|
||||
int dictRehash(dict *d, int n) {
|
||||
|
||||
// On Windows we choose not to execute the dict rehash since it's not
|
||||
@@ -215,7 +215,7 @@ int dictRehash(dict *d, int n) {
|
||||
de = d->ht[0].table[d->rehashidx];
|
||||
/* Move all the keys in this bucket from the old to the new hash HT */
|
||||
while(de) {
|
||||
unsigned int h;
|
||||
uint64_t h;
|
||||
|
||||
nextde = de->next;
|
||||
/* Get the index in the new hash table */
|
||||
@@ -379,7 +379,7 @@ dictEntry *dictAddOrFind(dict *d, void *key) {
|
||||
* dictDelete() and dictUnlink(), please check the top comment
|
||||
* of those functions. */
|
||||
static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
|
||||
unsigned int h, idx;
|
||||
uint64_t h, idx;
|
||||
dictEntry *he, *prevHe;
|
||||
int table;
|
||||
|
||||
@@ -493,7 +493,7 @@ void dictRelease(dict *d)
|
||||
dictEntry *dictFind(dict *d, const void *key)
|
||||
{
|
||||
dictEntry *he;
|
||||
unsigned int h, idx, table;
|
||||
uint64_t h, idx, table;
|
||||
|
||||
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
|
||||
if (dictIsRehashing(d)) _dictRehashStep(d);
|
||||
@@ -627,7 +627,7 @@ void dictReleaseIterator(dictIterator *iter)
|
||||
dictEntry *dictGetRandomKey(dict *d)
|
||||
{
|
||||
dictEntry *he, *orighe;
|
||||
unsigned int h;
|
||||
PORT_ULONG h;
|
||||
int listlen, listele;
|
||||
|
||||
if (dictSize(d) == 0) return NULL;
|
||||
@@ -636,7 +636,7 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
do {
|
||||
/* We are sure there are no elements in indexes from 0
|
||||
* to rehashidx-1 */
|
||||
h = (unsigned int) (d->rehashidx + (random() % (d->ht[0].size + WIN_PORT_FIX /* cast (unsigned int) */
|
||||
h = (PORT_ULONG) (d->rehashidx + (random() % (d->ht[0].size + WIN_PORT_FIX /* cast (unsigned int) */
|
||||
d->ht[1].size -
|
||||
d->rehashidx)));
|
||||
he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
|
||||
@@ -875,6 +875,15 @@ PORT_ULONG dictScan(dict *d,
|
||||
de = next;
|
||||
}
|
||||
|
||||
/* Set unmasked bits so incrementing the reversed cursor
|
||||
* operates on the masked bits */
|
||||
v |= ~m0;
|
||||
|
||||
/* Increment the reverse cursor */
|
||||
v = rev(v);
|
||||
v++;
|
||||
v = rev(v);
|
||||
|
||||
} else {
|
||||
t0 = &d->ht[0];
|
||||
t1 = &d->ht[1];
|
||||
@@ -909,22 +918,16 @@ PORT_ULONG dictScan(dict *d,
|
||||
de = next;
|
||||
}
|
||||
|
||||
/* Increment bits not covered by the smaller mask */
|
||||
v = (((v | m0) + 1) & ~m0) | (v & m0);
|
||||
/* Increment the reverse cursor not covered by the smaller mask.*/
|
||||
v |= ~m1;
|
||||
v = rev(v);
|
||||
v++;
|
||||
v = rev(v);
|
||||
|
||||
/* Continue while bits covered by mask difference is non-zero */
|
||||
} while (v & (m0 ^ m1));
|
||||
}
|
||||
|
||||
/* Set unmasked bits so incrementing the reversed cursor
|
||||
* operates on the masked bits of the smaller table */
|
||||
v |= ~m0;
|
||||
|
||||
/* Increment the reverse cursor */
|
||||
v = rev(v);
|
||||
v++;
|
||||
v = rev(v);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -972,9 +975,9 @@ static PORT_ULONG _dictNextPower(PORT_ULONG size)
|
||||
*
|
||||
* Note that if we are in the process of rehashing the hash table, the
|
||||
* index is always returned in the context of the second (new) hash table. */
|
||||
static int _dictKeyIndex(dict *d, const void *key, unsigned int hash, dictEntry **existing)
|
||||
static PORT_LONG _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
|
||||
{
|
||||
unsigned int idx, table;
|
||||
PORT_ULONG idx, table;
|
||||
dictEntry *he;
|
||||
if (existing) *existing = NULL;
|
||||
|
||||
@@ -1012,7 +1015,7 @@ void dictDisableResize(void) {
|
||||
dict_can_resize = 0;
|
||||
}
|
||||
|
||||
unsigned int dictGetHash(dict *d, const void *key) {
|
||||
uint64_t dictGetHash(dict *d, const void *key) {
|
||||
return dictHashKey(d, key);
|
||||
}
|
||||
|
||||
@@ -1021,9 +1024,9 @@ unsigned int dictGetHash(dict *d, const void *key) {
|
||||
* the hash value should be provided using dictGetHash.
|
||||
* no string / key comparison is performed.
|
||||
* return value is the reference to the dictEntry if found, or NULL if not found. */
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, unsigned int hash) {
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash) {
|
||||
dictEntry *he, **heref;
|
||||
unsigned int idx, table;
|
||||
PORT_ULONG idx, table;
|
||||
|
||||
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
|
||||
for (table = 0; table <= 1; table++) {
|
||||
|
||||
+1241
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -181,8 +181,8 @@ int dictRehashMilliseconds(dict *d, int ms);
|
||||
void dictSetHashFunctionSeed(uint8_t *seed);
|
||||
uint8_t *dictGetHashFunctionSeed(void);
|
||||
PORT_ULONG dictScan(dict *d, PORT_ULONG v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata);
|
||||
unsigned int dictGetHash(dict *d, const void *key);
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, unsigned int hash);
|
||||
uint64_t dictGetHash(dict *d, const void *key);
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash);
|
||||
|
||||
/* Hash table types */
|
||||
extern dictType dictTypeHeapStringCopyKey;
|
||||
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
/* Hash Tables Implementation.
|
||||
*
|
||||
* This file implements in-memory hash tables with insert/del/replace/find/
|
||||
* get-random-element operations. Hash tables will auto-resize if needed
|
||||
* tables of power of two in size are used, collisions are handled by
|
||||
* chaining. See the source code for more information... :)
|
||||
*
|
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#ifdef _WIN32
|
||||
#include "Win32_Interop/win32_types.h"
|
||||
#endif
|
||||
|
||||
#ifndef __DICT_H
|
||||
#define __DICT_H
|
||||
|
||||
#define DICT_OK 0
|
||||
#define DICT_ERR 1
|
||||
|
||||
/* Unused arguments generate annoying warnings... */
|
||||
#define DICT_NOTUSED(V) ((void) V)
|
||||
|
||||
typedef struct dictEntry {
|
||||
void *key;
|
||||
union {
|
||||
void *val;
|
||||
uint64_t u64;
|
||||
int64_t s64;
|
||||
double d;
|
||||
} v;
|
||||
struct dictEntry *next;
|
||||
} dictEntry;
|
||||
|
||||
typedef struct dictType {
|
||||
uint64_t (*hashFunction)(const void *key);
|
||||
void *(*keyDup)(void *privdata, const void *key);
|
||||
void *(*valDup)(void *privdata, const void *obj);
|
||||
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
|
||||
void (*keyDestructor)(void *privdata, void *key);
|
||||
void (*valDestructor)(void *privdata, void *obj);
|
||||
} dictType;
|
||||
|
||||
/* This is our hash table structure. Every dictionary has two of this as we
|
||||
* implement incremental rehashing, for the old to the new table. */
|
||||
typedef struct dictht {
|
||||
dictEntry **table;
|
||||
PORT_ULONG size;
|
||||
PORT_ULONG sizemask;
|
||||
PORT_ULONG used;
|
||||
} dictht;
|
||||
|
||||
typedef struct dict {
|
||||
dictType *type;
|
||||
void *privdata;
|
||||
dictht ht[2];
|
||||
PORT_LONG rehashidx; /* rehashing not in progress if rehashidx == -1 */
|
||||
PORT_ULONG iterators; /* number of iterators currently running */
|
||||
} dict;
|
||||
|
||||
/* If safe is set to 1 this is a safe iterator, that means, you can call
|
||||
* dictAdd, dictFind, and other functions against the dictionary even while
|
||||
* iterating. Otherwise it is a non safe iterator, and only dictNext()
|
||||
* should be called while iterating. */
|
||||
typedef struct dictIterator {
|
||||
dict *d;
|
||||
PORT_LONG index;
|
||||
int table, safe;
|
||||
dictEntry *entry, *nextEntry;
|
||||
/* unsafe iterator fingerprint for misuse detection. */
|
||||
PORT_LONGLONG fingerprint;
|
||||
} dictIterator;
|
||||
|
||||
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
typedef void (dictScanBucketFunction)(void *privdata, dictEntry **bucketref);
|
||||
|
||||
/* This is the initial size of every hash table */
|
||||
#define DICT_HT_INITIAL_SIZE 4
|
||||
|
||||
/* ------------------------------- Macros ------------------------------------*/
|
||||
#define dictFreeVal(d, entry) \
|
||||
if ((d)->type->valDestructor) \
|
||||
(d)->type->valDestructor((d)->privdata, (entry)->v.val)
|
||||
|
||||
#define dictSetVal(d, entry, _val_) do { \
|
||||
if ((d)->type->valDup) \
|
||||
(entry)->v.val = (d)->type->valDup((d)->privdata, _val_); \
|
||||
else \
|
||||
(entry)->v.val = (_val_); \
|
||||
} while(0)
|
||||
|
||||
#define dictSetSignedIntegerVal(entry, _val_) \
|
||||
do { (entry)->v.s64 = _val_; } while(0)
|
||||
|
||||
#define dictSetUnsignedIntegerVal(entry, _val_) \
|
||||
do { (entry)->v.u64 = _val_; } while(0)
|
||||
|
||||
#define dictSetDoubleVal(entry, _val_) \
|
||||
do { (entry)->v.d = _val_; } while(0)
|
||||
|
||||
#define dictFreeKey(d, entry) \
|
||||
if ((d)->type->keyDestructor) \
|
||||
(d)->type->keyDestructor((d)->privdata, (entry)->key)
|
||||
|
||||
#define dictSetKey(d, entry, _key_) do { \
|
||||
if ((d)->type->keyDup) \
|
||||
(entry)->key = (d)->type->keyDup((d)->privdata, _key_); \
|
||||
else \
|
||||
(entry)->key = (_key_); \
|
||||
} while(0)
|
||||
|
||||
#define dictCompareKeys(d, key1, key2) \
|
||||
(((d)->type->keyCompare) ? \
|
||||
(d)->type->keyCompare((d)->privdata, key1, key2) : \
|
||||
(key1) == (key2))
|
||||
|
||||
#define dictHashKey(d, key) (d)->type->hashFunction(key)
|
||||
#define dictGetKey(he) ((he)->key)
|
||||
#define dictGetVal(he) ((he)->v.val)
|
||||
#define dictGetSignedIntegerVal(he) ((he)->v.s64)
|
||||
#define dictGetUnsignedIntegerVal(he) ((he)->v.u64)
|
||||
#define dictGetDoubleVal(he) ((he)->v.d)
|
||||
#define dictSlots(d) ((d)->ht[0].size+(d)->ht[1].size)
|
||||
#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)
|
||||
#define dictIsRehashing(d) ((d)->rehashidx != -1)
|
||||
|
||||
/* API */
|
||||
dict *dictCreate(dictType *type, void *privDataPtr);
|
||||
int dictExpand(dict *d, PORT_ULONG size);
|
||||
int dictAdd(dict *d, void *key, void *val);
|
||||
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing);
|
||||
dictEntry *dictAddOrFind(dict *d, void *key);
|
||||
int dictReplace(dict *d, void *key, void *val);
|
||||
int dictDelete(dict *d, const void *key);
|
||||
dictEntry *dictUnlink(dict *ht, const void *key);
|
||||
void dictFreeUnlinkedEntry(dict *d, dictEntry *he);
|
||||
void dictRelease(dict *d);
|
||||
dictEntry * dictFind(dict *d, const void *key);
|
||||
void *dictFetchValue(dict *d, const void *key);
|
||||
int dictResize(dict *d);
|
||||
dictIterator *dictGetIterator(dict *d);
|
||||
dictIterator *dictGetSafeIterator(dict *d);
|
||||
dictEntry *dictNext(dictIterator *iter);
|
||||
void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
|
||||
void dictGetStats(char *buf, size_t bufsize, dict *d);
|
||||
uint64_t dictGenHashFunction(const void *key, int len);
|
||||
uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
void dictEmpty(dict *d, void(callback)(void*));
|
||||
void dictEnableResize(void);
|
||||
void dictDisableResize(void);
|
||||
int dictRehash(dict *d, int n);
|
||||
int dictRehashMilliseconds(dict *d, int ms);
|
||||
void dictSetHashFunctionSeed(uint8_t *seed);
|
||||
uint8_t *dictGetHashFunctionSeed(void);
|
||||
PORT_ULONG dictScan(dict *d, PORT_ULONG v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata);
|
||||
uint64_t dictGetHash(dict *d, const void *key);
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, unsigned int hash);
|
||||
|
||||
/* Hash table types */
|
||||
extern dictType dictTypeHeapStringCopyKey;
|
||||
extern dictType dictTypeHeapStrings;
|
||||
extern dictType dictTypeHeapStringCopyKeyValue;
|
||||
|
||||
#endif /* __DICT_H */
|
||||
+14
-19
@@ -60,8 +60,6 @@ struct evictionPoolEntry {
|
||||
|
||||
static struct evictionPoolEntry *EvictionPoolLRU;
|
||||
|
||||
PORT_ULONG LFUDecrAndReturn(robj *o);
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Implementation of eviction, aging and LRU
|
||||
* --------------------------------------------------------------------------*/
|
||||
@@ -310,8 +308,8 @@ PORT_ULONG LFUGetTimeInMinutes(void) {
|
||||
return (server.unixtime/60) & 65535;
|
||||
}
|
||||
|
||||
/* Given an object last decrement time, compute the minimum number of minutes
|
||||
* that elapsed since the last decrement. Handle overflow (ldt greater than
|
||||
/* Given an object last access time, compute the minimum number of minutes
|
||||
* that elapsed since the last access. Handle overflow (ldt greater than
|
||||
* the current 16 bits minutes time) considering the time as wrapping
|
||||
* exactly once. */
|
||||
PORT_ULONG LFUTimeElapsed(PORT_ULONG ldt) {
|
||||
@@ -332,25 +330,22 @@ uint8_t LFULogIncr(uint8_t counter) {
|
||||
return counter;
|
||||
}
|
||||
|
||||
/* If the object decrement time is reached, decrement the LFU counter and
|
||||
* update the decrement time field. Return the object frequency counter.
|
||||
/* If the object decrement time is reached decrement the LFU counter but
|
||||
* do not update LFU fields of the object, we update the access time
|
||||
* and counter in an explicit way when the object is really accessed.
|
||||
* And we will times halve the counter according to the times of
|
||||
* elapsed time than server.lfu_decay_time.
|
||||
* Return the object frequency counter.
|
||||
*
|
||||
* This function is used in order to scan the dataset for the best object
|
||||
* to fit: as we check for the candidate, we incrementally decrement the
|
||||
* counter of the scanned objects if needed. */
|
||||
#define LFU_DECR_INTERVAL 1
|
||||
PORT_ULONG LFUDecrAndReturn(robj *o) {
|
||||
PORT_ULONG ldt = o->lru >> 8;
|
||||
PORT_ULONG counter = o->lru & 255;
|
||||
if (LFUTimeElapsed(ldt) >= server.lfu_decay_time && counter) {
|
||||
if (counter > LFU_INIT_VAL*2) {
|
||||
counter /= 2;
|
||||
if (counter < LFU_INIT_VAL*2) counter = LFU_INIT_VAL*2;
|
||||
} else {
|
||||
counter--;
|
||||
}
|
||||
o->lru = (LFUGetTimeInMinutes()<<8) | counter;
|
||||
}
|
||||
PORT_ULONG LFUDecrAndReturn(robj *o) { WIN_PORT_FIX
|
||||
PORT_ULONG ldt = o->lru >> 8; WIN_PORT_FIX
|
||||
PORT_ULONG counter = o->lru & 255; WIN_PORT_FIX
|
||||
PORT_ULONG num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0; WIN_PORT_FIX
|
||||
if (num_periods)
|
||||
counter = (num_periods > counter) ? 0 : counter - num_periods;
|
||||
return counter;
|
||||
}
|
||||
|
||||
|
||||
+570
@@ -0,0 +1,570 @@
|
||||
/* Maxmemory directive handling (LRU eviction and other policies).
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2009-2016, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "bio.h"
|
||||
#include "atomicvar.h"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Data structures
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
/* To improve the quality of the LRU approximation we take a set of keys
|
||||
* that are good candidate for eviction across freeMemoryIfNeeded() calls.
|
||||
*
|
||||
* Entries inside the eviciton pool are taken ordered by idle time, putting
|
||||
* greater idle times to the right (ascending order).
|
||||
*
|
||||
* When an LFU policy is used instead, a reverse frequency indication is used
|
||||
* instead of the idle time, so that we still evict by larger value (larger
|
||||
* inverse frequency means to evict keys with the least frequent accesses).
|
||||
*
|
||||
* Empty entries have the key pointer set to NULL. */
|
||||
#define EVPOOL_SIZE 16
|
||||
#define EVPOOL_CACHED_SDS_SIZE 255
|
||||
struct evictionPoolEntry {
|
||||
PORT_ULONGLONG idle; /* Object idle time (inverse frequency for LFU) */
|
||||
sds key; /* Key name. */
|
||||
sds cached; /* Cached SDS object for key name. */
|
||||
int dbid; /* Key DB number. */
|
||||
};
|
||||
|
||||
static struct evictionPoolEntry *EvictionPoolLRU;
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Implementation of eviction, aging and LRU
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
/* Return the LRU clock, based on the clock resolution. This is a time
|
||||
* in a reduced-bits format that can be used to set and check the
|
||||
* object->lru field of redisObject structures. */
|
||||
unsigned int getLRUClock(void) {
|
||||
return (mstime()/LRU_CLOCK_RESOLUTION) & LRU_CLOCK_MAX;
|
||||
}
|
||||
|
||||
/* This function is used to obtain the current LRU clock.
|
||||
* If the current resolution is lower than the frequency we refresh the
|
||||
* LRU clock (as it should be in production servers) we return the
|
||||
* precomputed value, otherwise we need to resort to a system call. */
|
||||
unsigned int LRU_CLOCK(void) {
|
||||
unsigned int lruclock;
|
||||
if (1000/server.hz <= LRU_CLOCK_RESOLUTION) {
|
||||
atomicGet(server.lruclock,lruclock);
|
||||
} else {
|
||||
lruclock = getLRUClock();
|
||||
}
|
||||
return lruclock;
|
||||
}
|
||||
|
||||
/* Given an object returns the min number of milliseconds the object was never
|
||||
* requested, using an approximated LRU algorithm. */
|
||||
PORT_ULONGLONG estimateObjectIdleTime(robj *o) {
|
||||
PORT_ULONGLONG lruclock = LRU_CLOCK();
|
||||
if (lruclock >= o->lru) {
|
||||
return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;
|
||||
} else {
|
||||
return (lruclock + (LRU_CLOCK_MAX - o->lru)) *
|
||||
LRU_CLOCK_RESOLUTION;
|
||||
}
|
||||
}
|
||||
|
||||
/* freeMemoryIfNeeded() gets called when 'maxmemory' is set on the config
|
||||
* file to limit the max memory used by the server, before processing a
|
||||
* command.
|
||||
*
|
||||
* The goal of the function is to free enough memory to keep Redis under the
|
||||
* configured memory limit.
|
||||
*
|
||||
* The function starts calculating how many bytes should be freed to keep
|
||||
* Redis under the limit, and enters a loop selecting the best keys to
|
||||
* evict accordingly to the configured policy.
|
||||
*
|
||||
* If all the bytes needed to return back under the limit were freed the
|
||||
* function returns C_OK, otherwise C_ERR is returned, and the caller
|
||||
* should block the execution of commands that will result in more memory
|
||||
* used by the server.
|
||||
*
|
||||
* ------------------------------------------------------------------------
|
||||
*
|
||||
* LRU approximation algorithm
|
||||
*
|
||||
* Redis uses an approximation of the LRU algorithm that runs in constant
|
||||
* memory. Every time there is a key to expire, we sample N keys (with
|
||||
* N very small, usually in around 5) to populate a pool of best keys to
|
||||
* evict of M keys (the pool size is defined by EVPOOL_SIZE).
|
||||
*
|
||||
* The N keys sampled are added in the pool of good keys to expire (the one
|
||||
* with an old access time) if they are better than one of the current keys
|
||||
* in the pool.
|
||||
*
|
||||
* After the pool is populated, the best key we have in the pool is expired.
|
||||
* However note that we don't remove keys from the pool when they are deleted
|
||||
* so the pool may contain keys that no longer exist.
|
||||
*
|
||||
* When we try to evict a key, and all the entries in the pool don't exist
|
||||
* we populate it again. This time we'll be sure that the pool has at least
|
||||
* one key that can be evicted, if there is at least one key that can be
|
||||
* evicted in the whole database. */
|
||||
|
||||
/* Create a new eviction pool. */
|
||||
void evictionPoolAlloc(void) {
|
||||
struct evictionPoolEntry *ep;
|
||||
int j;
|
||||
|
||||
ep = zmalloc(sizeof(*ep)*EVPOOL_SIZE);
|
||||
for (j = 0; j < EVPOOL_SIZE; j++) {
|
||||
ep[j].idle = 0;
|
||||
ep[j].key = NULL;
|
||||
ep[j].cached = sdsnewlen(NULL,EVPOOL_CACHED_SDS_SIZE);
|
||||
ep[j].dbid = 0;
|
||||
}
|
||||
EvictionPoolLRU = ep;
|
||||
}
|
||||
|
||||
/* This is an helper function for freeMemoryIfNeeded(), it is used in order
|
||||
* to populate the evictionPool with a few entries every time we want to
|
||||
* expire a key. Keys with idle time smaller than one of the current
|
||||
* keys are added. Keys are always added if there are free entries.
|
||||
*
|
||||
* We insert keys on place in ascending order, so keys with the smaller
|
||||
* idle time are on the left, and keys with the higher idle time on the
|
||||
* right. */
|
||||
|
||||
void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
|
||||
int j, k, count;
|
||||
#ifndef _WIN32
|
||||
dictEntry *samples[server.maxmemory_samples];
|
||||
#else
|
||||
dictEntry **samples;
|
||||
samples = zmalloc(sizeof(dictEntry*) * server.maxmemory_samples);
|
||||
#endif
|
||||
count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);
|
||||
for (j = 0; j < count; j++) {
|
||||
PORT_ULONGLONG idle;
|
||||
sds key;
|
||||
robj *o;
|
||||
dictEntry *de;
|
||||
|
||||
de = samples[j];
|
||||
key = dictGetKey(de);
|
||||
|
||||
/* If the dictionary we are sampling from is not the main
|
||||
* dictionary (but the expires one) we need to lookup the key
|
||||
* again in the key dictionary to obtain the value object. */
|
||||
if (server.maxmemory_policy != MAXMEMORY_VOLATILE_TTL) {
|
||||
if (sampledict != keydict) de = dictFind(keydict, key);
|
||||
o = dictGetVal(de);
|
||||
}
|
||||
|
||||
/* Calculate the idle time according to the policy. This is called
|
||||
* idle just because the code initially handled LRU, but is in fact
|
||||
* just a score where an higher score means better candidate. */
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
|
||||
idle = estimateObjectIdleTime(o);
|
||||
} else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
/* When we use an LRU policy, we sort the keys by idle time
|
||||
* so that we expire keys starting from greater idle time.
|
||||
* However when the policy is an LFU one, we have a frequency
|
||||
* estimation, and we want to evict keys with lower frequency
|
||||
* first. So inside the pool we put objects using the inverted
|
||||
* frequency subtracting the actual frequency to the maximum
|
||||
* frequency of 255. */
|
||||
idle = 255-LFUDecrAndReturn(o);
|
||||
} else if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
|
||||
/* In this case the sooner the expire the better. */
|
||||
idle = ULLONG_MAX - (PORT_LONG)dictGetVal(de);
|
||||
} else {
|
||||
serverPanic("Unknown eviction policy in evictionPoolPopulate()");
|
||||
}
|
||||
|
||||
/* Insert the element inside the pool.
|
||||
* First, find the first empty bucket or the first populated
|
||||
* bucket that has an idle time smaller than our idle time. */
|
||||
k = 0;
|
||||
while (k < EVPOOL_SIZE &&
|
||||
pool[k].key &&
|
||||
pool[k].idle < idle) k++;
|
||||
if (k == 0 && pool[EVPOOL_SIZE-1].key != NULL) {
|
||||
/* Can't insert if the element is < the worst element we have
|
||||
* and there are no empty buckets. */
|
||||
continue;
|
||||
} else if (k < EVPOOL_SIZE && pool[k].key == NULL) {
|
||||
/* Inserting into empty position. No setup needed before insert. */
|
||||
} else {
|
||||
/* Inserting in the middle. Now k points to the first element
|
||||
* greater than the element to insert. */
|
||||
if (pool[EVPOOL_SIZE-1].key == NULL) {
|
||||
/* Free space on the right? Insert at k shifting
|
||||
* all the elements from k to end to the right. */
|
||||
|
||||
/* Save SDS before overwriting. */
|
||||
sds cached = pool[EVPOOL_SIZE-1].cached;
|
||||
memmove(pool+k+1,pool+k,
|
||||
sizeof(pool[0])*(EVPOOL_SIZE-k-1));
|
||||
pool[k].cached = cached;
|
||||
} else {
|
||||
/* No free space on right? Insert at k-1 */
|
||||
k--;
|
||||
/* Shift all elements on the left of k (included) to the
|
||||
* left, so we discard the element with smaller idle time. */
|
||||
sds cached = pool[0].cached; /* Save SDS before overwriting. */
|
||||
if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);
|
||||
memmove(pool,pool+1,sizeof(pool[0])*k);
|
||||
pool[k].cached = cached;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to reuse the cached SDS string allocated in the pool entry,
|
||||
* because allocating and deallocating this object is costly
|
||||
* (according to the profiler, not my fantasy. Remember:
|
||||
* premature optimizbla bla bla bla. */
|
||||
int klen = sdslen(key);
|
||||
if (klen > EVPOOL_CACHED_SDS_SIZE) {
|
||||
pool[k].key = sdsdup(key);
|
||||
} else {
|
||||
memcpy(pool[k].cached,key,klen+1);
|
||||
sdssetlen(pool[k].cached,klen);
|
||||
pool[k].key = pool[k].cached;
|
||||
}
|
||||
pool[k].idle = idle;
|
||||
pool[k].dbid = dbid;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
zfree(samples);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* LFU (Least Frequently Used) implementation.
|
||||
|
||||
* We have 24 total bits of space in each object in order to implement
|
||||
* an LFU (Least Frequently Used) eviction policy, since we re-use the
|
||||
* LRU field for this purpose.
|
||||
*
|
||||
* We split the 24 bits into two fields:
|
||||
*
|
||||
* 16 bits 8 bits
|
||||
* +----------------+--------+
|
||||
* + Last decr time | LOG_C |
|
||||
* +----------------+--------+
|
||||
*
|
||||
* LOG_C is a logarithmic counter that provides an indication of the access
|
||||
* frequency. However this field must also be decremented otherwise what used
|
||||
* to be a frequently accessed key in the past, will remain ranked like that
|
||||
* forever, while we want the algorithm to adapt to access pattern changes.
|
||||
*
|
||||
* So the remaining 16 bits are used in order to store the "decrement time",
|
||||
* a reduced-precision Unix time (we take 16 bits of the time converted
|
||||
* in minutes since we don't care about wrapping around) where the LOG_C
|
||||
* counter is halved if it has an high value, or just decremented if it
|
||||
* has a low value.
|
||||
*
|
||||
* New keys don't start at zero, in order to have the ability to collect
|
||||
* some accesses before being trashed away, so they start at COUNTER_INIT_VAL.
|
||||
* The logarithmic increment performed on LOG_C takes care of COUNTER_INIT_VAL
|
||||
* when incrementing the key, so that keys starting at COUNTER_INIT_VAL
|
||||
* (or having a smaller value) have a very high chance of being incremented
|
||||
* on access.
|
||||
*
|
||||
* During decrement, the value of the logarithmic counter is halved if
|
||||
* its current value is greater than two times the COUNTER_INIT_VAL, otherwise
|
||||
* it is just decremented by one.
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
/* Return the current time in minutes, just taking the least significant
|
||||
* 16 bits. The returned time is suitable to be stored as LDT (last decrement
|
||||
* time) for the LFU implementation. */
|
||||
PORT_ULONG LFUGetTimeInMinutes(void) {
|
||||
return (server.unixtime/60) & 65535;
|
||||
}
|
||||
|
||||
/* Given an object last decrement time, compute the minimum number of minutes
|
||||
* that elapsed since the last decrement. Handle overflow (ldt greater than
|
||||
* the current 16 bits minutes time) considering the time as wrapping
|
||||
* exactly once. */
|
||||
PORT_ULONG LFUTimeElapsed(PORT_ULONG ldt) {
|
||||
PORT_ULONG now = LFUGetTimeInMinutes();
|
||||
if (now >= ldt) return now-ldt;
|
||||
return 65535-ldt+now;
|
||||
}
|
||||
|
||||
/* Logarithmically increment a counter. The greater is the current counter value
|
||||
* the less likely is that it gets really implemented. Saturate it at 255. */
|
||||
uint8_t LFULogIncr(uint8_t counter) {
|
||||
if (counter == 255) return 255;
|
||||
double r = (double)rand()/RAND_MAX;
|
||||
double baseval = counter - LFU_INIT_VAL;
|
||||
if (baseval < 0) baseval = 0;
|
||||
double p = 1.0/(baseval*server.lfu_log_factor+1);
|
||||
if (r < p) counter++;
|
||||
return counter;
|
||||
}
|
||||
|
||||
/* If the object decrement time is reached decrement the LFU counter but
|
||||
* do not update LFU fields of the object, we update the access time
|
||||
* and counter in an explicit way when the object is really accessed.
|
||||
* And we will times halve the counter according to the times of
|
||||
* elapsed time than server.lfu_decay_time.
|
||||
* Return the object frequency counter.
|
||||
*
|
||||
* This function is used in order to scan the dataset for the best object
|
||||
* to fit: as we check for the candidate, we incrementally decrement the
|
||||
* counter of the scanned objects if needed. */
|
||||
PORT_ULONG LFUDecrAndReturn(robj *o) { WIN_PORT_FIX
|
||||
PORT_ULONG ldt = o->lru >> 8; WIN_PORT_FIX
|
||||
PORT_ULONG counter = o->lru & 255; WIN_PORT_FIX
|
||||
PORT_ULONG num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0; WIN_PORT_FIX
|
||||
if (num_periods)
|
||||
counter = (num_periods > counter) ? 0 : counter - num_periods;
|
||||
return counter;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* The external API for eviction: freeMemroyIfNeeded() is called by the
|
||||
* server when there is data to add in order to make space if needed.
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
/* We don't want to count AOF buffers and slaves output buffers as
|
||||
* used memory: the eviction should use mostly data size. This function
|
||||
* returns the sum of AOF and slaves buffer. */
|
||||
size_t freeMemoryGetNotCountedMemory(void) {
|
||||
size_t overhead = 0;
|
||||
int slaves = listLength(server.slaves);
|
||||
|
||||
if (slaves) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = listNodeValue(ln);
|
||||
overhead += getClientOutputBufferMemoryUsage(slave);
|
||||
}
|
||||
}
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
overhead += sdslen(server.aof_buf)+aofRewriteBufferSize();
|
||||
}
|
||||
return overhead;
|
||||
}
|
||||
|
||||
int freeMemoryIfNeeded(void) {
|
||||
size_t mem_reported, mem_used, mem_tofree, mem_freed;
|
||||
mstime_t latency, eviction_latency;
|
||||
PORT_LONGLONG delta;
|
||||
int slaves = listLength(server.slaves);
|
||||
|
||||
/* When clients are paused the dataset should be static not just from the
|
||||
* POV of clients not being able to write, but also from the POV of
|
||||
* expires and evictions of keys not being performed. */
|
||||
if (clientsArePaused()) return C_OK;
|
||||
|
||||
/* Check if we are over the memory usage limit. If we are not, no need
|
||||
* to subtract the slaves output buffers. We can just return ASAP. */
|
||||
mem_reported = zmalloc_used_memory();
|
||||
if (mem_reported <= server.maxmemory) return C_OK;
|
||||
|
||||
/* Remove the size of slaves output buffers and AOF buffer from the
|
||||
* count of used memory. */
|
||||
mem_used = mem_reported;
|
||||
size_t overhead = freeMemoryGetNotCountedMemory();
|
||||
mem_used = (mem_used > overhead) ? mem_used-overhead : 0;
|
||||
|
||||
/* Check if we are still over the memory limit. */
|
||||
if (mem_used <= server.maxmemory) return C_OK;
|
||||
|
||||
/* Compute how much memory we need to free. */
|
||||
mem_tofree = mem_used - server.maxmemory;
|
||||
mem_freed = 0;
|
||||
|
||||
if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)
|
||||
goto cant_free; /* We need to free memory, but policy forbids. */
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
while (mem_freed < mem_tofree) {
|
||||
int j, k, i, keys_freed = 0;
|
||||
static int next_db = 0;
|
||||
sds bestkey = NULL;
|
||||
int bestdbid;
|
||||
redisDb *db;
|
||||
dict *dict;
|
||||
dictEntry *de;
|
||||
|
||||
if (server.maxmemory_policy & (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
|
||||
server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
|
||||
{
|
||||
struct evictionPoolEntry *pool = EvictionPoolLRU;
|
||||
|
||||
while(bestkey == NULL) {
|
||||
PORT_ULONG total_keys = 0, keys;
|
||||
|
||||
/* We don't want to make local-db choices when expiring keys,
|
||||
* so to start populate the eviction pool sampling keys from
|
||||
* every DB. */
|
||||
for (i = 0; i < server.dbnum; i++) {
|
||||
db = server.db+i;
|
||||
dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
|
||||
db->dict : db->expires;
|
||||
if ((keys = dictSize(dict)) != 0) {
|
||||
evictionPoolPopulate(i, dict, db->dict, pool);
|
||||
total_keys += keys;
|
||||
}
|
||||
}
|
||||
if (!total_keys) break; /* No keys to evict. */
|
||||
|
||||
/* Go backward from best to worst element to evict. */
|
||||
for (k = EVPOOL_SIZE-1; k >= 0; k--) {
|
||||
if (pool[k].key == NULL) continue;
|
||||
bestdbid = pool[k].dbid;
|
||||
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
|
||||
de = dictFind(server.db[pool[k].dbid].dict,
|
||||
pool[k].key);
|
||||
} else {
|
||||
de = dictFind(server.db[pool[k].dbid].expires,
|
||||
pool[k].key);
|
||||
}
|
||||
|
||||
/* Remove the entry from the pool. */
|
||||
if (pool[k].key != pool[k].cached)
|
||||
sdsfree(pool[k].key);
|
||||
pool[k].key = NULL;
|
||||
pool[k].idle = 0;
|
||||
|
||||
/* If the key exists, is our pick. Otherwise it is
|
||||
* a ghost and we need to try the next element. */
|
||||
if (de) {
|
||||
bestkey = dictGetKey(de);
|
||||
break;
|
||||
} else {
|
||||
/* Ghost... Iterate again. */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* volatile-random and allkeys-random policy */
|
||||
else if (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM ||
|
||||
server.maxmemory_policy == MAXMEMORY_VOLATILE_RANDOM)
|
||||
{
|
||||
/* When evicting a random key, we try to evict a key for
|
||||
* each DB, so we use the static 'next_db' variable to
|
||||
* incrementally visit all DBs. */
|
||||
for (i = 0; i < server.dbnum; i++) {
|
||||
j = (++next_db) % server.dbnum;
|
||||
db = server.db+j;
|
||||
dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
|
||||
db->dict : db->expires;
|
||||
if (dictSize(dict) != 0) {
|
||||
de = dictGetRandomKey(dict);
|
||||
bestkey = dictGetKey(de);
|
||||
bestdbid = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally remove the selected key. */
|
||||
if (bestkey) {
|
||||
db = server.db+bestdbid;
|
||||
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
|
||||
propagateExpire(db,keyobj,server.lazyfree_lazy_eviction);
|
||||
/* We compute the amount of memory freed by db*Delete() alone.
|
||||
* It is possible that actually the memory needed to propagate
|
||||
* the DEL in AOF and replication link is greater than the one
|
||||
* we are freeing removing the key, but we can't account for
|
||||
* that otherwise we would never exit the loop.
|
||||
*
|
||||
* AOF and Output buffer memory will be freed eventually so
|
||||
* we only care about memory used by the key space. */
|
||||
delta = (PORT_LONGLONG) zmalloc_used_memory();
|
||||
latencyStartMonitor(eviction_latency);
|
||||
if (server.lazyfree_lazy_eviction)
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
latencyEndMonitor(eviction_latency);
|
||||
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
|
||||
latencyRemoveNestedEvent(latency,eviction_latency);
|
||||
delta -= (PORT_LONGLONG) zmalloc_used_memory();
|
||||
mem_freed += delta;
|
||||
server.stat_evictedkeys++;
|
||||
notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
|
||||
keyobj, db->id);
|
||||
decrRefCount(keyobj);
|
||||
keys_freed++;
|
||||
|
||||
/* When the memory to free starts to be big enough, we may
|
||||
* start spending so much time here that is impossible to
|
||||
* deliver data to the slaves fast enough, so we force the
|
||||
* transmission here inside the loop. */
|
||||
if (slaves) flushSlavesOutputBuffers();
|
||||
|
||||
/* Normally our stop condition is the ability to release
|
||||
* a fixed, pre-computed amount of memory. However when we
|
||||
* are deleting objects in another thread, it's better to
|
||||
* check, from time to time, if we already reached our target
|
||||
* memory, since the "mem_freed" amount is computed only
|
||||
* across the dbAsyncDelete() call, while the thread can
|
||||
* release the memory all the time. */
|
||||
if (server.lazyfree_lazy_eviction && !(keys_freed % 16)) {
|
||||
overhead = freeMemoryGetNotCountedMemory();
|
||||
mem_used = zmalloc_used_memory();
|
||||
mem_used = (mem_used > overhead) ? mem_used-overhead : 0;
|
||||
if (mem_used <= server.maxmemory) {
|
||||
mem_freed = mem_tofree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!keys_freed) {
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
goto cant_free; /* nothing to free... */
|
||||
}
|
||||
}
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
return C_OK;
|
||||
|
||||
cant_free:
|
||||
/* We are here if we are not able to reclaim memory. There is only one
|
||||
* last thing we can try: check if the lazyfree thread has jobs in queue
|
||||
* and wait... */
|
||||
while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
|
||||
if (((mem_reported - zmalloc_used_memory()) + mem_freed) >= mem_tofree)
|
||||
break;
|
||||
usleep(1000);
|
||||
}
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015-2016, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* This is a C++ to C conversion from the ardb project.
|
||||
* This file started out as:
|
||||
* https://github.com/yinqiwen/ardb/blob/d42503/src/geo/geohash_helper.cpp
|
||||
*/
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "geohash_helper.h"
|
||||
#include "debugmacro.h"
|
||||
#include <math.h>
|
||||
|
||||
#define D_R (M_PI / 180.0)
|
||||
#define R_MAJOR 6378137.0
|
||||
#define R_MINOR 6356752.3142
|
||||
#define RATIO (R_MINOR / R_MAJOR)
|
||||
#define ECCENT (sqrt(1.0 - (RATIO *RATIO)))
|
||||
#define COM (0.5 * ECCENT)
|
||||
|
||||
/// @brief The usual PI/180 constant
|
||||
const double DEG_TO_RAD = 0.017453292519943295769236907684886;
|
||||
/// @brief Earth's quatratic mean radius for WGS-84
|
||||
const double EARTH_RADIUS_IN_METERS = 6372797.560856;
|
||||
|
||||
const double MERCATOR_MAX = 20037726.37;
|
||||
const double MERCATOR_MIN = -20037726.37;
|
||||
|
||||
static inline double deg_rad(double ang) { return ang * D_R; }
|
||||
static inline double rad_deg(double ang) { return ang / D_R; }
|
||||
|
||||
/* This function is used in order to estimate the step (bits precision)
|
||||
* of the 9 search area boxes during radius queries. */
|
||||
uint8_t geohashEstimateStepsByRadius(double range_meters, double lat) {
|
||||
if (range_meters == 0) return 26;
|
||||
int step = 1;
|
||||
while (range_meters < MERCATOR_MAX) {
|
||||
range_meters *= 2;
|
||||
step++;
|
||||
}
|
||||
step -= 2; /* Make sure range is included in most of the base cases. */
|
||||
|
||||
/* Wider range torwards the poles... Note: it is possible to do better
|
||||
* than this approximation by computing the distance between meridians
|
||||
* at this latitude, but this does the trick for now. */
|
||||
if (lat > 66 || lat < -66) {
|
||||
step--;
|
||||
if (lat > 80 || lat < -80) step--;
|
||||
}
|
||||
|
||||
/* Frame to valid range. */
|
||||
if (step < 1) step = 1;
|
||||
if (step > 26) step = 26;
|
||||
return step;
|
||||
}
|
||||
|
||||
/* Return the bounding box of the search area centered at latitude,longitude
|
||||
* having a radius of radius_meter. bounds[0] - bounds[2] is the minimum
|
||||
* and maxium longitude, while bounds[1] - bounds[3] is the minimum and
|
||||
* maximum latitude.
|
||||
*
|
||||
* This function does not behave correctly with very large radius values, for
|
||||
* instance for the coordinates 81.634948934258375 30.561509253718668 and a
|
||||
* radius of 7083 kilometers, it reports as bounding boxes:
|
||||
*
|
||||
* min_lon 7.680495, min_lat -33.119473, max_lon 155.589402, max_lat 94.242491
|
||||
*
|
||||
* However, for instance, a min_lon of 7.680495 is not correct, because the
|
||||
* point -1.27579540014266968 61.33421815228281559 is at less than 7000
|
||||
* kilometers away.
|
||||
*
|
||||
* Since this function is currently only used as an optimization, the
|
||||
* optimization is not used for very big radiuses, however the function
|
||||
* should be fixed. */
|
||||
int geohashBoundingBox(double longitude, double latitude, double radius_meters,
|
||||
double *bounds) {
|
||||
if (!bounds) return 0;
|
||||
|
||||
bounds[0] = longitude - rad_deg(radius_meters/EARTH_RADIUS_IN_METERS/cos(deg_rad(latitude)));
|
||||
bounds[2] = longitude + rad_deg(radius_meters/EARTH_RADIUS_IN_METERS/cos(deg_rad(latitude)));
|
||||
bounds[1] = latitude - rad_deg(radius_meters/EARTH_RADIUS_IN_METERS);
|
||||
bounds[3] = latitude + rad_deg(radius_meters/EARTH_RADIUS_IN_METERS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Return a set of areas (center + 8) that are able to cover a range query
|
||||
* for the specified position and radius. */
|
||||
GeoHashRadius geohashGetAreasByRadius(double longitude, double latitude, double radius_meters) {
|
||||
GeoHashRange long_range, lat_range;
|
||||
GeoHashRadius radius;
|
||||
GeoHashBits hash;
|
||||
GeoHashNeighbors neighbors;
|
||||
GeoHashArea area;
|
||||
double min_lon, max_lon, min_lat, max_lat;
|
||||
double bounds[4];
|
||||
int steps;
|
||||
|
||||
geohashBoundingBox(longitude, latitude, radius_meters, bounds);
|
||||
min_lon = bounds[0];
|
||||
min_lat = bounds[1];
|
||||
max_lon = bounds[2];
|
||||
max_lat = bounds[3];
|
||||
|
||||
steps = geohashEstimateStepsByRadius(radius_meters,latitude);
|
||||
|
||||
geohashGetCoordRange(&long_range,&lat_range);
|
||||
geohashEncode(&long_range,&lat_range,longitude,latitude,steps,&hash);
|
||||
geohashNeighbors(&hash,&neighbors);
|
||||
geohashDecode(long_range,lat_range,hash,&area);
|
||||
|
||||
/* Check if the step is enough at the limits of the covered area.
|
||||
* Sometimes when the search area is near an edge of the
|
||||
* area, the estimated step is not small enough, since one of the
|
||||
* north / south / west / east square is too near to the search area
|
||||
* to cover everything. */
|
||||
int decrease_step = 0;
|
||||
{
|
||||
GeoHashArea north, south, east, west;
|
||||
|
||||
geohashDecode(long_range, lat_range, neighbors.north, &north);
|
||||
geohashDecode(long_range, lat_range, neighbors.south, &south);
|
||||
geohashDecode(long_range, lat_range, neighbors.east, &east);
|
||||
geohashDecode(long_range, lat_range, neighbors.west, &west);
|
||||
|
||||
if (geohashGetDistance(longitude,latitude,longitude,north.latitude.max)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,longitude,south.latitude.min)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,east.longitude.max,latitude)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,west.longitude.min,latitude)
|
||||
< radius_meters) decrease_step = 1;
|
||||
}
|
||||
|
||||
if (steps > 1 && decrease_step) {
|
||||
steps--;
|
||||
geohashEncode(&long_range,&lat_range,longitude,latitude,steps,&hash);
|
||||
geohashNeighbors(&hash,&neighbors);
|
||||
geohashDecode(long_range,lat_range,hash,&area);
|
||||
}
|
||||
|
||||
/* Exclude the search areas that are useless. */
|
||||
if (steps >= 2) {
|
||||
if (area.latitude.min < min_lat) {
|
||||
GZERO(neighbors.south);
|
||||
GZERO(neighbors.south_west);
|
||||
GZERO(neighbors.south_east);
|
||||
}
|
||||
if (area.latitude.max > max_lat) {
|
||||
GZERO(neighbors.north);
|
||||
GZERO(neighbors.north_east);
|
||||
GZERO(neighbors.north_west);
|
||||
}
|
||||
if (area.longitude.min < min_lon) {
|
||||
GZERO(neighbors.west);
|
||||
GZERO(neighbors.south_west);
|
||||
GZERO(neighbors.north_west);
|
||||
}
|
||||
if (area.longitude.max > max_lon) {
|
||||
GZERO(neighbors.east);
|
||||
GZERO(neighbors.south_east);
|
||||
GZERO(neighbors.north_east);
|
||||
}
|
||||
}
|
||||
radius.hash = hash;
|
||||
radius.neighbors = neighbors;
|
||||
radius.area = area;
|
||||
return radius;
|
||||
}
|
||||
|
||||
GeoHashRadius geohashGetAreasByRadiusWGS84(double longitude, double latitude,
|
||||
double radius_meters) {
|
||||
return geohashGetAreasByRadius(longitude, latitude, radius_meters);
|
||||
}
|
||||
|
||||
GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash) {
|
||||
uint64_t bits = hash.bits;
|
||||
bits <<= (52 - hash.step * 2);
|
||||
return bits;
|
||||
}
|
||||
|
||||
/* Calculate distance using haversin great circle distance formula. */
|
||||
double geohashGetDistance(double lon1d, double lat1d, double lon2d, double lat2d) {
|
||||
double lat1r, lon1r, lat2r, lon2r, u, v;
|
||||
lat1r = deg_rad(lat1d);
|
||||
lon1r = deg_rad(lon1d);
|
||||
lat2r = deg_rad(lat2d);
|
||||
lon2r = deg_rad(lon2d);
|
||||
u = sin((lat2r - lat1r) / 2);
|
||||
v = sin((lon2r - lon1r) / 2);
|
||||
return 2.0 * EARTH_RADIUS_IN_METERS *
|
||||
asin(sqrt(u * u + cos(lat1r) * cos(lat2r) * v * v));
|
||||
}
|
||||
|
||||
int geohashGetDistanceIfInRadius(double x1, double y1,
|
||||
double x2, double y2, double radius,
|
||||
double *distance) {
|
||||
*distance = geohashGetDistance(x1, y1, x2, y2);
|
||||
if (*distance > radius) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int geohashGetDistanceIfInRadiusWGS84(double x1, double y1, double x2,
|
||||
double y2, double radius,
|
||||
double *distance) {
|
||||
return geohashGetDistanceIfInRadius(x1, y1, x2, y2, radius, distance);
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015-2016, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* This is a C++ to C conversion from the ardb project.
|
||||
* This file started out as:
|
||||
* https://github.com/yinqiwen/ardb/blob/d42503/src/geo/geohash_helper.cpp
|
||||
*/
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "geohash_helper.h"
|
||||
#include "debugmacro.h"
|
||||
#include <math.h>
|
||||
|
||||
#define D_R (M_PI / 180.0)
|
||||
#define R_MAJOR 6378137.0
|
||||
#define R_MINOR 6356752.3142
|
||||
#define RATIO (R_MINOR / R_MAJOR)
|
||||
#define ECCENT (sqrt(1.0 - (RATIO *RATIO)))
|
||||
#define COM (0.5 * ECCENT)
|
||||
|
||||
/// @brief The usual PI/180 constant
|
||||
const double DEG_TO_RAD = 0.017453292519943295769236907684886;
|
||||
/// @brief Earth's quatratic mean radius for WGS-84
|
||||
const double EARTH_RADIUS_IN_METERS = 6372797.560856;
|
||||
|
||||
const double MERCATOR_MAX = 20037726.37;
|
||||
const double MERCATOR_MIN = -20037726.37;
|
||||
|
||||
static inline double deg_rad(double ang) { return ang * D_R; }
|
||||
static inline double rad_deg(double ang) { return ang / D_R; }
|
||||
|
||||
/* This function is used in order to estimate the step (bits precision)
|
||||
* of the 9 search area boxes during radius queries. */
|
||||
uint8_t geohashEstimateStepsByRadius(double range_meters, double lat) {
|
||||
if (range_meters == 0) return 26;
|
||||
int step = 1;
|
||||
while (range_meters < MERCATOR_MAX) {
|
||||
range_meters *= 2;
|
||||
step++;
|
||||
}
|
||||
step -= 2; /* Make sure range is included in most of the base cases. */
|
||||
|
||||
/* Wider range torwards the poles... Note: it is possible to do better
|
||||
* than this approximation by computing the distance between meridians
|
||||
* at this latitude, but this does the trick for now. */
|
||||
if (lat > 66 || lat < -66) {
|
||||
step--;
|
||||
if (lat > 80 || lat < -80) step--;
|
||||
}
|
||||
|
||||
/* Frame to valid range. */
|
||||
if (step < 1) step = 1;
|
||||
if (step > 26) step = 26;
|
||||
return step;
|
||||
}
|
||||
|
||||
/* Return the bounding box of the search area centered at latitude,longitude
|
||||
* having a radius of radius_meter. bounds[0] - bounds[2] is the minimum
|
||||
* and maxium longitude, while bounds[1] - bounds[3] is the minimum and
|
||||
* maximum latitude.
|
||||
*
|
||||
* This function does not behave correctly with very large radius values, for
|
||||
* instance for the coordinates 81.634948934258375 30.561509253718668 and a
|
||||
* radius of 7083 kilometers, it reports as bounding boxes:
|
||||
*
|
||||
* min_lon 7.680495, min_lat -33.119473, max_lon 155.589402, max_lat 94.242491
|
||||
*
|
||||
* However, for instance, a min_lon of 7.680495 is not correct, because the
|
||||
* point -1.27579540014266968 61.33421815228281559 is at less than 7000
|
||||
* kilometers away.
|
||||
*
|
||||
* Since this function is currently only used as an optimization, the
|
||||
* optimization is not used for very big radiuses, however the function
|
||||
* should be fixed. */
|
||||
int geohashBoundingBox(double longitude, double latitude, double radius_meters,
|
||||
double *bounds) {
|
||||
if (!bounds) return 0;
|
||||
|
||||
bounds[0] = longitude - rad_deg(radius_meters/EARTH_RADIUS_IN_METERS/cos(deg_rad(latitude)));
|
||||
bounds[2] = longitude + rad_deg(radius_meters/EARTH_RADIUS_IN_METERS/cos(deg_rad(latitude)));
|
||||
bounds[1] = latitude - rad_deg(radius_meters/EARTH_RADIUS_IN_METERS);
|
||||
bounds[3] = latitude + rad_deg(radius_meters/EARTH_RADIUS_IN_METERS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Return a set of areas (center + 8) that are able to cover a range query
|
||||
* for the specified position and radius. */
|
||||
GeoHashRadius geohashGetAreasByRadius(double longitude, double latitude, double radius_meters) {
|
||||
GeoHashRange long_range, lat_range;
|
||||
GeoHashRadius radius;
|
||||
GeoHashBits hash;
|
||||
GeoHashNeighbors neighbors;
|
||||
GeoHashArea area;
|
||||
double min_lon, max_lon, min_lat, max_lat;
|
||||
double bounds[4];
|
||||
int steps;
|
||||
|
||||
geohashBoundingBox(longitude, latitude, radius_meters, bounds);
|
||||
min_lon = bounds[0];
|
||||
min_lat = bounds[1];
|
||||
max_lon = bounds[2];
|
||||
max_lat = bounds[3];
|
||||
|
||||
steps = geohashEstimateStepsByRadius(radius_meters,latitude);
|
||||
|
||||
geohashGetCoordRange(&long_range,&lat_range);
|
||||
geohashEncode(&long_range,&lat_range,longitude,latitude,steps,&hash);
|
||||
geohashNeighbors(&hash,&neighbors);
|
||||
geohashDecode(long_range,lat_range,hash,&area);
|
||||
|
||||
/* Check if the step is enough at the limits of the covered area.
|
||||
* Sometimes when the search area is near an edge of the
|
||||
* area, the estimated step is not small enough, since one of the
|
||||
* north / south / west / east square is too near to the search area
|
||||
* to cover everything. */
|
||||
int decrease_step = 0;
|
||||
{
|
||||
GeoHashArea north, south, east, west;
|
||||
|
||||
geohashDecode(long_range, lat_range, neighbors.north, &north);
|
||||
geohashDecode(long_range, lat_range, neighbors.south, &south);
|
||||
geohashDecode(long_range, lat_range, neighbors.east, &east);
|
||||
geohashDecode(long_range, lat_range, neighbors.west, &west);
|
||||
|
||||
if (geohashGetDistance(longitude,latitude,longitude,north.latitude.max)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,longitude,south.latitude.min)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,east.longitude.max,latitude)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,west.longitude.min,latitude)
|
||||
< radius_meters) decrease_step = 1;
|
||||
}
|
||||
|
||||
if (steps > 1 && decrease_step) {
|
||||
steps--;
|
||||
geohashEncode(&long_range,&lat_range,longitude,latitude,steps,&hash);
|
||||
geohashNeighbors(&hash,&neighbors);
|
||||
geohashDecode(long_range,lat_range,hash,&area);
|
||||
}
|
||||
|
||||
/* Exclude the search areas that are useless. */
|
||||
if (steps >= 2) {
|
||||
if (area.latitude.min < min_lat) {
|
||||
GZERO(neighbors.south);
|
||||
GZERO(neighbors.south_west);
|
||||
GZERO(neighbors.south_east);
|
||||
}
|
||||
if (area.latitude.max > max_lat) {
|
||||
GZERO(neighbors.north);
|
||||
GZERO(neighbors.north_east);
|
||||
GZERO(neighbors.north_west);
|
||||
}
|
||||
if (area.longitude.min < min_lon) {
|
||||
GZERO(neighbors.west);
|
||||
GZERO(neighbors.south_west);
|
||||
GZERO(neighbors.north_west);
|
||||
}
|
||||
if (area.longitude.max > max_lon) {
|
||||
GZERO(neighbors.east);
|
||||
GZERO(neighbors.south_east);
|
||||
GZERO(neighbors.north_east);
|
||||
}
|
||||
}
|
||||
radius.hash = hash;
|
||||
radius.neighbors = neighbors;
|
||||
radius.area = area;
|
||||
return radius;
|
||||
}
|
||||
|
||||
GeoHashRadius geohashGetAreasByRadiusWGS84(double longitude, double latitude,
|
||||
double radius_meters) {
|
||||
return geohashGetAreasByRadius(longitude, latitude, radius_meters);
|
||||
}
|
||||
|
||||
GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash) {
|
||||
uint64_t bits = hash.bits;
|
||||
bits <<= (52 - hash.step * 2);
|
||||
return bits;
|
||||
}
|
||||
|
||||
/* Calculate distance using haversin great circle distance formula. */
|
||||
double geohashGetDistance(double lon1d, double lat1d, double lon2d, double lat2d) {
|
||||
double lat1r, lon1r, lat2r, lon2r, u, v;
|
||||
lat1r = deg_rad(lat1d);
|
||||
lon1r = deg_rad(lon1d);
|
||||
lat2r = deg_rad(lat2d);
|
||||
lon2r = deg_rad(lon2d);
|
||||
u = sin((lat2r - lat1r) / 2);
|
||||
v = sin((lon2r - lon1r) / 2);
|
||||
return 2.0 * EARTH_RADIUS_IN_METERS *
|
||||
asin(sqrt(u * u + cos(lat1r) * cos(lat2r) * v * v));
|
||||
}
|
||||
|
||||
int geohashGetDistanceIfInRadius(double x1, double y1,
|
||||
double x2, double y2, double radius,
|
||||
double *distance) {
|
||||
*distance = geohashGetDistance(x1, y1, x2, y2);
|
||||
if (*distance > radius) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int geohashGetDistanceIfInRadiusWGS84(double x1, double y1, double x2,
|
||||
double y2, double radius,
|
||||
double *distance) {
|
||||
return geohashGetDistanceIfInRadius(x1, y1, x2, y2, radius, distance);
|
||||
}
|
||||
+55
-24
@@ -475,9 +475,8 @@ int hllPatLen(unsigned char *ele, size_t elesize, PORT_LONG *regp) {
|
||||
|
||||
/* ================== Dense representation implementation ================== */
|
||||
|
||||
/* "Add" the element in the dense hyperloglog data structure.
|
||||
* Actually nothing is added, but the max 0 pattern counter of the subset
|
||||
* the element belongs to is incremented if needed.
|
||||
/* Low level function to set the dense HLL register at 'index' to the
|
||||
* specified value if the current value is smaller than 'count'.
|
||||
*
|
||||
* 'registers' is expected to have room for HLL_REGISTERS plus an
|
||||
* additional byte on the right. This requirement is met by sds strings
|
||||
@@ -486,12 +485,9 @@ int hllPatLen(unsigned char *ele, size_t elesize, PORT_LONG *regp) {
|
||||
* The function always succeed, however if as a result of the operation
|
||||
* the approximated cardinality changed, 1 is returned. Otherwise 0
|
||||
* is returned. */
|
||||
int hllDenseAdd(uint8_t *registers, unsigned char *ele, size_t elesize) {
|
||||
uint8_t oldcount, count;
|
||||
PORT_LONG index;
|
||||
int hllDenseSet(uint8_t *registers, long index, uint8_t count) {
|
||||
uint8_t oldcount;
|
||||
|
||||
/* Update the register if this element produced a longer run of zeroes. */
|
||||
count = hllPatLen(ele,elesize,&index);
|
||||
HLL_DENSE_GET_REGISTER(oldcount,registers,index);
|
||||
if (count > oldcount) {
|
||||
HLL_DENSE_SET_REGISTER(registers,index,count);
|
||||
@@ -501,6 +497,19 @@ int hllDenseAdd(uint8_t *registers, unsigned char *ele, size_t elesize) {
|
||||
}
|
||||
}
|
||||
|
||||
/* "Add" the element in the dense hyperloglog data structure.
|
||||
* Actually nothing is added, but the max 0 pattern counter of the subset
|
||||
* the element belongs to is incremented if needed.
|
||||
*
|
||||
* This is just a wrapper to hllDenseSet(), performing the hashing of the
|
||||
* element in order to retrieve the index and zero-run count. */
|
||||
int hllDenseAdd(uint8_t *registers, unsigned char *ele, size_t elesize) {
|
||||
long index;
|
||||
uint8_t count = hllPatLen(ele,elesize,&index);
|
||||
/* Update the register if this element produced a longer run of zeroes. */
|
||||
return hllDenseSet(registers,index,count);
|
||||
}
|
||||
|
||||
/* Compute SUM(2^-reg) in the dense representation.
|
||||
* PE is an array with a pre-computer table of values 2^-reg indexed by reg.
|
||||
* As a side effect the integer pointed by 'ezp' is set to the number
|
||||
@@ -623,9 +632,8 @@ int hllSparseToDense(robj *o) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* "Add" the element in the sparse hyperloglog data structure.
|
||||
* Actually nothing is added, but the max 0 pattern counter of the subset
|
||||
* the element belongs to is incremented if needed.
|
||||
/* Low level function to set the sparse HLL register at 'index' to the
|
||||
* specified value if the current value is smaller than 'count'.
|
||||
*
|
||||
* The object 'o' is the String object holding the HLL. The function requires
|
||||
* a reference to the object in order to be able to enlarge the string if
|
||||
@@ -639,15 +647,12 @@ int hllSparseToDense(robj *o) {
|
||||
* sparse to dense: this happens when a register requires to be set to a value
|
||||
* not representable with the sparse representation, or when the resulting
|
||||
* size would be greater than server.hll_sparse_max_bytes. */
|
||||
int hllSparseAdd(robj *o, unsigned char *ele, size_t elesize) {
|
||||
int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
struct hllhdr *hdr;
|
||||
uint8_t oldcount, count, *sparse, *end, *p, *prev, *next;
|
||||
PORT_LONG index, first, span;
|
||||
uint8_t oldcount, *sparse, *end, *p, *prev, *next;
|
||||
PORT_LONG first, span;
|
||||
PORT_LONG is_zero = 0, is_xzero = 0, is_val = 0, runlen = 0;
|
||||
|
||||
/* Update the register if this element produced a longer run of zeroes. */
|
||||
count = hllPatLen(ele,elesize,&index);
|
||||
|
||||
/* If the count is too big to be representable by the sparse representation
|
||||
* switch to dense representation. */
|
||||
if (count > HLL_SPARSE_VAL_MAX_VALUE) goto promote;
|
||||
@@ -880,11 +885,24 @@ promote: /* Promote to dense representation. */
|
||||
* Note that this in turn means that PFADD will make sure the command
|
||||
* is propagated to slaves / AOF, so if there is a sparse -> dense
|
||||
* convertion, it will be performed in all the slaves as well. */
|
||||
int dense_retval = hllDenseAdd(hdr->registers, ele, elesize);
|
||||
int dense_retval = hllDenseSet(hdr->registers,index,count);
|
||||
serverAssert(dense_retval == 1);
|
||||
return dense_retval;
|
||||
}
|
||||
|
||||
/* "Add" the element in the sparse hyperloglog data structure.
|
||||
* Actually nothing is added, but the max 0 pattern counter of the subset
|
||||
* the element belongs to is incremented if needed.
|
||||
*
|
||||
* This function is actually a wrapper for hllSparseSet(), it only performs
|
||||
* the hashshing of the elmenet to obtain the index and zeros run length. */
|
||||
int hllSparseAdd(robj *o, unsigned char *ele, size_t elesize) {
|
||||
long index;
|
||||
uint8_t count = hllPatLen(ele,elesize,&index);
|
||||
/* Update the register if this element produced a longer run of zeroes. */
|
||||
return hllSparseSet(o,index,count);
|
||||
}
|
||||
|
||||
/* Compute SUM(2^-reg) in the sparse representation.
|
||||
* PE is an array with a pre-computer table of values 2^-reg indexed by reg.
|
||||
* As a side effect the integer pointed by 'ezp' is set to the number
|
||||
@@ -1280,9 +1298,10 @@ void pfmergeCommand(client *c) {
|
||||
uint8_t max[HLL_REGISTERS];
|
||||
struct hllhdr *hdr;
|
||||
int j;
|
||||
int use_dense = 0; /* Use dense representation as target? */
|
||||
|
||||
/* Compute an HLL with M[i] = MAX(M[i]_j).
|
||||
* We we the maximum into the max array of registers. We'll write
|
||||
* We store the maximum into the max array of registers. We'll write
|
||||
* it to the target variable later. */
|
||||
memset(max,0,sizeof(max));
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
@@ -1291,6 +1310,11 @@ void pfmergeCommand(client *c) {
|
||||
if (o == NULL) continue; /* Assume empty HLL for non existing var. */
|
||||
if (isHLLObjectOrReply(c,o) != C_OK) return;
|
||||
|
||||
/* If at least one involved HLL is dense, use the dense representation
|
||||
* as target ASAP to save time and avoid the conversion step. */
|
||||
hdr = o->ptr;
|
||||
if (hdr->encoding == HLL_DENSE) use_dense = 1;
|
||||
|
||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
||||
* to MAX(max[i],hll[i]). */
|
||||
if (hllMerge(max,o) == C_ERR) {
|
||||
@@ -1314,22 +1338,29 @@ void pfmergeCommand(client *c) {
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
}
|
||||
|
||||
/* Only support dense objects as destination. */
|
||||
if (hllSparseToDense(o) == C_ERR) {
|
||||
/* Convert the destination object to dense representation if at least
|
||||
* one of the inputs was dense. */
|
||||
if (use_dense && hllSparseToDense(o) == C_ERR) {
|
||||
addReplySds(c,sdsnew(invalid_hll_err));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Write the resulting HLL to the destination HLL registers and
|
||||
* invalidate the cached value. */
|
||||
hdr = o->ptr;
|
||||
for (j = 0; j < HLL_REGISTERS; j++) {
|
||||
HLL_DENSE_SET_REGISTER(hdr->registers,j,max[j]);
|
||||
if (max[j] == 0) continue;
|
||||
hdr = o->ptr;
|
||||
switch(hdr->encoding) {
|
||||
case HLL_DENSE: hllDenseSet(hdr->registers,j,max[j]); break;
|
||||
case HLL_SPARSE: hllSparseSet(o,j,max[j]); break;
|
||||
}
|
||||
}
|
||||
hdr = o->ptr; /* o->ptr may be different now, as a side effect of
|
||||
last hllSparseSet() call. */
|
||||
HLL_INVALIDATE_CACHE(hdr);
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
/* We generate an PFADD event for PFMERGE for semantical simplicity
|
||||
/* We generate a PFADD event for PFMERGE for semantical simplicity
|
||||
* since in theory this is a mass-add of elements. */
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -109,6 +109,8 @@ void latencyAddSample(char *event, mstime_t latency) {
|
||||
dictAdd(server.latency_events,zstrdup(event),ts);
|
||||
}
|
||||
|
||||
if (latency > ts->max) ts->max = latency;
|
||||
|
||||
/* If the previous sample is in the same second, we update our old sample
|
||||
* if this latency is > of the old one, or just return. */
|
||||
prev = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
|
||||
@@ -120,8 +122,7 @@ void latencyAddSample(char *event, mstime_t latency) {
|
||||
|
||||
ts->samples[ts->idx].time = (int32_t)time(NULL); WIN_PORT_FIX /* cast (int32_t) */
|
||||
ts->samples[ts->idx].latency = (int32_t)latency; WIN_PORT_FIX /* cast (int32_t) */
|
||||
if (latency > ts->max) ts->max = (int32_t)latency; WIN_PORT_FIX /* cast (int32_t) */
|
||||
|
||||
|
||||
ts->idx++;
|
||||
if (ts->idx == LATENCY_TS_LEN) ts->idx = 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,625 @@
|
||||
/* The latency monitor allows to easily observe the sources of latency
|
||||
* in a Redis instance using the LATENCY command. Different latency
|
||||
* sources are monitored, like disk I/O, execution of commands, fork
|
||||
* system call, and so forth.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2014, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/* Dictionary type for latency events. */
|
||||
int dictStringKeyCompare(void *privdata, const void *key1, const void *key2) {
|
||||
UNUSED(privdata);
|
||||
return strcmp(key1,key2) == 0;
|
||||
}
|
||||
|
||||
uint64_t dictStringHash(const void *key) {
|
||||
return dictGenHashFunction(key, (int)strlen(key)); WIN_PORT_FIX /* cast (int) */
|
||||
}
|
||||
|
||||
void dictVanillaFree(void *privdata, void *val);
|
||||
|
||||
dictType latencyTimeSeriesDictType = {
|
||||
dictStringHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictStringKeyCompare, /* key compare */
|
||||
dictVanillaFree, /* key destructor */
|
||||
dictVanillaFree /* val destructor */
|
||||
};
|
||||
|
||||
/* ------------------------- Utility functions ------------------------------ */
|
||||
|
||||
#ifdef __linux__
|
||||
/* Returns 1 if Transparent Huge Pages support is enabled in the kernel.
|
||||
* Otherwise (or if we are unable to check) 0 is returned. */
|
||||
int THPIsEnabled(void) {
|
||||
char buf[1024];
|
||||
|
||||
FILE *fp = fopen("/sys/kernel/mm/transparent_hugepage/enabled","r");
|
||||
if (!fp) return 0;
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
fclose(fp);
|
||||
return (strstr(buf,"[never]") == NULL) ? 1 : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Report the amount of AnonHugePages in smap, in bytes. If the return
|
||||
* value of the function is non-zero, the process is being targeted by
|
||||
* THP support, and is likely to have memory usage / latency issues. */
|
||||
int THPGetAnonHugePagesSize(void) {
|
||||
return (int)zmalloc_get_smap_bytes_by_field("AnonHugePages:",-1); WIN_PORT_FIX /* cast (int) */
|
||||
}
|
||||
|
||||
/* ---------------------------- Latency API --------------------------------- */
|
||||
|
||||
/* Latency monitor initialization. We just need to create the dictionary
|
||||
* of time series, each time serie is craeted on demand in order to avoid
|
||||
* having a fixed list to maintain. */
|
||||
void latencyMonitorInit(void) {
|
||||
server.latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
|
||||
}
|
||||
|
||||
/* Add the specified sample to the specified time series "event".
|
||||
* This function is usually called via latencyAddSampleIfNeeded(), that
|
||||
* is a macro that only adds the sample if the latency is higher than
|
||||
* server.latency_monitor_threshold. */
|
||||
void latencyAddSample(char *event, mstime_t latency) {
|
||||
struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
|
||||
time_t now = time(NULL);
|
||||
int prev;
|
||||
|
||||
/* Create the time series if it does not exist. */
|
||||
if (ts == NULL) {
|
||||
ts = zmalloc(sizeof(*ts));
|
||||
ts->idx = 0;
|
||||
ts->max = 0;
|
||||
memset(ts->samples,0,sizeof(ts->samples));
|
||||
dictAdd(server.latency_events,zstrdup(event),ts);
|
||||
}
|
||||
|
||||
if (latency > ts->max) ts->max = latency;
|
||||
|
||||
/* If the previous sample is in the same second, we update our old sample
|
||||
* if this latency is > of the old one, or just return. */
|
||||
prev = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
|
||||
if (ts->samples[prev].time == now) {
|
||||
if (latency > ts->samples[prev].latency)
|
||||
ts->samples[prev].latency = (int32_t)latency; WIN_PORT_FIX /* cast (int32_t) */
|
||||
return;
|
||||
}
|
||||
|
||||
ts->samples[ts->idx].time = (int32_t)time(NULL); WIN_PORT_FIX /* cast (int32_t) */
|
||||
ts->samples[ts->idx].latency = (int32_t)latency; WIN_PORT_FIX /* cast (int32_t) */
|
||||
WIN_PORT_FIX /* cast (int32_t) */
|
||||
|
||||
ts->idx++;
|
||||
if (ts->idx == LATENCY_TS_LEN) ts->idx = 0;
|
||||
}
|
||||
|
||||
/* Reset data for the specified event, or all the events data if 'event' is
|
||||
* NULL.
|
||||
*
|
||||
* Note: this is O(N) even when event_to_reset is not NULL because makes
|
||||
* the code simpler and we have a small fixed max number of events. */
|
||||
int latencyResetEvent(char *event_to_reset) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
int resets = 0;
|
||||
|
||||
di = dictGetSafeIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
|
||||
if (event_to_reset == NULL || strcasecmp(event,event_to_reset) == 0) {
|
||||
dictDelete(server.latency_events, event);
|
||||
resets++;
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
return resets;
|
||||
}
|
||||
|
||||
/* ------------------------ Latency reporting (doctor) ---------------------- */
|
||||
|
||||
/* Analyze the samples avaialble for a given event and return a structure
|
||||
* populate with different metrics, average, MAD, min, max, and so forth.
|
||||
* Check latency.h definition of struct latenctStat for more info.
|
||||
* If the specified event has no elements the structure is populate with
|
||||
* zero values. */
|
||||
void analyzeLatencyForEvent(char *event, struct latencyStats *ls) {
|
||||
struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
|
||||
int j;
|
||||
uint64_t sum;
|
||||
|
||||
ls->all_time_high = ts ? ts->max : 0;
|
||||
ls->avg = 0;
|
||||
ls->min = 0;
|
||||
ls->max = 0;
|
||||
ls->mad = 0;
|
||||
ls->samples = 0;
|
||||
ls->period = 0;
|
||||
if (!ts) return;
|
||||
|
||||
/* First pass, populate everything but the MAD. */
|
||||
sum = 0;
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
if (ts->samples[j].time == 0) continue;
|
||||
ls->samples++;
|
||||
if (ls->samples == 1) {
|
||||
ls->min = ls->max = ts->samples[j].latency;
|
||||
} else {
|
||||
if (ls->min > ts->samples[j].latency)
|
||||
ls->min = ts->samples[j].latency;
|
||||
if (ls->max < ts->samples[j].latency)
|
||||
ls->max = ts->samples[j].latency;
|
||||
}
|
||||
sum += ts->samples[j].latency;
|
||||
|
||||
/* Track the oldest event time in ls->period. */
|
||||
if (ls->period == 0 || ts->samples[j].time < ls->period)
|
||||
ls->period = ts->samples[j].time;
|
||||
}
|
||||
|
||||
/* So far avg is actually the sum of the latencies, and period is
|
||||
* the oldest event time. We need to make the first an average and
|
||||
* the second a range of seconds. */
|
||||
if (ls->samples) {
|
||||
ls->avg = (int32_t)(sum / ls->samples); WIN_PORT_FIX /* cast (int32_t) */
|
||||
ls->period = time(NULL) - ls->period;
|
||||
if (ls->period == 0) ls->period = 1;
|
||||
}
|
||||
|
||||
/* Second pass, compute MAD. */
|
||||
sum = 0;
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
int64_t delta;
|
||||
|
||||
if (ts->samples[j].time == 0) continue;
|
||||
delta = (int64_t)ls->avg - ts->samples[j].latency;
|
||||
if (delta < 0) delta = -delta;
|
||||
sum += delta;
|
||||
}
|
||||
if (ls->samples) ls->mad = (int32_t)(sum / ls->samples); WIN_PORT_FIX /* cast (int32_t) */
|
||||
}
|
||||
|
||||
/* Create a human readable report of latency events for this Redis instance. */
|
||||
sds createLatencyReport(void) {
|
||||
sds report = sdsempty();
|
||||
int advise_better_vm = 0; /* Better virtual machines. */
|
||||
int advise_slowlog_enabled = 0; /* Enable slowlog. */
|
||||
int advise_slowlog_tuning = 0; /* Reconfigure slowlog. */
|
||||
int advise_slowlog_inspect = 0; /* Check your slowlog. */
|
||||
int advise_disk_contention = 0; /* Try to lower disk contention. */
|
||||
int advise_scheduler = 0; /* Intrinsic latency. */
|
||||
int advise_data_writeback = 0; /* data=writeback. */
|
||||
int advise_no_appendfsync = 0; /* don't fsync during rewrites. */
|
||||
int advise_local_disk = 0; /* Avoid remote disks. */
|
||||
int advise_ssd = 0; /* Use an SSD drive. */
|
||||
int advise_write_load_info = 0; /* Print info about AOF and write load. */
|
||||
int advise_hz = 0; /* Use higher HZ. */
|
||||
int advise_large_objects = 0; /* Deletion of large objects. */
|
||||
int advise_mass_eviction = 0; /* Avoid mass eviction of keys. */
|
||||
int advise_relax_fsync_policy = 0; /* appendfsync always is slow. */
|
||||
int advise_disable_thp = 0; /* AnonHugePages detected. */
|
||||
int advices = 0;
|
||||
|
||||
/* Return ASAP if the latency engine is disabled and it looks like it
|
||||
* was never enabled so far. */
|
||||
if (dictSize(server.latency_events) == 0 &&
|
||||
server.latency_monitor_threshold == 0)
|
||||
{
|
||||
report = sdscat(report,"I'm sorry, Dave, I can't do that. Latency monitoring is disabled in this Redis instance. You may use \"CONFIG SET latency-monitor-threshold <milliseconds>.\" in order to enable it. If we weren't in a deep space mission I'd suggest to take a look at http://redis.io/topics/latency-monitor.\n");
|
||||
return report;
|
||||
}
|
||||
|
||||
/* Show all the events stats and add for each event some event-related
|
||||
* comment depending on the values. */
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
int eventnum = 0;
|
||||
|
||||
di = dictGetSafeIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = dictGetVal(de);
|
||||
struct latencyStats ls;
|
||||
|
||||
if (ts == NULL) continue;
|
||||
eventnum++;
|
||||
if (eventnum == 1) {
|
||||
report = sdscat(report,"Dave, I have observed latency spikes in this Redis instance. You don't mind talking about it, do you Dave?\n\n");
|
||||
}
|
||||
analyzeLatencyForEvent(event,&ls);
|
||||
|
||||
report = sdscatprintf(report,
|
||||
"%d. %s: %d latency spikes (average %Iums, mean deviation %Iums, period %.2f sec). Worst all time event %Iums.", WIN_PORT_FIX /* %lu -> %Iu */
|
||||
eventnum, event,
|
||||
ls.samples,
|
||||
(PORT_ULONG) ls.avg,
|
||||
(PORT_ULONG) ls.mad,
|
||||
(double) ls.period/ls.samples,
|
||||
(PORT_ULONG) ts->max);
|
||||
|
||||
/* Fork */
|
||||
if (!strcasecmp(event,"fork")) {
|
||||
char *fork_quality;
|
||||
if (server.stat_fork_rate < 10) {
|
||||
fork_quality = "terrible";
|
||||
advise_better_vm = 1;
|
||||
advices++;
|
||||
} else if (server.stat_fork_rate < 25) {
|
||||
fork_quality = "poor";
|
||||
advise_better_vm = 1;
|
||||
advices++;
|
||||
} else if (server.stat_fork_rate < 100) {
|
||||
fork_quality = "good";
|
||||
} else {
|
||||
fork_quality = "excellent";
|
||||
}
|
||||
report = sdscatprintf(report,
|
||||
" Fork rate is %.2f GB/sec (%s).", server.stat_fork_rate,
|
||||
fork_quality);
|
||||
}
|
||||
|
||||
/* Potentially commands. */
|
||||
if (!strcasecmp(event,"command")) {
|
||||
if (server.slowlog_log_slower_than == 0) {
|
||||
advise_slowlog_enabled = 1;
|
||||
advices++;
|
||||
} else if (server.slowlog_log_slower_than/1000 >
|
||||
server.latency_monitor_threshold)
|
||||
{
|
||||
advise_slowlog_tuning = 1;
|
||||
advices++;
|
||||
}
|
||||
advise_slowlog_inspect = 1;
|
||||
advise_large_objects = 1;
|
||||
advices += 2;
|
||||
}
|
||||
|
||||
/* fast-command. */
|
||||
if (!strcasecmp(event,"fast-command")) {
|
||||
advise_scheduler = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
/* AOF and I/O. */
|
||||
if (!strcasecmp(event,"aof-write-pending-fsync")) {
|
||||
advise_local_disk = 1;
|
||||
advise_disk_contention = 1;
|
||||
advise_ssd = 1;
|
||||
advise_data_writeback = 1;
|
||||
advices += 4;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-write-active-child")) {
|
||||
advise_no_appendfsync = 1;
|
||||
advise_data_writeback = 1;
|
||||
advise_ssd = 1;
|
||||
advices += 3;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-write-alone")) {
|
||||
advise_local_disk = 1;
|
||||
advise_data_writeback = 1;
|
||||
advise_ssd = 1;
|
||||
advices += 3;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-fsync-always")) {
|
||||
advise_relax_fsync_policy = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-fstat") ||
|
||||
!strcasecmp(event,"rdb-unlik-temp-file")) {
|
||||
advise_disk_contention = 1;
|
||||
advise_local_disk = 1;
|
||||
advices += 2;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-rewrite-diff-write") ||
|
||||
!strcasecmp(event,"aof-rename")) {
|
||||
advise_write_load_info = 1;
|
||||
advise_data_writeback = 1;
|
||||
advise_ssd = 1;
|
||||
advise_local_disk = 1;
|
||||
advices += 4;
|
||||
}
|
||||
|
||||
/* Expire cycle. */
|
||||
if (!strcasecmp(event,"expire-cycle")) {
|
||||
advise_hz = 1;
|
||||
advise_large_objects = 1;
|
||||
advices += 2;
|
||||
}
|
||||
|
||||
/* Eviction cycle. */
|
||||
if (!strcasecmp(event,"eviction-del")) {
|
||||
advise_large_objects = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"eviction-cycle")) {
|
||||
advise_mass_eviction = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
report = sdscatlen(report,"\n",1);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
/* Add non event based advices. */
|
||||
if (THPGetAnonHugePagesSize() > 0) {
|
||||
advise_disable_thp = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
if (eventnum == 0 && advices == 0) {
|
||||
report = sdscat(report,"Dave, no latency spike was observed during the lifetime of this Redis instance, not in the slightest bit. I honestly think you ought to sit down calmly, take a stress pill, and think things over.\n");
|
||||
} else if (eventnum > 0 && advices == 0) {
|
||||
report = sdscat(report,"\nWhile there are latency events logged, I'm not able to suggest any easy fix. Please use the Redis community to get some help, providing this report in your help request.\n");
|
||||
} else {
|
||||
/* Add all the suggestions accumulated so far. */
|
||||
|
||||
/* Better VM. */
|
||||
report = sdscat(report,"\nI have a few advices for you:\n\n");
|
||||
if (advise_better_vm) {
|
||||
report = sdscat(report,"- If you are using a virtual machine, consider upgrading it with a faster one using an hypervisior that provides less latency during fork() calls. Xen is known to have poor fork() performance. Even in the context of the same VM provider, certain kinds of instances can execute fork faster than others.\n");
|
||||
}
|
||||
|
||||
/* Slow log. */
|
||||
if (advise_slowlog_enabled) {
|
||||
report = sdscatprintf(report,"- There are latency issues with potentially slow commands you are using. Try to enable the Slow Log Redis feature using the command 'CONFIG SET slowlog-log-slower-than %llu'. If the Slow log is disabled Redis is not able to log slow commands execution for you.\n", (PORT_ULONGLONG)server.latency_monitor_threshold*1000);
|
||||
}
|
||||
|
||||
if (advise_slowlog_tuning) {
|
||||
report = sdscatprintf(report,"- Your current Slow Log configuration only logs events that are slower than your configured latency monitor threshold. Please use 'CONFIG SET slowlog-log-slower-than %llu'.\n", (PORT_ULONGLONG)server.latency_monitor_threshold*1000);
|
||||
}
|
||||
|
||||
if (advise_slowlog_inspect) {
|
||||
report = sdscat(report,"- Check your Slow Log to understand what are the commands you are running which are too slow to execute. Please check http://redis.io/commands/slowlog for more information.\n");
|
||||
}
|
||||
|
||||
/* Intrinsic latency. */
|
||||
if (advise_scheduler) {
|
||||
report = sdscat(report,"- The system is slow to execute Redis code paths not containing system calls. This usually means the system does not provide Redis CPU time to run for long periods. You should try to:\n"
|
||||
" 1) Lower the system load.\n"
|
||||
" 2) Use a computer / VM just for Redis if you are running other softawre in the same system.\n"
|
||||
" 3) Check if you have a \"noisy neighbour\" problem.\n"
|
||||
" 4) Check with 'redis-cli --intrinsic-latency 100' what is the intrinsic latency in your system.\n"
|
||||
" 5) Check if the problem is allocator-related by recompiling Redis with MALLOC=libc, if you are using Jemalloc. However this may create fragmentation problems.\n");
|
||||
}
|
||||
|
||||
/* AOF / Disk latency. */
|
||||
if (advise_local_disk) {
|
||||
report = sdscat(report,"- It is strongly advised to use local disks for persistence, especially if you are using AOF. Remote disks provided by platform-as-a-service providers are known to be slow.\n");
|
||||
}
|
||||
|
||||
if (advise_ssd) {
|
||||
report = sdscat(report,"- SSD disks are able to reduce fsync latency, and total time needed for snapshotting and AOF log rewriting (resulting in smaller memory usage and smaller final AOF rewrite buffer flushes). With extremely high write load SSD disks can be a good option. However Redis should perform reasonably with high load using normal disks. Use this advice as a last resort.\n");
|
||||
}
|
||||
|
||||
if (advise_data_writeback) {
|
||||
report = sdscat(report,"- Mounting ext3/4 filesystems with data=writeback can provide a performance boost compared to data=ordered, however this mode of operation provides less guarantees, and sometimes it can happen that after a hard crash the AOF file will have an half-written command at the end and will require to be repaired before Redis restarts.\n");
|
||||
}
|
||||
|
||||
if (advise_disk_contention) {
|
||||
report = sdscat(report,"- Try to lower the disk contention. This is often caused by other disk intensive processes running in the same computer (including other Redis instances).\n");
|
||||
}
|
||||
|
||||
if (advise_no_appendfsync) {
|
||||
report = sdscat(report,"- Assuming from the point of view of data safety this is viable in your environment, you could try to enable the 'no-appendfsync-on-rewrite' option, so that fsync will not be performed while there is a child rewriting the AOF file or producing an RDB file (the moment where there is high disk contention).\n");
|
||||
}
|
||||
|
||||
if (advise_relax_fsync_policy && server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
report = sdscat(report,"- Your fsync policy is set to 'always'. It is very hard to get good performances with such a setup, if possible try to relax the fsync policy to 'onesec'.\n");
|
||||
}
|
||||
|
||||
if (advise_write_load_info) {
|
||||
report = sdscat(report,"- Latency during the AOF atomic rename operation or when the final difference is flushed to the AOF file at the end of the rewrite, sometimes is caused by very high write load, causing the AOF buffer to get very large. If possible try to send less commands to accomplish the same work, or use Lua scripts to group multiple operations into a single EVALSHA call.\n");
|
||||
}
|
||||
|
||||
if (advise_hz && server.hz < 100) {
|
||||
report = sdscat(report,"- In order to make the Redis keys expiring process more incremental, try to set the 'hz' configuration parameter to 100 using 'CONFIG SET hz 100'.\n");
|
||||
}
|
||||
|
||||
if (advise_large_objects) {
|
||||
report = sdscat(report,"- Deleting, expiring or evicting (because of maxmemory policy) large objects is a blocking operation. If you have very large objects that are often deleted, expired, or evicted, try to fragment those objects into multiple smaller objects.\n");
|
||||
}
|
||||
|
||||
if (advise_mass_eviction) {
|
||||
report = sdscat(report,"- Sudden changes to the 'maxmemory' setting via 'CONFIG SET', or allocation of large objects via sets or sorted sets intersections, STORE option of SORT, Redis Cluster large keys migrations (RESTORE command), may create sudden memory pressure forcing the server to block trying to evict keys. \n");
|
||||
}
|
||||
|
||||
if (advise_disable_thp) {
|
||||
report = sdscat(report,"- I detected a non zero amount of anonymous huge pages used by your process. This creates very serious latency events in different conditions, especially when Redis is persisting on disk. To disable THP support use the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled', make sure to also add it into /etc/rc.local so that the command will be executed again after a reboot. Note that even if you have already disabled THP, you still need to restart the Redis process to get rid of the huge pages already created.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
/* ---------------------- Latency command implementation -------------------- */
|
||||
|
||||
/* latencyCommand() helper to produce a time-delay reply for all the samples
|
||||
* in memory for the specified time series. */
|
||||
void latencyCommandReplyWithSamples(client *c, struct latencyTimeSeries *ts) {
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
int samples = 0, j;
|
||||
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
int i = (ts->idx + j) % LATENCY_TS_LEN;
|
||||
|
||||
if (ts->samples[i].time == 0) continue;
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyLongLong(c,ts->samples[i].time);
|
||||
addReplyLongLong(c,ts->samples[i].latency);
|
||||
samples++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,samples);
|
||||
}
|
||||
|
||||
/* latencyCommand() helper to produce the reply for the LATEST subcommand,
|
||||
* listing the last latency sample for every event type registered so far. */
|
||||
void latencyCommandReplyWithLatestEvents(client *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
addReplyMultiBulkLen(c,dictSize(server.latency_events));
|
||||
di = dictGetIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = dictGetVal(de);
|
||||
int last = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
|
||||
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyBulkCString(c,event);
|
||||
addReplyLongLong(c,ts->samples[last].time);
|
||||
addReplyLongLong(c,ts->samples[last].latency);
|
||||
addReplyLongLong(c,ts->max);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
#define LATENCY_GRAPH_COLS 80
|
||||
sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
|
||||
int j;
|
||||
struct sequence *seq = createSparklineSequence();
|
||||
sds graph = sdsempty();
|
||||
uint32_t min = 0, max = 0;
|
||||
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
int i = (ts->idx + j) % LATENCY_TS_LEN;
|
||||
int elapsed;
|
||||
char buf[64];
|
||||
|
||||
if (ts->samples[i].time == 0) continue;
|
||||
/* Update min and max. */
|
||||
if (seq->length == 0) {
|
||||
min = max = ts->samples[i].latency;
|
||||
} else {
|
||||
if (ts->samples[i].latency > max) max = ts->samples[i].latency;
|
||||
if (ts->samples[i].latency < min) min = ts->samples[i].latency;
|
||||
}
|
||||
/* Use as label the number of seconds / minutes / hours / days
|
||||
* ago the event happened. */
|
||||
elapsed = (int)(time(NULL) - ts->samples[i].time); WIN_PORT_FIX /* cast (int) */
|
||||
if (elapsed < 60)
|
||||
snprintf(buf,sizeof(buf),"%ds",elapsed);
|
||||
else if (elapsed < 3600)
|
||||
snprintf(buf,sizeof(buf),"%dm",elapsed/60);
|
||||
else if (elapsed < 3600*24)
|
||||
snprintf(buf,sizeof(buf),"%dh",elapsed/3600);
|
||||
else
|
||||
snprintf(buf,sizeof(buf),"%dd",elapsed/(3600*24));
|
||||
sparklineSequenceAddSample(seq,ts->samples[i].latency,buf);
|
||||
}
|
||||
|
||||
graph = sdscatprintf(graph,
|
||||
"%s - high %Iu ms, low %Iu ms (all time high %Iu ms)\n", event, WIN_PORT_FIX /* %ld -> %Id, %lu -> %Iu */
|
||||
(PORT_ULONG) max, (PORT_ULONG) min, (PORT_ULONG) ts->max);
|
||||
for (j = 0; j < LATENCY_GRAPH_COLS; j++)
|
||||
graph = sdscatlen(graph,"-",1);
|
||||
graph = sdscatlen(graph,"\n",1);
|
||||
graph = sparklineRender(graph,seq,LATENCY_GRAPH_COLS,4,SPARKLINE_FILL);
|
||||
freeSparklineSequence(seq);
|
||||
return graph;
|
||||
}
|
||||
|
||||
/* LATENCY command implementations.
|
||||
*
|
||||
* LATENCY SAMPLES: return time-latency samples for the specified event.
|
||||
* LATENCY LATEST: return the latest latency for all the events classes.
|
||||
* LATENCY DOCTOR: returns an human readable analysis of instance latency.
|
||||
* LATENCY GRAPH: provide an ASCII graph of the latency of the specified event.
|
||||
*/
|
||||
void latencyCommand(client *c) {
|
||||
struct latencyTimeSeries *ts;
|
||||
|
||||
if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) {
|
||||
/* LATENCY HISTORY <event> */
|
||||
ts = dictFetchValue(server.latency_events,c->argv[2]->ptr);
|
||||
if (ts == NULL) {
|
||||
addReplyMultiBulkLen(c,0);
|
||||
} else {
|
||||
latencyCommandReplyWithSamples(c,ts);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"graph") && c->argc == 3) {
|
||||
/* LATENCY GRAPH <event> */
|
||||
sds graph;
|
||||
dictEntry *de;
|
||||
char *event;
|
||||
|
||||
de = dictFind(server.latency_events,c->argv[2]->ptr);
|
||||
if (de == NULL) goto nodataerr;
|
||||
ts = dictGetVal(de);
|
||||
event = dictGetKey(de);
|
||||
|
||||
graph = latencyCommandGenSparkeline(event,ts);
|
||||
addReplyBulkCString(c,graph);
|
||||
sdsfree(graph);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
|
||||
/* LATENCY LATEST */
|
||||
latencyCommandReplyWithLatestEvents(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
|
||||
/* LATENCY DOCTOR */
|
||||
sds report = createLatencyReport();
|
||||
|
||||
addReplyBulkCBuffer(c,report,sdslen(report));
|
||||
sdsfree(report);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
|
||||
/* LATENCY RESET */
|
||||
if (c->argc == 2) {
|
||||
addReplyLongLong(c,latencyResetEvent(NULL));
|
||||
} else {
|
||||
int j, resets = 0;
|
||||
|
||||
for (j = 2; j < c->argc; j++)
|
||||
resets += latencyResetEvent(c->argv[j]->ptr);
|
||||
addReplyLongLong(c,resets);
|
||||
}
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
}
|
||||
return;
|
||||
|
||||
nodataerr:
|
||||
/* Common error when the user asks for an event we have no latency
|
||||
* information about. */
|
||||
addReplyErrorFormat(c,
|
||||
"No samples available for event '%s'", (char*) c->argv[2]->ptr);
|
||||
}
|
||||
|
||||
+9
-3
@@ -68,9 +68,15 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
robj *val = dictGetVal(de);
|
||||
size_t free_effort = lazyfreeGetFreeEffort(val);
|
||||
|
||||
/* If releasing the object is too much work, let's put it into the
|
||||
* lazy free list. */
|
||||
if (free_effort > LAZYFREE_THRESHOLD) {
|
||||
/* If releasing the object is too much work, do it in the background
|
||||
* by adding the object to the lazy free list.
|
||||
* Note that if the object is shared, to reclaim it now it is not
|
||||
* possible. This rarely happens, however sometimes the implementation
|
||||
* of parts of the Redis core may call incrRefCount() to protect
|
||||
* objects, and then call dbDelete(). In this case we'll fall
|
||||
* through and reach the dictFreeUnlinkedEntry() call, that will be
|
||||
* equivalent to just calling decrRefCount(). */
|
||||
if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
|
||||
dictSetVal(db->dict,de,NULL);
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
#include "server.h"
|
||||
#include "bio.h"
|
||||
#include "atomicvar.h"
|
||||
#include "cluster.h"
|
||||
|
||||
static size_t lazyfree_objects = 0;
|
||||
#ifndef _WIN32
|
||||
pthread_mutex_t lazyfree_objects_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
#else
|
||||
pthread_mutex_t lazyfree_objects_mutex;
|
||||
#endif
|
||||
|
||||
/* Return the number of currently pending objects to free. */
|
||||
size_t lazyfreeGetPendingObjectsCount(void) {
|
||||
size_t aux;
|
||||
atomicGet(lazyfree_objects,aux);
|
||||
return aux;
|
||||
}
|
||||
|
||||
/* Return the amount of work needed in order to free an object.
|
||||
* The return value is not always the actual number of allocations the
|
||||
* object is compoesd of, but a number proportional to it.
|
||||
*
|
||||
* For strings the function always returns 1.
|
||||
*
|
||||
* For aggregated objects represented by hash tables or other data structures
|
||||
* the function just returns the number of elements the object is composed of.
|
||||
*
|
||||
* Objects composed of single allocations are always reported as having a
|
||||
* single item even if they are actaully logical composed of multiple
|
||||
* elements.
|
||||
*
|
||||
* For lists the funciton returns the number of elements in the quicklist
|
||||
* representing the list. */
|
||||
size_t lazyfreeGetFreeEffort(robj *obj) {
|
||||
if (obj->type == OBJ_LIST) {
|
||||
quicklist *ql = obj->ptr;
|
||||
return ql->len;
|
||||
} else if (obj->type == OBJ_SET && obj->encoding == OBJ_ENCODING_HT) {
|
||||
dict *ht = obj->ptr;
|
||||
return dictSize(ht);
|
||||
} else if (obj->type == OBJ_ZSET && obj->encoding == OBJ_ENCODING_SKIPLIST){
|
||||
zset *zs = obj->ptr;
|
||||
return zs->zsl->length;
|
||||
} else if (obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) {
|
||||
dict *ht = obj->ptr;
|
||||
return dictSize(ht);
|
||||
} else {
|
||||
return 1; /* Everything else is a single allocation. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete a key, value, and associated expiration entry if any, from the DB.
|
||||
* If there are enough allocations to free the value object may be put into
|
||||
* a lazy free list instead of being freed synchronously. The lazy free list
|
||||
* will be reclaimed in a different bio.c thread. */
|
||||
#define LAZYFREE_THRESHOLD 64
|
||||
int dbAsyncDelete(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 the value is composed of a few allocations, to free in a lazy way
|
||||
* is actually just slower... So under a certain limit we just free
|
||||
* the object synchronously. */
|
||||
dictEntry *de = dictUnlink(db->dict,key->ptr);
|
||||
if (de) {
|
||||
robj *val = dictGetVal(de);
|
||||
size_t free_effort = lazyfreeGetFreeEffort(val);
|
||||
|
||||
/* If releasing the object is too much work, let's put it into the
|
||||
* lazy free list. */
|
||||
if (free_effort > LAZYFREE_THRESHOLD) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
|
||||
dictSetVal(db->dict,de,NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Release the key-val pair, or just the key if we set the val
|
||||
* field to NULL in order to lazy free it later. */
|
||||
if (de) {
|
||||
dictFreeUnlinkedEntry(db->dict,de);
|
||||
if (server.cluster_enabled) slotToKeyDel(key);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Empty a Redis DB asynchronously. What the function does actually is to
|
||||
* create a new empty set of hash tables and scheduling the old ones for
|
||||
* lazy freeing. */
|
||||
void emptyDbAsync(redisDb *db) {
|
||||
dict *oldht1 = db->dict, *oldht2 = db->expires;
|
||||
db->dict = dictCreate(&dbDictType,NULL);
|
||||
db->expires = dictCreate(&keyptrDictType,NULL);
|
||||
atomicIncr(lazyfree_objects,dictSize(oldht1));
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,oldht2);
|
||||
}
|
||||
|
||||
/* Empty the slots-keys map of Redis CLuster by creating a new empty one
|
||||
* and scheduiling the old for lazy freeing. */
|
||||
void slotToKeyFlushAsync(void) {
|
||||
rax *old = server.cluster->slots_to_keys;
|
||||
|
||||
server.cluster->slots_to_keys = raxNew();
|
||||
memset(server.cluster->slots_keys_count,0,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
atomicIncr(lazyfree_objects,old->numele);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,NULL,old);
|
||||
}
|
||||
|
||||
/* Release objects from the lazyfree thread. It's just decrRefCount()
|
||||
* updating the count of objects to release. */
|
||||
void lazyfreeFreeObjectFromBioThread(robj *o) {
|
||||
decrRefCount(o);
|
||||
atomicDecr(lazyfree_objects,1);
|
||||
}
|
||||
|
||||
/* Release a database from the lazyfree thread. The 'db' pointer is the
|
||||
* database which was substitutied with a fresh one in the main thread
|
||||
* when the database was logically deleted. 'sl' is a skiplist used by
|
||||
* Redis Cluster in order to take the hash slots -> keys mapping. This
|
||||
* may be NULL if Redis Cluster is disabled. */
|
||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2) {
|
||||
size_t numkeys = dictSize(ht1);
|
||||
dictRelease(ht1);
|
||||
dictRelease(ht2);
|
||||
atomicDecr(lazyfree_objects,numkeys);
|
||||
}
|
||||
|
||||
/* Release the skiplist mapping Redis Cluster keys to slots in the
|
||||
* lazyfree thread. */
|
||||
void lazyfreeFreeSlotsMapFromBioThread(rax *rt) {
|
||||
size_t len = rt->numele;
|
||||
raxFree(rt);
|
||||
atomicDecr(lazyfree_objects,len);
|
||||
}
|
||||
+287
-33
@@ -229,6 +229,28 @@ static pthread_mutex_t moduleGIL = PTHREAD_MUTEX_INITIALIZER;
|
||||
#else
|
||||
pthread_mutex_t moduleGIL;
|
||||
#endif
|
||||
/* Function pointer type for keyspace event notification subscriptions from modules. */
|
||||
typedef int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||
/* Keyspace notification subscriber information.
|
||||
* See RM_SubscribeToKeyspaceEvents() for more information. */
|
||||
typedef struct RedisModuleKeyspaceSubscriber {
|
||||
/* The module subscribed to the event */
|
||||
RedisModule *module;
|
||||
/* Notification callback in the module*/
|
||||
RedisModuleNotificationFunc notify_callback;
|
||||
/* A bit mask of the events the module is interested in */
|
||||
int event_mask;
|
||||
/* Active flag set on entry, to avoid reentrant subscribers
|
||||
* calling themselves */
|
||||
int active;
|
||||
} RedisModuleKeyspaceSubscriber;
|
||||
|
||||
/* The module keyspace notification subscribers list */
|
||||
static list *moduleKeyspaceSubscribers;
|
||||
|
||||
/* Static client recycled for all notification clients, to avoid allocating
|
||||
* per round. */
|
||||
static client *moduleKeyspaceSubscribersClient;
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Prototypes
|
||||
@@ -456,9 +478,7 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
|
||||
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
|
||||
client *c = ctx->client;
|
||||
|
||||
/* We don't want any automatic propagation here since in modules we handle
|
||||
* replication / AOF propagation in explicit ways. */
|
||||
preventCommandPropagation(c);
|
||||
if (c->flags & CLIENT_LUA) return;
|
||||
|
||||
/* Handle the replication of the final EXEC, since whatever a command
|
||||
* emits is always wrappered around MULTI/EXEC. */
|
||||
@@ -629,7 +649,7 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
|
||||
sds cmdname = sdsnew(name);
|
||||
|
||||
/* Check if the command name is busy. */
|
||||
if (lookupCommand((char*)name) != NULL) {
|
||||
if (lookupCommand(cmdname) != NULL) {
|
||||
sdsfree(cmdname);
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
@@ -676,6 +696,15 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
|
||||
ctx->module = module;
|
||||
}
|
||||
|
||||
/* Return non-zero if the module name is busy.
|
||||
* Otherwise zero is returned. */
|
||||
int RM_IsModuleNameBusy(const char *name) {
|
||||
sds modulename = sdsnew(name);
|
||||
dictEntry *de = dictFind(modules,modulename);
|
||||
sdsfree(modulename);
|
||||
return de != NULL;
|
||||
}
|
||||
|
||||
/* Return the current UNIX time in milliseconds. */
|
||||
PORT_LONGLONG RM_Milliseconds(void) {
|
||||
return mstime();
|
||||
@@ -800,7 +829,7 @@ RedisModuleString *RM_CreateStringPrintf(RedisModuleCtx *ctx, const char *fmt, .
|
||||
}
|
||||
|
||||
|
||||
/* Like RedisModule_CreatString(), but creates a string starting from a PORT_LONGLONG
|
||||
/* Like RedisModule_CreatString(), but creates a string starting from a long long
|
||||
* integer instead of taking a buffer and its length.
|
||||
*
|
||||
* The returned string must be released with RedisModule_FreeString() or by
|
||||
@@ -1178,6 +1207,9 @@ int RM_ReplyWithDouble(RedisModuleCtx *ctx, double d) {
|
||||
* in the context of a command execution. EXEC will be handled by the
|
||||
* RedisModuleCommandDispatcher() function. */
|
||||
void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
||||
/* Skip this if client explicitly wrap the command with MULTI, or if
|
||||
* the module command was called by a script. */
|
||||
if (ctx->client->flags & (CLIENT_MULTI|CLIENT_LUA)) return;
|
||||
/* If we already emitted MULTI return ASAP. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
|
||||
/* If this is a thread safe context, we do not want to wrap commands
|
||||
@@ -1230,6 +1262,7 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...)
|
||||
/* Release the argv. */
|
||||
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
|
||||
zfree(argv);
|
||||
server.dirty++;
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -1248,6 +1281,7 @@ int RM_ReplicateVerbatim(RedisModuleCtx *ctx) {
|
||||
alsoPropagate(ctx->client->cmd,ctx->client->db->id,
|
||||
ctx->client->argv,ctx->client->argc,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
server.dirty++;
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -1276,6 +1310,74 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
|
||||
return ctx->client->db->id;
|
||||
}
|
||||
|
||||
|
||||
/* Return the current context's flags. The flags provide information on the
|
||||
* current request context (whether the client is a Lua script or in a MULTI),
|
||||
* and about the Redis instance in general, i.e replication and persistence.
|
||||
*
|
||||
* The available flags are:
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_LUA: The command is running in a Lua script
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_MULTI: The command is running inside a transaction
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_MASTER: The Redis instance is a master
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a slave
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_READONLY: The Redis instance is read-only
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_CLUSTER: The Redis instance is in cluster mode
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_AOF: The Redis instance has AOF enabled
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_RDB: The instance has RDB enabled
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_MAXMEMORY: The instance has Maxmemory set
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_EVICT: Maxmemory is set and has an eviction
|
||||
* policy that may delete keys
|
||||
*/
|
||||
int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
|
||||
int flags = 0;
|
||||
/* Client specific flags */
|
||||
if (ctx->client) {
|
||||
if (ctx->client->flags & CLIENT_LUA)
|
||||
flags |= REDISMODULE_CTX_FLAGS_LUA;
|
||||
if (ctx->client->flags & CLIENT_MULTI)
|
||||
flags |= REDISMODULE_CTX_FLAGS_MULTI;
|
||||
}
|
||||
|
||||
if (server.cluster_enabled)
|
||||
flags |= REDISMODULE_CTX_FLAGS_CLUSTER;
|
||||
|
||||
/* Maxmemory and eviction policy */
|
||||
if (server.maxmemory > 0) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY;
|
||||
|
||||
if (server.maxmemory_policy != MAXMEMORY_NO_EVICTION)
|
||||
flags |= REDISMODULE_CTX_FLAGS_EVICT;
|
||||
}
|
||||
|
||||
/* Persistence flags */
|
||||
if (server.aof_state != AOF_OFF)
|
||||
flags |= REDISMODULE_CTX_FLAGS_AOF;
|
||||
if (server.saveparamslen > 0)
|
||||
flags |= REDISMODULE_CTX_FLAGS_RDB;
|
||||
|
||||
/* Replication flags */
|
||||
if (server.masterhost == NULL) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_MASTER;
|
||||
} else {
|
||||
flags |= REDISMODULE_CTX_FLAGS_SLAVE;
|
||||
if (server.repl_slave_ro)
|
||||
flags |= REDISMODULE_CTX_FLAGS_READONLY;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/* Change the currently selected DB. Returns an error if the id
|
||||
* is out of range.
|
||||
*
|
||||
@@ -1390,6 +1492,20 @@ int RM_DeleteKey(RedisModuleKey *key) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* If the key is open for writing, unlink it (that is delete it in a
|
||||
* non-blocking way, not reclaiming memory immediately) and setup the key to
|
||||
* accept new writes as an empty key (that will be created on demand).
|
||||
* On success REDISMODULE_OK is returned. If the key is not open for
|
||||
* writing REDISMODULE_ERR is returned. */
|
||||
int RM_UnlinkKey(RedisModuleKey *key) {
|
||||
if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
|
||||
if (key->value) {
|
||||
dbAsyncDelete(key->db,key->key);
|
||||
key->value = NULL;
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Return the key expire value, as milliseconds of remaining TTL.
|
||||
* If no TTL is associated with the key or if the key is empty,
|
||||
* REDISMODULE_NO_EXPIRE is returned. */
|
||||
@@ -2958,7 +3074,7 @@ int64_t RM_LoadSigned(RedisModuleIO *io) {
|
||||
void RM_SaveString(RedisModuleIO *io, RedisModuleString *s) {
|
||||
if (io->error) return;
|
||||
/* Save opcode. */
|
||||
int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING);
|
||||
ssize_t retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
/* Save value. */
|
||||
@@ -2976,7 +3092,7 @@ saveerr:
|
||||
void RM_SaveStringBuffer(RedisModuleIO *io, const char *str, size_t len) {
|
||||
if (io->error) return;
|
||||
/* Save opcode. */
|
||||
int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING);
|
||||
ssize_t retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
/* Save value. */
|
||||
@@ -3147,7 +3263,7 @@ void RM_DigestAddStringBuffer(RedisModuleDigest *md, unsigned char *ele, size_t
|
||||
mixDigest(md->o,ele,len);
|
||||
}
|
||||
|
||||
/* Like `RedisModule_DigestAddStringBuffer()` but takes a PORT_LONGLONG as input
|
||||
/* Like `RedisModule_DigestAddStringBuffer()` but takes a long long as input
|
||||
* that gets converted into a string before adding it to the digest. */
|
||||
void RM_DigestAddLongLong(RedisModuleDigest *md, PORT_LONGLONG ll) {
|
||||
char buf[LONG_STR_SIZE];
|
||||
@@ -3347,14 +3463,16 @@ void unblockClientFromModule(client *c) {
|
||||
RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), PORT_LONGLONG timeout_ms) {
|
||||
client *c = ctx->client;
|
||||
int islua = c->flags & CLIENT_LUA;
|
||||
int ismulti = c->flags & CLIENT_MULTI;
|
||||
|
||||
c->bpop.module_blocked_handle = zmalloc(sizeof(RedisModuleBlockedClient));
|
||||
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
|
||||
|
||||
/* We need to handle the invalid operation of calling modules blocking
|
||||
* commands from Lua. We actually create an already aborted (client set to
|
||||
* NULL) blocked client handle, and actually reply to Lua with an error. */
|
||||
bc->client = islua ? NULL : c;
|
||||
* commands from Lua or MULTI. We actually create an already aborted
|
||||
* (client set to NULL) blocked client handle, and actually reply with
|
||||
* an error. */
|
||||
bc->client = (islua || ismulti) ? NULL : c;
|
||||
bc->module = ctx->module;
|
||||
bc->reply_callback = reply_callback;
|
||||
bc->timeout_callback = timeout_callback;
|
||||
@@ -3365,9 +3483,11 @@ RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc
|
||||
bc->dbid = c->db->id;
|
||||
c->bpop.timeout = timeout_ms ? (mstime()+timeout_ms) : 0;
|
||||
|
||||
if (islua) {
|
||||
if (islua || ismulti) {
|
||||
c->bpop.module_blocked_handle = NULL;
|
||||
addReplyError(c,"Blocking module command called from Lua script");
|
||||
addReplyError(c, islua ?
|
||||
"Blocking module command called from Lua script" :
|
||||
"Blocking module command called from transaction");
|
||||
} else {
|
||||
blockClient(c,BLOCKED_MODULE);
|
||||
}
|
||||
@@ -3585,6 +3705,120 @@ void moduleReleaseGIL(void) {
|
||||
pthread_mutex_unlock(&moduleGIL);
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Module Keyspace Notifications API
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Subscribe to keyspace notifications. This is a low-level version of the
|
||||
* keyspace-notifications API. A module cand register callbacks to be notified
|
||||
* when keyspce events occur.
|
||||
*
|
||||
* Notification events are filtered by their type (string events, set events,
|
||||
* etc), and the subsriber callback receives only events that match a specific
|
||||
* mask of event types.
|
||||
*
|
||||
* When subscribing to notifications with RedisModule_SubscribeToKeyspaceEvents
|
||||
* the module must provide an event type-mask, denoting the events the subscriber
|
||||
* is interested in. This can be an ORed mask of any of the following flags:
|
||||
*
|
||||
* - REDISMODULE_NOTIFY_GENERIC: Generic commands like DEL, EXPIRE, RENAME
|
||||
* - REDISMODULE_NOTIFY_STRING: String events
|
||||
* - REDISMODULE_NOTIFY_LIST: List events
|
||||
* - REDISMODULE_NOTIFY_SET: Set events
|
||||
* - REDISMODULE_NOTIFY_HASH: Hash events
|
||||
* - REDISMODULE_NOTIFY_ZSET: Sorted Set events
|
||||
* - REDISMODULE_NOTIFY_EXPIRED: Expiration events
|
||||
* - REDISMODULE_NOTIFY_EVICTED: Eviction events
|
||||
* - REDISMODULE_NOTIFY_STREAM: Stream events
|
||||
* - REDISMODULE_NOTIFY_ALL: All events
|
||||
*
|
||||
* We do not distinguish between key events and keyspace events, and it is up
|
||||
* to the module to filter the actions taken based on the key.
|
||||
*
|
||||
* The subscriber signature is:
|
||||
*
|
||||
* int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type,
|
||||
* const char *event,
|
||||
* RedisModuleString *key);
|
||||
*
|
||||
* `type` is the event type bit, that must match the mask given at registration
|
||||
* time. The event string is the actual command being executed, and key is the
|
||||
* relevant Redis key.
|
||||
*
|
||||
* Notification callback gets executed with a redis context that can not be
|
||||
* used to send anything to the client, and has the db number where the event
|
||||
* occured as its selected db number.
|
||||
*
|
||||
* Notice that it is not necessary to enable norifications in redis.conf for
|
||||
* module notifications to work.
|
||||
*
|
||||
* Warning: the notification callbacks are performed in a synchronous manner,
|
||||
* so notification callbacks must to be fast, or they would slow Redis down.
|
||||
* If you need to take long actions, use threads to offload them.
|
||||
*
|
||||
* See https://redis.io/topics/notifications for more information.
|
||||
*/
|
||||
int RM_SubscribeToKeyspaceEvents(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc callback) {
|
||||
RedisModuleKeyspaceSubscriber *sub = zmalloc(sizeof(*sub));
|
||||
sub->module = ctx->module;
|
||||
sub->event_mask = types;
|
||||
sub->notify_callback = callback;
|
||||
sub->active = 0;
|
||||
|
||||
listAddNodeTail(moduleKeyspaceSubscribers, sub);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Dispatcher for keyspace notifications to module subscriber functions.
|
||||
* This gets called only if at least one module requested to be notified on
|
||||
* keyspace notifications */
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) {
|
||||
/* Don't do anything if there aren't any subscribers */
|
||||
if (listLength(moduleKeyspaceSubscribers) == 0) return;
|
||||
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(moduleKeyspaceSubscribers,&li);
|
||||
|
||||
/* Remove irrelevant flags from the type mask */
|
||||
type &= ~(NOTIFY_KEYEVENT | NOTIFY_KEYSPACE);
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModuleKeyspaceSubscriber *sub = ln->value;
|
||||
/* Only notify subscribers on events matching they registration,
|
||||
* and avoid subscribers triggering themselves */
|
||||
if ((sub->event_mask & type) && sub->active == 0) {
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
ctx.module = sub->module;
|
||||
ctx.client = moduleKeyspaceSubscribersClient;
|
||||
selectDb(ctx.client, dbid);
|
||||
|
||||
/* mark the handler as activer to avoid reentrant loops.
|
||||
* If the subscriber performs an action triggering itself,
|
||||
* it will not be notified about it. */
|
||||
sub->active = 1;
|
||||
sub->notify_callback(&ctx, type, event, key);
|
||||
sub->active = 0;
|
||||
moduleFreeContext(&ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Unsubscribe any notification subscirbers this module has upon unloading */
|
||||
void moduleUnsubscribeNotifications(RedisModule *module) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(moduleKeyspaceSubscribers,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModuleKeyspaceSubscriber *sub = ln->value;
|
||||
if (sub->module == module) {
|
||||
listDelNode(moduleKeyspaceSubscribers, ln);
|
||||
zfree(sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules API internals
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -3625,6 +3859,12 @@ void moduleInitModulesSystem(void) {
|
||||
|
||||
server.loadmodule_queue = listCreate();
|
||||
modules = dictCreate(&modulesDictType,NULL);
|
||||
|
||||
/* Set up the keyspace notification susbscriber list and static client */
|
||||
moduleKeyspaceSubscribers = listCreate();
|
||||
moduleKeyspaceSubscribersClient = createClient(-1);
|
||||
moduleKeyspaceSubscribersClient->flags |= CLIENT_MODULE;
|
||||
|
||||
moduleRegisterCoreAPI();
|
||||
if (pipe(server.module_blocked_pipe) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
@@ -3675,6 +3915,28 @@ void moduleFreeModuleStructure(struct RedisModule *module) {
|
||||
zfree(module);
|
||||
}
|
||||
|
||||
void moduleUnregisterCommands(struct RedisModule *module) {
|
||||
/* Unregister all the commands registered by this module. */
|
||||
dictIterator *di = dictGetSafeIterator(server.commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = dictGetVal(de);
|
||||
if (cmd->proc == RedisModuleCommandDispatcher) {
|
||||
RedisModuleCommandProxy *cp =
|
||||
(void*)(unsigned long)cmd->getkeys_proc;
|
||||
sds cmdname = cp->rediscmd->name;
|
||||
if (cp->module == module) {
|
||||
dictDelete(server.commands,cmdname);
|
||||
dictDelete(server.orig_commands,cmdname);
|
||||
sdsfree(cmdname);
|
||||
zfree(cp->rediscmd);
|
||||
zfree(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* Load a module and initialize it. On success C_OK is returned, otherwise
|
||||
* C_ERR is returned. */
|
||||
int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
@@ -3695,7 +3957,10 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
return C_ERR;
|
||||
}
|
||||
if (onload((void*)&ctx,module_argv,module_argc) == REDISMODULE_ERR) {
|
||||
if (ctx.module) moduleFreeModuleStructure(ctx.module);
|
||||
if (ctx.module) {
|
||||
moduleUnregisterCommands(ctx.module);
|
||||
moduleFreeModuleStructure(ctx.module);
|
||||
}
|
||||
dlclose(handle);
|
||||
serverLog(LL_WARNING,
|
||||
"Module %s initialization failed. Module not loaded",path);
|
||||
@@ -3729,25 +3994,10 @@ int moduleUnload(sds name) {
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Unregister all the commands registered by this module. */
|
||||
dictIterator *di = dictGetSafeIterator(server.commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = dictGetVal(de);
|
||||
if (cmd->proc == RedisModuleCommandDispatcher) {
|
||||
RedisModuleCommandProxy *cp =
|
||||
(void*)(PORT_ULONG)cmd->getkeys_proc;
|
||||
sds cmdname = cp->rediscmd->name;
|
||||
if (cp->module == module) {
|
||||
dictDelete(server.commands,cmdname);
|
||||
dictDelete(server.orig_commands,cmdname);
|
||||
sdsfree(cmdname);
|
||||
zfree(cp->rediscmd);
|
||||
zfree(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
moduleUnregisterCommands(module);
|
||||
|
||||
/* Remvoe any noification subscribers this module might have */
|
||||
moduleUnsubscribeNotifications(module);
|
||||
|
||||
/* Unregister all the hooks. TODO: Yet no hooks support here. */
|
||||
|
||||
@@ -3842,6 +4092,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(Strdup);
|
||||
REGISTER_API(CreateCommand);
|
||||
REGISTER_API(SetModuleAttribs);
|
||||
REGISTER_API(IsModuleNameBusy);
|
||||
REGISTER_API(WrongArity);
|
||||
REGISTER_API(ReplyWithLongLong);
|
||||
REGISTER_API(ReplyWithError);
|
||||
@@ -3882,6 +4133,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(Replicate);
|
||||
REGISTER_API(ReplicateVerbatim);
|
||||
REGISTER_API(DeleteKey);
|
||||
REGISTER_API(UnlinkKey);
|
||||
REGISTER_API(StringSet);
|
||||
REGISTER_API(StringDMA);
|
||||
REGISTER_API(StringTruncate);
|
||||
@@ -3905,6 +4157,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(IsKeysPositionRequest);
|
||||
REGISTER_API(KeyAtPos);
|
||||
REGISTER_API(GetClientId);
|
||||
REGISTER_API(GetContextFlags);
|
||||
REGISTER_API(PoolAlloc);
|
||||
REGISTER_API(CreateDataType);
|
||||
REGISTER_API(ModuleTypeSetValue);
|
||||
@@ -3943,4 +4196,5 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(DigestAddStringBuffer);
|
||||
REGISTER_API(DigestAddLongLong);
|
||||
REGISTER_API(DigestEndSequence);
|
||||
REGISTER_API(SubscribeToKeyspaceEvents);
|
||||
}
|
||||
|
||||
+4198
File diff suppressed because it is too large
Load Diff
+39
-15
@@ -395,6 +395,12 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
|
||||
addReplyString(c, "-ERR ", 5);
|
||||
addReplyString(c, s, len);
|
||||
addReplyString(c, "\r\n", 2);
|
||||
if (c->flags & CLIENT_MASTER) {
|
||||
char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
|
||||
serverLog(LL_WARNING,"== CRITICAL == This slave is sending an error "
|
||||
"to its master: '%s' after processing the command "
|
||||
"'%s'", s, cmdname);
|
||||
}
|
||||
}
|
||||
|
||||
void addReplyError(client *c, const char *err) {
|
||||
@@ -1018,10 +1024,15 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
* scenario think about 'KEYS *' against the loopback interface).
|
||||
*
|
||||
* However if we are over the maxmemory limit we ignore that and
|
||||
* just deliver as much data as it is possible to deliver. */
|
||||
* just deliver as much data as it is possible to deliver.
|
||||
*
|
||||
* Moreover, we also send as much as possible if the client is
|
||||
* a slave (otherwise, on high-speed traffic, the replication
|
||||
* buffer will grow indefinitely) */
|
||||
if (totwritten > NET_MAX_WRITES_PER_EVENT &&
|
||||
(server.maxmemory == 0 ||
|
||||
zmalloc_used_memory() < server.maxmemory)) break;
|
||||
zmalloc_used_memory() < server.maxmemory) &&
|
||||
!(c->flags & CLIENT_SLAVE)) break;
|
||||
}
|
||||
server.stat_net_output_bytes += totwritten;
|
||||
if (nwritten == -1) {
|
||||
@@ -1105,15 +1116,27 @@ int handleClientsWithPendingWrites(void) {
|
||||
/* Try to write buffers to the client socket. */
|
||||
if (writeToClient(c->fd, c, 0) == C_ERR) continue;
|
||||
|
||||
/* If there is nothing left, do nothing. Otherwise install
|
||||
* the write handler. */
|
||||
if (clientHasPendingReplies(c) &&
|
||||
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
|
||||
/* If after the synchronous writes above we still have data to
|
||||
* output to the client, we need to install the writable handler. */
|
||||
if (clientHasPendingReplies(c)) {
|
||||
int ae_flags = AE_WRITABLE;
|
||||
/* For the fsync=always policy, we want that a given FD is never
|
||||
* served for reading and writing in the same event loop iteration,
|
||||
* so that in the middle of receiving the query, and serving it
|
||||
* to the client, we'll call beforeSleep() that will do the
|
||||
* actual fsync of AOF to disk. AE_BARRIER ensures that. */
|
||||
if (server.aof_state == AOF_ON &&
|
||||
server.aof_fsync == AOF_FSYNC_ALWAYS)
|
||||
{
|
||||
ae_flags |= AE_BARRIER;
|
||||
}
|
||||
if (aeCreateFileEvent(server.el, c->fd, ae_flags,
|
||||
sendReplyToClient, c) == AE_ERR)
|
||||
{
|
||||
freeClientAsync(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
@@ -1213,7 +1236,7 @@ int processInlineBuffer(client *c) {
|
||||
/* Helper function. Trims query buffer to make the function that processes
|
||||
* multi bulk requests idempotent. */
|
||||
#define PROTO_DUMP_LEN 128
|
||||
static void setProtocolError(const char *errstr, client *c, int pos) {
|
||||
static void setProtocolError(const char *errstr, client *c, PORT_LONG pos) {
|
||||
if (server.verbosity <= LL_VERBOSE) {
|
||||
sds client = catClientInfoString(sdsempty(), c);
|
||||
|
||||
@@ -1255,7 +1278,8 @@ static void setProtocolError(const char *errstr, client *c, int pos) {
|
||||
* to be '*'. Otherwise for inline commands processInlineBuffer() is called. */
|
||||
int processMultibulkBuffer(client *c) {
|
||||
char *newline = NULL;
|
||||
int pos = 0, ok;
|
||||
PORT_LONG pos = 0;
|
||||
int ok;
|
||||
PORT_LONGLONG ll;
|
||||
|
||||
if (c->multibulklen == 0) {
|
||||
@@ -1286,7 +1310,7 @@ int processMultibulkBuffer(client *c) {
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
pos = (int) ((newline - c->querybuf) + 2); WIN_PORT_FIX /* cast (int) */
|
||||
pos = (PORT_LONG) ((newline - c->querybuf) + 2); WIN_PORT_FIX /* cast (int) */
|
||||
if (ll <= 0) {
|
||||
sdsrange(c->querybuf, pos, -1);
|
||||
return C_OK;
|
||||
@@ -1327,13 +1351,13 @@ int processMultibulkBuffer(client *c) {
|
||||
}
|
||||
|
||||
ok = string2ll(c->querybuf + pos + 1, newline - (c->querybuf + pos + 1), &ll);
|
||||
if (!ok || ll < 0 || ll > 512 * 1024 * 1024) {
|
||||
if (!ok || ll < 0 || ll > server.proto_max_bulk_len) {
|
||||
addReplyError(c, "Protocol error: invalid bulk length");
|
||||
setProtocolError("invalid bulk length", c, pos);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
pos += (int) (newline - (c->querybuf + pos) + 2); WIN_PORT_FIX /* cast (int) */
|
||||
pos += (PORT_LONG) (newline - (c->querybuf + pos) + 2); WIN_PORT_FIX /* cast (int) */
|
||||
if (ll >= PROTO_MBULK_BIG_ARG) {
|
||||
size_t qblen;
|
||||
|
||||
@@ -1353,7 +1377,7 @@ int processMultibulkBuffer(client *c) {
|
||||
}
|
||||
|
||||
/* Read bulk argument */
|
||||
if (sdslen(c->querybuf) - pos < (unsigned) (c->bulklen + 2)) {
|
||||
if (sdslen(c->querybuf) - pos < (size_t) (c->bulklen + 2)) {
|
||||
/* Not enough data (+2 == trailing \r\n) */
|
||||
break;
|
||||
}
|
||||
@@ -1363,7 +1387,7 @@ int processMultibulkBuffer(client *c) {
|
||||
* just use the current sds string. */
|
||||
if (pos == 0 &&
|
||||
c->bulklen >= PROTO_MBULK_BIG_ARG &&
|
||||
(signed) sdslen(c->querybuf) == c->bulklen + 2)
|
||||
sdslen(c->querybuf) == (size_t)c->bulklen + 2)
|
||||
{
|
||||
c->argv[c->argc++] = createObject(OBJ_STRING, c->querybuf);
|
||||
sdsIncrLen(c->querybuf, -2); /* remove CRLF */
|
||||
@@ -1376,7 +1400,7 @@ int processMultibulkBuffer(client *c) {
|
||||
else {
|
||||
c->argv[c->argc++] =
|
||||
createStringObject(c->querybuf + pos, c->bulklen);
|
||||
pos += (int) c->bulklen + 2; WIN_PORT_FIX /* cast (int) */
|
||||
pos += (PORT_LONG) c->bulklen + 2; WIN_PORT_FIX /* cast (int) */
|
||||
}
|
||||
c->bulklen = -1;
|
||||
c->multibulklen--;
|
||||
@@ -1479,7 +1503,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
if (c->reqtype == PROTO_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1
|
||||
&& c->bulklen >= PROTO_MBULK_BIG_ARG)
|
||||
{
|
||||
int remaining = (int) ((unsigned) (c->bulklen + 2) - sdslen(c->querybuf)); WIN_PORT_FIX /* cast (int) */
|
||||
ssize_t remaining = (size_t)(c->bulklen+2)-sdslen(c->querybuf);
|
||||
|
||||
if (remaining < readlen) readlen = remaining;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+7
-1
@@ -98,6 +98,12 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
|
||||
int len = -1;
|
||||
char buf[24];
|
||||
|
||||
/* If any modules are interested in events, notify the module system now.
|
||||
* This bypasses the notifications configuration, but the module engine
|
||||
* will only call event subscribers if the event type matches the types
|
||||
* they are interested in. */
|
||||
moduleNotifyKeyspaceEvent(type, event, key, dbid);
|
||||
|
||||
/* If notifications for this class of events are off, return ASAP. */
|
||||
if (!(server.notify_keyspace_events & type)) return;
|
||||
|
||||
@@ -115,7 +121,7 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
|
||||
decrRefCount(chanobj);
|
||||
}
|
||||
|
||||
/* __keyevente@<db>__:<event> <key> notifications. */
|
||||
/* __keyevent@<db>__:<event> <key> notifications. */
|
||||
if (server.notify_keyspace_events & NOTIFY_KEYEVENT) {
|
||||
chan = sdsnewlen("__keyevent@",11);
|
||||
if (len == -1) len = ll2string(buf,sizeof(buf),dbid);
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/* This file implements keyspace events notification via Pub/Sub ad
|
||||
* described at http://redis.io/topics/keyspace-events. */
|
||||
|
||||
/* Turn a string representing notification classes into an integer
|
||||
* representing notification classes flags xored.
|
||||
*
|
||||
* The function returns -1 if the input contains characters not mapping to
|
||||
* any class. */
|
||||
int keyspaceEventsStringToFlags(char *classes) {
|
||||
char *p = classes;
|
||||
int c, flags = 0;
|
||||
|
||||
while((c = *p++) != '\0') {
|
||||
switch(c) {
|
||||
case 'A': flags |= NOTIFY_ALL; break;
|
||||
case 'g': flags |= NOTIFY_GENERIC; break;
|
||||
case '$': flags |= NOTIFY_STRING; break;
|
||||
case 'l': flags |= NOTIFY_LIST; break;
|
||||
case 's': flags |= NOTIFY_SET; break;
|
||||
case 'h': flags |= NOTIFY_HASH; break;
|
||||
case 'z': flags |= NOTIFY_ZSET; break;
|
||||
case 'x': flags |= NOTIFY_EXPIRED; break;
|
||||
case 'e': flags |= NOTIFY_EVICTED; break;
|
||||
case 'K': flags |= NOTIFY_KEYSPACE; break;
|
||||
case 'E': flags |= NOTIFY_KEYEVENT; break;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
/* This function does exactly the revese of the function above: it gets
|
||||
* as input an integer with the xored flags and returns a string representing
|
||||
* the selected classes. The string returned is an sds string that needs to
|
||||
* be released with sdsfree(). */
|
||||
sds keyspaceEventsFlagsToString(int flags) {
|
||||
sds res;
|
||||
|
||||
res = sdsempty();
|
||||
if ((flags & NOTIFY_ALL) == NOTIFY_ALL) {
|
||||
res = sdscatlen(res,"A",1);
|
||||
} else {
|
||||
if (flags & NOTIFY_GENERIC) res = sdscatlen(res,"g",1);
|
||||
if (flags & NOTIFY_STRING) res = sdscatlen(res,"$",1);
|
||||
if (flags & NOTIFY_LIST) res = sdscatlen(res,"l",1);
|
||||
if (flags & NOTIFY_SET) res = sdscatlen(res,"s",1);
|
||||
if (flags & NOTIFY_HASH) res = sdscatlen(res,"h",1);
|
||||
if (flags & NOTIFY_ZSET) res = sdscatlen(res,"z",1);
|
||||
if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
|
||||
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
|
||||
}
|
||||
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
|
||||
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* The API provided to the rest of the Redis core is a simple function:
|
||||
*
|
||||
* notifyKeyspaceEvent(char *event, robj *key, int dbid);
|
||||
*
|
||||
* 'event' is a C string representing the event name.
|
||||
* 'key' is a Redis object representing the key name.
|
||||
* 'dbid' is the database ID where the key lives. */
|
||||
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
|
||||
sds chan;
|
||||
robj *chanobj, *eventobj;
|
||||
int len = -1;
|
||||
char buf[24];
|
||||
|
||||
/* If notifications for this class of events are off, return ASAP. */
|
||||
if (!(server.notify_keyspace_events & type)) return;
|
||||
|
||||
eventobj = createStringObject(event,strlen(event));
|
||||
|
||||
/* __keyspace@<db>__:<key> <event> notifications. */
|
||||
if (server.notify_keyspace_events & NOTIFY_KEYSPACE) {
|
||||
chan = sdsnewlen("__keyspace@",11);
|
||||
len = ll2string(buf,sizeof(buf),dbid);
|
||||
chan = sdscatlen(chan, buf, len);
|
||||
chan = sdscatlen(chan, "__:", 3);
|
||||
chan = sdscatsds(chan, key->ptr);
|
||||
chanobj = createObject(OBJ_STRING, chan);
|
||||
pubsubPublishMessage(chanobj, eventobj);
|
||||
decrRefCount(chanobj);
|
||||
}
|
||||
|
||||
/* __keyevente@<db>__:<event> <key> notifications. */
|
||||
if (server.notify_keyspace_events & NOTIFY_KEYEVENT) {
|
||||
chan = sdsnewlen("__keyevent@",11);
|
||||
if (len == -1) len = ll2string(buf,sizeof(buf),dbid);
|
||||
chan = sdscatlen(chan, buf, len);
|
||||
chan = sdscatlen(chan, "__:", 3);
|
||||
chan = sdscatsds(chan, eventobj->ptr);
|
||||
chanobj = createObject(OBJ_STRING, chan);
|
||||
pubsubPublishMessage(chanobj, key);
|
||||
decrRefCount(chanobj);
|
||||
}
|
||||
decrRefCount(eventobj);
|
||||
}
|
||||
+36
-13
@@ -563,11 +563,11 @@ int getDoubleFromObject(const robj *o, double *target) {
|
||||
if (sdsEncodedObject(o)) {
|
||||
errno = 0;
|
||||
value = strtod(o->ptr, &eptr);
|
||||
if (isspace(((const char*)o->ptr)[0]) ||
|
||||
eptr[0] != '\0' ||
|
||||
if (sdslen(o->ptr) == 0 ||
|
||||
isspace(((const char*)o->ptr)[0]) ||
|
||||
(size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
errno == EINVAL ||
|
||||
isnan(value))
|
||||
return C_ERR;
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
@@ -605,8 +605,12 @@ int getLongDoubleFromObject(robj *o, PORT_LONGDOUBLE *target) {
|
||||
if (sdsEncodedObject(o)) {
|
||||
errno = 0;
|
||||
value = IF_WIN32(wstrtod,strtold)(o->ptr,&eptr); // TODO: verify for 32-bit
|
||||
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
|
||||
errno == ERANGE || isnan(value))
|
||||
if (sdslen(o->ptr) == 0 ||
|
||||
isspace(((const char*)o->ptr)[0]) ||
|
||||
(size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
isnan(value))
|
||||
return C_ERR;
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
value = (PORT_LONG)o->ptr;
|
||||
@@ -729,7 +733,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
elesize += sizeof(quicklistNode)+ziplistBlobLen(node->zl);
|
||||
samples++;
|
||||
} while ((node = node->next) && samples < sample_size);
|
||||
asize += (double)elesize/samples*listTypeLength(o);
|
||||
asize += (double)elesize/samples*ql->len;
|
||||
} else if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
asize = sizeof(*o)+ziplistBlobLen(o->ptr);
|
||||
} else {
|
||||
@@ -1014,11 +1018,25 @@ robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
}
|
||||
|
||||
/* Object command allows to inspect the internals of an Redis Object.
|
||||
* Usage: OBJECT <refcount|encoding|idletime> <key> */
|
||||
* Usage: OBJECT <refcount|encoding|idletime|freq> <key> */
|
||||
void objectCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
||||
void *blenp = addDeferredMultiBulkLength(c);
|
||||
int blen = 0;
|
||||
blen++; addReplyStatus(c,
|
||||
"OBJECT <subcommand> key. Subcommands:");
|
||||
blen++; addReplyStatus(c,
|
||||
"refcount -- Return the number of references of the value associated with the specified key.");
|
||||
blen++; addReplyStatus(c,
|
||||
"encoding -- Return the kind of internal representation used in order to store the value associated with a key.");
|
||||
blen++; addReplyStatus(c,
|
||||
"idletime -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.");
|
||||
blen++; addReplyStatus(c,
|
||||
"freq -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.");
|
||||
setDeferredMultiBulkLength(c,blenp,blen);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,o->refcount);
|
||||
@@ -1037,13 +1055,18 @@ void objectCommand(client *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
|
||||
addReplyError(c,"An LRU maxmemory policy is selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
if (!(server.maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
|
||||
addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
return;
|
||||
}
|
||||
addReplyLongLong(c,o->lru&255);
|
||||
/* LFUDecrAndReturn should be called
|
||||
* in case of the key has not been accessed for a long time,
|
||||
* because we update the access time only
|
||||
* when the key is read or overwritten. */
|
||||
addReplyLongLong(c,LFUDecrAndReturn(o));
|
||||
} else {
|
||||
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime|freq)");
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try OBJECT help",
|
||||
(char *)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1076,7 +1099,7 @@ void memoryCommand(client *c) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
size_t usage = objectComputeSize(o,samples);
|
||||
usage += sdsAllocSize(c->argv[1]->ptr);
|
||||
usage += sdsAllocSize(c->argv[2]->ptr);
|
||||
usage += sizeof(dictEntry);
|
||||
addReplyLongLong(c,usage);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
|
||||
|
||||
+1190
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -149,7 +149,7 @@ REDIS_STATIC quicklistNode *quicklistCreateNode(void) {
|
||||
}
|
||||
|
||||
/* Return cached quicklist count */
|
||||
unsigned int quicklistCount(const quicklist *ql) { return (unsigned int)(ql->count); } WIN_PORT_FIX /* cast (unsigned int) */
|
||||
PORT_ULONG quicklistCount(const quicklist *ql) { return (unsigned int)(ql->count); } WIN_PORT_FIX /* cast (unsigned int) */
|
||||
|
||||
/* Free entire quicklist. */
|
||||
void quicklistRelease(quicklist *quicklist) {
|
||||
|
||||
+2651
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -158,7 +158,7 @@ int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
|
||||
void *(*saver)(unsigned char *data, unsigned int sz));
|
||||
int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
|
||||
unsigned int *sz, PORT_LONGLONG *slong);
|
||||
unsigned int quicklistCount(const quicklist *ql);
|
||||
PORT_ULONG quicklistCount(const quicklist *ql);
|
||||
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
|
||||
size_t quicklistGetLzf(const quicklistNode *node, void **data);
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/* quicklist.h - A generic doubly linked quicklist implementation
|
||||
*
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this quicklist of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this quicklist 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 __QUICKLIST_H__
|
||||
#define __QUICKLIST_H__
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Win32_Interop/win32_types_hiredis.h"
|
||||
#endif
|
||||
|
||||
/* Node, quicklist, and Iterator are the only data structures used currently. */
|
||||
|
||||
/* quicklistNode is a 32 byte struct describing a ziplist for a quicklist.
|
||||
* We use bit fields keep the quicklistNode at 32 bytes.
|
||||
* count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k).
|
||||
* encoding: 2 bits, RAW=1, LZF=2.
|
||||
* container: 2 bits, NONE=1, ZIPLIST=2.
|
||||
* recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
|
||||
* attempted_compress: 1 bit, boolean, used for verifying during testing.
|
||||
* extra: 12 bits, free for future use; pads out the remainder of 32 bits */
|
||||
typedef struct quicklistNode {
|
||||
struct quicklistNode *prev;
|
||||
struct quicklistNode *next;
|
||||
unsigned char *zl;
|
||||
unsigned int sz; /* ziplist size in bytes */
|
||||
unsigned int count : 16; /* count of items in ziplist */
|
||||
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
|
||||
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
|
||||
unsigned int recompress : 1; /* was this node previous compressed? */
|
||||
unsigned int attempted_compress : 1; /* node can't compress; too small */
|
||||
unsigned int extra : 10; /* more bits to steal for future usage */
|
||||
} quicklistNode;
|
||||
|
||||
/* quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'.
|
||||
* 'sz' is byte length of 'compressed' field.
|
||||
* 'compressed' is LZF data with total (compressed) length 'sz'
|
||||
* NOTE: uncompressed length is stored in quicklistNode->sz.
|
||||
* When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */
|
||||
typedef struct quicklistLZF {
|
||||
unsigned int sz; /* LZF size in bytes*/
|
||||
char compressed[];
|
||||
} quicklistLZF;
|
||||
|
||||
/* quicklist is a 32 byte struct (on 64-bit systems) describing a quicklist.
|
||||
* 'count' is the number of total entries.
|
||||
* 'len' is the number of quicklist nodes.
|
||||
* 'compress' is: -1 if compression disabled, otherwise it's the number
|
||||
* of quicklistNodes to leave uncompressed at ends of quicklist.
|
||||
* 'fill' is the user-requested (or default) fill factor. */
|
||||
typedef struct quicklist {
|
||||
quicklistNode *head;
|
||||
quicklistNode *tail;
|
||||
PORT_ULONG count; /* total count of all entries in all ziplists */
|
||||
unsigned int len; /* number of quicklistNodes */
|
||||
int fill : 16; /* fill factor for individual nodes */
|
||||
unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
|
||||
} quicklist;
|
||||
|
||||
typedef struct quicklistIter {
|
||||
const quicklist *quicklist;
|
||||
quicklistNode *current;
|
||||
unsigned char *zi;
|
||||
PORT_LONG offset; /* offset in current ziplist */
|
||||
int direction;
|
||||
} quicklistIter;
|
||||
|
||||
typedef struct quicklistEntry {
|
||||
const quicklist *quicklist;
|
||||
quicklistNode *node;
|
||||
unsigned char *zi;
|
||||
unsigned char *value;
|
||||
PORT_LONGLONG longval;
|
||||
unsigned int sz;
|
||||
int offset;
|
||||
} quicklistEntry;
|
||||
|
||||
#define QUICKLIST_HEAD 0
|
||||
#define QUICKLIST_TAIL -1
|
||||
|
||||
/* quicklist node encodings */
|
||||
#define QUICKLIST_NODE_ENCODING_RAW 1
|
||||
#define QUICKLIST_NODE_ENCODING_LZF 2
|
||||
|
||||
/* quicklist compression disable */
|
||||
#define QUICKLIST_NOCOMPRESS 0
|
||||
|
||||
/* quicklist container formats */
|
||||
#define QUICKLIST_NODE_CONTAINER_NONE 1
|
||||
#define QUICKLIST_NODE_CONTAINER_ZIPLIST 2
|
||||
|
||||
#define quicklistNodeIsCompressed(node) \
|
||||
((node)->encoding == QUICKLIST_NODE_ENCODING_LZF)
|
||||
|
||||
/* Prototypes */
|
||||
quicklist *quicklistCreate(void);
|
||||
quicklist *quicklistNew(int fill, int compress);
|
||||
void quicklistSetCompressDepth(quicklist *quicklist, int depth);
|
||||
void quicklistSetFill(quicklist *quicklist, int fill);
|
||||
void quicklistSetOptions(quicklist *quicklist, int fill, int depth);
|
||||
void quicklistRelease(quicklist *quicklist);
|
||||
int quicklistPushHead(quicklist *quicklist, void *value, const size_t sz);
|
||||
int quicklistPushTail(quicklist *quicklist, void *value, const size_t sz);
|
||||
void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
|
||||
int where);
|
||||
void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl);
|
||||
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
|
||||
unsigned char *zl);
|
||||
quicklist *quicklistCreateFromZiplist(int fill, int compress,
|
||||
unsigned char *zl);
|
||||
void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *node,
|
||||
void *value, const size_t sz);
|
||||
void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *node,
|
||||
void *value, const size_t sz);
|
||||
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
|
||||
int quicklistReplaceAtIndex(quicklist *quicklist, PORT_LONG index, void *data,
|
||||
int sz);
|
||||
int quicklistDelRange(quicklist *quicklist, const PORT_LONG start, const PORT_LONG stop);
|
||||
quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction);
|
||||
quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
|
||||
int direction, const PORT_LONGLONG idx);
|
||||
int quicklistNext(quicklistIter *iter, quicklistEntry *node);
|
||||
void quicklistReleaseIterator(quicklistIter *iter);
|
||||
quicklist *quicklistDup(quicklist *orig);
|
||||
int quicklistIndex(const quicklist *quicklist, const PORT_LONGLONG index,
|
||||
quicklistEntry *entry);
|
||||
void quicklistRewind(quicklist *quicklist, quicklistIter *li);
|
||||
void quicklistRewindTail(quicklist *quicklist, quicklistIter *li);
|
||||
void quicklistRotate(quicklist *quicklist);
|
||||
int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
|
||||
unsigned int *sz, PORT_LONGLONG *sval,
|
||||
void *(*saver)(unsigned char *data, unsigned int sz));
|
||||
int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
|
||||
unsigned int *sz, PORT_LONGLONG *slong);
|
||||
unsigned int quicklistCount(const quicklist *ql);
|
||||
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
|
||||
size_t quicklistGetLzf(const quicklistNode *node, void **data);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int quicklistTest(int argc, char *argv[]);
|
||||
#endif
|
||||
|
||||
/* Directions for iterators */
|
||||
#define AL_START_HEAD 0
|
||||
#define AL_START_TAIL 1
|
||||
|
||||
#endif /* __QUICKLIST_H__ */
|
||||
@@ -131,7 +131,7 @@ static inline void raxStackFree(raxStack *ts) {
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Radis tree implementation
|
||||
* Radix tree implementation
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
/* Allocate a new non compressed node with the specified number of children.
|
||||
@@ -186,10 +186,10 @@ raxNode *raxReallocForData(raxNode *n, void *data) {
|
||||
void raxSetData(raxNode *n, void *data) {
|
||||
n->iskey = 1;
|
||||
if (data != NULL) {
|
||||
n->isnull = 0;
|
||||
void **ndata = (void**)
|
||||
((char*)n+raxNodeCurrentLength(n)-sizeof(void*));
|
||||
memcpy(ndata,&data,sizeof(data));
|
||||
n->isnull = 0;
|
||||
} else {
|
||||
n->isnull = 1;
|
||||
}
|
||||
@@ -396,6 +396,7 @@ static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode
|
||||
position to 0 to signal this node represents
|
||||
the searched key. */
|
||||
}
|
||||
debugnode("Lookup stop node is",h);
|
||||
if (stopnode) *stopnode = h;
|
||||
if (plink) *plink = parentlink;
|
||||
if (splitpos && h->iscompr) *splitpos = j;
|
||||
@@ -424,18 +425,21 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
|
||||
* our key. We have just to reallocate the node and make space for the
|
||||
* data pointer. */
|
||||
if (i == len && (!h->iscompr || j == 0 /* not in the middle if j is 0 */)) {
|
||||
debugf("### Insert: node representing key exists\n");
|
||||
if (!h->iskey || h->isnull) {
|
||||
h = raxReallocForData(h,data);
|
||||
if (h) memcpy(parentlink,&h,sizeof(h));
|
||||
}
|
||||
if (h == NULL) {
|
||||
errno = ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
if (h->iskey) {
|
||||
if (old) *old = raxGetData(h);
|
||||
raxSetData(h,data);
|
||||
errno = 0;
|
||||
return 0; /* Element already exists. */
|
||||
}
|
||||
h = raxReallocForData(h,data);
|
||||
if (h == NULL) {
|
||||
errno = ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
memcpy(parentlink,&h,sizeof(h));
|
||||
raxSetData(h,data);
|
||||
rax->numele++;
|
||||
return 1; /* Element inserted. */
|
||||
@@ -734,9 +738,7 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
|
||||
}
|
||||
|
||||
/* We walked the radix tree as far as we could, but still there are left
|
||||
* chars in our string. We need to insert the missing nodes.
|
||||
* Note: while loop never entered if the node was split by ALGO2,
|
||||
* since i == len. */
|
||||
* chars in our string. We need to insert the missing nodes. */
|
||||
while(i < len) {
|
||||
raxNode *child;
|
||||
|
||||
@@ -871,7 +873,8 @@ raxNode *raxRemoveChild(raxNode *parent, raxNode *child) {
|
||||
memmove(((char*)cp)-1,cp,(parent->size-taillen-1)*sizeof(raxNode**));
|
||||
|
||||
/* Move the remaining "tail" pointer at the right position as well. */
|
||||
memmove(((char*)c)-1,c+1,taillen*sizeof(raxNode**)+parent->iskey*sizeof(void*));
|
||||
size_t valuelen = (parent->iskey && !parent->isnull) ? sizeof(void*) : 0;
|
||||
memmove(((char*)c)-1,c+1,taillen*sizeof(raxNode**)+valuelen);
|
||||
|
||||
/* 4. Update size. */
|
||||
parent->size--;
|
||||
@@ -1090,27 +1093,36 @@ int raxRemove(rax *rax, unsigned char *s, size_t len, void **old) {
|
||||
|
||||
/* This is the core of raxFree(): performs a depth-first scan of the
|
||||
* tree and releases all the nodes found. */
|
||||
void raxRecursiveFree(rax *rax, raxNode *n) {
|
||||
void raxRecursiveFree(rax *rax, raxNode *n, void (*free_callback)(void*)) {
|
||||
debugnode("free traversing",n);
|
||||
int numchildren = n->iscompr ? 1 : n->size;
|
||||
raxNode **cp = raxNodeLastChildPtr(n);
|
||||
while(numchildren--) {
|
||||
raxNode *child;
|
||||
memcpy(&child,cp,sizeof(child));
|
||||
raxRecursiveFree(rax,child);
|
||||
raxRecursiveFree(rax,child,free_callback);
|
||||
cp--;
|
||||
}
|
||||
debugnode("free depth-first",n);
|
||||
if (free_callback && n->iskey && !n->isnull)
|
||||
free_callback(raxGetData(n));
|
||||
rax_free(n);
|
||||
rax->numnodes--;
|
||||
}
|
||||
|
||||
/* Free a whole radix tree. */
|
||||
void raxFree(rax *rax) {
|
||||
raxRecursiveFree(rax,rax->head);
|
||||
/* Free a whole radix tree, calling the specified callback in order to
|
||||
* free the auxiliary data. */
|
||||
void raxFreeWithCallback(rax *rax, void (*free_callback)(void*)) {
|
||||
raxRecursiveFree(rax,rax->head,free_callback);
|
||||
assert(rax->numnodes == 0);
|
||||
rax_free(rax);
|
||||
}
|
||||
|
||||
/* Free a whole radix tree. */
|
||||
void raxFree(rax *rax) {
|
||||
raxFreeWithCallback(rax,NULL);
|
||||
}
|
||||
|
||||
/* ------------------------------- Iterator --------------------------------- */
|
||||
|
||||
/* Initialize a Rax iterator. This call should be performed a single time
|
||||
@@ -1172,7 +1184,7 @@ void raxIteratorDelChars(raxIterator *it, size_t count) {
|
||||
* The function returns 1 on success or 0 on out of memory. */
|
||||
int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
if (it->flags & RAX_ITER_EOF) {
|
||||
return 0;
|
||||
return 1;
|
||||
} else if (it->flags & RAX_ITER_JUST_SEEKED) {
|
||||
it->flags &= ~RAX_ITER_JUST_SEEKED;
|
||||
return 1;
|
||||
@@ -1184,10 +1196,6 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
size_t orig_stack_items = it->stack.items;
|
||||
raxNode *orig_node = it->node;
|
||||
|
||||
/* Clear the EOF flag: it will be set again if the EOF condition
|
||||
* is still valid. */
|
||||
it->flags &= ~RAX_ITER_EOF;
|
||||
|
||||
while(1) {
|
||||
int children = it->node->iscompr ? 1 : it->node->size;
|
||||
if (!noup && children) {
|
||||
@@ -1288,7 +1296,7 @@ int raxSeekGreatest(raxIterator *it) {
|
||||
* effect to the one of raxIteratorPrevSte(). */
|
||||
int raxIteratorPrevStep(raxIterator *it, int noup) {
|
||||
if (it->flags & RAX_ITER_EOF) {
|
||||
return 0;
|
||||
return 1;
|
||||
} else if (it->flags & RAX_ITER_JUST_SEEKED) {
|
||||
it->flags &= ~RAX_ITER_JUST_SEEKED;
|
||||
return 1;
|
||||
@@ -1409,6 +1417,7 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
|
||||
it->node = it->rt->head;
|
||||
if (!raxSeekGreatest(it)) return 0;
|
||||
assert(it->node->iskey);
|
||||
it->data = raxGetData(it->node);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1427,6 +1436,7 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
|
||||
/* We found our node, since the key matches and we have an
|
||||
* "equal" condition. */
|
||||
if (!raxIteratorAddChars(it,ele,len)) return 0; /* OOM. */
|
||||
it->data = raxGetData(it->node);
|
||||
} else if (lt || gt) {
|
||||
/* Exact key not found or eq flag not set. We have to set as current
|
||||
* key the one represented by the node we stopped at, and perform
|
||||
@@ -1499,6 +1509,7 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
|
||||
* the previous sub-tree. */
|
||||
if (nodechar < keychar) {
|
||||
if (!raxSeekGreatest(it)) return 0;
|
||||
it->data = raxGetData(it->node);
|
||||
} else {
|
||||
if (!raxIteratorAddChars(it,it->node->data,it->node->size))
|
||||
return 0;
|
||||
@@ -1615,8 +1626,8 @@ int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key
|
||||
int eq = 0, lt = 0, gt = 0;
|
||||
|
||||
if (op[0] == '=' || op[1] == '=') eq = 1;
|
||||
if (op[1] == '>') gt = 1;
|
||||
else if (op[1] == '<') lt = 1;
|
||||
if (op[0] == '>') gt = 1;
|
||||
else if (op[0] == '<') lt = 1;
|
||||
else if (op[1] != '=') return 0; /* Syntax error. */
|
||||
|
||||
size_t minlen = key_len < iter->key_len ? key_len : iter->key_len;
|
||||
@@ -1644,6 +1655,19 @@ void raxStop(raxIterator *it) {
|
||||
raxStackFree(&it->stack);
|
||||
}
|
||||
|
||||
/* Return if the iterator is in an EOF state. This happens when raxSeek()
|
||||
* failed to seek an appropriate element, so that raxNext() or raxPrev()
|
||||
* will return zero, or when an EOF condition was reached while iterating
|
||||
* with raxNext() and raxPrev(). */
|
||||
int raxEOF(raxIterator *it) {
|
||||
return it->flags & RAX_ITER_EOF;
|
||||
}
|
||||
|
||||
/* Return the number of elements inside the radix tree. */
|
||||
uint64_t raxSize(rax *rax) {
|
||||
return rax->numele;
|
||||
}
|
||||
|
||||
/* ----------------------------- Introspection ------------------------------ */
|
||||
|
||||
/* This function is mostly used for debugging and learning purposes.
|
||||
|
||||
+1730
File diff suppressed because it is too large
Load Diff
@@ -148,6 +148,7 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
|
||||
int raxRemove(rax *rax, unsigned char *s, size_t len, void **old);
|
||||
void *raxFind(rax *rax, unsigned char *s, size_t len);
|
||||
void raxFree(rax *rax);
|
||||
void raxFreeWithCallback(rax *rax, void (*free_callback)(void*));
|
||||
void raxStart(raxIterator *it, rax *rt);
|
||||
int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len);
|
||||
int raxNext(raxIterator *it);
|
||||
@@ -155,6 +156,8 @@ int raxPrev(raxIterator *it);
|
||||
int raxRandomWalk(raxIterator *it, size_t steps);
|
||||
int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key_len);
|
||||
void raxStop(raxIterator *it);
|
||||
int raxEOF(raxIterator *it);
|
||||
void raxShow(rax *rax);
|
||||
uint64_t raxSize(rax *rax);
|
||||
|
||||
#endif
|
||||
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
#ifndef RAX_H
|
||||
#define RAX_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Representation of a radix tree as implemented in this file, that contains
|
||||
* the strings "foo", "foobar" and "footer" after the insertion of each
|
||||
* word. When the node represents a key inside the radix tree, we write it
|
||||
* between [], otherwise it is written between ().
|
||||
*
|
||||
* This is the vanilla representation:
|
||||
*
|
||||
* (f) ""
|
||||
* \
|
||||
* (o) "f"
|
||||
* \
|
||||
* (o) "fo"
|
||||
* \
|
||||
* [t b] "foo"
|
||||
* / \
|
||||
* "foot" (e) (a) "foob"
|
||||
* / \
|
||||
* "foote" (r) (r) "fooba"
|
||||
* / \
|
||||
* "footer" [] [] "foobar"
|
||||
*
|
||||
* However, this implementation implements a very common optimization where
|
||||
* successive nodes having a single child are "compressed" into the node
|
||||
* itself as a string of characters, each representing a next-level child,
|
||||
* and only the link to the node representing the last character node is
|
||||
* provided inside the representation. So the above representation is turend
|
||||
* into:
|
||||
*
|
||||
* ["foo"] ""
|
||||
* |
|
||||
* [t b] "foo"
|
||||
* / \
|
||||
* "foot" ("er") ("ar") "foob"
|
||||
* / \
|
||||
* "footer" [] [] "foobar"
|
||||
*
|
||||
* However this optimization makes the implementation a bit more complex.
|
||||
* For instance if a key "first" is added in the above radix tree, a
|
||||
* "node splitting" operation is needed, since the "foo" prefix is no longer
|
||||
* composed of nodes having a single child one after the other. This is the
|
||||
* above tree and the resulting node splitting after this event happens:
|
||||
*
|
||||
*
|
||||
* (f) ""
|
||||
* /
|
||||
* (i o) "f"
|
||||
* / \
|
||||
* "firs" ("rst") (o) "fo"
|
||||
* / \
|
||||
* "first" [] [t b] "foo"
|
||||
* / \
|
||||
* "foot" ("er") ("ar") "foob"
|
||||
* / \
|
||||
* "footer" [] [] "foobar"
|
||||
*
|
||||
* Similarly after deletion, if a new chain of nodes having a single child
|
||||
* is created (the chain must also not include nodes that represent keys),
|
||||
* it must be compressed back into a single node.
|
||||
*
|
||||
*/
|
||||
|
||||
#define RAX_NODE_MAX_SIZE ((1<<29)-1)
|
||||
typedef struct raxNode {
|
||||
uint32_t iskey:1; /* Does this node contain a key? */
|
||||
uint32_t isnull:1; /* Associated value is NULL (don't store it). */
|
||||
uint32_t iscompr:1; /* Node is compressed. */
|
||||
uint32_t size:29; /* Number of children, or compressed string len. */
|
||||
/* Data layout is as follows:
|
||||
*
|
||||
* If node is not compressed we have 'size' bytes, one for each children
|
||||
* character, and 'size' raxNode pointers, point to each child node.
|
||||
* Note how the character is not stored in the children but in the
|
||||
* edge of the parents:
|
||||
*
|
||||
* [header strlen=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)
|
||||
*
|
||||
* if node is compressed (strlen != 0) the node has 1 children.
|
||||
* In that case the 'size' bytes of the string stored immediately at
|
||||
* the start of the data section, represent a sequence of successive
|
||||
* nodes linked one after the other, for which only the last one in
|
||||
* the sequence is actually represented as a node, and pointed to by
|
||||
* the current compressed node.
|
||||
*
|
||||
* [header strlen=3][xyz][z-ptr](value-ptr?)
|
||||
*
|
||||
* Both compressed and not compressed nodes can represent a key
|
||||
* with associated data in the radix tree at any level (not just terminal
|
||||
* nodes).
|
||||
*
|
||||
* If the node has an associated key (iskey=1) and is not NULL
|
||||
* (isnull=0), then after the raxNode pointers poiting to the
|
||||
* childen, an additional value pointer is present (as you can see
|
||||
* in the representation above as "value-ptr" field).
|
||||
*/
|
||||
unsigned char data[];
|
||||
} raxNode;
|
||||
|
||||
typedef struct rax {
|
||||
raxNode *head;
|
||||
uint64_t numele;
|
||||
uint64_t numnodes;
|
||||
} rax;
|
||||
|
||||
/* Stack data structure used by raxLowWalk() in order to, optionally, return
|
||||
* a list of parent nodes to the caller. The nodes do not have a "parent"
|
||||
* field for space concerns, so we use the auxiliary stack when needed. */
|
||||
#define RAX_STACK_STATIC_ITEMS 32
|
||||
typedef struct raxStack {
|
||||
void **stack; /* Points to static_items or an heap allocated array. */
|
||||
size_t items, maxitems; /* Number of items contained and total space. */
|
||||
/* Up to RAXSTACK_STACK_ITEMS items we avoid to allocate on the heap
|
||||
* and use this static array of pointers instead. */
|
||||
void *static_items[RAX_STACK_STATIC_ITEMS];
|
||||
int oom; /* True if pushing into this stack failed for OOM at some point. */
|
||||
} raxStack;
|
||||
|
||||
/* Radix tree iterator state is encapsulated into this data structure. */
|
||||
#define RAX_ITER_STATIC_LEN 128
|
||||
#define RAX_ITER_JUST_SEEKED (1<<0) /* Iterator was just seeked. Return current
|
||||
element for the first iteration and
|
||||
clear the flag. */
|
||||
#define RAX_ITER_EOF (1<<1) /* End of iteration reached. */
|
||||
#define RAX_ITER_SAFE (1<<2) /* Safe iterator, allows operations while
|
||||
iterating. But it is slower. */
|
||||
typedef struct raxIterator {
|
||||
int flags;
|
||||
rax *rt; /* Radix tree we are iterating. */
|
||||
unsigned char *key; /* The current string. */
|
||||
void *data; /* Data associated to this key. */
|
||||
size_t key_len; /* Current key length. */
|
||||
size_t key_max; /* Max key len the current key buffer can hold. */
|
||||
unsigned char key_static_string[RAX_ITER_STATIC_LEN];
|
||||
raxNode *node; /* Current node. Only for unsafe iteration. */
|
||||
raxStack stack; /* Stack used for unsafe iteration. */
|
||||
} raxIterator;
|
||||
|
||||
/* A special pointer returned for not found items. */
|
||||
extern void *raxNotFound;
|
||||
|
||||
/* Exported API. */
|
||||
rax *raxNew(void);
|
||||
int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
|
||||
int raxRemove(rax *rax, unsigned char *s, size_t len, void **old);
|
||||
void *raxFind(rax *rax, unsigned char *s, size_t len);
|
||||
void raxFree(rax *rax);
|
||||
void raxStart(raxIterator *it, rax *rt);
|
||||
int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len);
|
||||
int raxNext(raxIterator *it);
|
||||
int raxPrev(raxIterator *it);
|
||||
int raxRandomWalk(raxIterator *it, size_t steps);
|
||||
int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key_len);
|
||||
void raxStop(raxIterator *it);
|
||||
void raxShow(rax *rax);
|
||||
|
||||
#endif
|
||||
@@ -685,7 +685,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
if ((n = rdbSaveLen(rdb,ql->len)) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
do {
|
||||
while(node) {
|
||||
if (quicklistNodeIsCompressed(node)) {
|
||||
void *data;
|
||||
size_t compress_len = quicklistGetLzf(node, &data);
|
||||
@@ -695,7 +695,8 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
if ((n = rdbSaveRawString(rdb,node->zl,node->sz)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
} while ((node = node->next));
|
||||
node = node->next;
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown list encoding");
|
||||
}
|
||||
@@ -835,13 +836,10 @@ size_t rdbSavedObjectLen(robj *o) {
|
||||
* On error -1 is returned.
|
||||
* On success if the key was actually saved 1 is returned, otherwise 0
|
||||
* is returned (the key was already expired). */
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
|
||||
PORT_LONGLONG expiretime, PORT_LONGLONG now)
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, PORT_LONGLONG expiretime)
|
||||
{
|
||||
/* Save the expire time */
|
||||
if (expiretime != -1) {
|
||||
/* If this key is already expired skip it */
|
||||
if (expiretime < now) return 0;
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
|
||||
if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
|
||||
}
|
||||
@@ -854,11 +852,15 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
|
||||
}
|
||||
|
||||
/* Save an AUX field. */
|
||||
int rdbSaveAuxField(rio *rdb, void *key, size_t keylen, void *val, size_t vallen) {
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_AUX) == -1) return -1;
|
||||
if (rdbSaveRawString(rdb,key,keylen) == -1) return -1;
|
||||
if (rdbSaveRawString(rdb,val,vallen) == -1) return -1;
|
||||
return 1;
|
||||
ssize_t rdbSaveAuxField(rio *rdb, void *key, size_t keylen, void *val, size_t vallen) {
|
||||
ssize_t ret, len = 0;
|
||||
if ((ret = rdbSaveType(rdb,RDB_OPCODE_AUX)) == -1) return -1;
|
||||
len += ret;
|
||||
if ((ret = rdbSaveRawString(rdb,key,keylen)) == -1) return -1;
|
||||
len += ret;
|
||||
if ((ret = rdbSaveRawString(rdb,val,vallen)) == -1) return -1;
|
||||
len += ret;
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Wrapper for rdbSaveAuxField() used when key/val length can be obtained
|
||||
@@ -911,7 +913,6 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
dictEntry *de;
|
||||
char magic[10];
|
||||
int j;
|
||||
PORT_LONGLONG now = mstime();
|
||||
uint64_t cksum;
|
||||
size_t processed = 0;
|
||||
|
||||
@@ -955,7 +956,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
|
||||
initStaticStringObject(key,keystr);
|
||||
expire = getExpire(db,&key);
|
||||
if (rdbSaveKeyValuePair(rdb,&key,o,expire,now) == -1) goto werr;
|
||||
if (rdbSaveKeyValuePair(rdb,&key,o,expire) == -1) goto werr;
|
||||
|
||||
/* When this RDB is produced as part of an AOF rewrite, move
|
||||
* accumulated diff from parent to child while rewriting in
|
||||
@@ -971,6 +972,20 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
}
|
||||
di = NULL; /* So that we don't release it again on error. */
|
||||
|
||||
/* If we are storing the replication information on disk, persist
|
||||
* the script cache as well: on successful PSYNC after a restart, we need
|
||||
* to be able to process any EVALSHA inside the replication backlog the
|
||||
* master will send us. */
|
||||
if (rsi && dictSize(server.lua_scripts)) {
|
||||
di = dictGetIterator(server.lua_scripts);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *body = dictGetVal(de);
|
||||
if (rdbSaveAuxField(rdb,"lua",3,body->ptr,sdslen(body->ptr)) == -1)
|
||||
goto werr;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* EOF opcode */
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
|
||||
|
||||
@@ -1523,7 +1538,7 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
|
||||
/* Load an RDB file from the rio stream 'rdb'. On success C_OK is returned,
|
||||
* otherwise C_ERR is returned and 'errno' is set accordingly. */
|
||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
|
||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
uint64_t dbid;
|
||||
int type, rdbver;
|
||||
redisDb *db = server.db+0;
|
||||
@@ -1623,6 +1638,13 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
|
||||
}
|
||||
} else if (!strcasecmp(auxkey->ptr,"repl-offset")) {
|
||||
if (rsi) rsi->repl_offset = strtoll(auxval->ptr,NULL,10);
|
||||
} else if (!strcasecmp(auxkey->ptr,"lua")) {
|
||||
/* Load the script back in memory. */
|
||||
if (luaCreateFunction(NULL,server.lua,auxval) == NULL) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Can't load Lua script from RDB file! "
|
||||
"BODY: %s", auxval->ptr);
|
||||
}
|
||||
} else {
|
||||
/* We ignore fields we don't understand, as by AUX field
|
||||
* contract. */
|
||||
@@ -1644,7 +1666,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
|
||||
* received from the master. In the latter case, the master is
|
||||
* responsible for key expiry. If we would expire keys here, the
|
||||
* snapshot taken by the master may not be reflected on the slave. */
|
||||
if (server.masterhost == NULL && expiretime != -1 && expiretime < now) {
|
||||
if (server.masterhost == NULL && !loading_aof && expiretime != -1 && expiretime < now) {
|
||||
decrRefCount(key);
|
||||
decrRefCount(val);
|
||||
continue;
|
||||
@@ -1658,10 +1680,11 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
|
||||
decrRefCount(key);
|
||||
}
|
||||
/* Verify the checksum if RDB version is >= 5 */
|
||||
if (rdbver >= 5 && server.rdb_checksum) {
|
||||
if (rdbver >= 5) {
|
||||
uint64_t cksum, expected = rdb->cksum;
|
||||
|
||||
if (rioRead(rdb,&cksum,8) == 0) goto eoferr;
|
||||
if (server.rdb_checksum) {
|
||||
memrev64ifbe(&cksum);
|
||||
if (cksum == 0) {
|
||||
serverLog(LL_WARNING,"RDB file was saved with checksum disabled: no check performed.");
|
||||
@@ -1670,6 +1693,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
|
||||
rdbExitReportCorruptRDB("RDB CRC error");
|
||||
}
|
||||
}
|
||||
}
|
||||
return C_OK;
|
||||
|
||||
eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
@@ -1693,7 +1717,7 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi) {
|
||||
if ((fp = fopen(filename,IF_WIN32("rb","r"))) == NULL) return C_ERR;
|
||||
startLoading(fp);
|
||||
rioInitWithFile(&rdb,fp);
|
||||
retval = rdbLoadRio(&rdb,rsi);
|
||||
retval = rdbLoadRio(&rdb,rsi,0);
|
||||
fclose(fp);
|
||||
stopLoading();
|
||||
return retval;
|
||||
@@ -2041,6 +2065,9 @@ void bgsaveCommand(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
|
||||
if (server.rdb_child_pid != -1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
} else if (server.aof_child_pid != -1) {
|
||||
@@ -2053,7 +2080,7 @@ void bgsaveCommand(client *c) {
|
||||
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
|
||||
"possible.");
|
||||
}
|
||||
} else if (rdbSaveBackground(server.rdb_filename,NULL) == C_OK) {
|
||||
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
|
||||
addReplyStatus(c,"Background saving started");
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
@@ -2074,22 +2101,37 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
|
||||
*rsi = rsi_init;
|
||||
|
||||
/* If the instance is a master, we can populate the replication info
|
||||
* in all the cases, even if sometimes in incomplete (but safe) form. */
|
||||
if (!server.masterhost) {
|
||||
if (server.repl_backlog) rsi->repl_stream_db = server.slaveseldb;
|
||||
/* Note that if repl_backlog is NULL, it means that histories
|
||||
* following from this point will trigger a full synchronization
|
||||
* generating a SELECT statement, so we can leave the currently
|
||||
* selected DB set to -1. This allows a restarted master to reload
|
||||
* its replication ID/offset when there are no connected slaves. */
|
||||
* only when repl_backlog is not NULL. If the repl_backlog is NULL,
|
||||
* it means that the instance isn't in any replication chains. In this
|
||||
* scenario the replication info is useless, because when a slave
|
||||
* connects to us, the NULL repl_backlog will trigger a full
|
||||
* synchronization, at the same time we will use a new replid and clear
|
||||
* replid2. */
|
||||
if (!server.masterhost && server.repl_backlog) {
|
||||
/* Note that when server.slaveseldb is -1, it means that this master
|
||||
* didn't apply any write commands after a full synchronization.
|
||||
* So we can let repl_stream_db be 0, this allows a restarted slave
|
||||
* to reload replication ID/offset, it's safe because the next write
|
||||
* command must generate a SELECT statement. */
|
||||
rsi->repl_stream_db = server.slaveseldb == -1 ? 0 : server.slaveseldb;
|
||||
return rsi;
|
||||
}
|
||||
|
||||
/* If the instance is a slave we need a connected master in order to
|
||||
* fetch the currently selected DB. */
|
||||
/* If the instance is a slave we need a connected master
|
||||
* in order to fetch the currently selected DB. */
|
||||
if (server.master) {
|
||||
rsi->repl_stream_db = server.master->db->id;
|
||||
return rsi;
|
||||
}
|
||||
|
||||
/* If we have a cached master we can use it in order to populate the
|
||||
* replication selected DB info inside the RDB file: the slave can
|
||||
* increment the master_repl_offset only from data arriving from the
|
||||
* master, so if we are disconnected the offset in the cached master
|
||||
* is valid. */
|
||||
if (server.cached_master) {
|
||||
rsi->repl_stream_db = server.cached_master->db->id;
|
||||
return rsi;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+2136
File diff suppressed because it is too large
Load Diff
@@ -146,7 +146,7 @@ int rdbSaveBinaryDoubleValue(rio *rdb, double val);
|
||||
int rdbLoadBinaryDoubleValue(rio *rdb, double *val);
|
||||
int rdbSaveBinaryFloatValue(rio *rdb, float val);
|
||||
int rdbLoadBinaryFloatValue(rio *rdb, float *val);
|
||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi);
|
||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof);
|
||||
rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi);
|
||||
|
||||
#endif
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __RDB_H
|
||||
#define __RDB_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include "rio.h"
|
||||
|
||||
/* TBD: include only necessary headers. */
|
||||
#include "server.h"
|
||||
|
||||
/* The current RDB version. When the format changes in a way that is no longer
|
||||
* backward compatible this number gets incremented. */
|
||||
#define RDB_VERSION 8
|
||||
|
||||
/* Defines related to the dump file format. To store 32 bits lengths for short
|
||||
* keys requires a lot of space, so we check the most significant 2 bits of
|
||||
* the first byte to interpreter the length:
|
||||
*
|
||||
* 00|XXXXXX => if the two MSB are 00 the len is the 6 bits of this byte
|
||||
* 01|XXXXXX XXXXXXXX => 01, the len is 14 byes, 6 bits + 8 bits of next byte
|
||||
* 10|000000 [32 bit integer] => A full 32 bit len in net byte order will follow
|
||||
* 10|000001 [64 bit integer] => A full 64 bit len in net byte order will follow
|
||||
* 11|OBKIND this means: specially encoded object will follow. The six bits
|
||||
* number specify the kind of object that follows.
|
||||
* See the RDB_ENC_* defines.
|
||||
*
|
||||
* Lengths up to 63 are stored using a single byte, most DB keys, and may
|
||||
* values, will fit inside. */
|
||||
#define RDB_6BITLEN 0
|
||||
#define RDB_14BITLEN 1
|
||||
#define RDB_32BITLEN 0x80
|
||||
#define RDB_64BITLEN 0x81
|
||||
#define RDB_ENCVAL 3
|
||||
#define RDB_LENERR UINT64_MAX
|
||||
|
||||
/* When a length of a string object stored on disk has the first two bits
|
||||
* set, the remaining six bits specify a special encoding for the object
|
||||
* accordingly to the following defines: */
|
||||
#define RDB_ENC_INT8 0 /* 8 bit signed integer */
|
||||
#define RDB_ENC_INT16 1 /* 16 bit signed integer */
|
||||
#define RDB_ENC_INT32 2 /* 32 bit signed integer */
|
||||
#define RDB_ENC_LZF 3 /* string compressed with FASTLZ */
|
||||
|
||||
/* Dup object types to RDB object types. Only reason is readability (are we
|
||||
* dealing with RDB types or with in-memory object types?). */
|
||||
#define RDB_TYPE_STRING 0
|
||||
#define RDB_TYPE_LIST 1
|
||||
#define RDB_TYPE_SET 2
|
||||
#define RDB_TYPE_ZSET 3
|
||||
#define RDB_TYPE_HASH 4
|
||||
#define RDB_TYPE_ZSET_2 5 /* ZSET version 2 with doubles stored in binary. */
|
||||
#define RDB_TYPE_MODULE 6
|
||||
#define RDB_TYPE_MODULE_2 7 /* Module value with annotations for parsing without
|
||||
the generating module being loaded. */
|
||||
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
|
||||
|
||||
/* Object types for encoded objects. */
|
||||
#define RDB_TYPE_HASH_ZIPMAP 9
|
||||
#define RDB_TYPE_LIST_ZIPLIST 10
|
||||
#define RDB_TYPE_SET_INTSET 11
|
||||
#define RDB_TYPE_ZSET_ZIPLIST 12
|
||||
#define RDB_TYPE_HASH_ZIPLIST 13
|
||||
#define RDB_TYPE_LIST_QUICKLIST 14
|
||||
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
|
||||
|
||||
/* Test if a type is an object type. */
|
||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 14))
|
||||
|
||||
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
|
||||
#define RDB_OPCODE_AUX 250
|
||||
#define RDB_OPCODE_RESIZEDB 251
|
||||
#define RDB_OPCODE_EXPIRETIME_MS 252
|
||||
#define RDB_OPCODE_EXPIRETIME 253
|
||||
#define RDB_OPCODE_SELECTDB 254
|
||||
#define RDB_OPCODE_EOF 255
|
||||
|
||||
/* Module serialized values sub opcodes */
|
||||
#define RDB_MODULE_OPCODE_EOF 0 /* End of module value. */
|
||||
#define RDB_MODULE_OPCODE_SINT 1 /* Signed integer. */
|
||||
#define RDB_MODULE_OPCODE_UINT 2 /* Unsigned integer. */
|
||||
#define RDB_MODULE_OPCODE_FLOAT 3 /* Float. */
|
||||
#define RDB_MODULE_OPCODE_DOUBLE 4 /* Double. */
|
||||
#define RDB_MODULE_OPCODE_STRING 5 /* String. */
|
||||
|
||||
/* rdbLoad...() functions flags. */
|
||||
#define RDB_LOAD_NONE 0
|
||||
#define RDB_LOAD_ENC (1<<0)
|
||||
#define RDB_LOAD_PLAIN (1<<1)
|
||||
#define RDB_LOAD_SDS (1<<2)
|
||||
|
||||
#define RDB_SAVE_NONE 0
|
||||
#define RDB_SAVE_AOF_PREAMBLE (1<<0)
|
||||
|
||||
int rdbSaveType(rio *rdb, unsigned char type);
|
||||
int rdbLoadType(rio *rdb);
|
||||
int rdbSaveTime(rio *rdb, time_t t);
|
||||
time_t rdbLoadTime(rio *rdb);
|
||||
int rdbSaveLen(rio *rdb, uint64_t len);
|
||||
uint64_t rdbLoadLen(rio *rdb, int *isencoded);
|
||||
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
|
||||
int rdbSaveObjectType(rio *rdb, robj *o);
|
||||
int rdbLoadObjectType(rio *rdb);
|
||||
int rdbLoad(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, PORT_LONGLONG expiretime, PORT_LONGLONG now);
|
||||
robj *rdbLoadStringObject(rio *rdb);
|
||||
int rdbSaveStringObject(rio *rdb, robj *obj);
|
||||
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
|
||||
void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr);
|
||||
int rdbSaveBinaryDoubleValue(rio *rdb, double val);
|
||||
int rdbLoadBinaryDoubleValue(rio *rdb, double *val);
|
||||
int rdbSaveBinaryFloatValue(rio *rdb, float val);
|
||||
int rdbLoadBinaryFloatValue(rio *rdb, float *val);
|
||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi);
|
||||
rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi);
|
||||
|
||||
#endif
|
||||
+281
-48
@@ -132,6 +132,7 @@ static struct config {
|
||||
char *pattern;
|
||||
char *rdb_filename;
|
||||
int bigkeys;
|
||||
int hotkeys;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
char *auth;
|
||||
int output; /* output mode, see OUTPUT_* defines */
|
||||
@@ -184,20 +185,25 @@ static PORT_LONGLONG mstime(void) {
|
||||
}
|
||||
|
||||
static void cliRefreshPrompt(void) {
|
||||
int len;
|
||||
|
||||
if (config.eval_ldb) return;
|
||||
if (config.hostsocket != NULL)
|
||||
len = snprintf(config.prompt, sizeof(config.prompt), "redis %s",
|
||||
config.hostsocket);
|
||||
else
|
||||
len = anetFormatAddr(config.prompt, sizeof(config.prompt),
|
||||
config.hostip, config.hostport);
|
||||
|
||||
sds prompt = sdsempty();
|
||||
if (config.hostsocket != NULL) {
|
||||
prompt = sdscatfmt(prompt,"redis %s",config.hostsocket);
|
||||
} else {
|
||||
char addr[256];
|
||||
anetFormatAddr(addr, sizeof(addr), config.hostip, config.hostport);
|
||||
prompt = sdscatlen(prompt,addr,strlen(addr));
|
||||
}
|
||||
|
||||
/* Add [dbnum] if needed */
|
||||
if (config.dbnum != 0)
|
||||
len += snprintf(config.prompt + len, sizeof(config.prompt) - len, "[%d]",
|
||||
config.dbnum);
|
||||
snprintf(config.prompt + len, sizeof(config.prompt) - len, "> ");
|
||||
prompt = sdscatfmt(prompt,"[%i]",config.dbnum);
|
||||
|
||||
/* Copy the prompt in the static buffer. */
|
||||
prompt = sdscatlen(prompt,"> ",2);
|
||||
snprintf(config.prompt,sizeof(config.prompt),"%s",prompt);
|
||||
sdsfree(prompt);
|
||||
}
|
||||
|
||||
/* Return the name of the dotfile for the specified 'dotfilename'.
|
||||
@@ -242,6 +248,92 @@ static sds getDotfilePath(char *envoverride, char *dotfilename) {
|
||||
return dotPath;
|
||||
}
|
||||
|
||||
/* URL-style percent decoding. */
|
||||
#define isHexChar(c) (isdigit(c) || (c >= 'a' && c <= 'f'))
|
||||
#define decodeHexChar(c) (isdigit(c) ? c - '0' : c - 'a' + 10)
|
||||
#define decodeHex(h, l) ((decodeHexChar(h) << 4) + decodeHexChar(l))
|
||||
|
||||
static sds percentDecode(const char *pe, size_t len) {
|
||||
const char *end = pe + len;
|
||||
sds ret = sdsempty();
|
||||
const char *curr = pe;
|
||||
|
||||
while (curr < end) {
|
||||
if (*curr == '%') {
|
||||
if ((end - curr) < 2) {
|
||||
fprintf(stderr, "Incomplete URI encoding\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char h = tolower(*(++curr));
|
||||
char l = tolower(*(++curr));
|
||||
if (!isHexChar(h) || !isHexChar(l)) {
|
||||
fprintf(stderr, "Illegal character in URI encoding\n");
|
||||
exit(1);
|
||||
}
|
||||
char c = decodeHex(h, l);
|
||||
ret = sdscatlen(ret, &c, 1);
|
||||
curr++;
|
||||
} else {
|
||||
ret = sdscatlen(ret, curr++, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Parse a URI and extract the server connection information.
|
||||
* URI scheme is based on the the provisional specification[1] excluding support
|
||||
* for query parameters. Valid URIs are:
|
||||
* scheme: "redis://"
|
||||
* authority: [<username> ":"] <password> "@"] [<hostname> [":" <port>]]
|
||||
* path: ["/" [<db>]]
|
||||
*
|
||||
* [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */
|
||||
static void parseRedisUri(const char *uri) {
|
||||
|
||||
const char *scheme = "redis://";
|
||||
const char *curr = uri;
|
||||
const char *end = uri + strlen(uri);
|
||||
const char *userinfo, *username, *port, *host, *path;
|
||||
|
||||
/* URI must start with a valid scheme. */
|
||||
if (strncasecmp(scheme, curr, strlen(scheme))) {
|
||||
fprintf(stderr,"Invalid URI scheme\n");
|
||||
exit(1);
|
||||
}
|
||||
curr += strlen(scheme);
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract user info. */
|
||||
if ((userinfo = strchr(curr,'@'))) {
|
||||
if ((username = strchr(curr, ':')) && username < userinfo) {
|
||||
/* If provided, username is ignored. */
|
||||
curr = username + 1;
|
||||
}
|
||||
|
||||
config.auth = percentDecode(curr, userinfo - curr);
|
||||
curr = userinfo + 1;
|
||||
}
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract host and port. */
|
||||
path = strchr(curr, '/');
|
||||
if (*curr != '/') {
|
||||
host = path ? path - 1 : end;
|
||||
if ((port = strchr(curr, ':'))) {
|
||||
config.hostport = atoi(port + 1);
|
||||
host = port - 1;
|
||||
}
|
||||
config.hostip = sdsnewlen(curr, host - curr + 1);
|
||||
}
|
||||
curr = path ? path + 1 : end;
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract database number. */
|
||||
config.dbnum = atoi(curr);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Help functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -403,8 +495,7 @@ static void cliOutputHelp(int argc, char **argv) {
|
||||
if (argc == 0) {
|
||||
cliOutputGenericHelp();
|
||||
return;
|
||||
}
|
||||
else if (argc > 0 && argv[0][0] == '@') {
|
||||
} else if (argc > 0 && argv[0][0] == '@') {
|
||||
len = sizeof(commandGroups) / sizeof(char*);
|
||||
for (i = 0; i < len; i++) {
|
||||
if (strcasecmp(argv[0] + 1, commandGroups[i]) == 0) {
|
||||
@@ -430,8 +521,7 @@ static void cliOutputHelp(int argc, char **argv) {
|
||||
cliOutputCommandHelp(help, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (group == help->group) {
|
||||
cliOutputCommandHelp(help, 0);
|
||||
}
|
||||
@@ -452,8 +542,7 @@ static void completionCallback(const char *buf, linenoiseCompletions *lc) {
|
||||
startpos = 5;
|
||||
while (isspace(buf[startpos])) startpos++;
|
||||
mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
mask = CLI_HELP_COMMAND;
|
||||
}
|
||||
|
||||
@@ -564,8 +653,7 @@ static int cliConnect(int force) {
|
||||
|
||||
if (config.hostsocket == NULL) {
|
||||
context = redisConnect(config.hostip, config.hostport);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
context = redisConnectUnix(config.hostsocket);
|
||||
}
|
||||
|
||||
@@ -673,7 +761,7 @@ int isColorTerm(void) {
|
||||
return t != NULL && strstr(t, "xterm") != NULL;
|
||||
}
|
||||
|
||||
/* Helpe function for sdsCatColorizedLdbReply() appending colorize strings
|
||||
/* Helper function for sdsCatColorizedLdbReply() appending colorize strings
|
||||
* to an SDS string. */
|
||||
sds sdscatcolor(sds o, char *s, size_t len, char *color) {
|
||||
if (!isColorTerm()) return sdscatlen(o, s, len);
|
||||
@@ -953,7 +1041,7 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
for (j = 0; j < argc; j++)
|
||||
argvlen[j] = sdslen(argv[j]);
|
||||
|
||||
while (repeat--) {
|
||||
while(repeat-- > 0) {
|
||||
redisAppendCommandArgv(context, argc, (const char**) argv, argvlen);
|
||||
while (config.monitor_mode) {
|
||||
if (cliReadReply(output_raw) != REDIS_OK) exit(1);
|
||||
@@ -1206,6 +1294,7 @@ static void usage(void) {
|
||||
" -p <port> Server port (default: 6379).\n"
|
||||
" -s <socket> Server socket (overrides hostname and port).\n"
|
||||
" -a <password> Password to use when connecting to the server.\n"
|
||||
" -u <uri> Server URI.\n"
|
||||
" -r <repeat> Execute specified command N times.\n"
|
||||
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
||||
" It is possible to specify sub-second times like -i 0.1.\n"
|
||||
@@ -1237,6 +1326,8 @@ static void usage(void) {
|
||||
" no reply is received within <n> seconds.\n"
|
||||
" Default timeout: %d. Use 0 to wait forever.\n"
|
||||
" --bigkeys Sample Redis keys looking for big keys.\n"
|
||||
" --hotkeys Sample Redis keys looking for hot keys.\n"
|
||||
" only works when maxmemory-policy is *lfu.\n"
|
||||
" --scan List all keys using the SCAN command.\n"
|
||||
" --pattern <pat> Useful with --scan to specify a SCAN pattern.\n"
|
||||
" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
|
||||
@@ -1405,57 +1496,68 @@ static void repl(void) {
|
||||
cliRefreshPrompt();
|
||||
while ((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
PORT_LONG repeat = 1;
|
||||
int skipargs = 0;
|
||||
char *endptr = NULL;
|
||||
|
||||
argv = cliSplitArgs(line, &argc);
|
||||
|
||||
/* check if we have a repeat command option and
|
||||
* need to skip the first arg */
|
||||
if (argv && argc > 0) {
|
||||
errno = 0;
|
||||
repeat = strtol(argv[0], &endptr, 10);
|
||||
if (argc > 1 && *endptr == '\0') {
|
||||
if (errno == ERANGE || errno == EINVAL || repeat <= 0) {
|
||||
fputs("Invalid redis-cli repeat command option value.\n", stdout);
|
||||
sdsfreesplitres(argv, argc);
|
||||
linenoiseFree(line);
|
||||
continue;
|
||||
}
|
||||
skipargs = 1;
|
||||
} else {
|
||||
repeat = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Won't save auth command in history file */
|
||||
if (!(argv && argc > 0 && !strcasecmp(argv[0+skipargs], "auth"))) {
|
||||
if (history) linenoiseHistoryAdd(line);
|
||||
if (historyfile) linenoiseHistorySave(historyfile);
|
||||
}
|
||||
|
||||
if (argv == NULL) {
|
||||
printf("Invalid argument(s)\n");
|
||||
linenoiseFree(line);
|
||||
continue;
|
||||
}
|
||||
else if (argc > 0) {
|
||||
} else if (argc > 0) {
|
||||
if (strcasecmp(argv[0], "quit") == 0 ||
|
||||
strcasecmp(argv[0], "exit") == 0)
|
||||
{
|
||||
exit(0);
|
||||
}
|
||||
else if (argv[0][0] == ':') {
|
||||
} else if (argv[0][0] == ':') {
|
||||
cliSetPreferences(argv, argc, 1);
|
||||
sdsfreesplitres(argv,argc);
|
||||
linenoiseFree(line);
|
||||
continue;
|
||||
}
|
||||
else if (strcasecmp(argv[0], "restart") == 0) {
|
||||
} else if (strcasecmp(argv[0],"restart") == 0) {
|
||||
if (config.eval) {
|
||||
config.eval_ldb = 1;
|
||||
config.output = OUTPUT_RAW;
|
||||
return; /* Return to evalMode to restart the session. */
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
printf("Use 'restart' only in Lua debugging mode.");
|
||||
}
|
||||
}
|
||||
else if (argc == 3 && !strcasecmp(argv[0], "connect")) {
|
||||
} else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
|
||||
sdsfree(config.hostip);
|
||||
config.hostip = sdsnew(argv[1]);
|
||||
config.hostport = atoi(argv[2]);
|
||||
cliRefreshPrompt();
|
||||
cliConnect(1);
|
||||
}
|
||||
else if (argc == 1 && !strcasecmp(argv[0], "clear")) {
|
||||
} else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
|
||||
linenoiseClearScreen();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
PORT_LONGLONG start_time = mstime(), elapsed;
|
||||
int repeat, skipargs = 0;
|
||||
char *endptr;
|
||||
|
||||
repeat = strtol(argv[0], &endptr, 10);
|
||||
if (argc > 1 && *endptr == '\0' && repeat) {
|
||||
skipargs = 1;
|
||||
}
|
||||
else {
|
||||
repeat = 1;
|
||||
}
|
||||
|
||||
issueCommandRepeat(argc - skipargs, argv + skipargs, repeat);
|
||||
|
||||
@@ -1492,8 +1594,7 @@ static int noninteractive(int argc, char **argv) {
|
||||
argv = zrealloc(argv, (argc + 1) * sizeof(char*));
|
||||
argv[argc] = readArgFromStdin();
|
||||
retval = issueCommand(argc + 1, argv);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
retval = issueCommand(argc, argv);
|
||||
}
|
||||
return retval;
|
||||
@@ -2109,7 +2210,9 @@ static void pipeMode(void) {
|
||||
#define TYPE_SET 2
|
||||
#define TYPE_HASH 3
|
||||
#define TYPE_ZSET 4
|
||||
#define TYPE_NONE 5
|
||||
#define TYPE_STREAM 5
|
||||
#define TYPE_NONE 6
|
||||
#define TYPE_COUNT 7
|
||||
|
||||
static redisReply *sendScan(PORT_ULONGLONG *it) {
|
||||
redisReply *reply = redisCommand(context, "SCAN %llu", *it);
|
||||
@@ -2396,6 +2499,129 @@ static void findBigKeys(void) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void getKeyFreqs(redisReply *keys, PORT_ULONGLONG *freqs) {
|
||||
redisReply *reply;
|
||||
unsigned int i;
|
||||
|
||||
/* Pipeline OBJECT freq commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
redisAppendCommand(context, "OBJECT freq %s", keys->element[i]->str);
|
||||
}
|
||||
|
||||
/* Retrieve freqs */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
|
||||
fprintf(stderr, "Error getting freq for key '%s' (%d: %s)\n",
|
||||
keys->element[i]->str, context->err, context->errstr);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_INTEGER) {
|
||||
if(reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "Error: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else {
|
||||
fprintf(stderr, "Warning: OBJECT freq on '%s' failed (may have been deleted)\n", keys->element[i]->str);
|
||||
freqs[i] = 0;
|
||||
}
|
||||
} else {
|
||||
freqs[i] = reply->integer;
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
#define HOTKEYS_SAMPLE 16
|
||||
static void findHotKeys(void) {
|
||||
redisReply *keys, *reply;
|
||||
PORT_ULONGLONG counters[HOTKEYS_SAMPLE] = {0};
|
||||
sds hotkeys[HOTKEYS_SAMPLE] = {NULL};
|
||||
PORT_ULONGLONG sampled = 0, total_keys, *freqs = NULL, it = 0;
|
||||
unsigned int arrsize = 0, i, k;
|
||||
double pct;
|
||||
|
||||
/* Total keys pre scanning */
|
||||
total_keys = getDbSize();
|
||||
|
||||
/* Status message */
|
||||
printf("\n# Scanning the entire keyspace to find hot keys as well as\n");
|
||||
printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
|
||||
printf("# per 100 SCAN commands (not usually needed).\n\n");
|
||||
|
||||
/* SCAN loop */
|
||||
do {
|
||||
/* Calculate approximate percentage completion */
|
||||
pct = 100 * (double)sampled/total_keys;
|
||||
|
||||
/* Grab some keys and point to the keys array */
|
||||
reply = sendScan(&it);
|
||||
keys = reply->element[1];
|
||||
|
||||
/* Reallocate our freqs array if we need to */
|
||||
if(keys->elements > arrsize) {
|
||||
freqs = zrealloc(freqs, sizeof(PORT_ULONGLONG)*keys->elements);
|
||||
|
||||
if(!freqs) {
|
||||
fprintf(stderr, "Failed to allocate storage for keys!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
arrsize = keys->elements;
|
||||
}
|
||||
|
||||
getKeyFreqs(keys, freqs);
|
||||
|
||||
/* Now update our stats */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
sampled++;
|
||||
/* Update overall progress */
|
||||
if(sampled % 1000000 == 0) {
|
||||
printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
|
||||
}
|
||||
|
||||
/* Use eviction pool here */
|
||||
k = 0;
|
||||
while (k < HOTKEYS_SAMPLE && freqs[i] > counters[k]) k++;
|
||||
if (k == 0) continue;
|
||||
k--;
|
||||
if (k == 0 || counters[k] == 0) {
|
||||
sdsfree(hotkeys[k]);
|
||||
} else {
|
||||
sdsfree(hotkeys[0]);
|
||||
memmove(counters,counters+1,sizeof(counters[0])*k);
|
||||
memmove(hotkeys,hotkeys+1,sizeof(hotkeys[0])*k);
|
||||
}
|
||||
counters[k] = freqs[i];
|
||||
hotkeys[k] = sdsnew(keys->element[i]->str);
|
||||
printf(
|
||||
"[%05.2f%%] Hot key '%s' found so far with counter %llu\n",
|
||||
pct, keys->element[i]->str, freqs[i]);
|
||||
}
|
||||
|
||||
/* Sleep if we've been directed to do so */
|
||||
if(sampled && (sampled %100) == 0 && config.interval) {
|
||||
usleep(config.interval);
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
} while(it != 0);
|
||||
|
||||
if (freqs) zfree(freqs);
|
||||
|
||||
/* We're done */
|
||||
printf("\n-------- summary -------\n\n");
|
||||
|
||||
printf("Sampled %llu keys in the keyspace!\n", sampled);
|
||||
|
||||
for (i=1; i<= HOTKEYS_SAMPLE; i++) {
|
||||
k = HOTKEYS_SAMPLE - i;
|
||||
if(counters[k]>0) {
|
||||
printf("hot key found with counter: %llu\tkeyname: %s\n", counters[k], hotkeys[k]);
|
||||
sdsfree(hotkeys[k]);
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Stats mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -2782,6 +3008,7 @@ int main(int argc, char **argv) {
|
||||
config.pipe_mode = 0;
|
||||
config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
|
||||
config.bigkeys = 0;
|
||||
config.hotkeys = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
config.eval = NULL;
|
||||
@@ -2842,6 +3069,12 @@ int main(int argc, char **argv) {
|
||||
findBigKeys();
|
||||
}
|
||||
|
||||
/* Find hot keys */
|
||||
if (config.hotkeys) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
findHotKeys();
|
||||
}
|
||||
|
||||
/* Stat mode */
|
||||
if (config.stat_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
|
||||
+3112
File diff suppressed because it is too large
Load Diff
+131
-1
@@ -701,7 +701,12 @@ class RedisTrib
|
||||
|
||||
masters.each{|m| puts m}
|
||||
|
||||
# Alloc slots on masters
|
||||
# Rotating the list sometimes helps to get better initial
|
||||
# anti-affinity before the optimizer runs.
|
||||
interleaved.push interleaved.shift
|
||||
|
||||
# Alloc slots on masters. After interleaving to get just the first N
|
||||
# should be optimal. With slaves is more complex, see later...
|
||||
slots_per_node = ClusterHashSlots.to_f / masters_count
|
||||
first = 0
|
||||
cursor = 0.0
|
||||
@@ -769,6 +774,131 @@ class RedisTrib
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
optimize_anti_affinity
|
||||
end
|
||||
|
||||
def optimize_anti_affinity
|
||||
score,aux = get_anti_affinity_score
|
||||
return if score == 0
|
||||
|
||||
xputs ">>> Trying to optimize slaves allocation for anti-affinity"
|
||||
|
||||
maxiter = 500*@nodes.length # Effort is proportional to cluster size...
|
||||
while maxiter > 0
|
||||
score,offenders = get_anti_affinity_score
|
||||
break if score == 0 # Optimal anti affinity reached
|
||||
|
||||
# We'll try to randomly swap a slave's assigned master causing
|
||||
# an affinity problem with another random slave, to see if we
|
||||
# can improve the affinity.
|
||||
first = offenders.shuffle.first
|
||||
nodes = @nodes.select{|n| n != first && n.info[:replicate]}
|
||||
break if nodes.length == 0
|
||||
second = nodes.shuffle.first
|
||||
|
||||
first_master = first.info[:replicate]
|
||||
second_master = second.info[:replicate]
|
||||
first.set_as_replica(second_master)
|
||||
second.set_as_replica(first_master)
|
||||
|
||||
new_score,aux = get_anti_affinity_score
|
||||
# If the change actually makes thing worse, revert. Otherwise
|
||||
# leave as it is becuase the best solution may need a few
|
||||
# combined swaps.
|
||||
if new_score > score
|
||||
first.set_as_replica(first_master)
|
||||
second.set_as_replica(second_master)
|
||||
end
|
||||
|
||||
maxiter -= 1
|
||||
end
|
||||
|
||||
score,aux = get_anti_affinity_score
|
||||
if score == 0
|
||||
xputs "[OK] Perfect anti-affinity obtained!"
|
||||
elsif score >= 10000
|
||||
puts "[WARNING] Some slaves are in the same host as their master"
|
||||
else
|
||||
puts "[WARNING] Some slaves of the same master are in the same host"
|
||||
end
|
||||
end
|
||||
|
||||
# Return the anti-affinity score, which is a measure of the amount of
|
||||
# violations of anti-affinity in the current cluster layout, that is, how
|
||||
# badly the masters and slaves are distributed in the different IP
|
||||
# addresses so that slaves of the same master are not in the master
|
||||
# host and are also in different hosts.
|
||||
#
|
||||
# The score is calculated as follows:
|
||||
#
|
||||
# SAME_AS_MASTER = 10000 * each slave in the same IP of its master.
|
||||
# SAME_AS_SLAVE = 1 * each slave having the same IP as another slave
|
||||
# of the same master.
|
||||
# FINAL_SCORE = SAME_AS_MASTER + SAME_AS_SLAVE
|
||||
#
|
||||
# So a greater score means a worse anti-affinity level, while zero
|
||||
# means perfect anti-affinity.
|
||||
#
|
||||
# The anti affinity optimizator will try to get a score as low as
|
||||
# possible. Since we do not want to sacrifice the fact that slaves should
|
||||
# not be in the same host as the master, we assign 10000 times the score
|
||||
# to this violation, so that we'll optimize for the second factor only
|
||||
# if it does not impact the first one.
|
||||
#
|
||||
# The function returns two things: the above score, and the list of
|
||||
# offending slaves, so that the optimizer can try changing the
|
||||
# configuration of the slaves violating the anti-affinity goals.
|
||||
def get_anti_affinity_score
|
||||
score = 0
|
||||
offending = [] # List of offending slaves to return to the caller
|
||||
|
||||
# First, split nodes by host
|
||||
host_to_node = {}
|
||||
@nodes.each{|n|
|
||||
host = n.info[:host]
|
||||
host_to_node[host] = [] if host_to_node[host] == nil
|
||||
host_to_node[host] << n
|
||||
}
|
||||
|
||||
# Then, for each set of nodes in the same host, split by
|
||||
# related nodes (masters and slaves which are involved in
|
||||
# replication of each other)
|
||||
host_to_node.each{|host,nodes|
|
||||
related = {}
|
||||
nodes.each{|n|
|
||||
if !n.info[:replicate]
|
||||
name = n.info[:name]
|
||||
related[name] = [] if related[name] == nil
|
||||
related[name] << :m
|
||||
else
|
||||
name = n.info[:replicate]
|
||||
related[name] = [] if related[name] == nil
|
||||
related[name] << :s
|
||||
end
|
||||
}
|
||||
|
||||
# Now it's trivial to check, for each related group having the
|
||||
# same host, what is their local score.
|
||||
related.each{|id,types|
|
||||
next if types.length < 2
|
||||
types.sort! # Make sure :m if the first if any
|
||||
if types[0] == :m
|
||||
score += 10000 * (types.length-1)
|
||||
else
|
||||
score += 1 * types.length
|
||||
end
|
||||
|
||||
# Populate the list of offending nodes
|
||||
@nodes.each{|n|
|
||||
if n.info[:replicate] == id &&
|
||||
n.info[:host] == host
|
||||
offending << n
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
return score,offending
|
||||
end
|
||||
|
||||
def flush_nodes_config
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+48
-1
@@ -58,6 +58,41 @@
|
||||
#define REDISMODULE_HASH_CFIELDS (1<<2)
|
||||
#define REDISMODULE_HASH_EXISTS (1<<3)
|
||||
|
||||
/* Context Flags: Info about the current context returned by RM_GetContextFlags */
|
||||
|
||||
/* The command is running in the context of a Lua script */
|
||||
#define REDISMODULE_CTX_FLAGS_LUA 0x0001
|
||||
/* The command is running inside a Redis transaction */
|
||||
#define REDISMODULE_CTX_FLAGS_MULTI 0x0002
|
||||
/* The instance is a master */
|
||||
#define REDISMODULE_CTX_FLAGS_MASTER 0x0004
|
||||
/* The instance is a slave */
|
||||
#define REDISMODULE_CTX_FLAGS_SLAVE 0x0008
|
||||
/* The instance is read-only (usually meaning it's a slave as well) */
|
||||
#define REDISMODULE_CTX_FLAGS_READONLY 0x0010
|
||||
/* The instance is running in cluster mode */
|
||||
#define REDISMODULE_CTX_FLAGS_CLUSTER 0x0020
|
||||
/* The instance has AOF enabled */
|
||||
#define REDISMODULE_CTX_FLAGS_AOF 0x0040 //
|
||||
/* The instance has RDB enabled */
|
||||
#define REDISMODULE_CTX_FLAGS_RDB 0x0080 //
|
||||
/* The instance has Maxmemory set */
|
||||
#define REDISMODULE_CTX_FLAGS_MAXMEMORY 0x0100
|
||||
/* Maxmemory is set and has an eviction policy that may delete keys */
|
||||
#define REDISMODULE_CTX_FLAGS_EVICT 0x0200
|
||||
|
||||
|
||||
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
|
||||
#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */
|
||||
#define REDISMODULE_NOTIFY_LIST (1<<4) /* l */
|
||||
#define REDISMODULE_NOTIFY_SET (1<<5) /* s */
|
||||
#define REDISMODULE_NOTIFY_HASH (1<<6) /* h */
|
||||
#define REDISMODULE_NOTIFY_ZSET (1<<7) /* z */
|
||||
#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED) /* A */
|
||||
|
||||
|
||||
/* A special pointer that we can use between the core and the module to signal
|
||||
* field deletion, and that is impossible to be a valid pointer. */
|
||||
#define REDISMODULE_HASH_DELETE ((RedisModuleString*)(PORT_LONG)1)
|
||||
@@ -88,6 +123,7 @@ typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
|
||||
typedef int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
|
||||
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
|
||||
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
|
||||
@@ -119,7 +155,8 @@ void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsModuleNameBusy)(const char *name);
|
||||
int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, PORT_LONGLONG ll);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx);
|
||||
@@ -160,6 +197,7 @@ int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnlinkKey)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
|
||||
@@ -183,6 +221,7 @@ int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ..
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
|
||||
PORT_ULONGLONG REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
|
||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
|
||||
@@ -224,6 +263,8 @@ RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModu
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb);
|
||||
|
||||
#endif
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
@@ -238,6 +279,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(Strdup);
|
||||
REDISMODULE_GET_API(CreateCommand);
|
||||
REDISMODULE_GET_API(SetModuleAttribs);
|
||||
REDISMODULE_GET_API(IsModuleNameBusy);
|
||||
REDISMODULE_GET_API(WrongArity);
|
||||
REDISMODULE_GET_API(ReplyWithLongLong);
|
||||
REDISMODULE_GET_API(ReplyWithError);
|
||||
@@ -279,6 +321,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(Replicate);
|
||||
REDISMODULE_GET_API(ReplicateVerbatim);
|
||||
REDISMODULE_GET_API(DeleteKey);
|
||||
REDISMODULE_GET_API(UnlinkKey);
|
||||
REDISMODULE_GET_API(StringSet);
|
||||
REDISMODULE_GET_API(StringDMA);
|
||||
REDISMODULE_GET_API(StringTruncate);
|
||||
@@ -302,6 +345,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(IsKeysPositionRequest);
|
||||
REDISMODULE_GET_API(KeyAtPos);
|
||||
REDISMODULE_GET_API(GetClientId);
|
||||
REDISMODULE_GET_API(GetContextFlags);
|
||||
REDISMODULE_GET_API(PoolAlloc);
|
||||
REDISMODULE_GET_API(CreateDataType);
|
||||
REDISMODULE_GET_API(ModuleTypeSetValue);
|
||||
@@ -342,8 +386,11 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(IsBlockedTimeoutRequest);
|
||||
REDISMODULE_GET_API(GetBlockedClientPrivateData);
|
||||
REDISMODULE_GET_API(AbortBlock);
|
||||
REDISMODULE_GET_API(SubscribeToKeyspaceEvents);
|
||||
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
#ifndef REDISMODULE_H
|
||||
#define REDISMODULE_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* ---------------- Defines common between core and modules --------------- */
|
||||
|
||||
/* Error status return values. */
|
||||
#define REDISMODULE_OK 0
|
||||
#define REDISMODULE_ERR 1
|
||||
|
||||
/* API versions. */
|
||||
#define REDISMODULE_APIVER_1 1
|
||||
|
||||
/* API flags and constants */
|
||||
#define REDISMODULE_READ (1<<0)
|
||||
#define REDISMODULE_WRITE (1<<1)
|
||||
|
||||
#define REDISMODULE_LIST_HEAD 0
|
||||
#define REDISMODULE_LIST_TAIL 1
|
||||
|
||||
/* Key types. */
|
||||
#define REDISMODULE_KEYTYPE_EMPTY 0
|
||||
#define REDISMODULE_KEYTYPE_STRING 1
|
||||
#define REDISMODULE_KEYTYPE_LIST 2
|
||||
#define REDISMODULE_KEYTYPE_HASH 3
|
||||
#define REDISMODULE_KEYTYPE_SET 4
|
||||
#define REDISMODULE_KEYTYPE_ZSET 5
|
||||
#define REDISMODULE_KEYTYPE_MODULE 6
|
||||
|
||||
/* Reply types. */
|
||||
#define REDISMODULE_REPLY_UNKNOWN -1
|
||||
#define REDISMODULE_REPLY_STRING 0
|
||||
#define REDISMODULE_REPLY_ERROR 1
|
||||
#define REDISMODULE_REPLY_INTEGER 2
|
||||
#define REDISMODULE_REPLY_ARRAY 3
|
||||
#define REDISMODULE_REPLY_NULL 4
|
||||
|
||||
/* Postponed array length. */
|
||||
#define REDISMODULE_POSTPONED_ARRAY_LEN -1
|
||||
|
||||
/* Expire */
|
||||
#define REDISMODULE_NO_EXPIRE -1
|
||||
|
||||
/* Sorted set API flags. */
|
||||
#define REDISMODULE_ZADD_XX (1<<0)
|
||||
#define REDISMODULE_ZADD_NX (1<<1)
|
||||
#define REDISMODULE_ZADD_ADDED (1<<2)
|
||||
#define REDISMODULE_ZADD_UPDATED (1<<3)
|
||||
#define REDISMODULE_ZADD_NOP (1<<4)
|
||||
|
||||
/* Hash API flags. */
|
||||
#define REDISMODULE_HASH_NONE 0
|
||||
#define REDISMODULE_HASH_NX (1<<0)
|
||||
#define REDISMODULE_HASH_XX (1<<1)
|
||||
#define REDISMODULE_HASH_CFIELDS (1<<2)
|
||||
#define REDISMODULE_HASH_EXISTS (1<<3)
|
||||
|
||||
/* Context Flags: Info about the current context returned by RM_GetContextFlags */
|
||||
|
||||
/* The command is running in the context of a Lua script */
|
||||
#define REDISMODULE_CTX_FLAGS_LUA 0x0001
|
||||
/* The command is running inside a Redis transaction */
|
||||
#define REDISMODULE_CTX_FLAGS_MULTI 0x0002
|
||||
/* The instance is a master */
|
||||
#define REDISMODULE_CTX_FLAGS_MASTER 0x0004
|
||||
/* The instance is a slave */
|
||||
#define REDISMODULE_CTX_FLAGS_SLAVE 0x0008
|
||||
/* The instance is read-only (usually meaning it's a slave as well) */
|
||||
#define REDISMODULE_CTX_FLAGS_READONLY 0x0010
|
||||
/* The instance is running in cluster mode */
|
||||
#define REDISMODULE_CTX_FLAGS_CLUSTER 0x0020
|
||||
/* The instance has AOF enabled */
|
||||
#define REDISMODULE_CTX_FLAGS_AOF 0x0040 //
|
||||
/* The instance has RDB enabled */
|
||||
#define REDISMODULE_CTX_FLAGS_RDB 0x0080 //
|
||||
/* The instance has Maxmemory set */
|
||||
#define REDISMODULE_CTX_FLAGS_MAXMEMORY 0x0100
|
||||
/* Maxmemory is set and has an eviction policy that may delete keys */
|
||||
#define REDISMODULE_CTX_FLAGS_EVICT 0x0200
|
||||
|
||||
|
||||
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
|
||||
#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */
|
||||
#define REDISMODULE_NOTIFY_LIST (1<<4) /* l */
|
||||
#define REDISMODULE_NOTIFY_SET (1<<5) /* s */
|
||||
#define REDISMODULE_NOTIFY_HASH (1<<6) /* h */
|
||||
#define REDISMODULE_NOTIFY_ZSET (1<<7) /* z */
|
||||
#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED) /* A */
|
||||
|
||||
|
||||
/* A special pointer that we can use between the core and the module to signal
|
||||
* field deletion, and that is impossible to be a valid pointer. */
|
||||
#define REDISMODULE_HASH_DELETE ((RedisModuleString*)(PORT_LONG)1)
|
||||
|
||||
/* Error messages. */
|
||||
#define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value"
|
||||
|
||||
#define REDISMODULE_POSITIVE_INFINITE (1.0/0.0)
|
||||
#define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0)
|
||||
|
||||
#define REDISMODULE_NOT_USED(V) ((void) V)
|
||||
|
||||
/* ------------------------- End of common defines ------------------------ */
|
||||
|
||||
#ifndef REDISMODULE_CORE
|
||||
|
||||
typedef PORT_LONGLONG mstime_t;
|
||||
|
||||
/* Incomplete structures for compiler checks but opaque access. */
|
||||
typedef struct RedisModuleCtx RedisModuleCtx;
|
||||
typedef struct RedisModuleKey RedisModuleKey;
|
||||
typedef struct RedisModuleString RedisModuleString;
|
||||
typedef struct RedisModuleCallReply RedisModuleCallReply;
|
||||
typedef struct RedisModuleIO RedisModuleIO;
|
||||
typedef struct RedisModuleType RedisModuleType;
|
||||
typedef struct RedisModuleDigest RedisModuleDigest;
|
||||
typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
|
||||
typedef int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
|
||||
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
|
||||
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
|
||||
typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value);
|
||||
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
|
||||
typedef void (*RedisModuleTypeFreeFunc)(void *value);
|
||||
|
||||
#define REDISMODULE_TYPE_METHOD_VERSION 1
|
||||
typedef struct RedisModuleTypeMethods {
|
||||
uint64_t version;
|
||||
RedisModuleTypeLoadFunc rdb_load;
|
||||
RedisModuleTypeSaveFunc rdb_save;
|
||||
RedisModuleTypeRewriteFunc aof_rewrite;
|
||||
RedisModuleTypeMemUsageFunc mem_usage;
|
||||
RedisModuleTypeDigestFunc digest;
|
||||
RedisModuleTypeFreeFunc free;
|
||||
} RedisModuleTypeMethods;
|
||||
|
||||
#define REDISMODULE_GET_API(name) \
|
||||
RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name))
|
||||
|
||||
#define REDISMODULE_API_FUNC(x) (*x)
|
||||
|
||||
|
||||
void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_Realloc)(void *ptr, size_t bytes);
|
||||
void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsModuleNameBusy)(const char *name);
|
||||
int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, PORT_LONGLONG ll);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode);
|
||||
void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp);
|
||||
int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp);
|
||||
size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where);
|
||||
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply);
|
||||
PORT_LONGLONG REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply);
|
||||
size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply);
|
||||
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, PORT_LONGLONG ll);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, PORT_LONGLONG *ll);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d);
|
||||
void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnlinkKey)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
|
||||
mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...);
|
||||
int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
|
||||
PORT_ULONGLONG REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
|
||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
|
||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value);
|
||||
uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value);
|
||||
int64_t REDISMODULE_API_FUNC(RedisModule_LoadSigned)(RedisModuleIO *io);
|
||||
void REDISMODULE_API_FUNC(RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_LoadString)(RedisModuleIO *io);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double value);
|
||||
double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value);
|
||||
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
|
||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
|
||||
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
|
||||
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io);
|
||||
PORT_LONGLONG REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, PORT_LONGLONG ele);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestEndSequence)(RedisModuleDigest *md);
|
||||
|
||||
/* Experimental APIs */
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), PORT_LONGLONG timeout_ms);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AbortBlock)(RedisModuleBlockedClient *bc);
|
||||
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx);
|
||||
#endif
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
|
||||
void *getapifuncptr = ((void**)ctx)[0];
|
||||
RedisModule_GetApi = (int (*)(const char *, void *)) (PORT_ULONG)getapifuncptr;
|
||||
REDISMODULE_GET_API(Alloc);
|
||||
REDISMODULE_GET_API(Calloc);
|
||||
REDISMODULE_GET_API(Free);
|
||||
REDISMODULE_GET_API(Realloc);
|
||||
REDISMODULE_GET_API(Strdup);
|
||||
REDISMODULE_GET_API(CreateCommand);
|
||||
REDISMODULE_GET_API(SetModuleAttribs);
|
||||
REDISMODULE_GET_API(WrongArity);
|
||||
REDISMODULE_GET_API(ReplyWithLongLong);
|
||||
REDISMODULE_GET_API(ReplyWithError);
|
||||
REDISMODULE_GET_API(ReplyWithSimpleString);
|
||||
REDISMODULE_GET_API(ReplyWithArray);
|
||||
REDISMODULE_GET_API(ReplySetArrayLength);
|
||||
REDISMODULE_GET_API(ReplyWithStringBuffer);
|
||||
REDISMODULE_GET_API(ReplyWithString);
|
||||
REDISMODULE_GET_API(ReplyWithNull);
|
||||
REDISMODULE_GET_API(ReplyWithCallReply);
|
||||
REDISMODULE_GET_API(ReplyWithDouble);
|
||||
REDISMODULE_GET_API(ReplySetArrayLength);
|
||||
REDISMODULE_GET_API(GetSelectedDb);
|
||||
REDISMODULE_GET_API(SelectDb);
|
||||
REDISMODULE_GET_API(OpenKey);
|
||||
REDISMODULE_GET_API(CloseKey);
|
||||
REDISMODULE_GET_API(KeyType);
|
||||
REDISMODULE_GET_API(ValueLength);
|
||||
REDISMODULE_GET_API(ListPush);
|
||||
REDISMODULE_GET_API(ListPop);
|
||||
REDISMODULE_GET_API(StringToLongLong);
|
||||
REDISMODULE_GET_API(StringToDouble);
|
||||
REDISMODULE_GET_API(Call);
|
||||
REDISMODULE_GET_API(CallReplyProto);
|
||||
REDISMODULE_GET_API(FreeCallReply);
|
||||
REDISMODULE_GET_API(CallReplyInteger);
|
||||
REDISMODULE_GET_API(CallReplyType);
|
||||
REDISMODULE_GET_API(CallReplyLength);
|
||||
REDISMODULE_GET_API(CallReplyArrayElement);
|
||||
REDISMODULE_GET_API(CallReplyStringPtr);
|
||||
REDISMODULE_GET_API(CreateStringFromCallReply);
|
||||
REDISMODULE_GET_API(CreateString);
|
||||
REDISMODULE_GET_API(CreateStringFromLongLong);
|
||||
REDISMODULE_GET_API(CreateStringFromString);
|
||||
REDISMODULE_GET_API(CreateStringPrintf);
|
||||
REDISMODULE_GET_API(FreeString);
|
||||
REDISMODULE_GET_API(StringPtrLen);
|
||||
REDISMODULE_GET_API(AutoMemory);
|
||||
REDISMODULE_GET_API(Replicate);
|
||||
REDISMODULE_GET_API(ReplicateVerbatim);
|
||||
REDISMODULE_GET_API(DeleteKey);
|
||||
REDISMODULE_GET_API(StringSet);
|
||||
REDISMODULE_GET_API(StringDMA);
|
||||
REDISMODULE_GET_API(StringTruncate);
|
||||
REDISMODULE_GET_API(GetExpire);
|
||||
REDISMODULE_GET_API(SetExpire);
|
||||
REDISMODULE_GET_API(ZsetAdd);
|
||||
REDISMODULE_GET_API(ZsetIncrby);
|
||||
REDISMODULE_GET_API(ZsetScore);
|
||||
REDISMODULE_GET_API(ZsetRem);
|
||||
REDISMODULE_GET_API(ZsetRangeStop);
|
||||
REDISMODULE_GET_API(ZsetFirstInScoreRange);
|
||||
REDISMODULE_GET_API(ZsetLastInScoreRange);
|
||||
REDISMODULE_GET_API(ZsetFirstInLexRange);
|
||||
REDISMODULE_GET_API(ZsetLastInLexRange);
|
||||
REDISMODULE_GET_API(ZsetRangeCurrentElement);
|
||||
REDISMODULE_GET_API(ZsetRangeNext);
|
||||
REDISMODULE_GET_API(ZsetRangePrev);
|
||||
REDISMODULE_GET_API(ZsetRangeEndReached);
|
||||
REDISMODULE_GET_API(HashSet);
|
||||
REDISMODULE_GET_API(HashGet);
|
||||
REDISMODULE_GET_API(IsKeysPositionRequest);
|
||||
REDISMODULE_GET_API(KeyAtPos);
|
||||
REDISMODULE_GET_API(GetClientId);
|
||||
REDISMODULE_GET_API(PoolAlloc);
|
||||
REDISMODULE_GET_API(CreateDataType);
|
||||
REDISMODULE_GET_API(ModuleTypeSetValue);
|
||||
REDISMODULE_GET_API(ModuleTypeGetType);
|
||||
REDISMODULE_GET_API(ModuleTypeGetValue);
|
||||
REDISMODULE_GET_API(SaveUnsigned);
|
||||
REDISMODULE_GET_API(LoadUnsigned);
|
||||
REDISMODULE_GET_API(SaveSigned);
|
||||
REDISMODULE_GET_API(LoadSigned);
|
||||
REDISMODULE_GET_API(SaveString);
|
||||
REDISMODULE_GET_API(SaveStringBuffer);
|
||||
REDISMODULE_GET_API(LoadString);
|
||||
REDISMODULE_GET_API(LoadStringBuffer);
|
||||
REDISMODULE_GET_API(SaveDouble);
|
||||
REDISMODULE_GET_API(LoadDouble);
|
||||
REDISMODULE_GET_API(SaveFloat);
|
||||
REDISMODULE_GET_API(LoadFloat);
|
||||
REDISMODULE_GET_API(EmitAOF);
|
||||
REDISMODULE_GET_API(Log);
|
||||
REDISMODULE_GET_API(LogIOError);
|
||||
REDISMODULE_GET_API(StringAppendBuffer);
|
||||
REDISMODULE_GET_API(RetainString);
|
||||
REDISMODULE_GET_API(StringCompare);
|
||||
REDISMODULE_GET_API(GetContextFromIO);
|
||||
REDISMODULE_GET_API(Milliseconds);
|
||||
REDISMODULE_GET_API(DigestAddStringBuffer);
|
||||
REDISMODULE_GET_API(DigestAddLongLong);
|
||||
REDISMODULE_GET_API(DigestEndSequence);
|
||||
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
REDISMODULE_GET_API(GetThreadSafeContext);
|
||||
REDISMODULE_GET_API(FreeThreadSafeContext);
|
||||
REDISMODULE_GET_API(ThreadSafeContextLock);
|
||||
REDISMODULE_GET_API(ThreadSafeContextUnlock);
|
||||
REDISMODULE_GET_API(BlockClient);
|
||||
REDISMODULE_GET_API(UnblockClient);
|
||||
REDISMODULE_GET_API(IsBlockedReplyRequest);
|
||||
REDISMODULE_GET_API(IsBlockedTimeoutRequest);
|
||||
REDISMODULE_GET_API(GetBlockedClientPrivateData);
|
||||
REDISMODULE_GET_API(AbortBlock);
|
||||
#endif
|
||||
|
||||
RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* Things only defined for the modules core, not exported to modules
|
||||
* including this file. */
|
||||
#define RedisModuleString robj
|
||||
|
||||
#endif /* REDISMODULE_CORE */
|
||||
#endif /* REDISMOUDLE_H */
|
||||
+29
-1
@@ -1446,6 +1446,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
||||
cmd = sdscat(cmd,arg);
|
||||
}
|
||||
cmd = sdscatlen(cmd,"\r\n",2);
|
||||
va_end(ap);
|
||||
|
||||
/* Transfer command to the server. */
|
||||
if (syncWrite(fd,cmd,(ssize_t)sdslen(cmd),server.repl_syncio_timeout*1000) WIN_PORT_FIX /* cast (ssize_t) */
|
||||
@@ -1456,7 +1457,6 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
||||
IF_WIN32(wsa_strerror(errno),strerror(errno)));
|
||||
}
|
||||
sdsfree(cmd);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* Read the reply from the server. */
|
||||
@@ -2097,6 +2097,12 @@ void replicationUnsetMaster(void) {
|
||||
* with PSYNC version 2, there is no need for full resync after a
|
||||
* master switch. */
|
||||
server.slaveseldb = -1;
|
||||
|
||||
/* Once we turn from slave to master, we consider the starting time without
|
||||
* slaves (that is used to count the replication backlog time to live) as
|
||||
* starting from now. Otherwise the backlog will be freed after a
|
||||
* failover if slaves do not connect immediately. */
|
||||
server.repl_no_slaves_since = server.unixtime;
|
||||
}
|
||||
|
||||
/* This function is called when the slave lose the connection with the
|
||||
@@ -2516,6 +2522,11 @@ void waitCommand(client *c) {
|
||||
PORT_LONG numreplicas, ackreplicas;
|
||||
PORT_LONGLONG offset = c->woff;
|
||||
|
||||
if (server.masterhost) {
|
||||
addReplyError(c,"WAIT cannot be used with slave instances. Please also note that since Redis 4.0 if a slave is configured to be writable (which is not the default) writes to slaves are just local and are not propagated.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Argument parsing. */
|
||||
if (getLongFromObjectOrReply(c,c->argv[1],&numreplicas,NULL) != C_OK)
|
||||
return;
|
||||
@@ -2740,6 +2751,23 @@ void replicationCron(void) {
|
||||
time_t idle = server.unixtime - server.repl_no_slaves_since;
|
||||
|
||||
if (idle > server.repl_backlog_time_limit) {
|
||||
/* When we free the backlog, we always use a new
|
||||
* replication ID and clear the ID2. This is needed
|
||||
* because when there is no backlog, the master_repl_offset
|
||||
* is not updated, but we would still retain our replication
|
||||
* ID, leading to the following problem:
|
||||
*
|
||||
* 1. We are a master instance.
|
||||
* 2. Our slave is promoted to master. It's repl-id-2 will
|
||||
* be the same as our repl-id.
|
||||
* 3. We, yet as master, receive some updates, that will not
|
||||
* increment the master_repl_offset.
|
||||
* 4. Later we are turned into a slave, connecto to the new
|
||||
* master that will accept our PSYNC request by second
|
||||
* replication ID, but there will be data inconsistency
|
||||
* because we received writes. */
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
freeReplicationBacklog();
|
||||
serverLog(LL_NOTICE,
|
||||
"Replication backlog freed after %d seconds "
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -165,7 +165,7 @@ void rioInitWithFile(rio *r, FILE *fp) {
|
||||
/* ------------------- File descriptors set implementation ------------------- */
|
||||
|
||||
/* Returns 1 or 0 for success/failure.
|
||||
* The function returns success as PORT_LONG as we are able to correctly write
|
||||
* The function returns success as long as we are able to correctly write
|
||||
* to at least one file descriptor.
|
||||
*
|
||||
* When buf is NULL and len is 0, the function performs a flush operation
|
||||
@@ -315,7 +315,7 @@ void rioSetAutoSync(rio *r, off_t bytes) {
|
||||
* generating the Redis protocol for the Append Only File. */
|
||||
|
||||
/* Write multi bulk count in the format: "*<count>\r\n". */
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, int count) {
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, PORT_LONG count) {
|
||||
char cbuf[128];
|
||||
int clen;
|
||||
|
||||
@@ -331,7 +331,7 @@ size_t rioWriteBulkCount(rio *r, char prefix, int count) {
|
||||
size_t rioWriteBulkString(rio *r, const char *buf, size_t len) {
|
||||
size_t nwritten;
|
||||
|
||||
if ((nwritten = rioWriteBulkCount(r,'$',(int)len)) == 0) return 0; WIN_PORT_FIX /* cast (int) */
|
||||
if ((nwritten = rioWriteBulkCount(r,'$',(PORT_LONG)len)) == 0) return 0; WIN_PORT_FIX /* cast (PORT_LONG) */
|
||||
if (len > 0 && rioWrite(r,buf,len) == 0) return 0;
|
||||
if (rioWrite(r,"\r\n",2) == 0) return 0;
|
||||
return nwritten+len+2;
|
||||
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
/* rio.c is a simple stream-oriented I/O abstraction that provides an interface
|
||||
* to write code that can consume/produce data using different concrete input
|
||||
* and output devices. For instance the same rdb.c code using the rio
|
||||
* abstraction can be used to read and write the RDB format using in-memory
|
||||
* buffers or files.
|
||||
*
|
||||
* A rio object provides the following methods:
|
||||
* read: read from stream.
|
||||
* write: write to stream.
|
||||
* tell: get the current offset.
|
||||
*
|
||||
* It is also possible to set a 'checksum' method that is used by rio.c in order
|
||||
* to compute a checksum of the data written or read, or to query the rio object
|
||||
* for the current checksum.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Win32_Interop/Win32_Portability.h"
|
||||
#include "win32_interop/win32_types.h"
|
||||
#include "Win32_Interop/Win32_FDAPI.h"
|
||||
#endif
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
POSIX_ONLY(#include <unistd.h>)
|
||||
#include "rio.h"
|
||||
#include "util.h"
|
||||
#include "crc64.h"
|
||||
#include "config.h"
|
||||
#include "server.h"
|
||||
|
||||
/* ------------------------- Buffer I/O implementation ----------------------- */
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioBufferWrite(rio *r, const void *buf, size_t len) {
|
||||
r->io.buffer.ptr = sdscatlen(r->io.buffer.ptr,(char*)buf,len);
|
||||
r->io.buffer.pos += (off_t)len; WIN_PORT_FIX /* cast (off_t) */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioBufferRead(rio *r, void *buf, size_t len) {
|
||||
if (sdslen(r->io.buffer.ptr)-r->io.buffer.pos < len)
|
||||
return 0; /* not enough buffer to return len bytes. */
|
||||
memcpy(buf,r->io.buffer.ptr+r->io.buffer.pos,len);
|
||||
r->io.buffer.pos += (off_t)len; WIN_PORT_FIX /* cast (off_t) */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Returns read/write position in buffer. */
|
||||
static off_t rioBufferTell(rio *r) {
|
||||
return r->io.buffer.pos;
|
||||
}
|
||||
|
||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||
* and 0 on failures. */
|
||||
static int rioBufferFlush(rio *r) {
|
||||
UNUSED(r);
|
||||
return 1; /* Nothing to do, our write just appends to the buffer. */
|
||||
}
|
||||
|
||||
static const rio rioBufferIO = {
|
||||
rioBufferRead,
|
||||
rioBufferWrite,
|
||||
rioBufferTell,
|
||||
rioBufferFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
void rioInitWithBuffer(rio *r, sds s) {
|
||||
*r = rioBufferIO;
|
||||
r->io.buffer.ptr = s;
|
||||
r->io.buffer.pos = 0;
|
||||
}
|
||||
|
||||
/* --------------------- Stdio file pointer implementation ------------------- */
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
|
||||
size_t retval;
|
||||
|
||||
retval = fwrite(buf,len,1,r->io.file.fp);
|
||||
r->io.file.buffered += (off_t)len; WIN_PORT_FIX /* cast (off_t) */
|
||||
|
||||
if (r->io.file.autosync &&
|
||||
r->io.file.buffered >= r->io.file.autosync)
|
||||
{
|
||||
fflush(r->io.file.fp);
|
||||
aof_fsync(fileno(r->io.file.fp));
|
||||
r->io.file.buffered = 0;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioFileRead(rio *r, void *buf, size_t len) {
|
||||
return fread(buf,len,1,r->io.file.fp);
|
||||
}
|
||||
|
||||
/* Returns read/write position in file. */
|
||||
static off_t rioFileTell(rio *r) {
|
||||
return (off_t)ftello(r->io.file.fp); WIN_PORT_FIX /* cast (int) */
|
||||
}
|
||||
|
||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||
* and 0 on failures. */
|
||||
static int rioFileFlush(rio *r) {
|
||||
return (fflush(r->io.file.fp) == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
static const rio rioFileIO = {
|
||||
rioFileRead,
|
||||
rioFileWrite,
|
||||
rioFileTell,
|
||||
rioFileFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
void rioInitWithFile(rio *r, FILE *fp) {
|
||||
*r = rioFileIO;
|
||||
r->io.file.fp = fp;
|
||||
r->io.file.buffered = 0;
|
||||
r->io.file.autosync = 0;
|
||||
}
|
||||
|
||||
/* ------------------- File descriptors set implementation ------------------- */
|
||||
|
||||
/* Returns 1 or 0 for success/failure.
|
||||
* The function returns success as PORT_LONG as we are able to correctly write
|
||||
* to at least one file descriptor.
|
||||
*
|
||||
* When buf is NULL and len is 0, the function performs a flush operation
|
||||
* if there is some pending buffer, so this function is also used in order
|
||||
* to implement rioFdsetFlush(). */
|
||||
static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
|
||||
ssize_t retval;
|
||||
int j;
|
||||
unsigned char *p = (unsigned char*) buf;
|
||||
int doflush = (buf == NULL && len == 0);
|
||||
|
||||
/* To start we always append to our buffer. If it gets larger than
|
||||
* a given size, we actually write to the sockets. */
|
||||
if (len) {
|
||||
r->io.fdset.buf = sdscatlen(r->io.fdset.buf,buf,len);
|
||||
len = 0; /* Prevent entering the while below if we don't flush. */
|
||||
if (sdslen(r->io.fdset.buf) > PROTO_IOBUF_LEN) doflush = 1;
|
||||
}
|
||||
|
||||
if (doflush) {
|
||||
p = (unsigned char*) r->io.fdset.buf;
|
||||
len = sdslen(r->io.fdset.buf);
|
||||
}
|
||||
|
||||
/* Write in little chunchs so that when there are big writes we
|
||||
* parallelize while the kernel is sending data in background to
|
||||
* the TCP socket. */
|
||||
while(len) {
|
||||
size_t count = len < 1024 ? len : 1024;
|
||||
int broken = 0;
|
||||
for (j = 0; j < r->io.fdset.numfds; j++) {
|
||||
if (r->io.fdset.state[j] != 0) {
|
||||
/* Skip FDs alraedy in error. */
|
||||
broken++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Make sure to write 'count' bytes to the socket regardless
|
||||
* of short writes. */
|
||||
size_t nwritten = 0;
|
||||
while(nwritten != count) {
|
||||
retval = write(r->io.fdset.fds[j],p+nwritten,count-nwritten);
|
||||
if (retval <= 0) {
|
||||
/* With blocking sockets, which is the sole user of this
|
||||
* rio target, EWOULDBLOCK is returned only because of
|
||||
* the SO_SNDTIMEO socket option, so we translate the error
|
||||
* into one more recognizable by the user. */
|
||||
if (retval == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
nwritten += retval;
|
||||
}
|
||||
|
||||
if (nwritten != count) {
|
||||
/* Mark this FD as broken. */
|
||||
r->io.fdset.state[j] = errno;
|
||||
if (r->io.fdset.state[j] == 0) r->io.fdset.state[j] = EIO;
|
||||
}
|
||||
}
|
||||
if (broken == r->io.fdset.numfds) return 0; /* All the FDs in error. */
|
||||
p += count;
|
||||
len -= count;
|
||||
r->io.fdset.pos += count;
|
||||
}
|
||||
|
||||
if (doflush) sdsclear(r->io.fdset.buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioFdsetRead(rio *r, void *buf, size_t len) {
|
||||
UNUSED(r);
|
||||
UNUSED(buf);
|
||||
UNUSED(len);
|
||||
return 0; /* Error, this target does not support reading. */
|
||||
}
|
||||
|
||||
/* Returns read/write position in file. */
|
||||
static off_t rioFdsetTell(rio *r) {
|
||||
return r->io.fdset.pos;
|
||||
}
|
||||
|
||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||
* and 0 on failures. */
|
||||
static int rioFdsetFlush(rio *r) {
|
||||
/* Our flush is implemented by the write method, that recognizes a
|
||||
* buffer set to NULL with a count of zero as a flush request. */
|
||||
return (int)rioFdsetWrite(r,NULL,0); WIN_PORT_FIX /* cast (int) */
|
||||
}
|
||||
|
||||
static const rio rioFdsetIO = {
|
||||
rioFdsetRead,
|
||||
rioFdsetWrite,
|
||||
rioFdsetTell,
|
||||
rioFdsetFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
void rioInitWithFdset(rio *r, int *fds, int numfds) {
|
||||
int j;
|
||||
|
||||
*r = rioFdsetIO;
|
||||
r->io.fdset.fds = zmalloc(sizeof(int)*numfds);
|
||||
r->io.fdset.state = zmalloc(sizeof(int)*numfds);
|
||||
memcpy(r->io.fdset.fds,fds,sizeof(int)*numfds);
|
||||
for (j = 0; j < numfds; j++) r->io.fdset.state[j] = 0;
|
||||
r->io.fdset.numfds = numfds;
|
||||
r->io.fdset.pos = 0;
|
||||
r->io.fdset.buf = sdsempty();
|
||||
}
|
||||
|
||||
/* release the rio stream. */
|
||||
void rioFreeFdset(rio *r) {
|
||||
zfree(r->io.fdset.fds);
|
||||
zfree(r->io.fdset.state);
|
||||
sdsfree(r->io.fdset.buf);
|
||||
}
|
||||
|
||||
/* ---------------------------- Generic functions ---------------------------- */
|
||||
|
||||
/* This function can be installed both in memory and file streams when checksum
|
||||
* computation is needed. */
|
||||
void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len) {
|
||||
r->cksum = crc64(r->cksum,buf,len);
|
||||
}
|
||||
|
||||
/* Set the file-based rio object to auto-fsync every 'bytes' file written.
|
||||
* By default this is set to zero that means no automatic file sync is
|
||||
* performed.
|
||||
*
|
||||
* This feature is useful in a few contexts since when we rely on OS write
|
||||
* buffers sometimes the OS buffers way too much, resulting in too many
|
||||
* disk I/O concentrated in very little time. When we fsync in an explicit
|
||||
* way instead the I/O pressure is more distributed across time. */
|
||||
void rioSetAutoSync(rio *r, off_t bytes) {
|
||||
serverAssert(r->read == rioFileIO.read);
|
||||
r->io.file.autosync = bytes;
|
||||
}
|
||||
|
||||
/* --------------------------- Higher level interface --------------------------
|
||||
*
|
||||
* The following higher level functions use lower level rio.c functions to help
|
||||
* generating the Redis protocol for the Append Only File. */
|
||||
|
||||
/* Write multi bulk count in the format: "*<count>\r\n". */
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, int count) {
|
||||
char cbuf[128];
|
||||
int clen;
|
||||
|
||||
cbuf[0] = prefix;
|
||||
clen = 1+ll2string(cbuf+1,sizeof(cbuf)-1,count);
|
||||
cbuf[clen++] = '\r';
|
||||
cbuf[clen++] = '\n';
|
||||
if (rioWrite(r,cbuf,clen) == 0) return 0;
|
||||
return clen;
|
||||
}
|
||||
|
||||
/* Write binary-safe string in the format: "$<count>\r\n<payload>\r\n". */
|
||||
size_t rioWriteBulkString(rio *r, const char *buf, size_t len) {
|
||||
size_t nwritten;
|
||||
|
||||
if ((nwritten = rioWriteBulkCount(r,'$',(int)len)) == 0) return 0; WIN_PORT_FIX /* cast (int) */
|
||||
if (len > 0 && rioWrite(r,buf,len) == 0) return 0;
|
||||
if (rioWrite(r,"\r\n",2) == 0) return 0;
|
||||
return nwritten+len+2;
|
||||
}
|
||||
|
||||
/* Write a PORT_LONGLONG value in format: "$<count>\r\n<payload>\r\n". */
|
||||
size_t rioWriteBulkLongLong(rio *r, PORT_LONGLONG l) {
|
||||
char lbuf[32];
|
||||
unsigned int llen;
|
||||
|
||||
llen = ll2string(lbuf,sizeof(lbuf),l);
|
||||
return rioWriteBulkString(r,lbuf,llen);
|
||||
}
|
||||
|
||||
/* Write a double value in the format: "$<count>\r\n<payload>\r\n" */
|
||||
size_t rioWriteBulkDouble(rio *r, double d) {
|
||||
char dbuf[128];
|
||||
unsigned int dlen;
|
||||
|
||||
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
|
||||
return rioWriteBulkString(r,dbuf,dlen);
|
||||
}
|
||||
@@ -133,9 +133,9 @@ void rioInitWithFdset(rio *r, int *fds, int numfds);
|
||||
|
||||
void rioFreeFdset(rio *r);
|
||||
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, int count);
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, PORT_LONG count); WIN_PORT_FIX
|
||||
size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
|
||||
size_t rioWriteBulkLongLong(rio *r, PORT_LONGLONG l);
|
||||
size_t rioWriteBulkLongLong(rio *r, PORT_LONGLONG l); WIN_PORT_FIX
|
||||
size_t rioWriteBulkDouble(rio *r, double d);
|
||||
|
||||
struct redisObject;
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __REDIS_RIO_H
|
||||
#define __REDIS_RIO_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Win32_Interop/Win32_Portability.h"
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include "sds.h"
|
||||
|
||||
struct _rio {
|
||||
/* Backend functions.
|
||||
* Since this functions do not tolerate short writes or reads the return
|
||||
* value is simplified to: zero on error, non zero on complete success. */
|
||||
size_t (*read)(struct _rio *, void *buf, size_t len);
|
||||
size_t (*write)(struct _rio *, const void *buf, size_t len);
|
||||
off_t (*tell)(struct _rio *);
|
||||
int (*flush)(struct _rio *);
|
||||
/* The update_cksum method if not NULL is used to compute the checksum of
|
||||
* all the data that was read or written so far. The method should be
|
||||
* designed so that can be called with the current checksum, and the buf
|
||||
* and len fields pointing to the new block of data to add to the checksum
|
||||
* computation. */
|
||||
void (*update_cksum)(struct _rio *, const void *buf, size_t len);
|
||||
|
||||
/* The current checksum */
|
||||
uint64_t cksum;
|
||||
|
||||
/* number of bytes read or written */
|
||||
size_t processed_bytes;
|
||||
|
||||
/* maximum single read or write chunk size */
|
||||
size_t max_processing_chunk;
|
||||
|
||||
/* Backend-specific vars. */
|
||||
union {
|
||||
/* In-memory buffer target. */
|
||||
struct {
|
||||
sds ptr;
|
||||
off_t pos;
|
||||
} buffer;
|
||||
/* Stdio file pointer target. */
|
||||
struct {
|
||||
FILE *fp;
|
||||
off_t buffered; /* Bytes written since last fsync. */
|
||||
off_t autosync; /* fsync after 'autosync' bytes written. */
|
||||
} file;
|
||||
/* Multiple FDs target (used to write to N sockets). */
|
||||
struct {
|
||||
int *fds; /* File descriptors. */
|
||||
int *state; /* Error state of each fd. 0 (if ok) or errno. */
|
||||
int numfds;
|
||||
off_t pos;
|
||||
sds buf;
|
||||
} fdset;
|
||||
} io;
|
||||
};
|
||||
|
||||
typedef struct _rio rio;
|
||||
|
||||
/* The following functions are our interface with the stream. They'll call the
|
||||
* actual implementation of read / write / tell, and will update the checksum
|
||||
* if needed. */
|
||||
|
||||
static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
|
||||
while (len) {
|
||||
size_t bytes_to_write = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
|
||||
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_write);
|
||||
if (r->write(r,buf,bytes_to_write) == 0)
|
||||
return 0;
|
||||
buf = (char*)buf + bytes_to_write;
|
||||
len -= bytes_to_write;
|
||||
r->processed_bytes += bytes_to_write;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline size_t rioRead(rio *r, void *buf, size_t len) {
|
||||
while (len) {
|
||||
size_t bytes_to_read = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
|
||||
if (r->read(r,buf,bytes_to_read) == 0)
|
||||
return 0;
|
||||
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_read);
|
||||
buf = (char*)buf + bytes_to_read;
|
||||
len -= bytes_to_read;
|
||||
r->processed_bytes += bytes_to_read;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline off_t rioTell(rio *r) {
|
||||
return r->tell(r);
|
||||
}
|
||||
|
||||
static inline int rioFlush(rio *r) {
|
||||
return r->flush(r);
|
||||
}
|
||||
|
||||
void rioInitWithFile(rio *r, FILE *fp);
|
||||
void rioInitWithBuffer(rio *r, sds s);
|
||||
void rioInitWithFdset(rio *r, int *fds, int numfds);
|
||||
|
||||
void rioFreeFdset(rio *r);
|
||||
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, PORT_LONG count); WIN_PORT_FIX
|
||||
size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
|
||||
size_t rioWriteBulkLongLong(rio *r, PORT_LONGLONG l); WIN_PORT_FIX
|
||||
size_t rioWriteBulkDouble(rio *r, double d);
|
||||
|
||||
struct redisObject;
|
||||
int rioWriteBulkObject(rio *r, struct redisObject *obj);
|
||||
|
||||
void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len);
|
||||
void rioSetAutoSync(rio *r, off_t bytes);
|
||||
|
||||
#endif
|
||||
+54
-35
@@ -361,6 +361,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
static size_t cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
static int inuse = 0; /* Recursive calls detection. */
|
||||
|
||||
/* Reflect MULTI state */
|
||||
if (server.lua_multi_emitted || (server.lua_caller->flags & CLIENT_MULTI)) {
|
||||
c->flags |= CLIENT_MULTI;
|
||||
} else {
|
||||
c->flags &= ~CLIENT_MULTI;
|
||||
}
|
||||
|
||||
/* By using Lua debug hooks it is possible to trigger a recursive call
|
||||
* to luaRedisGenericCommand(), which normally should never happen.
|
||||
* To make this function reentrant is futile and makes it slower, but
|
||||
@@ -538,6 +545,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
* a Lua script in the context of AOF and slaves. */
|
||||
if (server.lua_replicate_commands &&
|
||||
!server.lua_multi_emitted &&
|
||||
!(server.lua_caller->flags & CLIENT_MULTI) &&
|
||||
server.lua_write_dirty &&
|
||||
server.lua_repl != PROPAGATE_NONE)
|
||||
{
|
||||
@@ -1136,18 +1144,38 @@ int redis_math_randomseed (lua_State *L) {
|
||||
* EVAL and SCRIPT commands implementation
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Define a lua function with the specified function name and body.
|
||||
* The function name musts be a 42 characters long string, since all the
|
||||
* functions we defined in the Lua context are in the form:
|
||||
/* Define a Lua function with the specified body.
|
||||
* The function name will be generated in the following form:
|
||||
*
|
||||
* f_<hex sha1 sum>
|
||||
*
|
||||
* On success C_OK is returned, and nothing is left on the Lua stack.
|
||||
* On error C_ERR is returned and an appropriate error is set in the
|
||||
* client context. */
|
||||
int luaCreateFunction(client *c, lua_State *lua, char *funcname, robj *body) {
|
||||
sds funcdef = sdsempty();
|
||||
* The function increments the reference count of the 'body' object as a
|
||||
* side effect of a successful call.
|
||||
*
|
||||
* On success a pointer to an SDS string representing the function SHA1 of the
|
||||
* just added function is returned (and will be valid until the next call
|
||||
* to scriptingReset() function), otherwise NULL is returned.
|
||||
*
|
||||
* The function handles the fact of being called with a script that already
|
||||
* exists, and in such a case, it behaves like in the success case.
|
||||
*
|
||||
* If 'c' is not NULL, on error the client is informed with an appropriate
|
||||
* error describing the nature of the problem and the Lua interpreter error. */
|
||||
sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
char funcname[43];
|
||||
dictEntry *de;
|
||||
|
||||
funcname[0] = 'f';
|
||||
funcname[1] = '_';
|
||||
sha1hex(funcname+2,body->ptr,sdslen(body->ptr));
|
||||
|
||||
sds sha = sdsnewlen(funcname+2,40);
|
||||
if ((de = dictFind(server.lua_scripts,sha)) != NULL) {
|
||||
sdsfree(sha);
|
||||
return dictGetKey(de);
|
||||
}
|
||||
|
||||
sds funcdef = sdsempty();
|
||||
funcdef = sdscat(funcdef,"function ");
|
||||
funcdef = sdscatlen(funcdef,funcname,42);
|
||||
funcdef = sdscatlen(funcdef,"() ",3);
|
||||
@@ -1155,30 +1183,34 @@ int luaCreateFunction(client *c, lua_State *lua, char *funcname, robj *body) {
|
||||
funcdef = sdscatlen(funcdef,"\nend",4);
|
||||
|
||||
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
|
||||
addReplyErrorFormat(c,"Error compiling script (new function): %s\n",
|
||||
if (c != NULL) {
|
||||
addReplyErrorFormat(c,
|
||||
"Error compiling script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
}
|
||||
lua_pop(lua,1);
|
||||
sdsfree(sha);
|
||||
sdsfree(funcdef);
|
||||
return C_ERR;
|
||||
return NULL;
|
||||
}
|
||||
sdsfree(funcdef);
|
||||
if (lua_pcall(lua,0,0,0)) {
|
||||
if (c != NULL) {
|
||||
addReplyErrorFormat(c,"Error running script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
}
|
||||
lua_pop(lua,1);
|
||||
return C_ERR;
|
||||
sdsfree(sha);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* We also save a SHA1 -> Original script map in a dictionary
|
||||
* so that we can replicate / write in the AOF all the
|
||||
* EVALSHA commands as EVAL using the original script. */
|
||||
{
|
||||
int retval = dictAdd(server.lua_scripts,
|
||||
sdsnewlen(funcname+2,40),body);
|
||||
serverAssertWithInfo(c,NULL,retval == DICT_OK);
|
||||
int retval = dictAdd(server.lua_scripts,sha,body);
|
||||
serverAssertWithInfo(c ? c : server.lua_client,NULL,retval == DICT_OK);
|
||||
incrRefCount(body);
|
||||
}
|
||||
return C_OK;
|
||||
return sha;
|
||||
}
|
||||
|
||||
/* This is the Lua script "count" hook that we use to detect scripts timeout. */
|
||||
@@ -1277,10 +1309,10 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
addReply(c, shared.noscripterr);
|
||||
return;
|
||||
}
|
||||
if (luaCreateFunction(c,lua,funcname,c->argv[1]) == C_ERR) {
|
||||
if (luaCreateFunction(c,lua,c->argv[1]) == NULL) {
|
||||
lua_pop(lua,1); /* remove the error handler from the stack. */
|
||||
/* The error is sent to the client by luaCreateFunction()
|
||||
* itself when it returns C_ERR. */
|
||||
* itself when it returns NULL. */
|
||||
return;
|
||||
}
|
||||
/* Now the following is guaranteed to return non nil */
|
||||
@@ -1441,22 +1473,9 @@ void scriptCommand(client *c) {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"load")) {
|
||||
char funcname[43];
|
||||
sds sha;
|
||||
|
||||
funcname[0] = 'f';
|
||||
funcname[1] = '_';
|
||||
sha1hex(funcname+2,c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
|
||||
sha = sdsnewlen(funcname+2,40);
|
||||
if (dictFind(server.lua_scripts,sha) == NULL) {
|
||||
if (luaCreateFunction(c,server.lua,funcname,c->argv[2])
|
||||
== C_ERR) {
|
||||
sdsfree(sha);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addReplyBulkCBuffer(c,funcname+2,40);
|
||||
sdsfree(sha);
|
||||
sds sha = luaCreateFunction(c,server.lua,c->argv[2]);
|
||||
if (sha == NULL) return; /* The error was sent by luaCreateFunction(). */
|
||||
addReplyBulkCBuffer(c,sha,40);
|
||||
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
|
||||
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
|
||||
if (server.lua_caller == NULL) {
|
||||
|
||||
+2397
File diff suppressed because it is too large
Load Diff
@@ -180,7 +180,7 @@ void sdsfree(sds s) {
|
||||
* the output will be "6" as the string was modified but the logical length
|
||||
* remains 6 bytes. */
|
||||
void sdsupdatelen(sds s) {
|
||||
int reallen = (int)strlen(s); WIN_PORT_FIX /* cast (int) */
|
||||
size_t reallen = strlen(s);
|
||||
sdssetlen(s, reallen);
|
||||
}
|
||||
|
||||
@@ -253,16 +253,23 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
sds sdsRemoveFreeSpace(sds s) {
|
||||
void *sh, *newsh;
|
||||
char type, oldtype = s[-1] & SDS_TYPE_MASK;
|
||||
int hdrlen;
|
||||
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
|
||||
size_t len = sdslen(s);
|
||||
sh = (char*)s-sdsHdrSize(oldtype);
|
||||
sh = (char*)s-oldhdrlen;
|
||||
|
||||
/* Check what would be the minimum SDS header that is just good enough to
|
||||
* fit this string. */
|
||||
type = sdsReqType(len);
|
||||
hdrlen = sdsHdrSize(type);
|
||||
if (oldtype==type) {
|
||||
newsh = s_realloc(sh, hdrlen+len+1);
|
||||
|
||||
/* If the type is the same, or at least a large enough type is still
|
||||
* required, we just realloc(), letting the allocator to do the copy
|
||||
* only if really needed. Otherwise if the change is huge, we manually
|
||||
* reallocate the string to use the different header type. */
|
||||
if (oldtype==type || type > SDS_TYPE_8) {
|
||||
newsh = s_realloc(sh, oldhdrlen+len+1);
|
||||
if (newsh == NULL) return NULL;
|
||||
s = (char*)newsh+hdrlen;
|
||||
s = (char*)newsh+oldhdrlen;
|
||||
} else {
|
||||
newsh = s_malloc(hdrlen+len+1);
|
||||
if (newsh == NULL) return NULL;
|
||||
@@ -317,7 +324,7 @@ void *sdsAllocPtr(sds s) {
|
||||
* ... check for nread <= 0 and handle it ...
|
||||
* sdsIncrLen(s, nread);
|
||||
*/
|
||||
void sdsIncrLen(sds s, int incr) {
|
||||
void sdsIncrLen(sds s, ssize_t incr) {
|
||||
unsigned char flags = s[-1];
|
||||
size_t len;
|
||||
switch(flags&SDS_TYPE_MASK) {
|
||||
@@ -720,7 +727,7 @@ sds sdstrim(sds s, const char *cset) {
|
||||
* s = sdsnew("Hello World");
|
||||
* sdsrange(s,1,-1); => "ello World"
|
||||
*/
|
||||
void sdsrange(sds s, int start, int end) {
|
||||
void sdsrange(sds s, ssize_t start, ssize_t end) {
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
if (len == 0) return;
|
||||
@@ -781,7 +788,7 @@ int sdscmp(const sds s1, const sds s2) {
|
||||
l2 = sdslen(s2);
|
||||
minlen = (l1 < l2) ? l1 : l2;
|
||||
cmp = memcmp(s1,s2,minlen);
|
||||
if (cmp == 0) return (int)(l1-l2); WIN_PORT_FIX /* cast (int) */
|
||||
if (cmp == 0) return l1>l2? 1: (l1<l2? -1: 0);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
@@ -801,8 +808,9 @@ int sdscmp(const sds s1, const sds s2) {
|
||||
* requires length arguments. sdssplit() is just the
|
||||
* same function but for zero-terminated strings.
|
||||
*/
|
||||
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) {
|
||||
int elements = 0, slots = 5, start = 0, j;
|
||||
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) {
|
||||
int elements = 0, slots = 5;
|
||||
long start = 0, j;
|
||||
sds *tokens;
|
||||
|
||||
if (seplen < 1 || len < 0) return NULL;
|
||||
|
||||
+1283
File diff suppressed because it is too large
Load Diff
+8
-4
@@ -2750,20 +2750,24 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
|
||||
ping_period = ri->down_after_period;
|
||||
if (ping_period > SENTINEL_PING_PERIOD) ping_period = SENTINEL_PING_PERIOD;
|
||||
|
||||
/* Send INFO to masters and slaves, not sentinels. */
|
||||
if ((ri->flags & SRI_SENTINEL) == 0 &&
|
||||
(ri->info_refresh == 0 ||
|
||||
(now - ri->info_refresh) > info_period))
|
||||
{
|
||||
/* Send INFO to masters and slaves, not sentinels. */
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelInfoReplyCallback, ri, "INFO");
|
||||
if (retval == C_OK) ri->link->pending_commands++;
|
||||
} else if ((now - ri->link->last_pong_time) > ping_period &&
|
||||
(now - ri->link->last_ping_time) > ping_period/2) {
|
||||
}
|
||||
|
||||
/* Send PING to all the three kinds of instances. */
|
||||
if ((now - ri->link->last_pong_time) > ping_period &&
|
||||
(now - ri->link->last_ping_time) > ping_period/2) {
|
||||
sentinelSendPing(ri);
|
||||
} else if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
|
||||
}
|
||||
|
||||
/* PUBLISH hello messages to all the three kinds of instances. */
|
||||
if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
|
||||
sentinelSendHello(ri);
|
||||
}
|
||||
}
|
||||
|
||||
+4487
File diff suppressed because it is too large
Load Diff
+42
-22
@@ -284,7 +284,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"readonly",readonlyCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"readwrite",readwriteCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,3,"r",0,NULL,2,2,2,0,0},
|
||||
{"object",objectCommand,-2,"r",0,NULL,2,2,2,0,0},
|
||||
{"memory",memoryCommand,-2,"r",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
@@ -462,8 +462,8 @@ int dictSdsKeyCompare(void *privdata, const void *key1,
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
l1 = (int) sdslen((sds) key1); WIN_PORT_FIX /* cast (int) */
|
||||
l2 = (int) sdslen((sds) key2); WIN_PORT_FIX /* cast (int) */
|
||||
if (l1 != l2) return 0;
|
||||
l2 = (int) sdslen((sds) key2); WIN_PORT_FIX /* cast (int) */
|
||||
if (l1 != l2) return 0;
|
||||
return memcmp(key1, key2, l1) == 0;
|
||||
}
|
||||
|
||||
@@ -926,12 +926,15 @@ void databasesCron(void) {
|
||||
/* Rehash */
|
||||
if (server.activerehashing) {
|
||||
for (j = 0; j < dbs_per_call; j++) {
|
||||
int work_done = incrementallyRehash(rehash_db % server.dbnum);
|
||||
rehash_db++;
|
||||
int work_done = incrementallyRehash(rehash_db);
|
||||
if (work_done) {
|
||||
/* If the function did some work, stop here, we'll do
|
||||
* more at the next cron loop. */
|
||||
break;
|
||||
} else {
|
||||
/* If this db didn't need rehash, we'll try the next one. */
|
||||
rehash_db++;
|
||||
rehash_db %= server.dbnum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1121,7 +1124,7 @@ int serverCron(struct aeEventLoop *eventLoop, PORT_LONGLONG id, void *clientData
|
||||
}
|
||||
else {
|
||||
/* If there is not a background saving/rewrite in progress check if
|
||||
* we have to save/rewrite now */
|
||||
* we have to save/rewrite now. */
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
struct saveparam *sp = server.saveparams + j;
|
||||
|
||||
@@ -1144,8 +1147,9 @@ int serverCron(struct aeEventLoop *eventLoop, PORT_LONGLONG id, void *clientData
|
||||
}
|
||||
}
|
||||
|
||||
/* Trigger an AOF rewrite if needed */
|
||||
if (server.rdb_child_pid == -1 &&
|
||||
/* Trigger an AOF rewrite if needed. */
|
||||
if (server.aof_state == AOF_ON &&
|
||||
server.rdb_child_pid == -1 &&
|
||||
server.aof_child_pid == -1 &&
|
||||
server.aof_rewrite_perc &&
|
||||
server.aof_current_size > server.aof_rewrite_min_size)
|
||||
@@ -1226,12 +1230,6 @@ int serverCron(struct aeEventLoop *eventLoop, PORT_LONGLONG id, void *clientData
|
||||
void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
UNUSED(eventLoop);
|
||||
|
||||
#ifdef WIN32
|
||||
//1) check if child has signaled parent to stop sending diffs
|
||||
//2) check if more data can be written to the child and write it
|
||||
aofProcessDiffRewriteEvents(eventLoop);
|
||||
#endif
|
||||
|
||||
/* Call the Redis Cluster before sleep function. Note that this function
|
||||
* may change the state of Redis Cluster (from ok to fail or vice versa),
|
||||
* so it's a good idea to call it before serving the unblocked clients
|
||||
@@ -1426,6 +1424,7 @@ void initServerConfig(void) {
|
||||
server.active_defrag_threshold_upper = CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER;
|
||||
server.active_defrag_cycle_min = CONFIG_DEFAULT_DEFRAG_CYCLE_MIN;
|
||||
server.active_defrag_cycle_max = CONFIG_DEFAULT_DEFRAG_CYCLE_MAX;
|
||||
server.proto_max_bulk_len = CONFIG_DEFAULT_PROTO_MAX_BULK_LEN;
|
||||
server.client_max_querybuf_len = PROTO_MAX_QUERYBUF_LEN;
|
||||
server.saveparams = NULL;
|
||||
server.loading = 0;
|
||||
@@ -1485,6 +1484,7 @@ void initServerConfig(void) {
|
||||
server.cluster_migration_barrier = CLUSTER_DEFAULT_MIGRATION_BARRIER;
|
||||
server.cluster_slave_validity_factor = CLUSTER_DEFAULT_SLAVE_VALIDITY;
|
||||
server.cluster_require_full_coverage = CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE;
|
||||
server.cluster_slave_no_failover = CLUSTER_DEFAULT_SLAVE_NO_FAILOVER;
|
||||
server.cluster_configfile = zstrdup(CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
server.cluster_announce_ip = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_IP;
|
||||
server.cluster_announce_port = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT;
|
||||
@@ -1603,16 +1603,29 @@ int restartServer(int flags, mstime_t delay) {
|
||||
|
||||
/* Check if we still have accesses to the executable that started this
|
||||
* server instance. */
|
||||
if (access(server.executable, X_OK) == -1) return C_ERR;
|
||||
if (access(server.executable,X_OK) == -1) {
|
||||
serverLog(LL_WARNING,"Can't restart: this process has no "
|
||||
"permissions to execute %s", server.executable);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Config rewriting. */
|
||||
if (flags & RESTART_SERVER_CONFIG_REWRITE &&
|
||||
server.configfile &&
|
||||
rewriteConfig(server.configfile) == -1) return C_ERR;
|
||||
rewriteConfig(server.configfile) == -1)
|
||||
{
|
||||
serverLog(LL_WARNING,"Can't restart: configuration rewrite process "
|
||||
"failed");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Perform a proper shutdown. */
|
||||
if (flags & RESTART_SERVER_GRACEFULLY &&
|
||||
prepareForShutdown(SHUTDOWN_NOFLAGS) != C_OK) return C_ERR;
|
||||
prepareForShutdown(SHUTDOWN_NOFLAGS) != C_OK)
|
||||
{
|
||||
serverLog(LL_WARNING,"Can't restart: error preparing for shutdown");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Close all file descriptors, with the exception of stdin, stdout, strerr
|
||||
* which are useful if we restart a Redis server which is not daemonized. */
|
||||
@@ -1624,6 +1637,8 @@ int restartServer(int flags, mstime_t delay) {
|
||||
|
||||
/* Execute the server with the original command line. */
|
||||
if (delay) usleep(delay * 1000);
|
||||
zfree(server.exec_argv[0]);
|
||||
server.exec_argv[0] = zstrdup(server.executable);
|
||||
execve(server.executable, server.exec_argv, environ);
|
||||
|
||||
/* If an error occurred here, there is nothing we can do, but exit. */
|
||||
@@ -1826,6 +1841,8 @@ void resetServerStats(void) {
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_expired_stale_perc = 0;
|
||||
server.stat_expired_time_cap_reached_count = 0;
|
||||
server.stat_evictedkeys = 0;
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
@@ -2348,8 +2365,9 @@ void call(client *c, int flags) {
|
||||
propagate_flags &= ~PROPAGATE_AOF;
|
||||
|
||||
/* Call propagate() only if at least one of AOF / replication
|
||||
* propagation is needed. */
|
||||
if (propagate_flags != PROPAGATE_NONE)
|
||||
* propagation is needed. Note that modules commands handle replication
|
||||
* in an explicit way, so we never replicate them automatically. */
|
||||
if (propagate_flags != PROPAGATE_NONE && !(c->cmd->flags & CMD_MODULE))
|
||||
propagate(c->cmd, c->db->id, c->argv, c->argc, propagate_flags);
|
||||
}
|
||||
|
||||
@@ -3197,6 +3215,8 @@ sds genRedisInfoString(char *section) {
|
||||
"sync_partial_ok:%lld\r\n"
|
||||
"sync_partial_err:%lld\r\n"
|
||||
"expired_keys:%lld\r\n"
|
||||
"expired_stale_perc:%.2f\r\n"
|
||||
"expired_time_cap_reached_count:%lld\r\n"
|
||||
"evicted_keys:%lld\r\n"
|
||||
"keyspace_hits:%lld\r\n"
|
||||
"keyspace_misses:%lld\r\n"
|
||||
@@ -3221,6 +3241,8 @@ sds genRedisInfoString(char *section) {
|
||||
server.stat_sync_partial_ok,
|
||||
server.stat_sync_partial_err,
|
||||
server.stat_expiredkeys,
|
||||
server.stat_expired_stale_perc*100,
|
||||
server.stat_expired_time_cap_reached_count,
|
||||
server.stat_evictedkeys,
|
||||
server.stat_keyspace_hits,
|
||||
server.stat_keyspace_misses,
|
||||
@@ -3862,9 +3884,7 @@ int main(int argc, char **argv) {
|
||||
dictSetHashFunctionSeed((uint8_t*) hashseed);
|
||||
server.sentinel_mode = checkForSentinelMode(argc, argv);
|
||||
initServerConfig();
|
||||
#ifndef _WIN32
|
||||
moduleInitModulesSystem();
|
||||
#endif
|
||||
moduleInitModulesSystem();
|
||||
|
||||
/* Store the executable path and arguments in a safe place in order
|
||||
* to be able to restart the server later. */
|
||||
|
||||
+4059
File diff suppressed because it is too large
Load Diff
+14
-4
@@ -174,6 +174,7 @@ POSIX_ONLY(#define LOG_MAX_LEN 1024) /* Default maximum length of syslog mess
|
||||
#define CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES (100<<20) /* don't defrag if frag overhead is below 100mb */
|
||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MIN 25 /* 25% CPU min (at lower threshold) */
|
||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MAX 75 /* 75% CPU max (at upper threshold) */
|
||||
#define CONFIG_DEFAULT_PROTO_MAX_BULK_LEN (512ll*1024*1024) /* Bulk request max size */
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
@@ -600,7 +601,7 @@ typedef struct redisObject {
|
||||
unsigned encoding:4;
|
||||
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
|
||||
* LFU data (least significant 8 bits frequency
|
||||
* and most significant 16 bits decreas time). */
|
||||
* and most significant 16 bits access time). */
|
||||
int refcount;
|
||||
void *ptr;
|
||||
} robj;
|
||||
@@ -952,6 +953,8 @@ struct redisServer {
|
||||
PORT_LONGLONG stat_numcommands; /* Number of processed commands */
|
||||
PORT_LONGLONG stat_numconnections; /* Number of connections received */
|
||||
PORT_LONGLONG stat_expiredkeys; /* Number of expired keys */
|
||||
double stat_expired_stale_perc; /* Percentage of keys probably expired */
|
||||
PORT_LONGLONG stat_expired_time_cap_reached_count; /* Early expire cylce stops.*/
|
||||
PORT_LONGLONG stat_evictedkeys; /* Number of evicted keys (maxmemory) */
|
||||
PORT_LONGLONG stat_keyspace_hits; /* Number of successful lookups of keys */
|
||||
PORT_LONGLONG stat_keyspace_misses; /* Number of failed lookups of keys */
|
||||
@@ -969,7 +972,7 @@ struct redisServer {
|
||||
list *slowlog; /* SLOWLOG list of commands */
|
||||
PORT_LONGLONG slowlog_entry_id; /* SLOWLOG current entry ID */
|
||||
PORT_LONGLONG slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
|
||||
PORT_ULONG slowlog_max_len; /* SLOWLOG max number of items logged */
|
||||
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
|
||||
size_t resident_set_size; /* RSS sampled in serverCron(). */
|
||||
PORT_LONGLONG stat_net_input_bytes; /* Bytes read from network. */
|
||||
PORT_LONGLONG stat_net_output_bytes; /* Bytes written to network. */
|
||||
@@ -1133,8 +1136,9 @@ struct redisServer {
|
||||
PORT_ULONGLONG maxmemory; /* Max number of memory bytes to use */
|
||||
int maxmemory_policy; /* Policy for key eviction */
|
||||
int maxmemory_samples; /* Pricision of random sampling */
|
||||
unsigned int lfu_log_factor; /* LFU logarithmic counter factor. */
|
||||
unsigned int lfu_decay_time; /* LFU counter decay factor. */
|
||||
int lfu_log_factor; /* LFU logarithmic counter factor. */
|
||||
int lfu_decay_time; /* LFU counter decay factor. */
|
||||
PORT_LONGLONG proto_max_bulk_len; /* Protocol bulk length maximum size. */
|
||||
/* Blocked clients */
|
||||
unsigned int bpop_blocked_clients; /* Number of clients blocked by lists */
|
||||
list *unblocked_clients; /* list of clients to unblock before next loop */
|
||||
@@ -1172,6 +1176,8 @@ struct redisServer {
|
||||
int cluster_slave_validity_factor; /* Slave max data age for failover. */
|
||||
int cluster_require_full_coverage; /* If true, put the cluster down if
|
||||
there is at least an uncovered slot.*/
|
||||
int cluster_slave_no_failover; /* Prevent slave from starting a failover
|
||||
if the master is in failure state. */
|
||||
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
|
||||
int cluster_announce_port; /* base port to announce on cluster bus. */
|
||||
int cluster_announce_bus_port; /* bus port to announce on cluster bus. */
|
||||
@@ -1334,6 +1340,8 @@ void moduleBlockedClientPipeReadable(aeEventLoop *el, int fd, void *privdata, in
|
||||
size_t moduleCount(void);
|
||||
void moduleAcquireGIL(void);
|
||||
void moduleReleaseGIL(void);
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||
|
||||
|
||||
/* Utils */
|
||||
PORT_LONGLONG ustime(void);
|
||||
@@ -1800,6 +1808,7 @@ void scriptingInit(int setup);
|
||||
int ldbRemoveChild(pid_t pid);
|
||||
void ldbKillForkedSessions(void);
|
||||
int ldbPendingChildren(void);
|
||||
sds luaCreateFunction(client *c, lua_State *lua, robj *body);
|
||||
|
||||
/* Blocked clients */
|
||||
void processUnblockedClients(void);
|
||||
@@ -1821,6 +1830,7 @@ void evictionPoolAlloc(void);
|
||||
#define LFU_INIT_VAL 5
|
||||
PORT_ULONG LFUGetTimeInMinutes(void);
|
||||
uint8_t LFULogIncr(uint8_t value);
|
||||
PORT_ULONG LFUDecrAndReturn(robj *o);
|
||||
|
||||
/* Keys hashing / comparison functions for dict.c hash tables. */
|
||||
uint64_t dictSdsHash(const void *key);
|
||||
|
||||
+2057
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -616,7 +616,7 @@ void hincrbyfloatCommand(client *c) {
|
||||
|
||||
value += incr;
|
||||
|
||||
char buf[256];
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
int len = ld2string(buf,sizeof(buf),value,1);
|
||||
new = sdsnewlen(buf,len);
|
||||
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
|
||||
|
||||
@@ -0,0 +1,825 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include <math.h>
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Hash type API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Check the length of a number of objects to see if we need to convert a
|
||||
* ziplist to a real hash. Note that we only check string encoded objects
|
||||
* as their string length can be queried in constant time. */
|
||||
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
||||
int i;
|
||||
|
||||
if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
|
||||
|
||||
for (i = start; i <= end; i++) {
|
||||
if (sdsEncodedObject(argv[i]) &&
|
||||
sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
|
||||
{
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the value from a ziplist encoded hash, identified by field.
|
||||
* Returns -1 when the field cannot be found. */
|
||||
int hashTypeGetFromZiplist(robj *o, sds field,
|
||||
unsigned char **vstr,
|
||||
unsigned int *vlen,
|
||||
PORT_LONGLONG *vll)
|
||||
{
|
||||
unsigned char *zl, *fptr = NULL, *vptr = NULL;
|
||||
int ret;
|
||||
|
||||
serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST);
|
||||
|
||||
zl = o->ptr;
|
||||
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
|
||||
if (fptr != NULL) {
|
||||
fptr = ziplistFind(fptr, (unsigned char*)field, (unsigned int)sdslen(field), 1); WIN_PORT_FIX /* cast (unsigned int) */
|
||||
if (fptr != NULL) {
|
||||
/* Grab pointer to the value (fptr points to the field) */
|
||||
vptr = ziplistNext(zl, fptr);
|
||||
serverAssert(vptr != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (vptr != NULL) {
|
||||
ret = ziplistGet(vptr, vstr, vlen, vll);
|
||||
serverAssert(ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get the value from a hash table encoded hash, identified by field.
|
||||
* Returns NULL when the field cannot be found, otherwise the SDS value
|
||||
* is returned. */
|
||||
sds hashTypeGetFromHashTable(robj *o, sds field) {
|
||||
dictEntry *de;
|
||||
|
||||
serverAssert(o->encoding == OBJ_ENCODING_HT);
|
||||
|
||||
de = dictFind(o->ptr, field);
|
||||
if (de == NULL) return NULL;
|
||||
return dictGetVal(de);
|
||||
}
|
||||
|
||||
/* Higher level function of hashTypeGet*() that returns the hash value
|
||||
* associated with the specified field. If the field is found C_OK
|
||||
* is returned, otherwise C_ERR. The returned object is returned by
|
||||
* reference in either *vstr and *vlen if it's returned in string form,
|
||||
* or stored in *vll if it's returned as a number.
|
||||
*
|
||||
* If *vll is populated *vstr is set to NULL, so the caller
|
||||
* can always check the function return by checking the return value
|
||||
* for C_OK and checking if vll (or vstr) is NULL. */
|
||||
int hashTypeGetValue(robj *o, sds field, unsigned char **vstr, unsigned int *vlen, PORT_LONGLONG *vll) {
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
*vstr = NULL;
|
||||
if (hashTypeGetFromZiplist(o, field, vstr, vlen, vll) == 0)
|
||||
return C_OK;
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
sds value;
|
||||
if ((value = hashTypeGetFromHashTable(o, field)) != NULL) {
|
||||
*vstr = (unsigned char*) value;
|
||||
*vlen = sdslen(value);
|
||||
return C_OK;
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Like hashTypeGetValue() but returns a Redis object, which is useful for
|
||||
* interaction with the hash type outside t_hash.c.
|
||||
* The function returns NULL if the field is not found in the hash. Otherwise
|
||||
* a newly allocated string object with the value is returned. */
|
||||
robj *hashTypeGetValueObject(robj *o, sds field) {
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
PORT_LONGLONG vll;
|
||||
|
||||
if (hashTypeGetValue(o,field,&vstr,&vlen,&vll) == C_ERR) return NULL;
|
||||
if (vstr) return createStringObject((char*)vstr,vlen);
|
||||
else return createStringObjectFromLongLong(vll);
|
||||
}
|
||||
|
||||
/* Higher level function using hashTypeGet*() to return the length of the
|
||||
* object associated with the requested field, or 0 if the field does not
|
||||
* exist. */
|
||||
size_t hashTypeGetValueLength(robj *o, sds field) {
|
||||
size_t len = 0;
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *vstr = NULL;
|
||||
unsigned int vlen = UINT_MAX;
|
||||
PORT_LONGLONG vll = LLONG_MAX;
|
||||
|
||||
if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0)
|
||||
len = vstr ? vlen : sdigits10(vll);
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
sds aux;
|
||||
|
||||
if ((aux = hashTypeGetFromHashTable(o, field)) != NULL)
|
||||
len = sdslen(aux);
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Test if the specified field exists in the given hash. Returns 1 if the field
|
||||
* exists, and 0 when it doesn't. */
|
||||
int hashTypeExists(robj *o, sds field) {
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *vstr = NULL;
|
||||
unsigned int vlen = UINT_MAX;
|
||||
PORT_LONGLONG vll = LLONG_MAX;
|
||||
|
||||
if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1;
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
if (hashTypeGetFromHashTable(o, field) != NULL) return 1;
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add a new field, overwrite the old with the new value if it already exists.
|
||||
* Return 0 on insert and 1 on update.
|
||||
*
|
||||
* By default, the key and value SDS strings are copied if needed, so the
|
||||
* caller retains ownership of the strings passed. However this behavior
|
||||
* can be effected by passing appropriate flags (possibly bitwise OR-ed):
|
||||
*
|
||||
* HASH_SET_TAKE_FIELD -- The SDS field ownership passes to the function.
|
||||
* HASH_SET_TAKE_VALUE -- The SDS value ownership passes to the function.
|
||||
*
|
||||
* When the flags are used the caller does not need to release the passed
|
||||
* SDS string(s). It's up to the function to use the string to create a new
|
||||
* entry or to free the SDS string before returning to the caller.
|
||||
*
|
||||
* HASH_SET_COPY corresponds to no flags passed, and means the default
|
||||
* semantics of copying the values if needed.
|
||||
*
|
||||
*/
|
||||
#define HASH_SET_TAKE_FIELD (1<<0)
|
||||
#define HASH_SET_TAKE_VALUE (1<<1)
|
||||
#define HASH_SET_COPY 0
|
||||
int hashTypeSet(robj *o, sds field, sds value, int flags) {
|
||||
int update = 0;
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl, *fptr, *vptr;
|
||||
|
||||
zl = o->ptr;
|
||||
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
|
||||
if (fptr != NULL) {
|
||||
fptr = ziplistFind(fptr, (unsigned char*)field, (unsigned int)sdslen(field), 1); WIN_PORT_FIX /* cast (unsigned int) */
|
||||
if (fptr != NULL) {
|
||||
/* Grab pointer to the value (fptr points to the field) */
|
||||
vptr = ziplistNext(zl, fptr);
|
||||
serverAssert(vptr != NULL);
|
||||
update = 1;
|
||||
|
||||
/* Delete value */
|
||||
zl = ziplistDelete(zl, &vptr);
|
||||
|
||||
/* Insert new value */
|
||||
zl = ziplistInsert(zl, vptr, (unsigned char*)value,
|
||||
(unsigned int)sdslen(value)); WIN_PORT_FIX /* cast (unsigned int) */
|
||||
}
|
||||
}
|
||||
|
||||
if (!update) {
|
||||
/* Push new field/value pair onto the tail of the ziplist */
|
||||
zl = ziplistPush(zl, (unsigned char*)field, (unsigned int)sdslen(field), WIN_PORT_FIX /* cast (unsigned int) */
|
||||
ZIPLIST_TAIL);
|
||||
zl = ziplistPush(zl, (unsigned char*)value, (unsigned int)sdslen(value), WIN_PORT_FIX /* cast (unsigned int) */
|
||||
ZIPLIST_TAIL);
|
||||
}
|
||||
o->ptr = zl;
|
||||
|
||||
/* Check if the ziplist needs to be converted to a hash table */
|
||||
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
dictEntry *de = dictFind(o->ptr,field);
|
||||
if (de) {
|
||||
sdsfree(dictGetVal(de));
|
||||
if (flags & HASH_SET_TAKE_VALUE) {
|
||||
dictGetVal(de) = value;
|
||||
value = NULL;
|
||||
} else {
|
||||
dictGetVal(de) = sdsdup(value);
|
||||
}
|
||||
update = 1;
|
||||
} else {
|
||||
sds f,v;
|
||||
if (flags & HASH_SET_TAKE_FIELD) {
|
||||
f = field;
|
||||
field = NULL;
|
||||
} else {
|
||||
f = sdsdup(field);
|
||||
}
|
||||
if (flags & HASH_SET_TAKE_VALUE) {
|
||||
v = value;
|
||||
value = NULL;
|
||||
} else {
|
||||
v = sdsdup(value);
|
||||
}
|
||||
dictAdd(o->ptr,f,v);
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
|
||||
/* Free SDS strings we did not referenced elsewhere if the flags
|
||||
* want this function to be responsible. */
|
||||
if (flags & HASH_SET_TAKE_FIELD && field) sdsfree(field);
|
||||
if (flags & HASH_SET_TAKE_VALUE && value) sdsfree(value);
|
||||
return update;
|
||||
}
|
||||
|
||||
/* Delete an element from a hash.
|
||||
* Return 1 on deleted and 0 on not found. */
|
||||
int hashTypeDelete(robj *o, sds field) {
|
||||
int deleted = 0;
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl, *fptr;
|
||||
|
||||
zl = o->ptr;
|
||||
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
|
||||
if (fptr != NULL) {
|
||||
fptr = ziplistFind(fptr, (unsigned char*)field, (unsigned int)sdslen(field), 1); WIN_PORT_FIX /* cast (unsigned int) */
|
||||
if (fptr != NULL) {
|
||||
zl = ziplistDelete(zl,&fptr);
|
||||
zl = ziplistDelete(zl,&fptr);
|
||||
o->ptr = zl;
|
||||
deleted = 1;
|
||||
}
|
||||
}
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
if (dictDelete((dict*)o->ptr, field) == C_OK) {
|
||||
deleted = 1;
|
||||
|
||||
/* Always check if the dictionary needs a resize after a delete. */
|
||||
if (htNeedsResize(o->ptr)) dictResize(o->ptr);
|
||||
}
|
||||
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/* Return the number of elements in a hash. */
|
||||
PORT_ULONG hashTypeLength(const robj *o) {
|
||||
PORT_ULONG length = ULONG_MAX;
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
length = ziplistLen(o->ptr) / 2;
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
length = (PORT_ULONG)dictSize((const dict*)o->ptr); WIN_PORT_FIX /* cast (PORT_ULONG) */
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
hashTypeIterator *hashTypeInitIterator(robj *subject) {
|
||||
hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator));
|
||||
hi->subject = subject;
|
||||
hi->encoding = subject->encoding;
|
||||
|
||||
if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
hi->fptr = NULL;
|
||||
hi->vptr = NULL;
|
||||
} else if (hi->encoding == OBJ_ENCODING_HT) {
|
||||
hi->di = dictGetIterator(subject->ptr);
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
return hi;
|
||||
}
|
||||
|
||||
void hashTypeReleaseIterator(hashTypeIterator *hi) {
|
||||
if (hi->encoding == OBJ_ENCODING_HT)
|
||||
dictReleaseIterator(hi->di);
|
||||
zfree(hi);
|
||||
}
|
||||
|
||||
/* Move to the next entry in the hash. Return C_OK when the next entry
|
||||
* could be found and C_ERR when the iterator reaches the end. */
|
||||
int hashTypeNext(hashTypeIterator *hi) {
|
||||
if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl;
|
||||
unsigned char *fptr, *vptr;
|
||||
|
||||
zl = hi->subject->ptr;
|
||||
fptr = hi->fptr;
|
||||
vptr = hi->vptr;
|
||||
|
||||
if (fptr == NULL) {
|
||||
/* Initialize cursor */
|
||||
serverAssert(vptr == NULL);
|
||||
fptr = ziplistIndex(zl, 0);
|
||||
} else {
|
||||
/* Advance cursor */
|
||||
serverAssert(vptr != NULL);
|
||||
fptr = ziplistNext(zl, vptr);
|
||||
}
|
||||
if (fptr == NULL) return C_ERR;
|
||||
|
||||
/* Grab pointer to the value (fptr points to the field) */
|
||||
vptr = ziplistNext(zl, fptr);
|
||||
serverAssert(vptr != NULL);
|
||||
|
||||
/* fptr, vptr now point to the first or next pair */
|
||||
hi->fptr = fptr;
|
||||
hi->vptr = vptr;
|
||||
} else if (hi->encoding == OBJ_ENCODING_HT) {
|
||||
if ((hi->de = dictNext(hi->di)) == NULL) return C_ERR;
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Get the field or value at iterator cursor, for an iterator on a hash value
|
||||
* encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */
|
||||
void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what,
|
||||
unsigned char **vstr,
|
||||
unsigned int *vlen,
|
||||
PORT_LONGLONG *vll)
|
||||
{
|
||||
int ret;
|
||||
|
||||
serverAssert(hi->encoding == OBJ_ENCODING_ZIPLIST);
|
||||
|
||||
if (what & OBJ_HASH_KEY) {
|
||||
ret = ziplistGet(hi->fptr, vstr, vlen, vll);
|
||||
serverAssert(ret);
|
||||
} else {
|
||||
ret = ziplistGet(hi->vptr, vstr, vlen, vll);
|
||||
serverAssert(ret);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the field or value at iterator cursor, for an iterator on a hash value
|
||||
* encoded as a hash table. Prototype is similar to
|
||||
* `hashTypeGetFromHashTable`. */
|
||||
sds hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what) {
|
||||
serverAssert(hi->encoding == OBJ_ENCODING_HT);
|
||||
|
||||
if (what & OBJ_HASH_KEY) {
|
||||
return dictGetKey(hi->de);
|
||||
} else {
|
||||
return dictGetVal(hi->de);
|
||||
}
|
||||
}
|
||||
|
||||
/* Higher level function of hashTypeCurrent*() that returns the hash value
|
||||
* at current iterator position.
|
||||
*
|
||||
* The returned element is returned by reference in either *vstr and *vlen if
|
||||
* it's returned in string form, or stored in *vll if it's returned as
|
||||
* a number.
|
||||
*
|
||||
* If *vll is populated *vstr is set to NULL, so the caller
|
||||
* can always check the function return by checking the return value
|
||||
* type checking if vstr == NULL. */
|
||||
void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, PORT_LONGLONG *vll) {
|
||||
if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
*vstr = NULL;
|
||||
hashTypeCurrentFromZiplist(hi, what, vstr, vlen, vll);
|
||||
} else if (hi->encoding == OBJ_ENCODING_HT) {
|
||||
sds ele = hashTypeCurrentFromHashTable(hi, what);
|
||||
*vstr = (unsigned char*) ele;
|
||||
*vlen = sdslen(ele);
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the key or value at the current iterator position as a new
|
||||
* SDS string. */
|
||||
sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what) {
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
PORT_LONGLONG vll;
|
||||
|
||||
hashTypeCurrentObject(hi,what,&vstr,&vlen,&vll);
|
||||
if (vstr) return sdsnewlen(vstr,vlen);
|
||||
return sdsfromlonglong(vll);
|
||||
}
|
||||
|
||||
robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {
|
||||
robj *o = lookupKeyWrite(c->db,key);
|
||||
if (o == NULL) {
|
||||
o = createHashObject();
|
||||
dbAdd(c->db,key,o);
|
||||
} else {
|
||||
if (o->type != OBJ_HASH) {
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
void hashTypeConvertZiplist(robj *o, int enc) {
|
||||
serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST);
|
||||
|
||||
if (enc == OBJ_ENCODING_ZIPLIST) {
|
||||
/* Nothing to do... */
|
||||
|
||||
} else if (enc == OBJ_ENCODING_HT) {
|
||||
hashTypeIterator *hi;
|
||||
dict *dict;
|
||||
int ret;
|
||||
|
||||
hi = hashTypeInitIterator(o);
|
||||
dict = dictCreate(&hashDictType, NULL);
|
||||
|
||||
while (hashTypeNext(hi) != C_ERR) {
|
||||
sds key, value;
|
||||
|
||||
key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
|
||||
value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
|
||||
ret = dictAdd(dict, key, value);
|
||||
if (ret != DICT_OK) {
|
||||
serverLogHexDump(LL_WARNING,"ziplist with dup elements dump",
|
||||
o->ptr,ziplistBlobLen(o->ptr));
|
||||
serverPanic("Ziplist corruption detected");
|
||||
}
|
||||
}
|
||||
hashTypeReleaseIterator(hi);
|
||||
zfree(o->ptr);
|
||||
o->encoding = OBJ_ENCODING_HT;
|
||||
o->ptr = dict;
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
}
|
||||
|
||||
void hashTypeConvert(robj *o, int enc) {
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
hashTypeConvertZiplist(o, enc);
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
serverPanic("Not implemented");
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Hash type commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void hsetnxCommand(client *c) {
|
||||
robj *o;
|
||||
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
hashTypeTryConversion(o,c->argv,2,3);
|
||||
|
||||
if (hashTypeExists(o, c->argv[2]->ptr)) {
|
||||
addReply(c, shared.czero);
|
||||
} else {
|
||||
hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY);
|
||||
addReply(c, shared.cone);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
|
||||
void hsetCommand(client *c) {
|
||||
int i, created = 0;
|
||||
robj *o;
|
||||
|
||||
if ((c->argc % 2) == 1) {
|
||||
addReplyError(c,"wrong number of arguments for HMSET");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
hashTypeTryConversion(o,c->argv,2,c->argc-1);
|
||||
|
||||
for (i = 2; i < c->argc; i += 2)
|
||||
created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);
|
||||
|
||||
/* HMSET (deprecated) and HSET return value is different. */
|
||||
char *cmdname = c->argv[0]->ptr;
|
||||
if (cmdname[1] == 's' || cmdname[1] == 'S') {
|
||||
/* HSET */
|
||||
addReplyLongLong(c, created);
|
||||
} else {
|
||||
/* HMSET */
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
void hincrbyCommand(client *c) {
|
||||
PORT_LONGLONG value, incr, oldvalue;
|
||||
robj *o;
|
||||
sds new;
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
|
||||
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&value) == C_OK) {
|
||||
if (vstr) {
|
||||
if (string2ll((char*)vstr,vlen,&value) == 0) {
|
||||
addReplyError(c,"hash value is not an integer");
|
||||
return;
|
||||
}
|
||||
} /* Else hashTypeGetValue() already stored it into &value */
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
oldvalue = value;
|
||||
if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
|
||||
(incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
|
||||
addReplyError(c,"increment or decrement would overflow");
|
||||
return;
|
||||
}
|
||||
value += incr;
|
||||
new = sdsfromlonglong(value);
|
||||
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
|
||||
addReplyLongLong(c,value);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
void hincrbyfloatCommand(client *c) {
|
||||
PORT_LONGDOUBLE value, incr;
|
||||
PORT_LONGLONG ll;
|
||||
robj *o;
|
||||
sds new;
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
|
||||
if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
|
||||
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&ll) == C_OK) {
|
||||
if (vstr) {
|
||||
if (string2ld((char*)vstr,vlen,&value) == 0) {
|
||||
addReplyError(c,"hash value is not a float");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
value = (PORT_LONGDOUBLE)ll;
|
||||
}
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
value += incr;
|
||||
|
||||
char buf[256];
|
||||
int len = ld2string(buf,sizeof(buf),value,1);
|
||||
new = sdsnewlen(buf,len);
|
||||
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
|
||||
addReplyBulkCBuffer(c,buf,len);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
/* Always replicate HINCRBYFLOAT as an HSET command with the final value
|
||||
* in order to make sure that differences in float pricision or formatting
|
||||
* will not create differences in replicas or after an AOF restart. */
|
||||
robj *aux, *newobj;
|
||||
aux = createStringObject("HSET",4);
|
||||
newobj = createRawStringObject(buf,len);
|
||||
rewriteClientCommandArgument(c,0,aux);
|
||||
decrRefCount(aux);
|
||||
rewriteClientCommandArgument(c,3,newobj);
|
||||
decrRefCount(newobj);
|
||||
}
|
||||
|
||||
static void addHashFieldToReply(client *c, robj *o, sds field) {
|
||||
int ret;
|
||||
|
||||
if (o == NULL) {
|
||||
addReply(c, shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *vstr = NULL;
|
||||
unsigned int vlen = UINT_MAX;
|
||||
PORT_LONGLONG vll = LLONG_MAX;
|
||||
|
||||
ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
|
||||
if (ret < 0) {
|
||||
addReply(c, shared.nullbulk);
|
||||
} else {
|
||||
if (vstr) {
|
||||
addReplyBulkCBuffer(c, vstr, vlen);
|
||||
} else {
|
||||
addReplyBulkLongLong(c, vll);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
sds value = hashTypeGetFromHashTable(o, field);
|
||||
if (value == NULL)
|
||||
addReply(c, shared.nullbulk);
|
||||
else
|
||||
addReplyBulkCBuffer(c, value, sdslen(value));
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
}
|
||||
|
||||
void hgetCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
addHashFieldToReply(c, o, c->argv[2]->ptr);
|
||||
}
|
||||
|
||||
void hmgetCommand(client *c) {
|
||||
robj *o;
|
||||
int i;
|
||||
|
||||
/* Don't abort when the key cannot be found. Non-existing keys are empty
|
||||
* hashes, where HMGET should respond with a series of null bulks. */
|
||||
o = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (o != NULL && o->type != OBJ_HASH) {
|
||||
addReply(c, shared.wrongtypeerr);
|
||||
return;
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c, c->argc-2);
|
||||
for (i = 2; i < c->argc; i++) {
|
||||
addHashFieldToReply(c, o, c->argv[i]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void hdelCommand(client *c) {
|
||||
robj *o;
|
||||
int j, deleted = 0, keyremoved = 0;
|
||||
|
||||
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
if (hashTypeDelete(o,c->argv[j]->ptr)) {
|
||||
deleted++;
|
||||
if (hashTypeLength(o) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
keyremoved = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
|
||||
c->db->id);
|
||||
server.dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
|
||||
void hlenCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
addReplyLongLong(c,hashTypeLength(o));
|
||||
}
|
||||
|
||||
void hstrlenCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
addReplyLongLong(c,hashTypeGetValueLength(o,c->argv[2]->ptr));
|
||||
}
|
||||
|
||||
static void addHashIteratorCursorToReply(client *c, 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)
|
||||
addReplyBulkCBuffer(c, vstr, vlen);
|
||||
else
|
||||
addReplyBulkLongLong(c, vll);
|
||||
} else if (hi->encoding == OBJ_ENCODING_HT) {
|
||||
sds value = hashTypeCurrentFromHashTable(hi, what);
|
||||
addReplyBulkCBuffer(c, value, sdslen(value));
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
}
|
||||
|
||||
void genericHgetallCommand(client *c, int flags) {
|
||||
robj *o;
|
||||
hashTypeIterator *hi;
|
||||
int multiplier = 0;
|
||||
int length, count = 0;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|
||||
|| checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
if (flags & OBJ_HASH_KEY) multiplier++;
|
||||
if (flags & OBJ_HASH_VALUE) multiplier++;
|
||||
|
||||
length = (int)(hashTypeLength(o) * multiplier); WIN_PORT_FIX /* cast (int) */
|
||||
addReplyMultiBulkLen(c, length);
|
||||
|
||||
hi = hashTypeInitIterator(o);
|
||||
while (hashTypeNext(hi) != C_ERR) {
|
||||
if (flags & OBJ_HASH_KEY) {
|
||||
addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);
|
||||
count++;
|
||||
}
|
||||
if (flags & OBJ_HASH_VALUE) {
|
||||
addHashIteratorCursorToReply(c, hi, OBJ_HASH_VALUE);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
hashTypeReleaseIterator(hi);
|
||||
serverAssert(count == length);
|
||||
}
|
||||
|
||||
void hkeysCommand(client *c) {
|
||||
genericHgetallCommand(c,OBJ_HASH_KEY);
|
||||
}
|
||||
|
||||
void hvalsCommand(client *c) {
|
||||
genericHgetallCommand(c,OBJ_HASH_VALUE);
|
||||
}
|
||||
|
||||
void hgetallCommand(client *c) {
|
||||
genericHgetallCommand(c,OBJ_HASH_KEY|OBJ_HASH_VALUE);
|
||||
}
|
||||
|
||||
void hexistsCommand(client *c) {
|
||||
robj *o;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
addReply(c, hashTypeExists(o,c->argv[2]->ptr) ? shared.cone : shared.czero);
|
||||
}
|
||||
|
||||
void hscanCommand(client *c) {
|
||||
robj *o;
|
||||
PORT_ULONG cursor;
|
||||
|
||||
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == C_ERR) return;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
scanGenericCommand(c,o,cursor);
|
||||
}
|
||||
+8
-2
@@ -774,15 +774,21 @@ void srandmemberCommand(client *c) {
|
||||
}
|
||||
|
||||
int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
|
||||
return setTypeSize(*(robj**)s1)-setTypeSize(*(robj**)s2);
|
||||
if (setTypeSize(*(robj**)s1) > setTypeSize(*(robj**)s2)) return 1;
|
||||
if (setTypeSize(*(robj**)s1) < setTypeSize(*(robj**)s2)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This is used by SDIFF and in this case we can receive NULL that should
|
||||
* be handled as empty sets. */
|
||||
int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
|
||||
robj *o1 = *(robj**)s1, *o2 = *(robj**)s2;
|
||||
unsigned long first = o1 ? setTypeSize(o1) : 0;
|
||||
unsigned long second = o2 ? setTypeSize(o2) : 0;
|
||||
|
||||
return (o2 ? setTypeSize(o2) : 0) - (o1 ? setTypeSize(o1) : 0);
|
||||
if (first < second) return 1;
|
||||
if (first > second) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sinterGenericCommand(client *c, robj **setkeys,
|
||||
|
||||
+1111
File diff suppressed because it is too large
Load Diff
+3074
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -93,7 +93,7 @@ int stringmatchlen(const char *pattern, int patternLen,
|
||||
}
|
||||
match = 0;
|
||||
while(1) {
|
||||
if (pattern[0] == '\\') {
|
||||
if (pattern[0] == '\\' && patternLen >= 2) {
|
||||
pattern++;
|
||||
patternLen--;
|
||||
if (pattern[0] == string[0])
|
||||
|
||||
+865
@@ -0,0 +1,865 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Win32_Interop/Win32_Portability.h"
|
||||
#include "Win32_Interop/win32fixes.h"
|
||||
#include "Win32_Interop/Win32_Time.h"
|
||||
#include <process.h> // for getpid
|
||||
#include <direct.h> // for getcwd
|
||||
#include <shlwapi.h> // for PathIsRelative
|
||||
#endif
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
POSIX_ONLY(#include <unistd.h>)
|
||||
POSIX_ONLY(#include <sys/time.h>)
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "sha1.h"
|
||||
|
||||
/* Glob-style pattern matching. */
|
||||
int stringmatchlen(const char *pattern, int patternLen,
|
||||
const char *string, int stringLen, int nocase)
|
||||
{
|
||||
while(patternLen) {
|
||||
switch(pattern[0]) {
|
||||
case '*':
|
||||
while (pattern[1] == '*') {
|
||||
pattern++;
|
||||
patternLen--;
|
||||
}
|
||||
if (patternLen == 1)
|
||||
return 1; /* match */
|
||||
while(stringLen) {
|
||||
if (stringmatchlen(pattern+1, patternLen-1,
|
||||
string, stringLen, nocase))
|
||||
return 1; /* match */
|
||||
string++;
|
||||
stringLen--;
|
||||
}
|
||||
return 0; /* no match */
|
||||
break;
|
||||
case '?':
|
||||
if (stringLen == 0)
|
||||
return 0; /* no match */
|
||||
string++;
|
||||
stringLen--;
|
||||
break;
|
||||
case '[':
|
||||
{
|
||||
int not, match;
|
||||
|
||||
pattern++;
|
||||
patternLen--;
|
||||
not = pattern[0] == '^';
|
||||
if (not) {
|
||||
pattern++;
|
||||
patternLen--;
|
||||
}
|
||||
match = 0;
|
||||
while(1) {
|
||||
if (pattern[0] == '\\') {
|
||||
pattern++;
|
||||
patternLen--;
|
||||
if (pattern[0] == string[0])
|
||||
match = 1;
|
||||
} else if (pattern[0] == ']') {
|
||||
break;
|
||||
} else if (patternLen == 0) {
|
||||
pattern--;
|
||||
patternLen++;
|
||||
break;
|
||||
} else if (pattern[1] == '-' && patternLen >= 3) {
|
||||
int start = pattern[0];
|
||||
int end = pattern[2];
|
||||
int c = string[0];
|
||||
if (start > end) {
|
||||
int t = start;
|
||||
start = end;
|
||||
end = t;
|
||||
}
|
||||
if (nocase) {
|
||||
start = tolower(start);
|
||||
end = tolower(end);
|
||||
c = tolower(c);
|
||||
}
|
||||
pattern += 2;
|
||||
patternLen -= 2;
|
||||
if (c >= start && c <= end)
|
||||
match = 1;
|
||||
} else {
|
||||
if (!nocase) {
|
||||
if (pattern[0] == string[0])
|
||||
match = 1;
|
||||
} else {
|
||||
if (tolower((int)pattern[0]) == tolower((int)string[0]))
|
||||
match = 1;
|
||||
}
|
||||
}
|
||||
pattern++;
|
||||
patternLen--;
|
||||
}
|
||||
if (not)
|
||||
match = !match;
|
||||
if (!match)
|
||||
return 0; /* no match */
|
||||
string++;
|
||||
stringLen--;
|
||||
break;
|
||||
}
|
||||
case '\\':
|
||||
if (patternLen >= 2) {
|
||||
pattern++;
|
||||
patternLen--;
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
if (!nocase) {
|
||||
if (pattern[0] != string[0])
|
||||
return 0; /* no match */
|
||||
} else {
|
||||
if (tolower((int)pattern[0]) != tolower((int)string[0]))
|
||||
return 0; /* no match */
|
||||
}
|
||||
string++;
|
||||
stringLen--;
|
||||
break;
|
||||
}
|
||||
pattern++;
|
||||
patternLen--;
|
||||
if (stringLen == 0) {
|
||||
while(*pattern == '*') {
|
||||
pattern++;
|
||||
patternLen--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (patternLen == 0 && stringLen == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stringmatch(const char *pattern, const char *string, int nocase) {
|
||||
return stringmatchlen(pattern,(int)strlen(pattern),string,(int)strlen(string),nocase); WIN_PORT_FIX /* cast (int) */
|
||||
}
|
||||
|
||||
/* Convert a string representing an amount of memory into the number of
|
||||
* bytes, so for instance memtoll("1Gb") will return 1073741824 that is
|
||||
* (1024*1024*1024).
|
||||
*
|
||||
* On parsing error, if *err is not NULL, it's set to 1, otherwise it's
|
||||
* set to 0. On error the function return value is 0, regardless of the
|
||||
* fact 'err' is NULL or not. */
|
||||
PORT_LONGLONG memtoll(const char *p, int *err) {
|
||||
const char *u;
|
||||
char buf[128];
|
||||
PORT_LONG mul; /* unit multiplier */
|
||||
PORT_LONGLONG val;
|
||||
unsigned int digits;
|
||||
|
||||
if (err) *err = 0;
|
||||
|
||||
/* Search the first non digit character. */
|
||||
u = p;
|
||||
if (*u == '-') u++;
|
||||
while(*u && isdigit(*u)) u++;
|
||||
if (*u == '\0' || !strcasecmp(u,"b")) {
|
||||
mul = 1;
|
||||
} else if (!strcasecmp(u,"k")) {
|
||||
mul = 1000;
|
||||
} else if (!strcasecmp(u,"kb")) {
|
||||
mul = 1024;
|
||||
} else if (!strcasecmp(u,"m")) {
|
||||
mul = 1000*1000;
|
||||
} else if (!strcasecmp(u,"mb")) {
|
||||
mul = 1024*1024;
|
||||
} else if (!strcasecmp(u,"g")) {
|
||||
mul = 1000L*1000*1000;
|
||||
} else if (!strcasecmp(u,"gb")) {
|
||||
mul = 1024L*1024*1024;
|
||||
} else {
|
||||
if (err) *err = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Copy the digits into a buffer, we'll use strtoll() to convert
|
||||
* the digit (without the unit) into a number. */
|
||||
digits = (unsigned int)(u-p); WIN_PORT_FIX /* cast (unsigned int) */
|
||||
if (digits >= sizeof(buf)) {
|
||||
if (err) *err = 1;
|
||||
return 0;
|
||||
}
|
||||
memcpy(buf,p,digits);
|
||||
buf[digits] = '\0';
|
||||
|
||||
char *endptr;
|
||||
errno = 0;
|
||||
val = strtoll(buf,&endptr,10);
|
||||
if ((val == 0 && errno == EINVAL) || *endptr != '\0') {
|
||||
if (err) *err = 1;
|
||||
return 0;
|
||||
}
|
||||
return val*mul;
|
||||
}
|
||||
|
||||
/* Return the number of digits of 'v' when converted to string in radix 10.
|
||||
* See ll2string() for more information. */
|
||||
uint32_t digits10(uint64_t v) {
|
||||
if (v < 10) return 1;
|
||||
if (v < 100) return 2;
|
||||
if (v < 1000) return 3;
|
||||
if (v < 1000000000000UL) {
|
||||
if (v < 100000000UL) {
|
||||
if (v < 1000000) {
|
||||
if (v < 10000) return 4;
|
||||
return 5 + (v >= 100000);
|
||||
}
|
||||
return 7 + (v >= 10000000UL);
|
||||
}
|
||||
if (v < 10000000000UL) {
|
||||
return 9 + (v >= 1000000000UL);
|
||||
}
|
||||
return 11 + (v >= 100000000000UL);
|
||||
}
|
||||
return 12 + digits10(v / 1000000000000UL);
|
||||
}
|
||||
|
||||
/* Like digits10() but for signed values. */
|
||||
uint32_t sdigits10(int64_t v) {
|
||||
if (v < 0) {
|
||||
/* Abs value of LLONG_MIN requires special handling. */
|
||||
uint64_t uv = (v != LLONG_MIN) ?
|
||||
(uint64_t)-v : ((uint64_t) LLONG_MAX)+1;
|
||||
return digits10(uv)+1; /* +1 for the minus. */
|
||||
} else {
|
||||
return digits10(v);
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert a PORT_LONGLONG into a string. Returns the number of
|
||||
* characters needed to represent the number.
|
||||
* If the buffer is not big enough to store the string, 0 is returned.
|
||||
*
|
||||
* Based on the following article (that apparently does not provide a
|
||||
* novel approach but only publicizes an already used technique):
|
||||
*
|
||||
* https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920
|
||||
*
|
||||
* Modified in order to handle signed integers since the original code was
|
||||
* designed for unsigned integers. */
|
||||
int ll2string(char *dst, size_t dstlen, PORT_LONGLONG svalue) {
|
||||
static const char digits[201] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
"4041424344454647484950515253545556575859"
|
||||
"6061626364656667686970717273747576777879"
|
||||
"8081828384858687888990919293949596979899";
|
||||
int negative;
|
||||
PORT_ULONGLONG value;
|
||||
|
||||
/* The main loop works with 64bit unsigned integers for simplicity, so
|
||||
* we convert the number here and remember if it is negative. */
|
||||
if (svalue < 0) {
|
||||
if (svalue != LLONG_MIN) {
|
||||
value = -svalue;
|
||||
} else {
|
||||
value = ((PORT_ULONGLONG) LLONG_MAX)+1;
|
||||
}
|
||||
negative = 1;
|
||||
} else {
|
||||
value = svalue;
|
||||
negative = 0;
|
||||
}
|
||||
|
||||
/* Check length. */
|
||||
uint32_t const length = digits10(value)+negative;
|
||||
if (length >= dstlen) return 0;
|
||||
|
||||
/* Null term. */
|
||||
uint32_t next = length;
|
||||
dst[next] = '\0';
|
||||
next--;
|
||||
while (value >= 100) {
|
||||
int const i = (value % 100) * 2;
|
||||
value /= 100;
|
||||
dst[next] = digits[i + 1];
|
||||
dst[next - 1] = digits[i];
|
||||
next -= 2;
|
||||
}
|
||||
|
||||
/* Handle last 1-2 digits. */
|
||||
if (value < 10) {
|
||||
dst[next] = '0' + (uint32_t) value;
|
||||
} else {
|
||||
int i = (uint32_t) value * 2;
|
||||
dst[next] = digits[i + 1];
|
||||
dst[next - 1] = digits[i];
|
||||
}
|
||||
|
||||
/* Add sign. */
|
||||
if (negative) dst[0] = '-';
|
||||
return length;
|
||||
}
|
||||
|
||||
/* Convert a string into a PORT_LONGLONG. Returns 1 if the string could be parsed
|
||||
* into a (non-overflowing) PORT_LONGLONG, 0 otherwise. The value will be set to
|
||||
* the parsed value when appropriate.
|
||||
*
|
||||
* Note that this function demands that the string strictly represents
|
||||
* a PORT_LONGLONG: no spaces or other characters before or after the string
|
||||
* representing the number are accepted, nor zeroes at the start if not
|
||||
* for the string "0" representing the zero number.
|
||||
*
|
||||
* Because of its strictness, it is safe to use this function to check if
|
||||
* you can convert a string into a PORT_LONGLONG, and obtain back the string
|
||||
* from the number without any loss in the string representation. */
|
||||
int string2ll(const char *s, size_t slen, PORT_LONGLONG *value) {
|
||||
const char *p = s;
|
||||
size_t plen = 0;
|
||||
int negative = 0;
|
||||
PORT_ULONGLONG v;
|
||||
|
||||
if (plen == slen)
|
||||
return 0;
|
||||
|
||||
/* Special case: first and only digit is 0. */
|
||||
if (slen == 1 && p[0] == '0') {
|
||||
if (value != NULL) *value = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (p[0] == '-') {
|
||||
negative = 1;
|
||||
p++; plen++;
|
||||
|
||||
/* Abort on only a negative sign. */
|
||||
if (plen == slen)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* First digit should be 1-9, otherwise the string should just be 0. */
|
||||
if (p[0] >= '1' && p[0] <= '9') {
|
||||
v = p[0]-'0';
|
||||
p++; plen++;
|
||||
} else if (p[0] == '0' && slen == 1) {
|
||||
*value = 0;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (plen < slen && p[0] >= '0' && p[0] <= '9') {
|
||||
if (v > (ULLONG_MAX / 10)) /* Overflow. */
|
||||
return 0;
|
||||
v *= 10;
|
||||
|
||||
if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
|
||||
return 0;
|
||||
v += p[0]-'0';
|
||||
|
||||
p++; plen++;
|
||||
}
|
||||
|
||||
/* Return if not all bytes were used. */
|
||||
if (plen < slen)
|
||||
return 0;
|
||||
|
||||
if (negative) {
|
||||
if (v > ((PORT_ULONGLONG)(-(LLONG_MIN+1))+1)) /* Overflow. */
|
||||
return 0;
|
||||
if (value != NULL) *value = -v;
|
||||
} else {
|
||||
if (v > LLONG_MAX) /* Overflow. */
|
||||
return 0;
|
||||
if (value != NULL) *value = v;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Convert a string into a long. Returns 1 if the string could be parsed into a
|
||||
* (non-overflowing) long, 0 otherwise. The value will be set to the parsed
|
||||
* value when appropriate. */
|
||||
int string2l(const char *s, size_t slen, PORT_LONG *lval) {
|
||||
PORT_LONGLONG llval;
|
||||
|
||||
if (!string2ll(s,slen,&llval))
|
||||
return 0;
|
||||
|
||||
if (llval < PORT_LONG_MIN || llval > PORT_LONG_MAX)
|
||||
return 0;
|
||||
|
||||
*lval = (PORT_LONG)llval;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Convert a string into a double. Returns 1 if the string could be parsed
|
||||
* into a (non-overflowing) double, 0 otherwise. The value will be set to
|
||||
* the parsed value when appropriate.
|
||||
*
|
||||
* Note that this function demands that the string strictly represents
|
||||
* a double: no spaces or other characters before or after the string
|
||||
* representing the number are accepted. */
|
||||
int string2ld(const char *s, size_t slen, PORT_LONGDOUBLE *dp) {
|
||||
char buf[256];
|
||||
PORT_LONGDOUBLE value;
|
||||
char *eptr;
|
||||
|
||||
if (slen >= sizeof(buf)) return 0;
|
||||
memcpy(buf,s,slen);
|
||||
buf[slen] = '\0';
|
||||
|
||||
errno = 0;
|
||||
value = strtold(buf, &eptr);
|
||||
if (isspace(buf[0]) || eptr[0] != '\0' ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
errno == EINVAL ||
|
||||
isnan(value))
|
||||
return 0;
|
||||
|
||||
if (dp) *dp = value;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Convert a double to a string representation. Returns the number of bytes
|
||||
* required. The representation should always be parsable by strtod(3).
|
||||
* This function does not support human-friendly formatting like ld2string
|
||||
* does. It is intented mainly to be used inside t_zset.c when writing scores
|
||||
* into a ziplist representing a sorted set. */
|
||||
int d2string(char *buf, size_t len, double value) {
|
||||
if (isnan(value)) {
|
||||
len = snprintf(buf,len,"nan");
|
||||
} else if (isinf(value)) {
|
||||
if (value < 0)
|
||||
len = snprintf(buf,len,"-inf");
|
||||
else
|
||||
len = snprintf(buf,len,"inf");
|
||||
} else if (value == 0) {
|
||||
/* See: http://en.wikipedia.org/wiki/Signed_zero, "Comparisons". */
|
||||
if (1.0/value < 0)
|
||||
len = snprintf(buf,len,"-0");
|
||||
else
|
||||
len = snprintf(buf,len,"0");
|
||||
} else {
|
||||
#if (DBL_MANT_DIG >= 52) && (LLONG_MAX == 0x7fffffffffffffffLL)
|
||||
/* Check if the float is in a safe range to be casted into a
|
||||
* PORT_LONGLONG. We are assuming that PORT_LONGLONG is 64 bit here.
|
||||
* Also we are assuming that there are no implementations around where
|
||||
* double has precision < 52 bit.
|
||||
*
|
||||
* Under this assumptions we test if a double is inside an interval
|
||||
* where casting to PORT_LONGLONG is safe. Then using two castings we
|
||||
* make sure the decimal part is zero. If all this is true we use
|
||||
* integer printing function that is much faster. */
|
||||
double min = -4503599627370495; /* (2^52)-1 */
|
||||
double max = 4503599627370496; /* -(2^52) */
|
||||
if (value > min && value < max && value == ((double)((PORT_LONGLONG)value)))
|
||||
len = ll2string(buf,len,(PORT_LONGLONG)value);
|
||||
else
|
||||
#endif
|
||||
len = snprintf(buf,len,"%.17g",value);
|
||||
}
|
||||
|
||||
return (int)len; WIN_PORT_FIX /* cast (int) */
|
||||
}
|
||||
|
||||
/* Convert a PORT_LONGDOUBLE into a string. If humanfriendly is non-zero
|
||||
* it does not use exponential format and trims trailing zeroes at the end,
|
||||
* however this results in loss of precision. Otherwise exp format is used
|
||||
* and the output of snprintf() is not modified.
|
||||
*
|
||||
* The function returns the length of the string or zero if there was not
|
||||
* enough buffer room to store it. */
|
||||
int ld2string(char *buf, size_t len, PORT_LONGDOUBLE value, int humanfriendly) {
|
||||
size_t l;
|
||||
|
||||
if (isinf(value)) {
|
||||
/* Libc in odd systems (Hi Solaris!) will format infinite in a
|
||||
* different way, so better to handle it in an explicit way. */
|
||||
if (len < 5) return 0; /* No room. 5 is "-inf\0" */
|
||||
if (value > 0) {
|
||||
memcpy(buf,"inf",3);
|
||||
l = 3;
|
||||
} else {
|
||||
memcpy(buf,"-inf",4);
|
||||
l = 4;
|
||||
}
|
||||
} else if (humanfriendly) {
|
||||
/* We use 17 digits precision since with 128 bit floats that precision
|
||||
* after rounding is able to represent most small decimal numbers in a
|
||||
* way that is "non surprising" for the user (that is, most small
|
||||
* decimal numbers will be represented in a way that when converted
|
||||
* back into a string are exactly the same as what the user typed.) */
|
||||
l = snprintf(buf,len,"%.17Lf", value);
|
||||
if (l+1 > len) return 0; /* No room. */
|
||||
/* Now remove trailing zeroes after the '.' */
|
||||
if (strchr(buf,'.') != NULL) {
|
||||
char *p = buf+l-1;
|
||||
while(*p == '0') {
|
||||
p--;
|
||||
l--;
|
||||
}
|
||||
if (*p == '.') l--;
|
||||
}
|
||||
} else {
|
||||
l = snprintf(buf,len,"%.17Lg", value);
|
||||
if (l+1 > len) return 0; /* No room. */
|
||||
}
|
||||
buf[l] = '\0';
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Generate the Redis "Run ID", a SHA1-sized random number that identifies a
|
||||
* given execution of Redis, so that if you are talking with an instance
|
||||
* having run_id == A, and you reconnect and it has run_id == B, you can be
|
||||
* sure that it is either a different instance or it was restarted. */
|
||||
void getRandomHexChars(char *p, unsigned int len) {
|
||||
char *charset = "0123456789abcdef";
|
||||
unsigned int j;
|
||||
|
||||
/* Global state. */
|
||||
static int seed_initialized = 0;
|
||||
static unsigned char seed[20]; /* The SHA1 seed, from /dev/urandom. */
|
||||
static uint64_t counter = 0; /* The counter we hash with the seed. */
|
||||
|
||||
if (!seed_initialized) {
|
||||
/* Initialize a seed and use SHA1 in counter mode, where we hash
|
||||
* the same seed with a progressive counter. For the goals of this
|
||||
* function we just need non-colliding strings, there are no
|
||||
* cryptographic security needs. */
|
||||
FILE *fp = fopen("/dev/urandom","r");
|
||||
if (fp && fread(seed,sizeof(seed),1,fp) == 1)
|
||||
seed_initialized = 1;
|
||||
if (fp) fclose(fp);
|
||||
}
|
||||
|
||||
if (seed_initialized) {
|
||||
while(len) {
|
||||
unsigned char digest[20];
|
||||
SHA1_CTX ctx;
|
||||
unsigned int copylen = len > 20 ? 20 : len;
|
||||
|
||||
SHA1Init(&ctx);
|
||||
SHA1Update(&ctx, seed, sizeof(seed));
|
||||
SHA1Update(&ctx, (unsigned char*)&counter,sizeof(counter));
|
||||
SHA1Final(digest, &ctx);
|
||||
counter++;
|
||||
|
||||
memcpy(p,digest,copylen);
|
||||
/* Convert to hex digits. */
|
||||
for (j = 0; j < copylen; j++) p[j] = charset[p[j] & 0x0F];
|
||||
len -= copylen;
|
||||
p += copylen;
|
||||
}
|
||||
} else {
|
||||
/* If we can't read from /dev/urandom, do some reasonable effort
|
||||
* in order to create some entropy, since this function is used to
|
||||
* generate run_id and cluster instance IDs */
|
||||
char *x = p;
|
||||
unsigned int l = len;
|
||||
struct timeval tv;
|
||||
pid_t pid = getpid();
|
||||
|
||||
/* Use time and PID to fill the initial array. */
|
||||
gettimeofday(&tv,NULL);
|
||||
if (l >= sizeof(tv.tv_usec)) {
|
||||
memcpy(x,&tv.tv_usec,sizeof(tv.tv_usec));
|
||||
l -= sizeof(tv.tv_usec);
|
||||
x += sizeof(tv.tv_usec);
|
||||
}
|
||||
if (l >= sizeof(tv.tv_sec)) {
|
||||
memcpy(x,&tv.tv_sec,sizeof(tv.tv_sec));
|
||||
l -= sizeof(tv.tv_sec);
|
||||
x += sizeof(tv.tv_sec);
|
||||
}
|
||||
if (l >= sizeof(pid)) {
|
||||
memcpy(x,&pid,sizeof(pid));
|
||||
l -= sizeof(pid);
|
||||
x += sizeof(pid);
|
||||
}
|
||||
/* Finally xor it with rand() output, that was already seeded with
|
||||
* time() at startup, and convert to hex digits. */
|
||||
for (j = 0; j < len; j++) {
|
||||
p[j] ^= rand();
|
||||
p[j] = charset[p[j] & 0x0F];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Given the filename, return the absolute path as an SDS string, or NULL
|
||||
* if it fails for some reason. Note that "filename" may be an absolute path
|
||||
* already, this will be detected and handled correctly.
|
||||
*
|
||||
* The function does not try to normalize everything, but only the obvious
|
||||
* case of one or more "../" appearning at the start of "filename"
|
||||
* relative path. */
|
||||
#ifdef _WIN32
|
||||
sds getAbsolutePath(char *filename) {
|
||||
char fullPath[MAX_PATH];
|
||||
DWORD gfpnResult;
|
||||
sds abspath;
|
||||
sds relpath = sdsnew(filename);
|
||||
|
||||
relpath = sdstrim(relpath, " \r\n\t");
|
||||
|
||||
if (!PathIsRelative(relpath)) return relpath;
|
||||
|
||||
gfpnResult = GetFullPathNameA(relpath, sizeof(fullPath), fullPath, NULL);
|
||||
sdsfree(relpath);
|
||||
|
||||
if (gfpnResult == 0 || gfpnResult > sizeof(fullPath)) {
|
||||
return NULL;
|
||||
}
|
||||
abspath = sdsnew(fullPath);
|
||||
return abspath;
|
||||
}
|
||||
#else
|
||||
sds getAbsolutePath(char *filename) {
|
||||
char cwd[1024];
|
||||
sds abspath;
|
||||
sds relpath = sdsnew(filename);
|
||||
|
||||
relpath = sdstrim(relpath," \r\n\t");
|
||||
if (relpath[0] == '/') return relpath; /* Path is already absolute. */
|
||||
|
||||
/* If path is relative, join cwd and relative path. */
|
||||
if (getcwd(cwd,sizeof(cwd)) == NULL) {
|
||||
sdsfree(relpath);
|
||||
return NULL;
|
||||
}
|
||||
abspath = sdsnew(cwd);
|
||||
if (sdslen(abspath) && abspath[sdslen(abspath)-1] != '/')
|
||||
abspath = sdscat(abspath,"/");
|
||||
|
||||
/* At this point we have the current path always ending with "/", and
|
||||
* the trimmed relative path. Try to normalize the obvious case of
|
||||
* trailing ../ elements at the start of the path.
|
||||
*
|
||||
* For every "../" we find in the filename, we remove it and also remove
|
||||
* the last element of the cwd, unless the current cwd is "/". */
|
||||
while (sdslen(relpath) >= 3 &&
|
||||
relpath[0] == '.' && relpath[1] == '.' && relpath[2] == '/')
|
||||
{
|
||||
sdsrange(relpath,3,-1);
|
||||
if (sdslen(abspath) > 1) {
|
||||
char *p = abspath + sdslen(abspath)-2;
|
||||
int trimlen = 1;
|
||||
|
||||
while(*p != '/') {
|
||||
p--;
|
||||
trimlen++;
|
||||
}
|
||||
sdsrange(abspath,0,-(trimlen+1));
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally glue the two parts together. */
|
||||
abspath = sdscatsds(abspath,relpath);
|
||||
sdsfree(relpath);
|
||||
return abspath;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Return true if the specified path is just a file basename without any
|
||||
* relative or absolute path. This function just checks that no / or \
|
||||
* character exists inside the specified path, that's enough in the
|
||||
* environments where Redis runs. */
|
||||
int pathIsBaseName(char *path) {
|
||||
return strchr(path,'/') == NULL && strchr(path,'\\') == NULL;
|
||||
}
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
#include <assert.h>
|
||||
|
||||
static void test_string2ll(void) {
|
||||
char buf[32];
|
||||
PORT_LONGLONG v;
|
||||
|
||||
/* May not start with +. */
|
||||
strcpy(buf,"+1");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 0);
|
||||
|
||||
/* Leading space. */
|
||||
strcpy(buf," 1");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 0);
|
||||
|
||||
/* Trailing space. */
|
||||
strcpy(buf,"1 ");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 0);
|
||||
|
||||
/* May not start with 0. */
|
||||
strcpy(buf,"01");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 0);
|
||||
|
||||
strcpy(buf,"-1");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 1);
|
||||
assert(v == -1);
|
||||
|
||||
strcpy(buf,"0");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 1);
|
||||
assert(v == 0);
|
||||
|
||||
strcpy(buf,"1");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 1);
|
||||
assert(v == 1);
|
||||
|
||||
strcpy(buf,"99");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 1);
|
||||
assert(v == 99);
|
||||
|
||||
strcpy(buf,"-99");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 1);
|
||||
assert(v == -99);
|
||||
|
||||
strcpy(buf,"-9223372036854775808");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 1);
|
||||
assert(v == LLONG_MIN);
|
||||
|
||||
strcpy(buf,"-9223372036854775809"); /* overflow */
|
||||
assert(string2ll(buf,strlen(buf),&v) == 0);
|
||||
|
||||
strcpy(buf,"9223372036854775807");
|
||||
assert(string2ll(buf,strlen(buf),&v) == 1);
|
||||
assert(v == LLONG_MAX);
|
||||
|
||||
strcpy(buf,"9223372036854775808"); /* overflow */
|
||||
assert(string2ll(buf,strlen(buf),&v) == 0);
|
||||
}
|
||||
|
||||
static void test_string2l(void) {
|
||||
char buf[32];
|
||||
PORT_LONG v;
|
||||
|
||||
/* May not start with +. */
|
||||
strcpy(buf,"+1");
|
||||
assert(string2l(buf,strlen(buf),&v) == 0);
|
||||
|
||||
/* May not start with 0. */
|
||||
strcpy(buf,"01");
|
||||
assert(string2l(buf,strlen(buf),&v) == 0);
|
||||
|
||||
strcpy(buf,"-1");
|
||||
assert(string2l(buf,strlen(buf),&v) == 1);
|
||||
assert(v == -1);
|
||||
|
||||
strcpy(buf,"0");
|
||||
assert(string2l(buf,strlen(buf),&v) == 1);
|
||||
assert(v == 0);
|
||||
|
||||
strcpy(buf,"1");
|
||||
assert(string2l(buf,strlen(buf),&v) == 1);
|
||||
assert(v == 1);
|
||||
|
||||
strcpy(buf,"99");
|
||||
assert(string2l(buf,strlen(buf),&v) == 1);
|
||||
assert(v == 99);
|
||||
|
||||
strcpy(buf,"-99");
|
||||
assert(string2l(buf,strlen(buf),&v) == 1);
|
||||
assert(v == -99);
|
||||
|
||||
#if PORT_LONG_MAX != LLONG_MAX
|
||||
strcpy(buf,"-2147483648");
|
||||
assert(string2l(buf,strlen(buf),&v) == 1);
|
||||
assert(v == PORT_LONG_MIN);
|
||||
|
||||
strcpy(buf,"-2147483649"); /* overflow */
|
||||
assert(string2l(buf,strlen(buf),&v) == 0);
|
||||
|
||||
strcpy(buf,"2147483647");
|
||||
assert(string2l(buf,strlen(buf),&v) == 1);
|
||||
assert(v == PORT_LONG_MAX);
|
||||
|
||||
strcpy(buf,"2147483648"); /* overflow */
|
||||
assert(string2l(buf,strlen(buf),&v) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void test_ll2string(void) {
|
||||
char buf[32];
|
||||
PORT_LONGLONG v;
|
||||
int sz;
|
||||
|
||||
v = 0;
|
||||
sz = ll2string(buf, sizeof buf, v);
|
||||
assert(sz == 1);
|
||||
assert(!strcmp(buf, "0"));
|
||||
|
||||
v = -1;
|
||||
sz = ll2string(buf, sizeof buf, v);
|
||||
assert(sz == 2);
|
||||
assert(!strcmp(buf, "-1"));
|
||||
|
||||
v = 99;
|
||||
sz = ll2string(buf, sizeof buf, v);
|
||||
assert(sz == 2);
|
||||
assert(!strcmp(buf, "99"));
|
||||
|
||||
v = -99;
|
||||
sz = ll2string(buf, sizeof buf, v);
|
||||
assert(sz == 3);
|
||||
assert(!strcmp(buf, "-99"));
|
||||
|
||||
v = -2147483648;
|
||||
sz = ll2string(buf, sizeof buf, v);
|
||||
assert(sz == 11);
|
||||
assert(!strcmp(buf, "-2147483648"));
|
||||
|
||||
v = LLONG_MIN;
|
||||
sz = ll2string(buf, sizeof buf, v);
|
||||
assert(sz == 20);
|
||||
assert(!strcmp(buf, "-9223372036854775808"));
|
||||
|
||||
v = LLONG_MAX;
|
||||
sz = ll2string(buf, sizeof buf, v);
|
||||
assert(sz == 19);
|
||||
assert(!strcmp(buf, "9223372036854775807"));
|
||||
}
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
int utilTest(int argc, char **argv) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
|
||||
test_string2ll();
|
||||
test_string2l();
|
||||
test_ll2string();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -33,6 +33,11 @@
|
||||
#include <stdint.h>
|
||||
#include "sds.h"
|
||||
|
||||
/* The maximum number of characters needed to represent a long double
|
||||
* as a string (long double has a huge range).
|
||||
* This should be the size of the buffer given to ld2string */
|
||||
#define MAX_LONG_DOUBLE_CHARS 5*1024
|
||||
|
||||
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
|
||||
int stringmatch(const char *p, const char *s, int nocase);
|
||||
PORT_LONGLONG memtoll(const char *p, int *err);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __REDIS_UTIL_H
|
||||
#define __REDIS_UTIL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "sds.h"
|
||||
|
||||
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
|
||||
int stringmatch(const char *p, const char *s, int nocase);
|
||||
PORT_LONGLONG memtoll(const char *p, int *err);
|
||||
uint32_t digits10(uint64_t v);
|
||||
uint32_t sdigits10(int64_t v);
|
||||
int ll2string(char *s, size_t len, PORT_LONGLONG value);
|
||||
int string2ll(const char *s, size_t slen, PORT_LONGLONG *value);
|
||||
int string2l(const char *s, size_t slen, PORT_LONG *value);
|
||||
int string2ld(const char *s, size_t slen, PORT_LONGDOUBLE *dp);
|
||||
int d2string(char *buf, size_t len, double value);
|
||||
int ld2string(char *buf, size_t len, PORT_LONGDOUBLE value, int humanfriendly);
|
||||
sds getAbsolutePath(char *filename);
|
||||
int pathIsBaseName(char *path);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int utilTest(int argc, char **argv);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "4.0.2.3"
|
||||
#define REDIS_VERSION "4.0.10"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#define REDIS_VERSION "4.0.2.3"
|
||||
+6
-6
@@ -53,20 +53,20 @@
|
||||
* <prevlen> <encoding>
|
||||
*
|
||||
* The length of the previous entry, <prevlen>, is encoded in the following way:
|
||||
* If this length is smaller than 255 bytes, it will only consume a single
|
||||
* If this length is smaller than 254 bytes, it will only consume a single
|
||||
* byte representing the length as an unsinged 8 bit integer. When the length
|
||||
* is greater than or equal to 255, it will consume 5 bytes. The first byte is
|
||||
* set to 255 (FF) to indicate a larger value is following. The remaining 4
|
||||
* is greater than or equal to 254, it will consume 5 bytes. The first byte is
|
||||
* set to 254 (FE) to indicate a larger value is following. The remaining 4
|
||||
* bytes take the length of the previous entry as value.
|
||||
*
|
||||
* So practically an entry is encoded in the following way:
|
||||
*
|
||||
* <prevlen from 0 to 254> <encoding> <entry>
|
||||
* <prevlen from 0 to 253> <encoding> <entry>
|
||||
*
|
||||
* Or alternatively if the previous entry length is greater than 254 bytes
|
||||
* Or alternatively if the previous entry length is greater than 253 bytes
|
||||
* the following encoding is used:
|
||||
*
|
||||
* 0xFF <4 bytes unsigned little endian prevlen> <encoding> <entry>
|
||||
* 0xFE <4 bytes unsigned little endian prevlen> <encoding> <entry>
|
||||
*
|
||||
* The encoding field of the entry depends on the content of the
|
||||
* entry. When the entry is a string, the first 2 bits of the encoding first
|
||||
|
||||
+1940
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user