Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first commit

  • Loading branch information...
commit ed9b544e10b84cd43348ddfab7068b610a5df1f7 0 parents
@antirez antirez authored
Showing with 13,641 additions and 0 deletions.
  1. +12 −0 BETATESTING.txt
  2. +1 −0  BUGS
  3. +10 −0 COPYING
  4. +58 −0 Makefile
  5. +1 −0  README
  6. +17 −0 TODO
  7. +285 −0 adlist.c
  8. +90 −0 adlist.h
  9. +368 −0 ae.c
  10. +106 −0 ae.h
  11. +268 −0 anet.c
  12. +49 −0 anet.h
  13. +460 −0 benchmark.c
  14. +28 −0 client-libraries/README
  15. +2 −0  client-libraries/erlang/.hg_archival.txt
  16. +2 −0  client-libraries/erlang/.hgignore
  17. +22 −0 client-libraries/erlang/LICENSE
  18. +29 −0 client-libraries/erlang/Makefile
  19. +1 −0  client-libraries/erlang/include/erldis.hrl
  20. +9 −0 client-libraries/erlang/src/Makefile
  21. +272 −0 client-libraries/erlang/src/client.erl
  22. +82 −0 client-libraries/erlang/src/erldis.erl
  23. +68 −0 client-libraries/erlang/src/proto.erl
  24. +51 −0 client-libraries/erlang/support/include.mk
  25. +12 −0 client-libraries/erlang/test/Makefile
  26. +88 −0 client-libraries/erlang/test/erldis_tests.erl
  27. +10 −0 client-libraries/erlang/test/proto_tests.erl
  28. +330 −0 client-libraries/php/redis.php
  29. +78 −0 client-libraries/php/tests.php
  30. +930 −0 client-libraries/python/redis.py
  31. +20 −0 client-libraries/ruby/LICENSE
  32. +31 −0 client-libraries/ruby/README.markdown
  33. +12 −0 client-libraries/ruby/README.rdoc
  34. +58 −0 client-libraries/ruby/Rakefile
  35. +15 −0 client-libraries/ruby/bench.rb
  36. +33 −0 client-libraries/ruby/bin/distredis
  37. +16 −0 client-libraries/ruby/examples/basic.rb
  38. +18 −0 client-libraries/ruby/examples/incr-decr.rb
  39. +26 −0 client-libraries/ruby/examples/list.rb
  40. +36 −0 client-libraries/ruby/examples/sets.rb
  41. +11 −0 client-libraries/ruby/fill.rb
  42. +188 −0 client-libraries/ruby/lib/better_timeout.rb
  43. +111 −0 client-libraries/ruby/lib/dist_redis.rb
  44. +73 −0 client-libraries/ruby/lib/hash_ring.rb
  45. +836 −0 client-libraries/ruby/lib/redis.rb
  46. +267 −0 client-libraries/ruby/spec/redis_spec.rb
  47. +4 −0 client-libraries/ruby/spec/spec_helper.rb
  48. +116 −0 client-libraries/ruby/tasks/redis.tasks.rb
  49. +579 −0 dict.c
  50. +136 −0 dict.h
  51. +121 −0 doc/Benchmarks.html
  52. +39 −0 doc/BgsaveCommand.html
  53. +44 −0 doc/CommandReference.html
  54. +36 −0 doc/Credits.html
  55. +38 −0 doc/DbsizeCommand.html
  56. +42 −0 doc/DelCommand.html
  57. +37 −0 doc/DesignPatterns.html
  58. +42 −0 doc/ExistsCommand.html
  59. +47 −0 doc/FAQ.html
  60. +39 −0 doc/FlushallCommand.html
  61. +39 −0 doc/FlushdbCommand.html
  62. +39 −0 doc/GetCommand.html
  63. +43 −0 doc/IncrCommand.html
  64. +50 −0 doc/InfoCommand.html
  65. +42 −0 doc/KeysCommand.html
  66. +39 −0 doc/LastsaveCommand.html
  67. +41 −0 doc/LindexCommand.html
  68. +42 −0 doc/LlenCommand.html
  69. +41 −0 doc/LpopCommand.html
  70. +42 −0 doc/LrangeCommand.html
  71. +43 −0 doc/LremCommand.html
  72. +39 −0 doc/LsetCommand.html
  73. +47 −0 doc/LtrimCommand.html
  74. +42 −0 doc/MoveCommand.html
  75. +143 −0 doc/ProtocolSpecification.html
  76. +38 −0 doc/QuitCommand.html
  77. +109 −0 doc/README.html
  78. +39 −0 doc/RandomkeyCommand.html
  79. +39 −0 doc/RenameCommand.html
  80. +44 −0 doc/RenamenxCommand.html
  81. +44 −0 doc/ReplyTypes.html
  82. +40 −0 doc/RpushCommand.html
  83. +43 −0 doc/SaddCommand.html
  84. +39 −0 doc/SaveCommand.html
  85. +42 −0 doc/ScardCommand.html
  86. +39 −0 doc/SelectCommand.html
  87. +39 −0 doc/SetCommand.html
  88. +42 −0 doc/SetnxCommand.html
  89. +39 −0 doc/ShutdownCommand.html
  90. +40 −0 doc/SinterCommand.html
  91. +39 −0 doc/SinterstoreCommand.html
  92. +43 −0 doc/SismemberCommand.html
  93. +39 −0 doc/SmembersCommand.html
  94. +60 −0 doc/SortCommand.html
  95. +43 −0 doc/SremCommand.html
  96. +38 −0 doc/TemplateCommand.html
  97. +252 −0 doc/TwitterAlikeExample.html
  98. +44 −0 doc/TypeCommand.html
  99. +40 −0 doc/VersionControl.html
  100. +36 −0 doc/index.html
  101. BIN  doc/redis.png
  102. +25 −0 doc/style.css
  103. +340 −0 redis-cli.c
  104. +3,037 −0 redis.c
  105. +66 −0 redis.conf
  106. +329 −0 sds.c
  107. +63 −0 sds.h
  108. +807 −0 test-redis.tcl
  109. +82 −0 zmalloc.c
  110. +40 −0 zmalloc.h
12 BETATESTING.txt
@@ -0,0 +1,12 @@
+Hello betatester!
+
+This Redis Server distribution is just a preview, it is by no mean an usable
+product, but probably it can already give you some feeling about what the
+final release is going to be.
+
+Be aware that if you want to use Redis in production the server may not be perfectly stable or may cotanin unfixed bugs. We did our best to ensure this distribution is of good quality and bug free but the development is currently very fast.
+
+Please send feedbacks to antirez at gmail dot com.
+
+Enjoy,
+antirez
1  BUGS
@@ -0,0 +1 @@
+Plese check http://code.google.com/p/redis/issues/list
10 COPYING
@@ -0,0 +1,10 @@
+Copyright (c) 2006-2009, Salvatore Sanfilippo
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
58 Makefile
@@ -0,0 +1,58 @@
+# Redis Makefile
+# Copyright (C) 2009 Salvatore Sanfilippo <antirez at gmail dot com>
+# This file is released under the BSD license, see the COPYING file
+
+DEBUG?= -g
+CFLAGS?= -O2 -Wall -W -DSDS_ABORT_ON_OOM
+CCOPT= $(CFLAGS)
+
+OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o
+BENCHOBJ = ae.o anet.o benchmark.o sds.o adlist.o zmalloc.o
+CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o
+
+PRGNAME = redis-server
+BENCHPRGNAME = redis-benchmark
+CLIPRGNAME = redis-cli
+
+all: redis-server redis-benchmark redis-cli
+
+# Deps (use make dep to generate this)
+adlist.o: adlist.c adlist.h
+ae.o: ae.c ae.h
+anet.o: anet.c anet.h
+benchmark.o: benchmark.c ae.h anet.h sds.h adlist.h
+dict.o: dict.c dict.h
+redis-cli.o: redis-cli.c anet.h sds.h adlist.h
+redis.o: redis.c ae.h sds.h anet.h dict.h adlist.h
+sds.o: sds.c sds.h
+sha1.o: sha1.c sha1.h
+zmalloc.o: zmalloc.c
+
+redis-server: $(OBJ)
+ $(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ)
+ @echo ""
+ @echo "Hint: To run the test-redis.tcl script is a good idea."
+ @echo "Launch the redis server with ./redis-server, then in another"
+ @echo "terminal window enter this directory and run 'make test'."
+ @echo ""
+
+redis-benchmark: $(BENCHOBJ)
+ $(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ)
+
+redis-cli: $(CLIOBJ)
+ $(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ)
+
+.c.o:
+ $(CC) -c $(CCOPT) $(DEBUG) $(COMPILE_TIME) $<
+
+clean:
+ rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) *.o
+
+dep:
+ $(CC) -MM *.c
+
+test:
+ tclsh test-redis.tcl
+
+bench:
+ ./redis-benchmark
1  README
@@ -0,0 +1 @@
+Check the 'doc' directory. doc/README.html is a good starting point :)
17 TODO
@@ -0,0 +1,17 @@
+BETA 8 TODO
+- keys expire
+- sunion ssub
+- write integers in a special way on disk (and on memory?)
+- compact types for disk storing of short strings (no 4 bytes overhead!)
+- network layer stresser in test in demo
+- maxclients directive
+- check 'server.dirty' everywere
+- replication tests
+- command line client. If the last argument of a bulk command is missing get it from stdin. Example:
+ $ echo "bar" | redis-client SET foo
+ $ redis-client SET foo bar
+ $ redis-client GET foo
+ bar
+ $
+- Make Redis aware of the memory it is using thanks to getrusage() and report this info with the INFO command.
+- INFO command: clients, slave/master, requests/second in the last N seconds, memory usage, uptime, dirty, lastsave
285 adlist.c
@@ -0,0 +1,285 @@
+/* adlist.c - A generic doubly linked list implementation
+ *
+ * Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include <stdlib.h>
+#include "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;
+}
+
+/* Free the whole list.
+ *
+ * This function can't fail. */
+void listRelease(list *list)
+{
+ unsigned int 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;
+ }
+ zfree(list);
+}
+
+/* Add a new node to the list, to head, contaning 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, contaning 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;
+}
+
+/* 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 listNextElement() 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);
+}
+
+/* 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 = listGetItarotr(list,<direction>);
+ * while ((node = listNextIterator(iter)) != NULL) {
+ * DoSomethingWith(listNodeValue(node));
+ * }
+ *
+ * */
+listNode *listNextElement(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;
+ iter = listGetIterator(orig, AL_START_HEAD);
+ while((node = listNextElement(iter)) != NULL) {
+ void *value;
+
+ if (copy->dup) {
+ value = copy->dup(node->value);
+ if (value == NULL) {
+ listRelease(copy);
+ listReleaseIterator(iter);
+ return NULL;
+ }
+ } else
+ value = node->value;
+ if (listAddNodeTail(copy, value) == NULL) {
+ listRelease(copy);
+ listReleaseIterator(iter);
+ return NULL;
+ }
+ }
+ listReleaseIterator(iter);
+ 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;
+
+ iter = listGetIterator(list, AL_START_HEAD);
+ while((node = listNextElement(iter)) != NULL) {
+ if (list->match) {
+ if (list->match(node->value, key)) {
+ listReleaseIterator(iter);
+ return node;
+ }
+ } else {
+ if (key == node->value) {
+ listReleaseIterator(iter);
+ return node;
+ }
+ }
+ }
+ listReleaseIterator(iter);
+ 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 penultimante
+ * and so on. If the index is out of range NULL is returned. */
+listNode *listIndex(list *list, int 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;
+}
90 adlist.h
@@ -0,0 +1,90 @@
+/* adlist.h - A generic doubly linked list implementation
+ *
+ * Copyright (c) 2006-2009, 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 __ADLIST_H__
+#define __ADLIST_H__
+
+/* Node, List, and Iterator are the only data structures used currently. */
+
+typedef struct listNode {
+ struct listNode *prev;
+ struct listNode *next;
+ void *value;
+} listNode;
+
+typedef struct list {
+ listNode *head;
+ listNode *tail;
+ void *(*dup)(void *ptr);
+ void (*free)(void *ptr);
+ int (*match)(void *ptr, void *key);
+ unsigned int len;
+} list;
+
+typedef struct listIter {
+ listNode *next;
+ listNode *prev;
+ int direction;
+} listIter;
+
+/* Functions implemented as macros */
+#define listLength(l) ((l)->len)
+#define listFirst(l) ((l)->head)
+#define listLast(l) ((l)->tail)
+#define listPrevNode(n) ((n)->prev)
+#define listNextNode(n) ((n)->next)
+#define listNodeValue(n) ((n)->value)
+
+#define listSetDupMethod(l,m) ((l)->dup = (m))
+#define listSetFreeMethod(l,m) ((l)->free = (m))
+#define listSetMatchMethod(l,m) ((l)->match = (m))
+
+#define listGetDupMethod(l) ((l)->dup)
+#define listGetFree(l) ((l)->free)
+#define listGetMatchMethod(l) ((l)->match)
+
+/* Prototypes */
+list *listCreate(void);
+void listRelease(list *list);
+list *listAddNodeHead(list *list, void *value);
+list *listAddNodeTail(list *list, void *value);
+void listDelNode(list *list, listNode *node);
+listIter *listGetIterator(list *list, int direction);
+listNode *listNextElement(listIter *iter);
+void listReleaseIterator(listIter *iter);
+list *listDup(list *orig);
+listNode *listSearchKey(list *list, void *key);
+listNode *listIndex(list *list, int index);
+
+/* Directions for iterators */
+#define AL_START_HEAD 0
+#define AL_START_TAIL 1
+
+#endif /* __ADLIST_H__ */
368 ae.c
@@ -0,0 +1,368 @@
+/* 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-2009, 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 <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "ae.h"
+#include "zmalloc.h"
+
+aeEventLoop *aeCreateEventLoop(void) {
+ aeEventLoop *eventLoop;
+
+ eventLoop = zmalloc(sizeof(*eventLoop));
+ if (!eventLoop) return NULL;
+ eventLoop->fileEventHead = NULL;
+ eventLoop->timeEventHead = NULL;
+ eventLoop->timeEventNextId = 0;
+ eventLoop->stop = 0;
+ return eventLoop;
+}
+
+void aeDeleteEventLoop(aeEventLoop *eventLoop) {
+ zfree(eventLoop);
+}
+
+void aeStop(aeEventLoop *eventLoop) {
+ eventLoop->stop = 1;
+}
+
+int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
+ aeFileProc *proc, void *clientData,
+ aeEventFinalizerProc *finalizerProc)
+{
+ aeFileEvent *fe;
+
+ fe = zmalloc(sizeof(*fe));
+ if (fe == NULL) return AE_ERR;
+ fe->fd = fd;
+ fe->mask = mask;
+ fe->fileProc = proc;
+ fe->finalizerProc = finalizerProc;
+ fe->clientData = clientData;
+ fe->next = eventLoop->fileEventHead;
+ eventLoop->fileEventHead = fe;
+ return AE_OK;
+}
+
+void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
+{
+ aeFileEvent *fe, *prev = NULL;
+
+ fe = eventLoop->fileEventHead;
+ while(fe) {
+ if (fe->fd == fd && fe->mask == mask) {
+ if (prev == NULL)
+ eventLoop->fileEventHead = fe->next;
+ else
+ prev->next = fe->next;
+ if (fe->finalizerProc)
+ fe->finalizerProc(eventLoop, fe->clientData);
+ zfree(fe);
+ return;
+ }
+ prev = fe;
+ fe = fe->next;
+ }
+}
+
+static void aeGetTime(long *seconds, long *milliseconds)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ *seconds = tv.tv_sec;
+ *milliseconds = tv.tv_usec/1000;
+}
+
+static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) {
+ long cur_sec, cur_ms, when_sec, when_ms;
+
+ aeGetTime(&cur_sec, &cur_ms);
+ when_sec = 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;
+}
+
+long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
+ aeTimeProc *proc, void *clientData,
+ aeEventFinalizerProc *finalizerProc)
+{
+ long long 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->next = eventLoop->timeEventHead;
+ eventLoop->timeEventHead = te;
+ return id;
+}
+
+int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
+{
+ aeTimeEvent *te, *prev = NULL;
+
+ te = eventLoop->timeEventHead;
+ while(te) {
+ if (te->id == id) {
+ if (prev == NULL)
+ eventLoop->timeEventHead = te->next;
+ else
+ prev->next = te->next;
+ if (te->finalizerProc)
+ te->finalizerProc(eventLoop, te->clientData);
+ zfree(te);
+ return AE_OK;
+ }
+ prev = te;
+ 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. */
+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 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 occurrs (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
+ * 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 maxfd = 0, numfd = 0, processed = 0;
+ fd_set rfds, wfds, efds;
+ aeFileEvent *fe = eventLoop->fileEventHead;
+ aeTimeEvent *te;
+ long long maxId;
+ AE_NOTUSED(flags);
+
+ /* Nothing to do? return ASAP */
+ if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ FD_ZERO(&efds);
+
+ /* Check file events */
+ if (flags & AE_FILE_EVENTS) {
+ while (fe != NULL) {
+ if (fe->mask & AE_READABLE) FD_SET(fe->fd, &rfds);
+ if (fe->mask & AE_WRITABLE) FD_SET(fe->fd, &wfds);
+ if (fe->mask & AE_EXCEPTION) FD_SET(fe->fd, &efds);
+ if (maxfd < fe->fd) maxfd = fe->fd;
+ numfd++;
+ fe = fe->next;
+ }
+ }
+ /* 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 (numfd || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
+ int retval;
+ aeTimeEvent *shortest = NULL;
+ struct timeval tv, *tvp;
+
+ if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
+ shortest = aeSearchNearestTimer(eventLoop);
+ if (shortest) {
+ long now_sec, now_ms;
+
+ /* Calculate the time missing for the nearest
+ * timer to fire. */
+ aeGetTime(&now_sec, &now_ms);
+ tvp = &tv;
+ tvp->tv_sec = shortest->when_sec - now_sec;
+ if (shortest->when_ms < now_ms) {
+ tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
+ tvp->tv_sec --;
+ } else {
+ tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
+ }
+ } else {
+ /* If we have to check for events but need to return
+ * ASAP because of AE_DONT_WAIT we need to se 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 */
+ }
+ }
+
+ retval = select(maxfd+1, &rfds, &wfds, &efds, tvp);
+ if (retval > 0) {
+ fe = eventLoop->fileEventHead;
+ while(fe != NULL) {
+ int fd = (int) fe->fd;
+
+ if ((fe->mask & AE_READABLE && FD_ISSET(fd, &rfds)) ||
+ (fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds)) ||
+ (fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds)))
+ {
+ int mask = 0;
+
+ if (fe->mask & AE_READABLE && FD_ISSET(fd, &rfds))
+ mask |= AE_READABLE;
+ if (fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds))
+ mask |= AE_WRITABLE;
+ if (fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds))
+ mask |= AE_EXCEPTION;
+ fe->fileProc(eventLoop, fe->fd, fe->clientData, mask);
+ processed++;
+ /* After an event is processed our file event list
+ * may no longer be the same, so what we do
+ * is to clear the bit for this file descriptor and
+ * restart again from the head. */
+ fe = eventLoop->fileEventHead;
+ FD_CLR(fd, &rfds);
+ FD_CLR(fd, &wfds);
+ FD_CLR(fd, &efds);
+ } else {
+ fe = fe->next;
+ }
+ }
+ }
+ }
+ /* Check time events */
+ if (flags & AE_TIME_EVENTS) {
+ te = eventLoop->timeEventHead;
+ maxId = eventLoop->timeEventNextId-1;
+ while(te) {
+ long now_sec, now_ms;
+ long long id;
+
+ 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);
+ /* After an event is processed our time event list may
+ * no longer be the same, so we restart from head.
+ * Still we make sure to don't process events registered
+ * by event handlers itself in order to don't loop forever.
+ * To do so we saved the max ID we want to handle. */
+ if (retval != AE_NOMORE) {
+ aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
+ } else {
+ aeDeleteTimeEvent(eventLoop, id);
+ }
+ te = eventLoop->timeEventHead;
+ } else {
+ te = te->next;
+ }
+ }
+ }
+ return processed; /* return the number of processed file/time events */
+}
+
+/* Wait for millseconds until the given file descriptor becomes
+ * writable/readable/exception */
+int aeWait(int fd, int mask, long long milliseconds) {
+ struct timeval tv;
+ fd_set rfds, wfds, efds;
+ int retmask = 0, retval;
+
+ tv.tv_sec = milliseconds/1000;
+ tv.tv_usec = (milliseconds%1000)*1000;
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ FD_ZERO(&efds);
+
+ if (mask & AE_READABLE) FD_SET(fd,&rfds);
+ if (mask & AE_WRITABLE) FD_SET(fd,&wfds);
+ if (mask & AE_EXCEPTION) FD_SET(fd,&efds);
+ if ((retval = select(fd+1, &rfds, &wfds, &efds, &tv)) > 0) {
+ if (FD_ISSET(fd,&rfds)) retmask |= AE_READABLE;
+ if (FD_ISSET(fd,&wfds)) retmask |= AE_WRITABLE;
+ if (FD_ISSET(fd,&efds)) retmask |= AE_EXCEPTION;
+ return retmask;
+ } else {
+ return retval;
+ }
+}
+
+void aeMain(aeEventLoop *eventLoop)
+{
+ eventLoop->stop = 0;
+ while (!eventLoop->stop)
+ aeProcessEvents(eventLoop, AE_ALL_EVENTS);
+}
106 ae.h
@@ -0,0 +1,106 @@
+/* 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-2009, 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__
+
+struct aeEventLoop;
+
+/* Types and data structures */
+typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
+typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
+typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
+
+/* File event structure */
+typedef struct aeFileEvent {
+ int fd;
+ int mask; /* one of AE_(READABLE|WRITABLE|EXCEPTION) */
+ aeFileProc *fileProc;
+ aeEventFinalizerProc *finalizerProc;
+ void *clientData;
+ struct aeFileEvent *next;
+} aeFileEvent;
+
+/* Time event structure */
+typedef struct aeTimeEvent {
+ long long id; /* time event identifier. */
+ long when_sec; /* seconds */
+ long when_ms; /* milliseconds */
+ aeTimeProc *timeProc;
+ aeEventFinalizerProc *finalizerProc;
+ void *clientData;
+ struct aeTimeEvent *next;
+} aeTimeEvent;
+
+/* State of an event based program */
+typedef struct aeEventLoop {
+ long long timeEventNextId;
+ aeFileEvent *fileEventHead;
+ aeTimeEvent *timeEventHead;
+ int stop;
+} aeEventLoop;
+
+/* Defines */
+#define AE_OK 0
+#define AE_ERR -1
+
+#define AE_READABLE 1
+#define AE_WRITABLE 2
+#define AE_EXCEPTION 4
+
+#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_NOMORE -1
+
+/* Macros */
+#define AE_NOTUSED(V) ((void) V)
+
+/* Prototypes */
+aeEventLoop *aeCreateEventLoop(void);
+void aeDeleteEventLoop(aeEventLoop *eventLoop);
+void aeStop(aeEventLoop *eventLoop);
+int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
+ aeFileProc *proc, void *clientData,
+ aeEventFinalizerProc *finalizerProc);
+void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask);
+long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
+ aeTimeProc *proc, void *clientData,
+ aeEventFinalizerProc *finalizerProc);
+int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id);
+int aeProcessEvents(aeEventLoop *eventLoop, int flags);
+int aeWait(int fd, int mask, long long milliseconds);
+void aeMain(aeEventLoop *eventLoop);
+
+#endif
268 anet.c
@@ -0,0 +1,268 @@
+/* anet.c -- Basic TCP socket stuff made a bit less boring
+ *
+ * Copyright (c) 2006-2009, 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 <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "anet.h"
+
+static void anetSetError(char *err, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!err) return;
+ va_start(ap, fmt);
+ vsnprintf(err, ANET_ERR_LEN, fmt, ap);
+ va_end(ap);
+}
+
+int anetNonBlock(char *err, int fd)
+{
+ int flags;
+
+ /* Set the socket nonblocking.
+ * Note that fcntl(2) for F_GETFL and F_SETFL can't be
+ * interrupted by a signal. */
+ if ((flags = fcntl(fd, F_GETFL)) == -1) {
+ anetSetError(err, "fcntl(F_GETFL): %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ return ANET_OK;
+}
+
+int anetTcpNoDelay(char *err, int fd)
+{
+ int yes = 1;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1)
+ {
+ anetSetError(err, "setsockopt TCP_NODELAY: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ return ANET_OK;
+}
+
+int anetSetSendBuffer(char *err, int fd, int buffsize)
+{
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
+ {
+ anetSetError(err, "setsockopt SO_SNDBUF: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ return ANET_OK;
+}
+
+int anetTcpKeepAlive(char *err, int fd)
+{
+ int yes = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
+ anetSetError(err, "setsockopt SO_KEEPALIVE: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ return ANET_OK;
+}
+
+int anetResolve(char *err, char *host, char *ipbuf)
+{
+ struct sockaddr_in sa;
+
+ sa.sin_family = AF_INET;
+ if (inet_aton(host, &sa.sin_addr) == 0) {
+ struct hostent *he;
+
+ he = gethostbyname(host);
+ if (he == NULL) {
+ anetSetError(err, "can't resolve: %s\n", host);
+ return ANET_ERR;
+ }
+ memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
+ }
+ strcpy(ipbuf,inet_ntoa(sa.sin_addr));
+ return ANET_OK;
+}
+
+#define ANET_CONNECT_NONE 0
+#define ANET_CONNECT_NONBLOCK 1
+static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
+{
+ int s, on = 1;
+ struct sockaddr_in sa;
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+ anetSetError(err, "creating socket: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ /* Make sure connection-intensive things like the redis benckmark
+ * will be able to close/open sockets a zillion of times */
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ if (inet_aton(addr, &sa.sin_addr) == 0) {
+ struct hostent *he;
+
+ he = gethostbyname(addr);
+ if (he == NULL) {
+ anetSetError(err, "can't resolve: %s\n", addr);
+ close(s);
+ return ANET_ERR;
+ }
+ memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
+ }
+ if (flags & ANET_CONNECT_NONBLOCK) {
+ if (anetNonBlock(err,s) != ANET_OK)
+ return ANET_ERR;
+ }
+ if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+ if (errno == EINPROGRESS &&
+ flags & ANET_CONNECT_NONBLOCK)
+ return s;
+
+ anetSetError(err, "connect: %s\n", strerror(errno));
+ close(s);
+ return ANET_ERR;
+ }
+ return s;
+}
+
+int anetTcpConnect(char *err, char *addr, int port)
+{
+ return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE);
+}
+
+int anetTcpNonBlockConnect(char *err, char *addr, int port)
+{
+ return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
+}
+
+/* Like read(2) but make sure 'count' is read before to return
+ * (unless error or EOF condition is encountered) */
+int anetRead(int fd, void *buf, int count)
+{
+ int nread, totlen = 0;
+ while(totlen != count) {
+ nread = read(fd,buf,count-totlen);
+ if (nread == 0) return totlen;
+ if (nread == -1) return -1;
+ totlen += nread;
+ buf += nread;
+ }
+ return totlen;
+}
+
+/* Like write(2) but make sure 'count' is read before to return
+ * (unless error is encountered) */
+int anetWrite(int fd, void *buf, int count)
+{
+ int nwritten, totlen = 0;
+ while(totlen != count) {
+ nwritten = write(fd,buf,count-totlen);
+ if (nwritten == 0) return totlen;
+ if (nwritten == -1) return -1;
+ totlen += nwritten;
+ buf += nwritten;
+ }
+ return totlen;
+}
+
+int anetTcpServer(char *err, int port, char *bindaddr)
+{
+ int s, on = 1;
+ struct sockaddr_in sa;
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+ anetSetError(err, "socket: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+ anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
+ close(s);
+ return ANET_ERR;
+ }
+ memset(&sa,0,sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ sa.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (bindaddr) {
+ if (inet_aton(bindaddr, &sa.sin_addr) == 0) {
+ anetSetError(err, "Invalid bind address\n");
+ close(s);
+ return ANET_ERR;
+ }
+ }
+ if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+ anetSetError(err, "bind: %s\n", strerror(errno));
+ close(s);
+ return ANET_ERR;
+ }
+ if (listen(s, 32) == -1) {
+ anetSetError(err, "listen: %s\n", strerror(errno));
+ close(s);
+ return ANET_ERR;
+ }
+ return s;
+}
+
+int anetAccept(char *err, int serversock, char *ip, int *port)
+{
+ int fd;
+ struct sockaddr_in sa;
+ unsigned int saLen;
+
+ while(1) {
+ saLen = sizeof(sa);
+ fd = accept(serversock, (struct sockaddr*)&sa, &saLen);
+ if (fd == -1) {
+ if (errno == EINTR)
+ continue;
+ else {
+ anetSetError(err, "accept: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ }
+ break;
+ }
+ if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
+ if (port) *port = ntohs(sa.sin_port);
+ return fd;
+}
49 anet.h
@@ -0,0 +1,49 @@
+/* anet.c -- Basic TCP socket stuff made a bit less boring
+ *
+ * Copyright (c) 2006-2009, 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 ANET_H
+#define ANET_H
+
+#define ANET_OK 0
+#define ANET_ERR -1
+#define ANET_ERR_LEN 256
+
+int anetTcpConnect(char *err, char *addr, int port);
+int anetTcpNonBlockConnect(char *err, char *addr, int port);
+int anetRead(int fd, void *buf, int count);
+int anetResolve(char *err, char *host, char *ipbuf);
+int anetTcpServer(char *err, int port, char *bindaddr);
+int anetAccept(char *err, int serversock, char *ip, int *port);
+int anetWrite(int fd, void *buf, int count);
+int anetNonBlock(char *err, int fd);
+int anetTcpNoDelay(char *err, int fd);
+int anetTcpKeepAlive(char *err, int fd);
+
+#endif
460 benchmark.c
@@ -0,0 +1,460 @@
+/* Redis benchmark utility.
+ *
+ * Copyright (c) 2006-2009, 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <signal.h>
+#include <assert.h>
+
+#include "ae.h"
+#include "anet.h"
+#include "sds.h"
+#include "adlist.h"
+#include "zmalloc.h"
+
+#define REPLY_INT 0
+#define REPLY_RETCODE 1
+#define REPLY_BULK 2
+
+#define CLIENT_CONNECTING 0
+#define CLIENT_SENDQUERY 1
+#define CLIENT_READREPLY 2
+
+#define MAX_LATENCY 5000
+
+#define REDIS_NOTUSED(V) ((void) V)
+
+static struct config {
+ int numclients;
+ int requests;
+ int liveclients;
+ int donerequests;
+ int keysize;
+ int datasize;
+ aeEventLoop *el;
+ char *hostip;
+ int hostport;
+ int keepalive;
+ long long start;
+ long long totlatency;
+ int *latency;
+ list *clients;
+ int quiet;
+ int loop;
+} config;
+
+typedef struct _client {
+ int state;
+ int fd;
+ sds obuf;
+ sds ibuf;
+ int readlen; /* readlen == -1 means read a single line */
+ unsigned int written; /* bytes of 'obuf' already written */
+ int replytype;
+ long long start; /* start time in milliseconds */
+} *client;
+
+/* Prototypes */
+static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
+static void createMissingClients(client c);
+
+/* Implementation */
+static long long mstime(void) {
+ struct timeval tv;
+ long long mst;
+
+ gettimeofday(&tv, NULL);
+ mst = ((long)tv.tv_sec)*1000;
+ mst += tv.tv_usec/1000;
+ return mst;
+}
+
+static void freeClient(client c) {
+ listNode *ln;
+
+ aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
+ aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
+ sdsfree(c->ibuf);
+ sdsfree(c->obuf);
+ close(c->fd);
+ zfree(c);
+ config.liveclients--;
+ ln = listSearchKey(config.clients,c);
+ assert(ln != NULL);
+ listDelNode(config.clients,ln);
+}
+
+static void freeAllClients(void) {
+ listNode *ln = config.clients->head, *next;
+
+ while(ln) {
+ next = ln->next;
+ freeClient(ln->value);
+ ln = next;
+ }
+}
+
+static void resetClient(client c) {
+ aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
+ aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
+ aeCreateFileEvent(config.el,c->fd, AE_WRITABLE,writeHandler,c,NULL);
+ sdsfree(c->ibuf);
+ c->ibuf = sdsempty();
+ c->readlen = (c->replytype == REPLY_BULK) ? -1 : 0;
+ c->written = 0;
+ c->state = CLIENT_SENDQUERY;
+ c->start = mstime();
+}
+
+static void clientDone(client c) {
+ long long latency;
+ config.donerequests ++;
+ latency = mstime() - c->start;
+ if (latency > MAX_LATENCY) latency = MAX_LATENCY;
+ config.latency[latency]++;
+
+ if (config.donerequests == config.requests) {
+ freeClient(c);
+ aeStop(config.el);
+ return;
+ }
+ if (config.keepalive) {
+ resetClient(c);
+ } else {
+ config.liveclients--;
+ createMissingClients(c);
+ config.liveclients++;
+ freeClient(c);
+ }
+}
+
+static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
+{
+ char buf[1024];
+ int nread;
+ client c = privdata;
+ REDIS_NOTUSED(el);
+ REDIS_NOTUSED(fd);
+ REDIS_NOTUSED(mask);
+
+ nread = read(c->fd, buf, 1024);
+ if (nread == -1) {
+ fprintf(stderr, "Reading from socket: %s\n", strerror(errno));
+ freeClient(c);
+ return;
+ }
+ if (nread == 0) {
+ fprintf(stderr, "EOF from client\n");
+ freeClient(c);
+ return;
+ }
+ c->ibuf = sdscatlen(c->ibuf,buf,nread);
+
+ if (c->replytype == REPLY_INT ||
+ c->replytype == REPLY_RETCODE ||
+ (c->replytype == REPLY_BULK && c->readlen == -1)) {
+ char *p;
+
+ if ((p = strchr(c->ibuf,'\n')) != NULL) {
+ if (c->replytype == REPLY_BULK) {
+ *p = '\0';
+ *(p-1) = '\0';
+ if (memcmp(c->ibuf,"nil",3) == 0) {
+ clientDone(c);
+ return;
+ }
+ c->readlen = atoi(c->ibuf)+2;
+ c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
+ } else {
+ c->ibuf = sdstrim(c->ibuf,"\r\n");
+ clientDone(c);
+ return;
+ }
+ }
+ }
+ /* bulk read */
+ if ((unsigned)c->readlen == sdslen(c->ibuf))
+ clientDone(c);
+}
+
+static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
+{
+ client c = privdata;
+ REDIS_NOTUSED(el);
+ REDIS_NOTUSED(fd);
+ REDIS_NOTUSED(mask);
+
+ if (c->state == CLIENT_CONNECTING) {
+ c->state = CLIENT_SENDQUERY;
+ c->start = mstime();
+ }
+ if (sdslen(c->obuf) > c->written) {
+ void *ptr = c->obuf+c->written;
+ int len = sdslen(c->obuf) - c->written;
+ int nwritten = write(c->fd, ptr, len);
+ if (nwritten == -1) {
+ fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
+ freeClient(c);
+ return;
+ }
+ c->written += nwritten;
+ if (sdslen(c->obuf) == c->written) {
+ aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
+ aeCreateFileEvent(config.el,c->fd,AE_READABLE,readHandler,c,NULL);
+ c->state = CLIENT_READREPLY;
+ }
+ }
+}
+
+static client createClient(void) {
+ client c = zmalloc(sizeof(struct _client));
+ char err[ANET_ERR_LEN];
+
+ c->fd = anetTcpNonBlockConnect(err,config.hostip,config.hostport);
+ if (c->fd == ANET_ERR) {
+ zfree(c);
+ fprintf(stderr,"Connect: %s\n",err);
+ return NULL;
+ }
+ anetTcpNoDelay(NULL,c->fd);
+ c->obuf = sdsempty();
+ c->ibuf = sdsempty();
+ c->readlen = 0;
+ c->written = 0;
+ c->state = CLIENT_CONNECTING;
+ aeCreateFileEvent(config.el, c->fd, AE_WRITABLE, writeHandler, c, NULL);
+ config.liveclients++;
+ listAddNodeTail(config.clients,c);
+ return c;
+}
+
+static void createMissingClients(client c) {
+ while(config.liveclients < config.numclients) {
+ client new = createClient();
+ if (!new) continue;
+ sdsfree(new->obuf);
+ new->obuf = sdsdup(c->obuf);
+ new->replytype = c->replytype;
+ if (c->replytype == REPLY_BULK)
+ new->readlen = -1;
+ }
+}
+
+static void showLatencyReport(char *title) {
+ int j, seen = 0;
+ float perc, reqpersec;
+
+ reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
+ if (!config.quiet) {
+ printf("====== %s ======\n", title);
+ printf(" %d requests completed in %.2f seconds\n", config.donerequests,
+ (float)config.totlatency/1000);
+ printf(" %d parallel clients\n", config.numclients);
+ printf(" %d bytes payload\n", config.datasize);
+ printf(" keep alive: %d\n", config.keepalive);
+ printf("\n");
+ for (j = 0; j <= MAX_LATENCY; j++) {
+ if (config.latency[j]) {
+ seen += config.latency[j];
+ perc = ((float)seen*100)/config.donerequests;
+ printf("%.2f%% <= %d milliseconds\n", perc, j);
+ }
+ }
+ printf("%.2f requests per second\n\n", reqpersec);
+ } else {
+ printf("%s: %.2f requests per second\n", title, reqpersec);
+ }
+}
+
+static void prepareForBenchmark(void)
+{
+ memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1));
+ config.start = mstime();
+ config.donerequests = 0;
+}
+
+static void endBenchmark(char *title) {
+ config.totlatency = mstime()-config.start;
+ showLatencyReport(title);
+ freeAllClients();
+}
+
+void parseOptions(int argc, char **argv) {
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ int lastarg = i==argc-1;
+
+ if (!strcmp(argv[i],"-c") && !lastarg) {
+ config.numclients = atoi(argv[i+1]);
+ i++;
+ } else if (!strcmp(argv[i],"-n") && !lastarg) {
+ config.requests = atoi(argv[i+1]);
+ i++;
+ } else if (!strcmp(argv[i],"-k") && !lastarg) {
+ config.keepalive = atoi(argv[i+1]);
+ i++;
+ } else if (!strcmp(argv[i],"-h") && !lastarg) {
+ char *ip = zmalloc(32);
+ if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
+ printf("Can't resolve %s\n", argv[i]);
+ exit(1);
+ }
+ config.hostip = ip;
+ i++;
+ } else if (!strcmp(argv[i],"-p") && !lastarg) {
+ config.hostport = atoi(argv[i+1]);
+ i++;
+ } else if (!strcmp(argv[i],"-d") && !lastarg) {
+ config.datasize = atoi(argv[i+1]);
+ i++;
+ if (config.datasize < 1) config.datasize=1;
+ if (config.datasize > 1024*1024) config.datasize = 1024*1024;
+ } else if (!strcmp(argv[i],"-q")) {
+ config.quiet = 1;
+ } else if (!strcmp(argv[i],"-l")) {
+ config.loop = 1;
+ } else {
+ printf("Wrong option '%s' or option argument missing\n\n",argv[i]);
+ printf("Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n");
+ printf(" -h <hostname> Server hostname (default 127.0.0.1)\n");
+ printf(" -p <hostname> Server port (default 6379)\n");
+ printf(" -c <clients> Number of parallel connections (default 50)\n");
+ printf(" -n <requests> Total number of requests (default 10000)\n");
+ printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n");
+ printf(" -k <boolean> 1=keep alive 0=reconnect (default 1)\n");
+ printf(" -q Quiet. Just show query/sec values\n");
+ printf(" -l Loop. Run the tests forever\n");
+ exit(1);
+ }
+ }
+}
+
+int main(int argc, char **argv) {
+ client c;
+
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
+
+ config.numclients = 50;
+ config.requests = 10000;
+ config.liveclients = 0;
+ config.el = aeCreateEventLoop();
+ config.keepalive = 1;
+ config.donerequests = 0;
+ config.datasize = 3;
+ config.quiet = 0;
+ config.loop = 0;
+ config.latency = NULL;
+ config.clients = listCreate();
+ config.latency = zmalloc(sizeof(int)*(MAX_LATENCY+1));
+
+ config.hostip = "127.0.0.1";
+ config.hostport = 6379;
+
+ parseOptions(argc,argv);
+
+ if (config.keepalive == 0) {
+ printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' in order to use a lot of clients/requests\n");
+ }
+
+ do {
+ prepareForBenchmark();
+ c = createClient();
+ if (!c) exit(1);
+ c->obuf = sdscat(c->obuf,"PING\r\n");
+ c->replytype = REPLY_RETCODE;
+ createMissingClients(c);
+ aeMain(config.el);
+ endBenchmark("PING");
+
+ prepareForBenchmark();
+ c = createClient();
+ if (!c) exit(1);
+ c->obuf = sdscatprintf(c->obuf,"SET foo %d\r\n",config.datasize);
+ {
+ char *data = zmalloc(config.datasize+2);
+ memset(data,'x',config.datasize);
+ data[config.datasize] = '\r';
+ data[config.datasize+1] = '\n';
+ c->obuf = sdscatlen(c->obuf,data,config.datasize+2);
+ }
+ c->replytype = REPLY_RETCODE;
+ createMissingClients(c);
+ aeMain(config.el);
+ endBenchmark("SET");
+
+ prepareForBenchmark();
+ c = createClient();
+ if (!c) exit(1);
+ c->obuf = sdscat(c->obuf,"GET foo\r\n");
+ c->replytype = REPLY_BULK;
+ c->readlen = -1;
+ createMissingClients(c);
+ aeMain(config.el);
+ endBenchmark("GET");
+
+ prepareForBenchmark();
+ c = createClient();
+ if (!c) exit(1);
+ c->obuf = sdscat(c->obuf,"INCR counter\r\n");
+ c->replytype = REPLY_INT;
+ createMissingClients(c);
+ aeMain(config.el);
+ endBenchmark("INCR");
+
+ prepareForBenchmark();
+ c = createClient();
+ if (!c) exit(1);
+ c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
+ c->replytype = REPLY_INT;
+ createMissingClients(c);
+ aeMain(config.el);
+ endBenchmark("LPUSH");
+
+ prepareForBenchmark();
+ c = createClient();
+ if (!c) exit(1);
+ c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
+ c->replytype = REPLY_BULK;
+ c->readlen = -1;
+ createMissingClients(c);
+ aeMain(config.el);
+ endBenchmark("LPOP");
+
+ printf("\n");
+ } while(config.loop);
+
+ return 0;
+}
28 client-libraries/README
@@ -0,0 +1,28 @@
+Redis client libraries
+----------------------
+
+In this directory you'll find client libraries for different languages.
+This are the latest releases available at the time this Redis tar.gz for this
+release was created, and are good for most uses, but if you need more fresh
+code or recent bugfixes read more.
+
+How to get the lastest versions of client libraries source code
+---------------------------------------------------------------
+
+Note that while the PHP and Python versions are the most uptodate available
+libraries, the Ruby and Erlang libraries have their own sites so you may want
+to grab this libraries from their main sites:
+
+Ruby lib source code:
+http://github.com/ezmobius/redis-rb/tree/master
+
+Erlang lib source code:
+http://bitbucket.org/adroll/erldis/
+
+For the languages with development code in the Redis SVN, check this urls for unstable versions of the libs:
+
+Python lib source code:
+http://code.google.com/p/redis/source/browse/#svn/trunk/client-libraries/python
+
+PHP lib source code:
+http://code.google.com/p/redis/source/browse/#svn/trunk/client-libraries/php
2  client-libraries/erlang/.hg_archival.txt
@@ -0,0 +1,2 @@
+repo: 9e1f35ed7fdc7b3da7f5ff66a71d1975b85e2ae5
+node: 7f98e864d76b0b2a7427049b943fb1c0dad0df2a
2  client-libraries/erlang/.hgignore
@@ -0,0 +1,2 @@
+syntax: glob
+*.beam
22 client-libraries/erlang/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2009
+adroll.com
+Valentino Volonghi
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 client-libraries/erlang/Makefile
@@ -0,0 +1,29 @@
+LIBDIR=`erl -eval 'io:format("~s~n", [code:lib_dir()])' -s init stop -noshell`
+
+all:
+ mkdir -p ebin/
+ (cd src;$(MAKE))
+ (cd test;$(MAKE))
+
+clean: clean_tests
+ (cd src;$(MAKE) clean)
+ rm -rf erl_crash.dump *.beam
+
+clean_tests:
+ (cd test;$(MAKE) clean)
+ rm -rf erl_crash.dump *.beam
+
+test: clean
+ mkdir -p ebin/
+ (cd src;$(MAKE))
+ (cd test;$(MAKE))
+ (cd test;$(MAKE) test)
+
+testrun: all
+ mkdir -p ebin/
+ (cd test;$(MAKE) test)
+
+install: all
+ mkdir -p ${LIBDIR}/erldis-0.0.1/{ebin,include}
+ for i in ebin/*.beam; do install $$i $(LIBDIR)/erldis-0.0.1/$$i ; done
+ for i in include/*.hrl; do install $$i $(LIBDIR)/erldis-0.0.1/$$i ; done
1  client-libraries/erlang/include/erldis.hrl
@@ -0,0 +1 @@
+-record(redis, {socket,buffer=[],reply_caller,parsers,remaining=0,pstate=empty,results=[]}).
9 client-libraries/erlang/src/Makefile
@@ -0,0 +1,9 @@
+include ../support/include.mk
+
+all: $(EBIN_FILES)
+
+debug:
+ $(MAKE) DEBUG=-DDEBUG
+
+clean:
+ rm -rf $(EBIN_FILES) erl_crash.dump
272 client-libraries/erlang/src/client.erl
@@ -0,0 +1,272 @@
+-module(client).
+-behavior(gen_server).
+
+-export([start/1, start/2, connect/1, connect/2, asend/2, send/3, send/2,
+ disconnect/1, ssend/3, str/1, format/1, sformat/1, ssend/2,
+ get_all_results/1]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
+
+-include("erldis.hrl").
+
+-define(EOL, "\r\n").
+
+
+%% Helpers
+str(X) when is_list(X) ->
+ X;
+str(X) when is_atom(X) ->
+ atom_to_list(X);
+str(X) when is_binary(X) ->
+ binary_to_list(X);
+str(X) when is_integer(X) ->
+ integer_to_list(X);
+str(X) when is_float(X) ->
+ float_to_list(X).
+
+format([], Result) ->
+ string:join(lists:reverse(Result), ?EOL);
+format([Line|Rest], Result) ->
+ JoinedLine = string:join([str(X) || X <- Line], " "),
+ format(Rest, [JoinedLine|Result]).
+
+format(Lines) ->
+ format(Lines, []).
+sformat(Line) ->
+ format([Line], []).
+
+get_parser(Cmd)
+ when Cmd =:= set orelse Cmd =:= setnx orelse Cmd =:= del
+ orelse Cmd =:= exists orelse Cmd =:= rename orelse Cmd =:= renamenx
+ orelse Cmd =:= rpush orelse Cmd =:= lpush orelse Cmd =:= ltrim
+ orelse Cmd =:= lset orelse Cmd =:= sadd orelse Cmd =:= srem
+ orelse Cmd =:= sismember orelse Cmd =:= select orelse Cmd =:= move
+ orelse Cmd =:= save orelse Cmd =:= bgsave orelse Cmd =:= flushdb
+ orelse Cmd =:= flushall ->
+ fun proto:parse/2;
+get_parser(Cmd) when Cmd =:= lrem ->
+ fun proto:parse_special/2;
+get_parser(Cmd)
+ when Cmd =:= incr orelse Cmd =:= incrby orelse Cmd =:= decr
+ orelse Cmd =:= decrby orelse Cmd =:= llen orelse Cmd =:= scard ->
+ fun proto:parse_int/2;
+get_parser(Cmd) when Cmd =:= type ->
+ fun proto:parse_types/2;
+get_parser(Cmd) when Cmd =:= randomkey ->
+ fun proto:parse_string/2;
+get_parser(Cmd)
+ when Cmd =:= get orelse Cmd =:= lindex orelse Cmd =:= lpop
+ orelse Cmd =:= rpop ->
+ fun proto:single_stateful_parser/2;
+get_parser(Cmd)
+ when Cmd =:= keys orelse Cmd =:= lrange orelse Cmd =:= sinter
+ orelse Cmd =:= smembers orelse Cmd =:= sort ->
+ fun proto:stateful_parser/2.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+%% Exported API
+start(Host) ->
+ connect(Host).
+start(Host, Port) ->
+ connect(Host, Port).
+
+connect(Host) ->
+ connect(Host, 6379).
+connect(Host, Port) ->
+ gen_server:start_link(?MODULE, [Host, Port], []).
+
+ssend(Client, Cmd) -> ssend(Client, Cmd, []).
+ssend(Client, Cmd, Args) ->
+ gen_server:cast(Client, {send, sformat([Cmd|Args]), get_parser(Cmd)}).
+
+send(Client, Cmd) -> send(Client, Cmd, []).
+send(Client, Cmd, Args) ->
+ gen_server:cast(Client, {send,
+ string:join([str(Cmd), format(Args)], " "), get_parser(Cmd)}).
+
+asend(Client, Cmd) ->
+ gen_server:cast(Client, {asend, Cmd}).
+disconnect(Client) ->
+ gen_server:call(Client, disconnect).
+
+get_all_results(Client) ->
+ gen_server:call(Client, get_all_results).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+
+%% gen_server callbacks
+init([Host, Port]) ->
+ process_flag(trap_exit, true),
+ ConnectOptions = [list, {active, once}, {packet, line}, {nodelay, true}],
+ case gen_tcp:connect(Host, Port, ConnectOptions) of
+ {error, Why} ->
+ {error, {socket_error, Why}};
+ {ok, Socket} ->
+ {ok, #redis{socket=Socket, parsers=queue:new()}}
+ end.
+
+handle_call({send, Cmd, Parser}, From, State=#redis{parsers=Parsers}) ->
+ gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
+ {noreply, State#redis{reply_caller=fun(V) -> gen_server:reply(From, lists:nth(1, V)) end,
+ parsers=queue:in(Parser, Parsers), remaining=1}};
+
+handle_call(disconnect, _From, State) ->
+ {stop, normal, ok, State};
+handle_call(get_all_results, From, State) ->
+ case queue:is_empty(State#redis.parsers) of
+ true ->
+ % answers came earlier than we could start listening...
+ % Very unlikely but totally possible.
+ {reply, lists:reverse(State#redis.results), State#redis{results=[]}};
+ false ->
+ % We are here earlier than results came, so just make
+ % ourselves wait until stuff is ready.
+ {noreply, State#redis{reply_caller=fun(V) -> gen_server:reply(From, V) end}}
+ end;
+handle_call(_, _From, State) -> {noreply, State}.
+
+
+handle_cast({asend, Cmd}, State) ->
+ gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
+ {noreply, State};
+handle_cast({send, Cmd, Parser}, State=#redis{parsers=Parsers, remaining=Remaining}) ->
+ % how we should do here: if remaining is already != 0 then we'll
+ % let handle_info take care of keeping track how many remaining things
+ % there are. If instead it's 0 we are the first call so let's just
+ % do it.
+ gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
+ NewParsers = queue:in(Parser, Parsers),
+ case Remaining of
+ 0 ->
+ {noreply, State#redis{remaining=1, parsers=NewParsers}};
+ _ ->
+ {noreply, State#redis{parsers=NewParsers}}
+ end;
+handle_cast(_Msg, State) -> {noreply, State}.
+
+
+trim2({ok, S}) ->
+ string:substr(S, 1, length(S)-2);
+trim2(S) ->
+ trim2({ok, S}).
+
+% This is useful to know if there are more messages still coming.
+get_remaining(ParsersQueue) ->
+ case queue:is_empty(ParsersQueue) of
+ true -> 0;
+ false -> 1
+ end.
+
+% This function helps with pipelining by creating a pubsub system with
+% the caller. The caller could submit multiple requests and not listen
+% until later when all or some of them have been answered, at that
+% point 2 conditions can be true:
+% 1) We still need to process more things in this response chain
+% 2) We are finished.
+%
+% And these 2 are together with the following 2:
+% 1) We called get_all_results before the end of the responses.
+% 2) We called get_all_results after the end of the responses.
+%
+% If there's stuff missing in the chain we just push results, this also
+% happens when there's nothing more to process BUT we haven't requested
+% results yet.
+% In case we have requested results: if requests are not yet ready we
+% just push them, otherwise we finally answer all of them.
+save_or_reply(Result, State=#redis{results=Results, reply_caller=ReplyCaller, parsers=Parsers}) ->
+ case get_remaining(Parsers) of
+ 1 ->
+ State#redis{results=[Result|Results], remaining=1, pstate=empty, buffer=[]};
+ 0 ->
+ % We don't reverse results here because if all the requests
+ % come in and then we submit another one, if we reverse
+ % they will be scrambled in the results field of the record.
+ % instead if we wait just before we reply they will be
+ % in the right order.
+ FullResults = [Result|Results],
+ NewState = case ReplyCaller of
+ undefined ->
+ State#redis{results=FullResults};
+ _ ->
+ ReplyCaller(lists:reverse(FullResults)),
+ State#redis{results=[]}
+ end,
+ NewState#redis{remaining=0, pstate=empty,
+ reply_caller=undefined, buffer=[],
+ parsers=Parsers}
+ end.
+
+handle_info({tcp, Socket, Data}, State) ->
+ {{value, Parser}, NewParsers} = queue:out(State#redis.parsers),
+ Trimmed = trim2(Data),
+ NewState = case {State#redis.remaining-1, Parser(State#redis.pstate, Trimmed)} of
+ % This line contained an error code. Next line will hold
+ % The error message that we will parse.
+ {0, error} ->
+ % reinsert the parser in the front, next step is still gonna be needed
+ State#redis{remaining=1, pstate=error,
+ parsers=queue:in_r(Parser, NewParsers)};
+
+ % The stateful parser just started and tells us the number
+ % of results that we will have to parse for those calls
+ % where more than one result is expected. The next
+ % line will start with the first item to read.
+ {0, {hold, Remaining}} ->
+ % Reset the remaining value to the number of results
+ % that we need to parse.
+ % and reinsert the parser in the front, next step is still gonna be needed
+ State#redis{remaining=Remaining, pstate=read,
+ parsers=queue:in_r(Parser, NewParsers)};
+
+ % We either had only one thing to read or we are at the
+ % end of the stuff that we need to read. either way
+ % just pack up the buffer and send.
+ {0, {read, NBytes}} ->
+ inet:setopts(Socket, [{packet, 0}]), % go into raw mode to read bytes
+ CurrentValue = trim2(gen_tcp:recv(Socket, NBytes+2)), % also consume the \r\n
+ inet:setopts(Socket, [{packet, line}]), % go back to line mode
+ OldBuffer = State#redis.buffer,
+ case OldBuffer of
+ [] ->
+ save_or_reply(CurrentValue, State#redis{parsers=NewParsers});
+ _ ->
+ save_or_reply(lists:reverse([CurrentValue|OldBuffer]), State#redis{parsers=NewParsers})
+ end;
+
+
+ % The stateful parser tells us to read some bytes
+ {N, {read, NBytes}} ->
+ inet:setopts(Socket, [{packet, 0}]), % go into raw mode to read bytes
+ CurrentValue = trim2(gen_tcp:recv(Socket, NBytes+2)), % also consume the \r\n
+ inet:setopts(Socket, [{packet, line}]), % go back to line mode
+ OldBuffer = State#redis.buffer,
+ State#redis{remaining=N, buffer=[CurrentValue|OldBuffer],
+ pstate=read, parsers=queue:in_r(Parser, NewParsers)};
+
+
+ % Simple return values contained in a single line
+ {0, Value} ->
+ save_or_reply(Value, State#redis{parsers=NewParsers})
+
+ end,
+ inet:setopts(Socket, [{active, once}]),
+ {noreply, NewState};
+handle_info(_Info, State) -> {noreply, State}.
+
+
+terminate(_Reason, State) ->
+ case State#redis.socket of
+ undefined ->
+ pass;
+ Socket ->
+ gen_tcp:close(Socket)
+ end,
+ ok.
+
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
82 client-libraries/erlang/src/erldis.erl
@@ -0,0 +1,82 @@
+-module(erldis).
+
+-compile(export_all).
+-define(EOL, "\r\n").
+
+%% helpers
+flatten({error, Message}) ->
+ {error, Message};
+flatten(List) when is_list(List)->
+ lists:flatten(List).
+
+%% exposed API
+connect(Host) ->
+ client:connect(Host).
+
+quit(Client) ->
+ client:asend(Client, "QUIT"),
+ client:disconnect(Client).
+
+%% Commands operating on string values
+internal_set_like(Client, Command, Key, Value) ->
+ client:send(Client, Command, [[Key, length(Value)],
+ [Value]]).
+
+get_all_results(Client) -> client:get_all_results(Client).
+
+set(Client, Key, Value) -> internal_set_like(Client, set, Key, Value).
+setnx(Client, Key, Value) -> internal_set_like(Client, setnx, Key, Value).
+incr(Client, Key) -> client:ssend(Client, incr, [Key]).
+incrby(Client, Key, By) -> client:ssend(Client, incrby, [Key, By]).
+decr(Client, Key) -> client:ssend(Client, decr, [Key]).
+decrby(Client, Key, By) -> client:ssend(Client, decrby, [Key, By]).
+get(Client, Key) -> client:ssend(Client, get, [Key]).
+
+
+%% Commands operating on every value
+exists(Client, Key) -> client:ssend(Client, exists, [Key]).
+del(Client, Key) -> client:ssend(Client, del, [Key]).
+type(Client, Key) -> client:ssend(Client, type, [Key]).
+keys(Client, Pattern) -> client:ssend(Client, keys, [Pattern]).
+randomkey(Client, Key) -> client:ssend(Client, randomkey, [Key]).
+rename(Client, OldKey, NewKey) -> client:ssend(Client, rename, [OldKey, NewKey]).
+renamenx(Client, OldKey, NewKey) -> client:ssend(Client, renamenx, [OldKey, NewKey]).
+
+%% Commands operating on both lists and sets
+sort(Client, Key) -> client:ssend(Client, sort, [Key]).
+sort(Client, Key, Extra) -> client:ssend(Client, sort, [Key, Extra]).
+
+%% Commands operating on lists
+rpush(Client, Key, Value) -> internal_set_like(Client, rpush, Key, Value).
+lpush(Client, Key, Value) -> internal_set_like(Client, lpush, Key, Value).
+llen(Client, Key) -> client:ssend(Client, llen, [Key]).
+lrange(Client, Key, Start, End) -> client:ssend(Client, lrange, [Key, Start, End]).
+ltrim(Client, Key, Start, End) -> client:ssend(Client, ltrim, [Key, Start, End]).
+lindex(Client, Key, Index) -> client:ssend(Client, lindex, [Key, Index]).
+lpop(Client, Key) -> client:ssend(Client, lpop, [Key]).
+rpop(Client, Key) -> client:ssend(Client, rpop, [Key]).
+lrem(Client, Key, Number, Value) ->
+ client:send(Client, lrem, [[Key, Number, length(Value)],
+ [Value]]).
+lset(Client, Key, Index, Value) ->
+ client:send(Client, lset, [[Key, Index, length(Value)],
+ [Value]]).
+
+%% Commands operating on sets
+sadd(Client, Key, Value) -> internal_set_like(Client, sadd, Key, Value).
+srem(Client, Key, Value) -> internal_set_like(Client, srem, Key, Value).
+scard(Client, Key) -> client:ssend(Client, scard, [Key]).
+sismember(Client, Key, Value) -> internal_set_like(Client, sismember, Key, Value).
+sintersect(Client, Keys) -> client:ssend(Client, sinter, Keys).
+smembers(Client, Key) -> client:ssend(Client, smembers, [Key]).
+
+
+%% Multiple DB commands
+flushdb(Client) -> client:ssend(Client, flushdb).
+flushall(Client) -> client:ssend(Client, flushall).
+select(Client, Index) -> client:ssend(Client, select, [Index]).
+move(Client, Key, DBIndex) -> client:ssend(Client, move, [Key, DBIndex]).
+save(Client) -> client:ssend(Client, save).
+bgsave(Client) -> client:ssend(Client, bgsave).
+lastsave(Client) -> client:ssend(Client, lastsave).
+shutdown(Client) -> client:asend(Client, shutdown).
68 client-libraries/erlang/src/proto.erl
@@ -0,0 +1,68 @@
+-module(proto).
+
+-export([parse/2, parse_int/2, parse_types/2,
+ parse_string/2, stateful_parser/2,
+ single_stateful_parser/2, parse_special/2]).
+
+
+parse(empty, "+OK") ->
+ ok;
+parse(empty, "+PONG") ->
+ pong;