Unverified Commit 5cbc73c6 authored by jared's avatar jared Committed by GitHub
Browse files

Merge pull request #1 from antirez/unstable

merge redis
parents a16aa03a d5536e04
...@@ -8,13 +8,13 @@ each source file that you contribute. ...@@ -8,13 +8,13 @@ each source file that you contribute.
# IMPORTANT: HOW TO USE REDIS GITHUB ISSUES # IMPORTANT: HOW TO USE REDIS GITHUB ISSUES
* Github issues SHOULD ONLY BE USED to report bugs, and for DETAILED feature * Github issues SHOULD ONLY BE USED to report bugs, and for DETAILED feature
requests. Everything else belongs to the Redis Google Group. requests. Everything else belongs to the Redis Google Group:
https://groups.google.com/forum/m/#!forum/Redis-db
PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected
bugs in the Github issues system. We'll be very happy to help you and provide bugs in the Github issues system. We'll be very happy to help you and provide
all the support at the Reddit sub: all the support in the mailing list.
http://reddit.com/r/redis
There is also an active community of Redis users at Stack Overflow: There is also an active community of Redis users at Stack Overflow:
...@@ -22,7 +22,12 @@ each source file that you contribute. ...@@ -22,7 +22,12 @@ each source file that you contribute.
# How to provide a patch for a new feature # How to provide a patch for a new feature
1. If it is a major feature or a semantical change, please post it as a new submission in r/redis on Reddit at http://reddit.com/r/redis. Try to be passionate about why the feature is needed, make users upvote your proposal to gain traction and so forth. Read feedbacks about the community. But in this first step **please don't write code yet**. 1. If it is a major feature or a semantical change, please don't start coding
straight away: if your feature is not a conceptual fit you'll lose a lot of
time writing the code without any reason. Start by posting in the mailing list
and creating an issue at Github with the description of, exactly, what you want
to accomplish and why. Use cases are important for features to be accepted.
Here you'll see if there is consensus about your idea.
2. If in step 1 you get an acknowledgment from the project leaders, use the 2. If in step 1 you get an acknowledgment from the project leaders, use the
following procedure to submit a patch: following procedure to submit a patch:
...@@ -30,9 +35,16 @@ each source file that you contribute. ...@@ -30,9 +35,16 @@ each source file that you contribute.
a. Fork Redis on github ( http://help.github.com/fork-a-repo/ ) a. Fork Redis on github ( http://help.github.com/fork-a-repo/ )
b. Create a topic branch (git checkout -b my_branch) b. Create a topic branch (git checkout -b my_branch)
c. Push to your branch (git push origin my_branch) c. Push to your branch (git push origin my_branch)
d. Initiate a pull request on github ( http://help.github.com/send-pull-requests/ ) d. Initiate a pull request on github ( https://help.github.com/articles/creating-a-pull-request/ )
e. Done :) e. Done :)
For minor fixes just open a pull request on Github. 3. Keep in mind that we are very overloaded, so issues and PRs sometimes wait
for a *very* long time. However this is not lack of interest, as the project
gets more and more users, we find ourselves in a constant need to prioritize
certain issues/PRs over others. If you think your issue/PR is very important
try to popularize it, have other users commenting and sharing their point of
view and so forth. This helps.
4. For minor fixes just open a pull request on Github.
Thanks! Thanks!
...@@ -34,7 +34,21 @@ Redis Manifesto ...@@ -34,7 +34,21 @@ Redis Manifesto
so that the complexity is obvious and more complex operations can be so that the complexity is obvious and more complex operations can be
performed as the sum of the basic operations. performed as the sum of the basic operations.
4 - Code is like a poem; it's not just something we write to reach some 4 - We believe in code efficiency. Computers get faster and faster, yet we
believe that abusing computing capabilities is not wise: the amount of
operations you can do for a given amount of energy remains anyway a
significant parameter: it allows to do more with less computers and, at
the same time, having a smaller environmental impact. Similarly Redis is
able to "scale down" to smaller devices. It is perfectly usable in a
Raspberry Pi and other small ARM based computers. Faster code having
just the layers of abstractions that are really needed will also result,
often, in more predictable performances. We think likewise about memory
usage, one of the fundamental goals of the Redis project is to
incrementally build more and more memory efficient data structures, so that
problems that were not approachable in RAM in the past will be perfectly
fine to handle in the future.
5 - Code is like a poem; it's not just something we write to reach some
practical result. Sometimes people that are far from the Redis philosophy practical result. Sometimes people that are far from the Redis philosophy
suggest using other code written by other authors (frequently in other suggest using other code written by other authors (frequently in other
languages) in order to implement something Redis currently lacks. But to us languages) in order to implement something Redis currently lacks. But to us
...@@ -45,23 +59,48 @@ Redis Manifesto ...@@ -45,23 +59,48 @@ Redis Manifesto
when needed. At the same time, when writing the Redis story we're trying to when needed. At the same time, when writing the Redis story we're trying to
write smaller stories that will fit in to other code. write smaller stories that will fit in to other code.
5 - We're against complexity. We believe designing systems is a fight against 6 - We're against complexity. We believe designing systems is a fight against
complexity. We'll accept to fight the complexity when it's worthwhile but complexity. We'll accept to fight the complexity when it's worthwhile but
we'll try hard to recognize when a small feature is not worth 1000s of lines we'll try hard to recognize when a small feature is not worth 1000s of lines
of code. Most of the time the best way to fight complexity is by not of code. Most of the time the best way to fight complexity is by not
creating it at all. creating it at all. Complexity is also a form of lock-in: code that is
very hard to understand cannot be modified by users in an independent way
regardless of the license. One of the main Redis goals is to remain
understandable, enough for a single programmer to have a clear idea of how
it works in detail just reading the source code for a couple of weeks.
7 - Threading is not a silver bullet. Instead of making Redis threaded we
believe on the idea of an efficient (mostly) single threaded Redis core.
Multiple of such cores, that may run in the same computer or may run
in multiple computers, are abstracted away as a single big system by
higher order protocols and features: Redis Cluster and the upcoming
Redis Proxy are our main goals. A shared nothing approach is not just
much simpler (see the previous point in this document), is also optimal
in NUMA systems. In the specific case of Redis it allows for each instance
to have a more limited amount of data, making the Redis persist-by-fork
approach more sounding. In the future we may explore parallelism only for
I/O, which is the low hanging fruit: minimal complexity could provide an
improved single process experience.
6 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits 8 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
naturally into a distributed version of Redis and 2) a more complex API that naturally into a distributed version of Redis and 2) a more complex API that
supports multi-key operations. Both are useful if used judiciously but supports multi-key operations. Both are useful if used judiciously but
there's no way to make the more complex multi-keys API distributed in an there's no way to make the more complex multi-keys API distributed in an
opaque way without violating our other principles. We don't want to provide opaque way without violating our other principles. We don't want to provide
the illusion of something that will work magically when actually it can't in the illusion of something that will work magically when actually it can't in
all cases. Instead we'll provide commands to quickly migrate keys from one all cases. Instead we'll provide commands to quickly migrate keys from one
instance to another to perform multi-key operations and expose the tradeoffs instance to another to perform multi-key operations and expose the
to the user. trade-offs to the user.
7 - We optimize for joy. We believe writing code is a lot of hard work, and the 9 - We optimize for joy. We believe writing code is a lot of hard work, and the
only way it can be worth is by enjoying it. When there is no longer joy in only way it can be worth is by enjoying it. When there is no longer joy in
writing code, the best thing to do is stop. To prevent this, we'll avoid writing code, the best thing to do is stop. To prevent this, we'll avoid
taking paths that will make Redis less of a joy to develop. taking paths that will make Redis less of a joy to develop.
10 - All the above points are put together in what we call opportunistic
programming: trying to get the most for the user with minimal increases
in complexity (hanging fruits). Solve 95% of the problem with 5% of the
code when it is acceptable. Avoid a fixed schedule but follow the flow of
user requests, inspiration, Redis internal readiness for certain features
(sometimes many past changes reach a critical point making a previously
complex feature very easy to obtain).
...@@ -119,7 +119,7 @@ parameter (the path of the configuration file): ...@@ -119,7 +119,7 @@ parameter (the path of the configuration file):
It is possible to alter the Redis configuration by passing parameters directly It is possible to alter the Redis configuration by passing parameters directly
as options using the command line. Examples: as options using the command line. Examples:
% ./redis-server --port 9999 --slaveof 127.0.0.1 6379 % ./redis-server --port 9999 --replicaof 127.0.0.1 6379
% ./redis-server /etc/redis/6379.conf --loglevel debug % ./redis-server /etc/redis/6379.conf --loglevel debug
All the options in redis.conf are also supported as options using the command All the options in redis.conf are also supported as options using the command
...@@ -166,6 +166,8 @@ for Ubuntu and Debian systems: ...@@ -166,6 +166,8 @@ for Ubuntu and Debian systems:
% cd utils % cd utils
% ./install_server.sh % ./install_server.sh
_Note_: `install_server.sh` will not work on Mac OSX; it is built for Linux only.
The script will ask you a few questions and will setup everything you need The script will ask you a few questions and will setup everything you need
to run Redis properly as a background daemon that will start again on to run Redis properly as a background daemon that will start again on
system reboots. system reboots.
...@@ -216,7 +218,7 @@ Inside the root are the following important directories: ...@@ -216,7 +218,7 @@ Inside the root are the following important directories:
* `src`: contains the Redis implementation, written in C. * `src`: contains the Redis implementation, written in C.
* `tests`: contains the unit tests, implemented in Tcl. * `tests`: contains the unit tests, implemented in Tcl.
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `anitrez/redis`. An exception to this rule is `deps/geohash-int` which is the low level geocoding library used by Redis: it originated from a different project, but at this point it diverged so much that it is developed as a separated entity directly inside the Redis repository. * `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `antirez/redis`.
There are a few more directories but they are not very important for our goals There are a few more directories but they are not very important for our goals
here. We'll focus mostly on `src`, where the Redis implementation is contained, here. We'll focus mostly on `src`, where the Redis implementation is contained,
...@@ -227,7 +229,7 @@ of complexity incrementally. ...@@ -227,7 +229,7 @@ of complexity incrementally.
Note: lately Redis was refactored quite a bit. Function names and file Note: lately Redis was refactored quite a bit. Function names and file
names have been changed, so you may find that this documentation reflects the names have been changed, so you may find that this documentation reflects the
`unstable` branch more closely. For instance in Redis 3.0 the `server.c` `unstable` branch more closely. For instance in Redis 3.0 the `server.c`
and `server.h` files were named to `redis.c` and `redis.h`. However the overall and `server.h` files were named `redis.c` and `redis.h`. However the overall
structure is the same. Keep in mind that all the new developments and pull structure is the same. Keep in mind that all the new developments and pull
requests should be performed against the `unstable` branch. requests should be performed against the `unstable` branch.
...@@ -245,7 +247,7 @@ A few important fields in this structure are: ...@@ -245,7 +247,7 @@ A few important fields in this structure are:
* `server.db` is an array of Redis databases, where data is stored. * `server.db` is an array of Redis databases, where data is stored.
* `server.commands` is the command table. * `server.commands` is the command table.
* `server.clients` is a linked list of clients connected to the server. * `server.clients` is a linked list of clients connected to the server.
* `server.master` is a special client, the master, if the instance is a slave. * `server.master` is a special client, the master, if the instance is a replica.
There are tons of other fields. Most fields are commented directly inside There are tons of other fields. Most fields are commented directly inside
the structure definition. the structure definition.
...@@ -323,7 +325,7 @@ Inside server.c you can find code that handles other vital things of the Redis s ...@@ -323,7 +325,7 @@ Inside server.c you can find code that handles other vital things of the Redis s
networking.c networking.c
--- ---
This file defines all the I/O functions with clients, masters and slaves This file defines all the I/O functions with clients, masters and replicas
(which in Redis are just special clients): (which in Redis are just special clients):
* `createClient()` allocates and initializes a new client. * `createClient()` allocates and initializes a new client.
...@@ -390,21 +392,21 @@ replication.c ...@@ -390,21 +392,21 @@ replication.c
This is one of the most complex files inside Redis, it is recommended to This is one of the most complex files inside Redis, it is recommended to
approach it only after getting a bit familiar with the rest of the code base. approach it only after getting a bit familiar with the rest of the code base.
In this file there is the implementation of both the master and slave role In this file there is the implementation of both the master and replica role
of Redis. of Redis.
One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing slave instances connected One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing replica instances connected
to our master, so that the slaves can get the writes performed by the clients: to our master, so that the replicas can get the writes performed by the clients:
this way their data set will remain synchronized with the one in the master. this way their data set will remain synchronized with the one in the master.
This file also implements both the `SYNC` and `PSYNC` commands that are This file also implements both the `SYNC` and `PSYNC` commands that are
used in order to perform the first synchronization between masters and used in order to perform the first synchronization between masters and
slaves, or to continue the replication after a disconnection. replicas, or to continue the replication after a disconnection.
Other C files Other C files
--- ---
* `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c` and `t_zset.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types. * `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c`, `t_zset.c` and `t_stream.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types.
* `ae.c` implements the Redis event loop, it's a self contained library which is simple to read and understand. * `ae.c` implements the Redis event loop, it's a self contained library which is simple to read and understand.
* `sds.c` is the Redis string library, check http://github.com/antirez/sds for more information. * `sds.c` is the Redis string library, check http://github.com/antirez/sds for more information.
* `anet.c` is a library to use POSIX networking in a simpler way compared to the raw interface exposed by the kernel. * `anet.c` is a library to use POSIX networking in a simpler way compared to the raw interface exposed by the kernel.
......
...@@ -2,7 +2,6 @@ This directory contains all Redis dependencies, except for the libc that ...@@ -2,7 +2,6 @@ This directory contains all Redis dependencies, except for the libc that
should be provided by the operating system. should be provided by the operating system.
* **Jemalloc** is our memory allocator, used as replacement for libc malloc on Linux by default. It has good performances and excellent fragmentation behavior. This component is upgraded from time to time. * **Jemalloc** is our memory allocator, used as replacement for libc malloc on Linux by default. It has good performances and excellent fragmentation behavior. This component is upgraded from time to time.
* **geohash-int** is inside the dependencies directory but is actually part of the Redis project, since it is our private fork (heavily modified) of a library initially developed for Ardb, which is in turn a fork of Redis.
* **hiredis** is the official C client library for Redis. It is used by redis-cli, redis-benchmark and Redis Sentinel. It is part of the Redis official ecosystem but is developed externally from the Redis repository, so we just upgrade it as needed. * **hiredis** is the official C client library for Redis. It is used by redis-cli, redis-benchmark and Redis Sentinel. It is part of the Redis official ecosystem but is developed externally from the Redis repository, so we just upgrade it as needed.
* **linenoise** is a readline replacement. It is developed by the same authors of Redis but is managed as a separated project and updated as needed. * **linenoise** is a readline replacement. It is developed by the same authors of Redis but is managed as a separated project and updated as needed.
* **lua** is Lua 5.1 with minor changes for security and additional libraries. * **lua** is Lua 5.1 with minor changes for security and additional libraries.
...@@ -22,7 +21,7 @@ just following tose steps: ...@@ -22,7 +21,7 @@ just following tose steps:
1. Remove the jemalloc directory. 1. Remove the jemalloc directory.
2. Substitute it with the new jemalloc source tree. 2. Substitute it with the new jemalloc source tree.
3. Edit the Makefile localted in the same directoy as the README you are 3. Edit the Makefile localted in the same directory as the README you are
reading, and change the --with-version in the Jemalloc configure script reading, and change the --with-version in the Jemalloc configure script
options with the version you are using. This is required because otherwise options with the version you are using. This is required because otherwise
Jemalloc configuration script is broken and will not work nested in another Jemalloc configuration script is broken and will not work nested in another
...@@ -42,15 +41,10 @@ the following additional steps: ...@@ -42,15 +41,10 @@ the following additional steps:
changed, otherwise you could just copy the old implementation if you are changed, otherwise you could just copy the old implementation if you are
upgrading just to a similar version of Jemalloc. upgrading just to a similar version of Jemalloc.
Geohash
---
This is never upgraded since it's part of the Redis project. If there are changes to merge from Ardb there is the need to manually check differences, but at this point the source code is pretty different.
Hiredis Hiredis
--- ---
Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is adviced to take a lot of care: Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is advised to take a lot of care:
1. Check with diff if hiredis API changed and what impact it could have in Redis. 1. Check with diff if hiredis API changed and what impact it could have in Redis.
2. Make sure thet the SDS library inside Hiredis and inside Redis are compatible. 2. Make sure thet the SDS library inside Hiredis and inside Redis are compatible.
...@@ -83,6 +77,6 @@ and our version: ...@@ -83,6 +77,6 @@ and our version:
1. Makefile is modified to allow a different compiler than GCC. 1. Makefile is modified to allow a different compiler than GCC.
2. We have the implementation source code, and directly link to the following external libraries: `lua_cjson.o`, `lua_struct.o`, `lua_cmsgpack.o` and `lua_bit.o`. 2. We have the implementation source code, and directly link to the following external libraries: `lua_cjson.o`, `lua_struct.o`, `lua_cmsgpack.o` and `lua_bit.o`.
3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order toa void direct bytecode exectuion. 3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order toa void direct bytecode execution.
...@@ -8,6 +8,12 @@ os: ...@@ -8,6 +8,12 @@ os:
- linux - linux
- osx - osx
branches:
only:
- staging
- trying
- master
before_script: before_script:
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi - if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
......
### 1.0.0 (unreleased) ### 1.0.0 (unreleased)
**Fixes**: **BREAKING CHANGES**:
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
protocol errors. This is consistent with the RESP specification. On 32-bit
platforms, the upper bound is lowered to `SIZE_MAX`.
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
User code should compare this to `size_t` values as well. If it was used to
compare to other values, casting might be necessary or can be removed, if
casting was applied before.
### 0.14.0 (2018-09-25)
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8])
* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8])
* Fix bulk and multi-bulk length truncation (Justin Brewer [109197])
* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94])
* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6])
* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1])
* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b])
* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96])
* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234])
* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129])
* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c])
* Fix libevent leak (zfz [515228])
* Clean up GCC warning (Ichito Nagata [2ec774])
* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88])
* Solaris compilation fix (Donald Whyte [41b07d])
* Reorder linker arguments when building examples (Tustfarm-heart [06eedd])
* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999])
* libuv use after free fix (Paul Scott [cbb956])
* Properly close socket fd on reconnect attempt (WSL [64d1ec])
* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78])
* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5])
* Update libevent (Chris Xin [386802])
* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e])
* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6])
* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3])
* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb])
* Compatibility fix for strerror_r (Tom Lee [bb1747])
* Properly detect integer parse/overflow errors (Justin Brewer [93421f])
* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40])
* Catch a buffer overflow when formatting the error message * Catch a buffer overflow when formatting the error message
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 * Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
* Fix warnings, when compiled with -Wshadow * Fix warnings, when compiled with -Wshadow
...@@ -9,11 +53,6 @@ ...@@ -9,11 +53,6 @@
**BREAKING CHANGES**: **BREAKING CHANGES**:
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
User code should compare this to `size_t` values as well.
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
* Remove backwards compatibility macro's * Remove backwards compatibility macro's
This removes the following old function aliases, use the new name now: This removes the following old function aliases, use the new name now:
...@@ -94,7 +133,7 @@ The parser, standalone since v0.12.0, can now be compiled on Windows ...@@ -94,7 +133,7 @@ The parser, standalone since v0.12.0, can now be compiled on Windows
* Add IPv6 support * Add IPv6 support
* Remove possiblity of multiple close on same fd * Remove possibility of multiple close on same fd
* Add ability to bind source address on connect * Add ability to bind source address on connect
......
...@@ -36,13 +36,13 @@ endef ...@@ -36,13 +36,13 @@ endef
export REDIS_TEST_CONFIG export REDIS_TEST_CONFIG
# Fallback to gcc when $CC is not in $PATH. # Fallback to gcc when $CC is not in $PATH.
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc') CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
CXX:=$(shell sh -c 'type $(CXX) >/dev/null 2>/dev/null && echo $(CXX) || echo g++') CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
OPTIMIZATION?=-O3 OPTIMIZATION?=-O3
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
DEBUG_FLAGS?= -g -ggdb DEBUG_FLAGS?= -g -ggdb
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(ARCH) REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
REAL_LDFLAGS=$(LDFLAGS) $(ARCH) REAL_LDFLAGS=$(LDFLAGS)
DYLIBSUFFIX=so DYLIBSUFFIX=so
STLIBSUFFIX=a STLIBSUFFIX=a
...@@ -58,12 +58,11 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') ...@@ -58,12 +58,11 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
ifeq ($(uname_S),SunOS) ifeq ($(uname_S),SunOS)
REAL_LDFLAGS+= -ldl -lnsl -lsocket REAL_LDFLAGS+= -ldl -lnsl -lsocket
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
INSTALL= cp -r
endif endif
ifeq ($(uname_S),Darwin) ifeq ($(uname_S),Darwin)
DYLIBSUFFIX=dylib DYLIBSUFFIX=dylib
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
endif endif
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
...@@ -94,7 +93,7 @@ hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) ...@@ -94,7 +93,7 @@ hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME)
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
...@@ -161,11 +160,7 @@ clean: ...@@ -161,11 +160,7 @@ clean:
dep: dep:
$(CC) -MM *.c $(CC) -MM *.c
ifeq ($(uname_S),SunOS) INSTALL?= cp -pPR
INSTALL?= cp -r
endif
INSTALL?= cp -a
$(PKGCONFNAME): hiredis.h $(PKGCONFNAME): hiredis.h
@echo "Generating $@ for pkgconfig..." @echo "Generating $@ for pkgconfig..."
...@@ -181,8 +176,9 @@ $(PKGCONFNAME): hiredis.h ...@@ -181,8 +176,9 @@ $(PKGCONFNAME): hiredis.h
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis.h async.h read.h sds.h adapters $(INSTALL_INCLUDE_PATH) $(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
......
...@@ -73,8 +73,8 @@ static void redisLibeventDelWrite(void *privdata) { ...@@ -73,8 +73,8 @@ static void redisLibeventDelWrite(void *privdata) {
static void redisLibeventCleanup(void *privdata) { static void redisLibeventCleanup(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(e->rev); event_free(e->rev);
event_del(e->wev); event_free(e->wev);
free(e); free(e);
} }
......
...@@ -15,15 +15,12 @@ typedef struct redisLibuvEvents { ...@@ -15,15 +15,12 @@ typedef struct redisLibuvEvents {
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
redisLibuvEvents* p = (redisLibuvEvents*)handle->data; redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
int ev = (status ? p->events : events);
if (status != 0) { if (p->context != NULL && (ev & UV_READABLE)) {
return;
}
if (p->context != NULL && (events & UV_READABLE)) {
redisAsyncHandleRead(p->context); redisAsyncHandleRead(p->context);
} }
if (p->context != NULL && (events & UV_WRITABLE)) { if (p->context != NULL && (ev & UV_WRITABLE)) {
redisAsyncHandleWrite(p->context); redisAsyncHandleWrite(p->context);
} }
} }
......
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin) # Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
environment: environment:
matrix: matrix:
- CYG_ROOT: C:\cygwin64 - CYG_BASH: C:\cygwin64\bin\bash
CYG_SETUP: setup-x86_64.exe
CYG_MIRROR: http://cygwin.mirror.constant.com
CYG_CACHE: C:\cygwin64\var\cache\setup
CYG_BASH: C:\cygwin64\bin\bash
CC: gcc CC: gcc
- CYG_ROOT: C:\cygwin - CYG_BASH: C:\cygwin\bin\bash
CYG_SETUP: setup-x86.exe
CYG_MIRROR: http://cygwin.mirror.constant.com
CYG_CACHE: C:\cygwin\var\cache\setup
CYG_BASH: C:\cygwin\bin\bash
CC: gcc CC: gcc
TARGET: 32bit TARGET: 32bit
TARGET_VARS: 32bit-vars TARGET_VARS: 32bit-vars
# Cache Cygwin files to speed up build
cache:
- '%CYG_CACHE%'
clone_depth: 1 clone_depth: 1
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail # Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
...@@ -27,8 +16,6 @@ init: ...@@ -27,8 +16,6 @@ init:
# Install needed build dependencies # Install needed build dependencies
install: install:
- ps: 'Start-FileDownload "http://cygwin.com/$env:CYG_SETUP" -FileName "$env:CYG_SETUP"'
- '%CYG_SETUP% --quiet-mode --no-shortcuts --only-site --root "%CYG_ROOT%" --site "%CYG_MIRROR%" --local-package-dir "%CYG_CACHE%" --packages automake,bison,gcc-core,libtool,make,gettext-devel,gettext,intltool,pkg-config,clang,llvm > NUL 2>&1'
- '%CYG_BASH% -lc "cygcheck -dc cygwin"' - '%CYG_BASH% -lc "cygcheck -dc cygwin"'
build_script: build_script:
......
...@@ -336,7 +336,8 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { ...@@ -336,7 +336,8 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
if (ac->err == 0) { if (ac->err == 0) {
/* For clean disconnects, there should be no pending callbacks. */ /* For clean disconnects, there should be no pending callbacks. */
assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); int ret = __redisShiftCallback(&ac->replies,NULL);
assert(ret == REDIS_ERR);
} else { } else {
/* Disconnection is caused by an error, make sure that pending /* Disconnection is caused by an error, make sure that pending
* callbacks cannot call new commands. */ * callbacks cannot call new commands. */
...@@ -364,6 +365,7 @@ void redisAsyncDisconnect(redisAsyncContext *ac) { ...@@ -364,6 +365,7 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
dict *callbacks; dict *callbacks;
redisCallback *cb;
dictEntry *de; dictEntry *de;
int pvariant; int pvariant;
char *stype; char *stype;
...@@ -387,16 +389,28 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, ...@@ -387,16 +389,28 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
de = dictFind(callbacks,sname); de = dictFind(callbacks,sname);
if (de != NULL) { if (de != NULL) {
memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); cb = dictGetEntryVal(de);
/* If this is an subscribe reply decrease pending counter. */
if (strcasecmp(stype+pvariant,"subscribe") == 0) {
cb->pending_subs -= 1;
}
memcpy(dstcb,cb,sizeof(*dstcb));
/* If this is an unsubscribe message, remove it. */ /* If this is an unsubscribe message, remove it. */
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
dictDelete(callbacks,sname); if (cb->pending_subs == 0)
dictDelete(callbacks,sname);
/* If this was the last unsubscribe message, revert to /* If this was the last unsubscribe message, revert to
* non-subscribe mode. */ * non-subscribe mode. */
assert(reply->element[2]->type == REDIS_REPLY_INTEGER); assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
if (reply->element[2]->integer == 0)
/* Unset subscribed flag only when no pipelined pending subscribe. */
if (reply->element[2]->integer == 0
&& dictSize(ac->sub.channels) == 0
&& dictSize(ac->sub.patterns) == 0)
c->flags &= ~REDIS_SUBSCRIBED; c->flags &= ~REDIS_SUBSCRIBED;
} }
} }
...@@ -410,7 +424,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, ...@@ -410,7 +424,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
void redisProcessCallbacks(redisAsyncContext *ac) { void redisProcessCallbacks(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb = {NULL, NULL, NULL}; redisCallback cb = {NULL, NULL, 0, NULL};
void *reply = NULL; void *reply = NULL;
int status; int status;
...@@ -492,22 +506,22 @@ void redisProcessCallbacks(redisAsyncContext *ac) { ...@@ -492,22 +506,22 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
* write event fires. When connecting was not successful, the connect callback * write event fires. When connecting was not successful, the connect callback
* is called with a REDIS_ERR status and the context is free'd. */ * is called with a REDIS_ERR status and the context is free'd. */
static int __redisAsyncHandleConnect(redisAsyncContext *ac) { static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
int completed = 0;
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
if (redisCheckSocketError(c) == REDIS_ERR) { /* Error! */
/* Try again later when connect(2) is still in progress. */ redisCheckSocketError(c);
if (errno == EINPROGRESS) if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
return REDIS_OK;
if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
return REDIS_ERR; return REDIS_ERR;
} else if (completed == 1) {
/* connected! */
if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
c->flags |= REDIS_CONNECTED;
return REDIS_OK;
} else {
return REDIS_OK;
} }
/* Mark context as connected. */
c->flags |= REDIS_CONNECTED;
if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
return REDIS_OK;
} }
/* This function should be called when the socket is readable. /* This function should be called when the socket is readable.
...@@ -583,6 +597,9 @@ static const char *nextArgument(const char *start, const char **str, size_t *len ...@@ -583,6 +597,9 @@ static const char *nextArgument(const char *start, const char **str, size_t *len
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb; redisCallback cb;
struct dict *cbdict;
dictEntry *de;
redisCallback *existcb;
int pvariant, hasnext; int pvariant, hasnext;
const char *cstr, *astr; const char *cstr, *astr;
size_t clen, alen; size_t clen, alen;
...@@ -596,6 +613,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ...@@ -596,6 +613,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
/* Setup callback */ /* Setup callback */
cb.fn = fn; cb.fn = fn;
cb.privdata = privdata; cb.privdata = privdata;
cb.pending_subs = 1;
/* Find out which command will be appended. */ /* Find out which command will be appended. */
p = nextArgument(cmd,&cstr,&clen); p = nextArgument(cmd,&cstr,&clen);
...@@ -612,9 +630,18 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ...@@ -612,9 +630,18 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
while ((p = nextArgument(p,&astr,&alen)) != NULL) { while ((p = nextArgument(p,&astr,&alen)) != NULL) {
sname = sdsnewlen(astr,alen); sname = sdsnewlen(astr,alen);
if (pvariant) if (pvariant)
ret = dictReplace(ac->sub.patterns,sname,&cb); cbdict = ac->sub.patterns;
else else
ret = dictReplace(ac->sub.channels,sname,&cb); cbdict = ac->sub.channels;
de = dictFind(cbdict,sname);
if (de != NULL) {
existcb = dictGetEntryVal(de);
cb.pending_subs = existcb->pending_subs + 1;
}
ret = dictReplace(cbdict,sname,&cb);
if (ret == 0) sdsfree(sname); if (ret == 0) sdsfree(sname);
} }
...@@ -676,6 +703,8 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv ...@@ -676,6 +703,8 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv
int len; int len;
int status; int status;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
if (len < 0)
return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
sdsfree(cmd); sdsfree(cmd);
return status; return status;
......
...@@ -45,6 +45,7 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); ...@@ -45,6 +45,7 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
typedef struct redisCallback { typedef struct redisCallback {
struct redisCallback *next; /* simple singly linked list */ struct redisCallback *next; /* simple singly linked list */
redisCallbackFn *fn; redisCallbackFn *fn;
int pending_subs;
void *privdata; void *privdata;
} redisCallback; } redisCallback;
...@@ -92,6 +93,10 @@ typedef struct redisAsyncContext { ...@@ -92,6 +93,10 @@ typedef struct redisAsyncContext {
/* Regular command callbacks */ /* Regular command callbacks */
redisCallbackList replies; redisCallbackList replies;
/* Address used for connect() */
struct sockaddr *saddr;
size_t addrlen;
/* Subscription callbacks */ /* Subscription callbacks */
struct { struct {
redisCallbackList invalid; redisCallbackList invalid;
......
#ifndef __HIREDIS_FMACRO_H #ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H
#if defined(__linux__)
#define _BSD_SOURCE
#define _DEFAULT_SOURCE
#endif
#if defined(__CYGWIN__)
#include <sys/cdefs.h>
#endif
#if defined(__sun__)
#define _POSIX_C_SOURCE 200112L
#else
#if !(defined(__APPLE__) && defined(__MACH__)) && !(defined(__FreeBSD__))
#define _XOPEN_SOURCE 600 #define _XOPEN_SOURCE 600
#endif #define _POSIX_C_SOURCE 200112L
#endif
#if defined(__APPLE__) && defined(__MACH__) #if defined(__APPLE__) && defined(__MACH__)
#define _OSX /* Enable TCP_KEEPALIVE */
#define _DARWIN_C_SOURCE
#endif #endif
#endif #endif
...@@ -47,7 +47,9 @@ static redisReply *createReplyObject(int type); ...@@ -47,7 +47,9 @@ static redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len); static void *createStringObject(const redisReadTask *task, char *str, size_t len);
static void *createArrayObject(const redisReadTask *task, int elements); static void *createArrayObject(const redisReadTask *task, int elements);
static void *createIntegerObject(const redisReadTask *task, long long value); static void *createIntegerObject(const redisReadTask *task, long long value);
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
static void *createNilObject(const redisReadTask *task); static void *createNilObject(const redisReadTask *task);
static void *createBoolObject(const redisReadTask *task, int bval);
/* Default set of functions to build the reply. Keep in mind that such a /* Default set of functions to build the reply. Keep in mind that such a
* function returning NULL is interpreted as OOM. */ * function returning NULL is interpreted as OOM. */
...@@ -55,7 +57,9 @@ static redisReplyObjectFunctions defaultFunctions = { ...@@ -55,7 +57,9 @@ static redisReplyObjectFunctions defaultFunctions = {
createStringObject, createStringObject,
createArrayObject, createArrayObject,
createIntegerObject, createIntegerObject,
createDoubleObject,
createNilObject, createNilObject,
createBoolObject,
freeReplyObject freeReplyObject
}; };
...@@ -82,18 +86,19 @@ void freeReplyObject(void *reply) { ...@@ -82,18 +86,19 @@ void freeReplyObject(void *reply) {
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
break; /* Nothing to free */ break; /* Nothing to free */
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
case REDIS_REPLY_MAP:
case REDIS_REPLY_SET:
if (r->element != NULL) { if (r->element != NULL) {
for (j = 0; j < r->elements; j++) for (j = 0; j < r->elements; j++)
if (r->element[j] != NULL) freeReplyObject(r->element[j]);
freeReplyObject(r->element[j]);
free(r->element); free(r->element);
} }
break; break;
case REDIS_REPLY_ERROR: case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS: case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
if (r->str != NULL) case REDIS_REPLY_DOUBLE:
free(r->str); free(r->str);
break; break;
} }
free(r); free(r);
...@@ -125,7 +130,9 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len ...@@ -125,7 +130,9 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
...@@ -134,7 +141,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len ...@@ -134,7 +141,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
static void *createArrayObject(const redisReadTask *task, int elements) { static void *createArrayObject(const redisReadTask *task, int elements) {
redisReply *r, *parent; redisReply *r, *parent;
r = createReplyObject(REDIS_REPLY_ARRAY); r = createReplyObject(task->type);
if (r == NULL) if (r == NULL)
return NULL; return NULL;
...@@ -150,7 +157,9 @@ static void *createArrayObject(const redisReadTask *task, int elements) { ...@@ -150,7 +157,9 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
...@@ -167,7 +176,41 @@ static void *createIntegerObject(const redisReadTask *task, long long value) { ...@@ -167,7 +176,41 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r;
}
return r;
}
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
redisReply *r, *parent;
r = createReplyObject(REDIS_REPLY_DOUBLE);
if (r == NULL)
return NULL;
r->dval = value;
r->str = malloc(len+1);
if (r->str == NULL) {
freeReplyObject(r);
return NULL;
}
/* The double reply also has the original protocol string representing a
* double as a null terminated string. This way the caller does not need
* to format back for string conversion, especially since Redis does efforts
* to make the string more human readable avoiding the calssical double
* decimal string conversion artifacts. */
memcpy(r->str, str, len);
r->str[len] = '\0';
if (task->parent) {
parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
...@@ -182,7 +225,28 @@ static void *createNilObject(const redisReadTask *task) { ...@@ -182,7 +225,28 @@ static void *createNilObject(const redisReadTask *task) {
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r;
}
return r;
}
static void *createBoolObject(const redisReadTask *task, int bval) {
redisReply *r, *parent;
r = createReplyObject(REDIS_REPLY_BOOL);
if (r == NULL)
return NULL;
r->integer = bval != 0;
if (task->parent) {
parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
...@@ -432,11 +496,7 @@ cleanup: ...@@ -432,11 +496,7 @@ cleanup:
} }
sdsfree(curarg); sdsfree(curarg);
free(cmd);
/* No need to check cmd since it is the last statement that can fail,
* but do it anyway to be as defensive as possible. */
if (cmd != NULL)
free(cmd);
return error_type; return error_type;
} }
...@@ -581,7 +641,7 @@ void __redisSetError(redisContext *c, int type, const char *str) { ...@@ -581,7 +641,7 @@ void __redisSetError(redisContext *c, int type, const char *str) {
} else { } else {
/* Only REDIS_ERR_IO may lack a description! */ /* Only REDIS_ERR_IO may lack a description! */
assert(type == REDIS_ERR_IO); assert(type == REDIS_ERR_IO);
__redis_strerror_r(errno, c->errstr, sizeof(c->errstr)); strerror_r(errno, c->errstr, sizeof(c->errstr));
} }
} }
...@@ -596,14 +656,8 @@ static redisContext *redisContextInit(void) { ...@@ -596,14 +656,8 @@ static redisContext *redisContextInit(void) {
if (c == NULL) if (c == NULL)
return NULL; return NULL;
c->err = 0;
c->errstr[0] = '\0';
c->obuf = sdsempty(); c->obuf = sdsempty();
c->reader = redisReaderCreate(); c->reader = redisReaderCreate();
c->tcp.host = NULL;
c->tcp.source_addr = NULL;
c->unix_sock.path = NULL;
c->timeout = NULL;
if (c->obuf == NULL || c->reader == NULL) { if (c->obuf == NULL || c->reader == NULL) {
redisFree(c); redisFree(c);
...@@ -618,18 +672,14 @@ void redisFree(redisContext *c) { ...@@ -618,18 +672,14 @@ void redisFree(redisContext *c) {
return; return;
if (c->fd > 0) if (c->fd > 0)
close(c->fd); close(c->fd);
if (c->obuf != NULL)
sdsfree(c->obuf); sdsfree(c->obuf);
if (c->reader != NULL) redisReaderFree(c->reader);
redisReaderFree(c->reader); free(c->tcp.host);
if (c->tcp.host) free(c->tcp.source_addr);
free(c->tcp.host); free(c->unix_sock.path);
if (c->tcp.source_addr) free(c->timeout);
free(c->tcp.source_addr); free(c->saddr);
if (c->unix_sock.path)
free(c->unix_sock.path);
if (c->timeout)
free(c->timeout);
free(c); free(c);
} }
...@@ -710,6 +760,8 @@ redisContext *redisConnectNonBlock(const char *ip, int port) { ...@@ -710,6 +760,8 @@ redisContext *redisConnectNonBlock(const char *ip, int port) {
redisContext *redisConnectBindNonBlock(const char *ip, int port, redisContext *redisConnectBindNonBlock(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisContextInit(); redisContext *c = redisContextInit();
if (c == NULL)
return NULL;
c->flags &= ~REDIS_BLOCK; c->flags &= ~REDIS_BLOCK;
redisContextConnectBindTcp(c,ip,port,NULL,source_addr); redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
return c; return c;
...@@ -718,6 +770,8 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port, ...@@ -718,6 +770,8 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port,
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisContextInit(); redisContext *c = redisContextInit();
if (c == NULL)
return NULL;
c->flags &= ~REDIS_BLOCK; c->flags &= ~REDIS_BLOCK;
c->flags |= REDIS_REUSEADDR; c->flags |= REDIS_REUSEADDR;
redisContextConnectBindTcp(c,ip,port,NULL,source_addr); redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
...@@ -789,7 +843,7 @@ int redisEnableKeepAlive(redisContext *c) { ...@@ -789,7 +843,7 @@ int redisEnableKeepAlive(redisContext *c) {
/* Use this function to handle a read event on the descriptor. It will try /* Use this function to handle a read event on the descriptor. It will try
* and read some bytes from the socket and feed them to the reply parser. * and read some bytes from the socket and feed them to the reply parser.
* *
* After this function is called, you may use redisContextReadReply to * After this function is called, you may use redisGetReplyFromReader to
* see if there is a reply available. */ * see if there is a reply available. */
int redisBufferRead(redisContext *c) { int redisBufferRead(redisContext *c) {
char buf[1024*16]; char buf[1024*16];
...@@ -1007,9 +1061,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) { ...@@ -1007,9 +1061,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) {
void *redisCommand(redisContext *c, const char *format, ...) { void *redisCommand(redisContext *c, const char *format, ...) {
va_list ap; va_list ap;
void *reply = NULL;
va_start(ap,format); va_start(ap,format);
reply = redisvCommand(c,format,ap); void *reply = redisvCommand(c,format,ap);
va_end(ap); va_end(ap);
return reply; return reply;
} }
......
...@@ -40,9 +40,9 @@ ...@@ -40,9 +40,9 @@
#include "sds.h" /* for sds */ #include "sds.h" /* for sds */
#define HIREDIS_MAJOR 0 #define HIREDIS_MAJOR 0
#define HIREDIS_MINOR 13 #define HIREDIS_MINOR 14
#define HIREDIS_PATCH 3 #define HIREDIS_PATCH 0
#define HIREDIS_SONAME 0.13 #define HIREDIS_SONAME 0.14
/* Connection type can be blocking or non-blocking and is set in the /* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */ * least significant bit of the flags field in redisContext. */
...@@ -80,30 +80,6 @@ ...@@ -80,30 +80,6 @@
* SO_REUSEADDR is being used. */ * SO_REUSEADDR is being used. */
#define REDIS_CONNECT_RETRIES 10 #define REDIS_CONNECT_RETRIES 10
/* strerror_r has two completely different prototypes and behaviors
* depending on system issues, so we need to operate on the error buffer
* differently depending on which strerror_r we're using. */
#ifndef _GNU_SOURCE
/* "regular" POSIX strerror_r that does the right thing. */
#define __redis_strerror_r(errno, buf, len) \
do { \
strerror_r((errno), (buf), (len)); \
} while (0)
#else
/* "bad" GNU strerror_r we need to clean up after. */
#define __redis_strerror_r(errno, buf, len) \
do { \
char *err_str = strerror_r((errno), (buf), (len)); \
/* If return value _isn't_ the start of the buffer we passed in, \
* then GNU strerror_r returned an internal static buffer and we \
* need to copy the result into our private buffer. */ \
if (err_str != (buf)) { \
strncpy((buf), err_str, ((len) - 1)); \
buf[(len)-1] = '\0'; \
} \
} while (0)
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
...@@ -112,8 +88,10 @@ extern "C" { ...@@ -112,8 +88,10 @@ extern "C" {
typedef struct redisReply { typedef struct redisReply {
int type; /* REDIS_REPLY_* */ int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
size_t len; /* Length of string */ size_t len; /* Length of string */
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
and REDIS_REPLY_DOUBLE (in additionl to dval). */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply; } redisReply;
...@@ -158,6 +136,9 @@ typedef struct redisContext { ...@@ -158,6 +136,9 @@ typedef struct redisContext {
char *path; char *path;
} unix_sock; } unix_sock;
/* For non-blocking connect */
struct sockadr *saddr;
size_t addrlen;
} redisContext; } redisContext;
redisContext *redisConnect(const char *ip, int port); redisContext *redisConnect(const char *ip, int port);
......
...@@ -65,12 +65,13 @@ static void redisContextCloseFd(redisContext *c) { ...@@ -65,12 +65,13 @@ static void redisContextCloseFd(redisContext *c) {
} }
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
int errorno = errno; /* snprintf() may change errno */
char buf[128] = { 0 }; char buf[128] = { 0 };
size_t len = 0; size_t len = 0;
if (prefix != NULL) if (prefix != NULL)
len = snprintf(buf,sizeof(buf),"%s: ",prefix); len = snprintf(buf,sizeof(buf),"%s: ",prefix);
__redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len); strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
__redisSetError(c,type,buf); __redisSetError(c,type,buf);
} }
...@@ -135,14 +136,13 @@ int redisKeepAlive(redisContext *c, int interval) { ...@@ -135,14 +136,13 @@ int redisKeepAlive(redisContext *c, int interval) {
val = interval; val = interval;
#ifdef _OSX #if defined(__APPLE__) && defined(__MACH__)
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR; return REDIS_ERR;
} }
#else #else
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) #if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
val = interval;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR; return REDIS_ERR;
...@@ -221,8 +221,10 @@ static int redisContextWaitReady(redisContext *c, long msec) { ...@@ -221,8 +221,10 @@ static int redisContextWaitReady(redisContext *c, long msec) {
return REDIS_ERR; return REDIS_ERR;
} }
if (redisCheckSocketError(c) != REDIS_OK) if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
redisCheckSocketError(c);
return REDIS_ERR; return REDIS_ERR;
}
return REDIS_OK; return REDIS_OK;
} }
...@@ -232,8 +234,28 @@ static int redisContextWaitReady(redisContext *c, long msec) { ...@@ -232,8 +234,28 @@ static int redisContextWaitReady(redisContext *c, long msec) {
return REDIS_ERR; return REDIS_ERR;
} }
int redisCheckConnectDone(redisContext *c, int *completed) {
int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen);
if (rc == 0) {
*completed = 1;
return REDIS_OK;
}
switch (errno) {
case EISCONN:
*completed = 1;
return REDIS_OK;
case EALREADY:
case EINPROGRESS:
case EWOULDBLOCK:
*completed = 0;
return REDIS_OK;
default:
return REDIS_ERR;
}
}
int redisCheckSocketError(redisContext *c) { int redisCheckSocketError(redisContext *c) {
int err = 0; int err = 0, errno_saved = errno;
socklen_t errlen = sizeof(err); socklen_t errlen = sizeof(err);
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
...@@ -241,6 +263,10 @@ int redisCheckSocketError(redisContext *c) { ...@@ -241,6 +263,10 @@ int redisCheckSocketError(redisContext *c) {
return REDIS_ERR; return REDIS_ERR;
} }
if (err == 0) {
err = errno_saved;
}
if (err) { if (err) {
errno = err; errno = err;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
...@@ -285,8 +311,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, ...@@ -285,8 +311,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
* This is a bit ugly, but atleast it works and doesn't leak memory. * This is a bit ugly, but atleast it works and doesn't leak memory.
**/ **/
if (c->tcp.host != addr) { if (c->tcp.host != addr) {
if (c->tcp.host) free(c->tcp.host);
free(c->tcp.host);
c->tcp.host = strdup(addr); c->tcp.host = strdup(addr);
} }
...@@ -299,8 +324,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, ...@@ -299,8 +324,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
memcpy(c->timeout, timeout, sizeof(struct timeval)); memcpy(c->timeout, timeout, sizeof(struct timeval));
} }
} else { } else {
if (c->timeout) free(c->timeout);
free(c->timeout);
c->timeout = NULL; c->timeout = NULL;
} }
...@@ -356,6 +380,7 @@ addrretry: ...@@ -356,6 +380,7 @@ addrretry:
n = 1; n = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
sizeof(n)) < 0) { sizeof(n)) < 0) {
freeaddrinfo(bservinfo);
goto error; goto error;
} }
} }
...@@ -374,12 +399,27 @@ addrretry: ...@@ -374,12 +399,27 @@ addrretry:
goto error; goto error;
} }
} }
/* For repeat connection */
if (c->saddr) {
free(c->saddr);
}
c->saddr = malloc(p->ai_addrlen);
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
c->addrlen = p->ai_addrlen;
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
if (errno == EHOSTUNREACH) { if (errno == EHOSTUNREACH) {
redisContextCloseFd(c); redisContextCloseFd(c);
continue; continue;
} else if (errno == EINPROGRESS && !blocking) { } else if (errno == EINPROGRESS) {
/* This is ok. */ if (blocking) {
goto wait_for_ready;
}
/* This is ok.
* Note that even when it's in blocking mode, we unset blocking
* for `connect()`
*/
} else if (errno == EADDRNOTAVAIL && reuseaddr) { } else if (errno == EADDRNOTAVAIL && reuseaddr) {
if (++reuses >= REDIS_CONNECT_RETRIES) { if (++reuses >= REDIS_CONNECT_RETRIES) {
goto error; goto error;
...@@ -388,6 +428,7 @@ addrretry: ...@@ -388,6 +428,7 @@ addrretry:
goto addrretry; goto addrretry;
} }
} else { } else {
wait_for_ready:
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
goto error; goto error;
} }
...@@ -411,7 +452,10 @@ addrretry: ...@@ -411,7 +452,10 @@ addrretry:
error: error:
rv = REDIS_ERR; rv = REDIS_ERR;
end: end:
freeaddrinfo(servinfo); if(servinfo) {
freeaddrinfo(servinfo);
}
return rv; // Need to return REDIS_OK if alright return rv; // Need to return REDIS_OK if alright
} }
...@@ -431,7 +475,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time ...@@ -431,7 +475,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
struct sockaddr_un sa; struct sockaddr_un sa;
long timeout_msec = -1; long timeout_msec = -1;
if (redisCreateSocket(c,AF_LOCAL) < 0) if (redisCreateSocket(c,AF_UNIX) < 0)
return REDIS_ERR; return REDIS_ERR;
if (redisSetBlocking(c,0) != REDIS_OK) if (redisSetBlocking(c,0) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
...@@ -448,15 +492,14 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time ...@@ -448,15 +492,14 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
memcpy(c->timeout, timeout, sizeof(struct timeval)); memcpy(c->timeout, timeout, sizeof(struct timeval));
} }
} else { } else {
if (c->timeout) free(c->timeout);
free(c->timeout);
c->timeout = NULL; c->timeout = NULL;
} }
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
sa.sun_family = AF_LOCAL; sa.sun_family = AF_UNIX;
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS && !blocking) { if (errno == EINPROGRESS && !blocking) {
......
...@@ -37,10 +37,6 @@ ...@@ -37,10 +37,6 @@
#include "hiredis.h" #include "hiredis.h"
#if defined(__sun)
#define AF_LOCAL AF_UNIX
#endif
int redisCheckSocketError(redisContext *c); int redisCheckSocketError(redisContext *c);
int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextSetTimeout(redisContext *c, const struct timeval tv);
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
...@@ -49,5 +45,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, ...@@ -49,5 +45,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
const char *source_addr); const char *source_addr);
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
int redisKeepAlive(redisContext *c, int interval); int redisKeepAlive(redisContext *c, int interval);
int redisCheckConnectDone(redisContext *c, int *completed);
#endif #endif
...@@ -29,9 +29,9 @@ ...@@ -29,9 +29,9 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#include "fmacros.h" #include "fmacros.h"
#include <string.h> #include <string.h>
#include <strings.h>
#include <stdlib.h> #include <stdlib.h>
#ifndef _MSC_VER #ifndef _MSC_VER
#include <unistd.h> #include <unistd.h>
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <ctype.h> #include <ctype.h>
#include <limits.h>
#include <math.h>
#include "read.h" #include "read.h"
#include "sds.h" #include "sds.h"
...@@ -52,11 +54,9 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) { ...@@ -52,11 +54,9 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
} }
/* Clear input buffer on errors. */ /* Clear input buffer on errors. */
if (r->buf != NULL) { sdsfree(r->buf);
sdsfree(r->buf); r->buf = NULL;
r->buf = NULL; r->pos = r->len = 0;
r->pos = r->len = 0;
}
/* Reset task stack. */ /* Reset task stack. */
r->ridx = -1; r->ridx = -1;
...@@ -143,33 +143,79 @@ static char *seekNewline(char *s, size_t len) { ...@@ -143,33 +143,79 @@ static char *seekNewline(char *s, size_t len) {
return NULL; return NULL;
} }
/* Read a long long value starting at *s, under the assumption that it will be /* Convert a string into a long long. Returns REDIS_OK if the string could be
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */ * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
static long long readLongLong(char *s) { * will be set to the parsed value when appropriate.
long long v = 0; *
int dec, mult = 1; * Note that this function demands that the string strictly represents
char c; * a long long: no spaces or other characters before or after the string
* representing the number are accepted, nor zeroes at the start if not
if (*s == '-') { * for the string "0" representing the zero number.
mult = -1; *
s++; * Because of its strictness, it is safe to use this function to check if
} else if (*s == '+') { * you can convert a string into a long long, and obtain back the string
mult = 1; * from the number without any loss in the string representation. */
s++; static int string2ll(const char *s, size_t slen, long long *value) {
const char *p = s;
size_t plen = 0;
int negative = 0;
unsigned long long v;
if (plen == slen)
return REDIS_ERR;
/* Special case: first and only digit is 0. */
if (slen == 1 && p[0] == '0') {
if (value != NULL) *value = 0;
return REDIS_OK;
} }
while ((c = *(s++)) != '\r') { if (p[0] == '-') {
dec = c - '0'; negative = 1;
if (dec >= 0 && dec < 10) { p++; plen++;
v *= 10;
v += dec; /* Abort on only a negative sign. */
} else { if (plen == slen)
/* Should not happen... */ return REDIS_ERR;
return -1; }
}
/* 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 REDIS_OK;
} else {
return REDIS_ERR;
} }
return mult*v; while (plen < slen && p[0] >= '0' && p[0] <= '9') {
if (v > (ULLONG_MAX / 10)) /* Overflow. */
return REDIS_ERR;
v *= 10;
if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
return REDIS_ERR;
v += p[0]-'0';
p++; plen++;
}
/* Return if not all bytes were used. */
if (plen < slen)
return REDIS_ERR;
if (negative) {
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
return REDIS_ERR;
if (value != NULL) *value = -v;
} else {
if (v > LLONG_MAX) /* Overflow. */
return REDIS_ERR;
if (value != NULL) *value = v;
}
return REDIS_OK;
} }
static char *readLine(redisReader *r, int *_len) { static char *readLine(redisReader *r, int *_len) {
...@@ -198,7 +244,9 @@ static void moveToNextTask(redisReader *r) { ...@@ -198,7 +244,9 @@ static void moveToNextTask(redisReader *r) {
cur = &(r->rstack[r->ridx]); cur = &(r->rstack[r->ridx]);
prv = &(r->rstack[r->ridx-1]); prv = &(r->rstack[r->ridx-1]);
assert(prv->type == REDIS_REPLY_ARRAY); assert(prv->type == REDIS_REPLY_ARRAY ||
prv->type == REDIS_REPLY_MAP ||
prv->type == REDIS_REPLY_SET);
if (cur->idx == prv->elements-1) { if (cur->idx == prv->elements-1) {
r->ridx--; r->ridx--;
} else { } else {
...@@ -220,10 +268,58 @@ static int processLineItem(redisReader *r) { ...@@ -220,10 +268,58 @@ static int processLineItem(redisReader *r) {
if ((p = readLine(r,&len)) != NULL) { if ((p = readLine(r,&len)) != NULL) {
if (cur->type == REDIS_REPLY_INTEGER) { if (cur->type == REDIS_REPLY_INTEGER) {
if (r->fn && r->fn->createInteger) if (r->fn && r->fn->createInteger) {
obj = r->fn->createInteger(cur,readLongLong(p)); long long v;
else if (string2ll(p, len, &v) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad integer value");
return REDIS_ERR;
}
obj = r->fn->createInteger(cur,v);
} else {
obj = (void*)REDIS_REPLY_INTEGER; obj = (void*)REDIS_REPLY_INTEGER;
}
} else if (cur->type == REDIS_REPLY_DOUBLE) {
if (r->fn && r->fn->createDouble) {
char buf[326], *eptr;
double d;
if ((size_t)len >= sizeof(buf)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Double value is too large");
return REDIS_ERR;
}
memcpy(buf,p,len);
buf[len] = '\0';
if (strcasecmp(buf,",inf") == 0) {
d = 1.0/0.0; /* Positive infinite. */
} else if (strcasecmp(buf,",-inf") == 0) {
d = -1.0/0.0; /* Nevative infinite. */
} else {
d = strtod((char*)buf,&eptr);
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad double value");
return REDIS_ERR;
}
}
obj = r->fn->createDouble(cur,d,buf,len);
} else {
obj = (void*)REDIS_REPLY_DOUBLE;
}
} else if (cur->type == REDIS_REPLY_NIL) {
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
else
obj = (void*)REDIS_REPLY_NIL;
} else if (cur->type == REDIS_REPLY_BOOL) {
int bval = p[0] == 't' || p[0] == 'T';
if (r->fn && r->fn->createBool)
obj = r->fn->createBool(cur,bval);
else
obj = (void*)REDIS_REPLY_BOOL;
} else { } else {
/* Type will be error or status. */ /* Type will be error or status. */
if (r->fn && r->fn->createString) if (r->fn && r->fn->createString)
...@@ -250,7 +346,7 @@ static int processBulkItem(redisReader *r) { ...@@ -250,7 +346,7 @@ static int processBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj = NULL; void *obj = NULL;
char *p, *s; char *p, *s;
long len; long long len;
unsigned long bytelen; unsigned long bytelen;
int success = 0; int success = 0;
...@@ -259,9 +355,20 @@ static int processBulkItem(redisReader *r) { ...@@ -259,9 +355,20 @@ static int processBulkItem(redisReader *r) {
if (s != NULL) { if (s != NULL) {
p = r->buf+r->pos; p = r->buf+r->pos;
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
len = readLongLong(p);
if (len < 0) { if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad bulk string length");
return REDIS_ERR;
}
if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bulk string length out of range");
return REDIS_ERR;
}
if (len == -1) {
/* The nil object can always be created. */ /* The nil object can always be created. */
if (r->fn && r->fn->createNil) if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur); obj = r->fn->createNil(cur);
...@@ -299,12 +406,13 @@ static int processBulkItem(redisReader *r) { ...@@ -299,12 +406,13 @@ static int processBulkItem(redisReader *r) {
return REDIS_ERR; return REDIS_ERR;
} }
static int processMultiBulkItem(redisReader *r) { /* Process the array, map and set types. */
static int processAggregateItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj; void *obj;
char *p; char *p;
long elements; long long elements;
int root = 0; int root = 0, len;
/* Set error for nested multi bulks with depth > 7 */ /* Set error for nested multi bulks with depth > 7 */
if (r->ridx == 8) { if (r->ridx == 8) {
...@@ -313,10 +421,21 @@ static int processMultiBulkItem(redisReader *r) { ...@@ -313,10 +421,21 @@ static int processMultiBulkItem(redisReader *r) {
return REDIS_ERR; return REDIS_ERR;
} }
if ((p = readLine(r,NULL)) != NULL) { if ((p = readLine(r,&len)) != NULL) {
elements = readLongLong(p); if (string2ll(p, len, &elements) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad multi-bulk length");
return REDIS_ERR;
}
root = (r->ridx == 0); root = (r->ridx == 0);
if (elements < -1 || elements > INT_MAX) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Multi-bulk length out of range");
return REDIS_ERR;
}
if (elements == -1) { if (elements == -1) {
if (r->fn && r->fn->createNil) if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur); obj = r->fn->createNil(cur);
...@@ -330,10 +449,12 @@ static int processMultiBulkItem(redisReader *r) { ...@@ -330,10 +449,12 @@ static int processMultiBulkItem(redisReader *r) {
moveToNextTask(r); moveToNextTask(r);
} else { } else {
if (cur->type == REDIS_REPLY_MAP) elements *= 2;
if (r->fn && r->fn->createArray) if (r->fn && r->fn->createArray)
obj = r->fn->createArray(cur,elements); obj = r->fn->createArray(cur,elements);
else else
obj = (void*)REDIS_REPLY_ARRAY; obj = (void*)(long)cur->type;
if (obj == NULL) { if (obj == NULL) {
__redisReaderSetErrorOOM(r); __redisReaderSetErrorOOM(r);
...@@ -381,12 +502,27 @@ static int processItem(redisReader *r) { ...@@ -381,12 +502,27 @@ static int processItem(redisReader *r) {
case ':': case ':':
cur->type = REDIS_REPLY_INTEGER; cur->type = REDIS_REPLY_INTEGER;
break; break;
case ',':
cur->type = REDIS_REPLY_DOUBLE;
break;
case '_':
cur->type = REDIS_REPLY_NIL;
break;
case '$': case '$':
cur->type = REDIS_REPLY_STRING; cur->type = REDIS_REPLY_STRING;
break; break;
case '*': case '*':
cur->type = REDIS_REPLY_ARRAY; cur->type = REDIS_REPLY_ARRAY;
break; break;
case '%':
cur->type = REDIS_REPLY_MAP;
break;
case '~':
cur->type = REDIS_REPLY_SET;
break;
case '#':
cur->type = REDIS_REPLY_BOOL;
break;
default: default:
__redisReaderSetErrorProtocolByte(r,*p); __redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR; return REDIS_ERR;
...@@ -402,11 +538,16 @@ static int processItem(redisReader *r) { ...@@ -402,11 +538,16 @@ static int processItem(redisReader *r) {
case REDIS_REPLY_ERROR: case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS: case REDIS_REPLY_STATUS:
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
case REDIS_REPLY_DOUBLE:
case REDIS_REPLY_NIL:
case REDIS_REPLY_BOOL:
return processLineItem(r); return processLineItem(r);
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
return processBulkItem(r); return processBulkItem(r);
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
return processMultiBulkItem(r); case REDIS_REPLY_MAP:
case REDIS_REPLY_SET:
return processAggregateItem(r);
default: default:
assert(NULL); assert(NULL);
return REDIS_ERR; /* Avoid warning. */ return REDIS_ERR; /* Avoid warning. */
...@@ -416,12 +557,10 @@ static int processItem(redisReader *r) { ...@@ -416,12 +557,10 @@ static int processItem(redisReader *r) {
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
redisReader *r; redisReader *r;
r = calloc(sizeof(redisReader),1); r = calloc(1,sizeof(redisReader));
if (r == NULL) if (r == NULL)
return NULL; return NULL;
r->err = 0;
r->errstr[0] = '\0';
r->fn = fn; r->fn = fn;
r->buf = sdsempty(); r->buf = sdsempty();
r->maxbuf = REDIS_READER_MAX_BUF; r->maxbuf = REDIS_READER_MAX_BUF;
...@@ -435,10 +574,11 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { ...@@ -435,10 +574,11 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
} }
void redisReaderFree(redisReader *r) { void redisReaderFree(redisReader *r) {
if (r == NULL)
return;
if (r->reply != NULL && r->fn && r->fn->freeObject) if (r->reply != NULL && r->fn && r->fn->freeObject)
r->fn->freeObject(r->reply); r->fn->freeObject(r->reply);
if (r->buf != NULL) sdsfree(r->buf);
sdsfree(r->buf);
free(r); free(r);
} }
......
...@@ -53,6 +53,14 @@ ...@@ -53,6 +53,14 @@
#define REDIS_REPLY_NIL 4 #define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5 #define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6 #define REDIS_REPLY_ERROR 6
#define REDIS_REPLY_DOUBLE 7
#define REDIS_REPLY_BOOL 8
#define REDIS_REPLY_VERB 9
#define REDIS_REPLY_MAP 9
#define REDIS_REPLY_SET 10
#define REDIS_REPLY_ATTR 11
#define REDIS_REPLY_PUSH 12
#define REDIS_REPLY_BIGNUM 13
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ #define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
...@@ -73,7 +81,9 @@ typedef struct redisReplyObjectFunctions { ...@@ -73,7 +81,9 @@ typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t); void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int); void *(*createArray)(const redisReadTask*, int);
void *(*createInteger)(const redisReadTask*, long long); void *(*createInteger)(const redisReadTask*, long long);
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
void *(*createNil)(const redisReadTask*); void *(*createNil)(const redisReadTask*);
void *(*createBool)(const redisReadTask*, int);
void (*freeObject)(void*); void (*freeObject)(void*);
} redisReplyObjectFunctions; } redisReplyObjectFunctions;
......
...@@ -219,7 +219,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { ...@@ -219,7 +219,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
hdrlen = sdsHdrSize(type); hdrlen = sdsHdrSize(type);
if (oldtype==type) { if (oldtype==type) {
newsh = s_realloc(sh, hdrlen+newlen+1); newsh = s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) return NULL; if (newsh == NULL) {
s_free(sh);
return NULL;
}
s = (char*)newsh+hdrlen; s = (char*)newsh+hdrlen;
} else { } else {
/* Since the header size changes, need to move the string forward, /* Since the header size changes, need to move the string forward,
...@@ -592,6 +595,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -592,6 +595,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
/* Make sure there is always space for at least 1 char. */ /* Make sure there is always space for at least 1 char. */
if (sdsavail(s)==0) { if (sdsavail(s)==0) {
s = sdsMakeRoomFor(s,1); s = sdsMakeRoomFor(s,1);
if (s == NULL) goto fmt_error;
} }
switch(*f) { switch(*f) {
...@@ -605,6 +609,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -605,6 +609,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
l = (next == 's') ? strlen(str) : sdslen(str); l = (next == 's') ? strlen(str) : sdslen(str);
if (sdsavail(s) < l) { if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l); s = sdsMakeRoomFor(s,l);
if (s == NULL) goto fmt_error;
} }
memcpy(s+i,str,l); memcpy(s+i,str,l);
sdsinclen(s,l); sdsinclen(s,l);
...@@ -621,6 +626,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -621,6 +626,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
l = sdsll2str(buf,num); l = sdsll2str(buf,num);
if (sdsavail(s) < l) { if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l); s = sdsMakeRoomFor(s,l);
if (s == NULL) goto fmt_error;
} }
memcpy(s+i,buf,l); memcpy(s+i,buf,l);
sdsinclen(s,l); sdsinclen(s,l);
...@@ -638,6 +644,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -638,6 +644,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
l = sdsull2str(buf,unum); l = sdsull2str(buf,unum);
if (sdsavail(s) < l) { if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l); s = sdsMakeRoomFor(s,l);
if (s == NULL) goto fmt_error;
} }
memcpy(s+i,buf,l); memcpy(s+i,buf,l);
sdsinclen(s,l); sdsinclen(s,l);
...@@ -662,6 +669,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -662,6 +669,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
/* Add null-term */ /* Add null-term */
s[i] = '\0'; s[i] = '\0';
return s; return s;
fmt_error:
va_end(ap);
return NULL;
} }
/* Remove the part of the string from left and from right composed just of /* Remove the part of the string from left and from right composed just of
...@@ -1018,10 +1029,18 @@ sds *sdssplitargs(const char *line, int *argc) { ...@@ -1018,10 +1029,18 @@ sds *sdssplitargs(const char *line, int *argc) {
if (*p) p++; if (*p) p++;
} }
/* add the token to the vector */ /* add the token to the vector */
vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); {
vector[*argc] = current; char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
(*argc)++; if (new_vector == NULL) {
current = NULL; s_free(vector);
return NULL;
}
vector = new_vector;
vector[*argc] = current;
(*argc)++;
current = NULL;
}
} else { } else {
/* Even on empty input string return something not NULL. */ /* Even on empty input string return something not NULL. */
if (vector == NULL) vector = s_malloc(sizeof(void*)); if (vector == NULL) vector = s_malloc(sizeof(void*));
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment