Unverified Commit fb1f4f4e authored by Wander Hillen's avatar Wander Hillen Committed by GitHub
Browse files

Merge branch 'unstable' into minor-typos

parents dda8cc18 6e98214f
...@@ -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.
...@@ -435,7 +437,7 @@ top comment inside `server.c`. ...@@ -435,7 +437,7 @@ top comment inside `server.c`.
After the command operates in some way, it returns a reply to the client, After the command operates in some way, it returns a reply to the client,
usually using `addReply()` or a similar function defined inside `networking.c`. usually using `addReply()` or a similar function defined inside `networking.c`.
There are tons of commands implementations inside th Redis source code There are tons of commands implementations inside the Redis source code
that can serve as examples of actual commands implementations. To write that can serve as examples of actual commands implementations. To write
a few toy commands can be a good exercise to familiarize with the code base. a few toy commands can be a good exercise to familiarize with the code base.
......
TLS Support -- Work In Progress
===============================
This is a brief note to capture current thoughts/ideas and track pending action
items.
Getting Started
---------------
### Building
To build with TLS support you'll need OpenSSL development libraries (e.g.
libssl-dev on Debian/Ubuntu).
Run `make BUILD_TLS=yes`.
### Tests
To run Redis test suite with TLS, you'll need TLS support for TCL (i.e.
`tcl-tls` package on Debian/Ubuntu).
1. Run `./utils/gen-test-certs.sh` to generate a root CA and a server
certificate.
2. Run `./runtest --tls` or `./runtest-cluster --tls` to run Redis and Redis
Cluster tests in TLS mode.
### Running manually
To manually run a Redis server with TLS mode (assuming `gen-test-certs.sh` was
invoked so sample certificates/keys are available):
./src/redis-server --tls-port 6379 --port 0 \
--tls-cert-file ./tests/tls/redis.crt \
--tls-key-file ./tests/tls/redis.key \
--tls-ca-cert-file ./tests/tls/ca.crt
To connect to this Redis server with `redis-cli`:
./src/redis-cli --tls \
--cert ./tests/tls/redis.crt \
--key ./tests/tls/redis.key \
--cacert ./tests/tls/ca.crt
This will disable TCP and enable TLS on port 6379. It's also possible to have
both TCP and TLS available, but you'll need to assign different ports.
To make a Replica connect to the master using TLS, use `--tls-replication yes`,
and to make Redis Cluster use TLS across nodes use `--tls-cluster yes`.
Connections
-----------
All socket operations now go through a connection abstraction layer that hides
I/O and read/write event handling from the caller.
**Multi-threading I/O is not currently supported for TLS**, as a TLS connection
needs to do its own manipulation of AE events which is not thread safe. The
solution is probably to manage independent AE loops for I/O threads and longer
term association of connections with threads. This may potentially improve
overall performance as well.
Sync IO for TLS is currently implemented in a hackish way, i.e. making the
socket blocking and configuring socket-level timeout. This means the timeout
value may not be so accurate, and there would be a lot of syscall overhead.
However I believe that getting rid of syncio completely in favor of pure async
work is probably a better move than trying to fix that. For replication it would
probably not be so hard. For cluster keys migration it might be more difficult,
but there are probably other good reasons to improve that part anyway.
To-Do List
==========
Additional TLS Features
-----------------------
1. Add metrics to INFO?
2. Add session caching support. Check if/how it's handled by clients to assess
how useful/important it is.
redis-benchmark
---------------
The current implementation is a mix of using hiredis for parsing and basic
networking (establishing connections), but directly manipulating sockets for
most actions.
This will need to be cleaned up for proper TLS support. The best approach is
probably to migrate to hiredis async mode.
redis-cli
---------
1. Add support for TLS in --slave and --rdb modes.
Others
------
Consider the implications of allowing TLS to be configured on a separate port,
making Redis listening on multiple ports.
This impacts many things, like
1. Startup banner port notification
2. Proctitle
3. How slaves announce themselves
4. Cluster bus port calculation
...@@ -41,9 +41,13 @@ distclean: ...@@ -41,9 +41,13 @@ distclean:
.PHONY: distclean .PHONY: distclean
ifeq ($(BUILD_TLS),yes)
HIREDIS_MAKE_FLAGS = USE_SSL=1
endif
hiredis: .make-prerequisites hiredis: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd hiredis && $(MAKE) static cd hiredis && $(MAKE) static $(HIREDIS_MAKE_FLAGS)
.PHONY: hiredis .PHONY: hiredis
...@@ -77,7 +81,7 @@ JEMALLOC_LDFLAGS= $(LDFLAGS) ...@@ -77,7 +81,7 @@ JEMALLOC_LDFLAGS= $(LDFLAGS)
jemalloc: .make-prerequisites jemalloc: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd jemalloc && ./configure --with-lg-quantum=3 --with-jemalloc-prefix=je_ --enable-cc-silence CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" cd jemalloc && ./configure --with-version=5.1.0-0-g0 --with-lg-quantum=3 --with-jemalloc-prefix=je_ --enable-cc-silence CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)"
cd jemalloc && $(MAKE) CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" lib/libjemalloc.a cd jemalloc && $(MAKE) CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" lib/libjemalloc.a
.PHONY: jemalloc .PHONY: jemalloc
...@@ -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.
...@@ -13,22 +12,39 @@ How to upgrade the above dependencies ...@@ -13,22 +12,39 @@ How to upgrade the above dependencies
Jemalloc Jemalloc
--- ---
Jemalloc is unmodified. We only change settings via the `configure` script of Jemalloc using the `--with-lg-quantum` option, setting it to the value of 3 instead of 4. This provides us with more size classes that better suit the Redis data structures, in order to gain memory efficiency. Jemalloc is modified with changes that allow us to implement the Redis
active defragmentation logic. However this feature of Redis is not mandatory
So in order to upgrade jemalloc: and Redis is able to understand if the Jemalloc version it is compiled
against supports such Redis-specific modifications. So in theory, if you
are not interested in the active defragmentation, you can replace Jemalloc
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 directory as the README you are
Geohash reading, and change the --with-version in the Jemalloc configure script
--- options with the version you are using. This is required because otherwise
Jemalloc configuration script is broken and will not work nested in another
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. git repository.
However note that we change Jemalloc settings via the `configure` script of Jemalloc using the `--with-lg-quantum` option, setting it to the value of 3 instead of 4. This provides us with more size classes that better suit the Redis data structures, in order to gain memory efficiency.
If you want to upgrade Jemalloc while also providing support for
active defragmentation, in addition to the above steps you need to perform
the following additional steps:
5. In Jemalloc three, file `include/jemalloc/jemalloc_macros.h.in`, make sure
to add `#define JEMALLOC_FRAG_HINT`.
6. Implement the function `je_get_defrag_hint()` inside `src/jemalloc.c`. You
can see how it is implemented in the current Jemalloc source tree shipped
with Redis, and rewrite it according to the new Jemalloc internals, if they
changed, otherwise you could just copy the old implementation if you are
upgrading just to a similar version of Jemalloc.
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.
...@@ -61,6 +77,6 @@ and our version: ...@@ -61,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.
...@@ -5,3 +5,4 @@ ...@@ -5,3 +5,4 @@
/*.dylib /*.dylib
/*.a /*.a
/*.pc /*.pc
*.dSYM
...@@ -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
...@@ -20,20 +26,72 @@ addons: ...@@ -20,20 +26,72 @@ addons:
- libc6-dev-i386 - libc6-dev-i386
- libc6-dbg:i386 - libc6-dbg:i386
- gcc-multilib - gcc-multilib
- g++-multilib
- valgrind - valgrind
env: env:
- CFLAGS="-Werror" - BITS="32"
- PRE="valgrind --track-origins=yes --leak-check=full" - BITS="64"
- TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
- TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
matrix: script:
exclude: - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON";
- os: osx if [ "$TRAVIS_OS_NAME" == "osx" ]; then
env: PRE="valgrind --track-origins=yes --leak-check=full" if [ "$BITS" == "32" ]; then
CFLAGS="-m32 -Werror";
CXXFLAGS="-m32 -Werror";
LDFLAGS="-m32";
EXTRA_CMAKE_OPTS=;
else
CFLAGS="-Werror";
CXXFLAGS="-Werror";
fi;
else
TEST_PREFIX="valgrind --track-origins=yes --leak-check=full";
if [ "$BITS" == "32" ]; then
CFLAGS="-m32 -Werror";
CXXFLAGS="-m32 -Werror";
LDFLAGS="-m32";
EXTRA_CMAKE_OPTS=;
else
CFLAGS="-Werror";
CXXFLAGS="-Werror";
fi;
fi;
export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
- mkdir build/ && cd build/
- cmake .. ${EXTRA_CMAKE_OPTS}
- make VERBOSE=1
- ctest -V
- os: osx matrix:
env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" include:
# Windows MinGW cross compile on Linux
- os: linux
dist: xenial
compiler: mingw
addons:
apt:
packages:
- ninja-build
- gcc-mingw-w64-x86-64
- g++-mingw-w64-x86-64
script:
- mkdir build && cd build
- CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on
- ninja -v
script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example # Windows MSVC 2017
- os: windows
compiler: msvc
env:
- MATRIX_EVAL="CC=cl.exe && CXX=cl.exe"
before_install:
- eval "${MATRIX_EVAL}"
install:
- choco install ninja
script:
- mkdir build && cd build
- cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 &&
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release &&
ninja -v'
- ctest -V
### 1.0.0 (unreleased) ### 1.0.0 (unreleased)
**Fixes**: **BREAKING CHANGES**:
* Catch a buffer overflow when formatting the error message * Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 protocol errors. This is consistent with the RESP specification. On 32-bit
* Fix warnings, when compiled with -Wshadow platforms, the upper bound is lowered to `SIZE_MAX`.
* Make hiredis compile in Cygwin on Windows, now CI-tested
* 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.x.x (unreleased)
**BREAKING CHANGES**: **BREAKING CHANGES**:
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string * Change `redisReply.len` to `size_t`, as it denotes the the size of a string
...@@ -14,6 +20,50 @@ ...@@ -14,6 +20,50 @@
User code should compare this to `size_t` values as well. 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. If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
### 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
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
* Fix warnings, when compiled with -Wshadow
* Make hiredis compile in Cygwin on Windows, now CI-tested
* 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`.
* 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 +144,7 @@ The parser, standalone since v0.12.0, can now be compiled on Windows ...@@ -94,7 +144,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
......
CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0)
INCLUDE(GNUInstallDirs)
PROJECT(hiredis)
OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
MACRO(getVersionBit name)
SET(VERSION_REGEX "^#define ${name} (.+)$")
FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h"
VERSION_BIT REGEX ${VERSION_REGEX})
STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}")
ENDMACRO(getVersionBit)
getVersionBit(HIREDIS_MAJOR)
getVersionBit(HIREDIS_MINOR)
getVersionBit(HIREDIS_PATCH)
getVersionBit(HIREDIS_SONAME)
SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}")
MESSAGE("Detected version: ${VERSION}")
PROJECT(hiredis VERSION "${VERSION}")
SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
ADD_LIBRARY(hiredis SHARED
async.c
dict.c
hiredis.c
net.c
read.c
sds.c
sockcompat.c)
SET_TARGET_PROPERTIES(hiredis
PROPERTIES
VERSION "${HIREDIS_SONAME}")
IF(WIN32 OR MINGW)
TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32)
ENDIF()
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .)
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
INSTALL(TARGETS hiredis
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
INSTALL(FILES hiredis.h read.h sds.h async.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(DIRECTORY adapters
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
IF(ENABLE_SSL)
IF (NOT OPENSSL_ROOT_DIR)
IF (APPLE)
SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
ENDIF()
ENDIF()
FIND_PACKAGE(OpenSSL REQUIRED)
ADD_LIBRARY(hiredis_ssl SHARED
ssl.c)
TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
INSTALL(TARGETS hiredis_ssl
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
INSTALL(FILES hiredis_ssl.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
ENDIF()
IF(NOT (WIN32 OR MINGW))
ENABLE_TESTING()
ADD_EXECUTABLE(hiredis-test test.c)
TARGET_LINK_LIBRARIES(hiredis-test hiredis)
ADD_TEST(NAME hiredis-test
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
ENDIF()
# Add examples
IF(ENABLE_EXAMPLES)
ADD_SUBDIRECTORY(examples)
ENDIF(ENABLE_EXAMPLES)
...@@ -3,11 +3,17 @@ ...@@ -3,11 +3,17 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com> # Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file # This file is released under the BSD license, see the COPYING file
OBJ=net.o hiredis.o sds.o async.o read.o OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o
SSL_OBJ=ssl.o
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
ifeq ($(USE_SSL),1)
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
endif
TESTS=hiredis-test TESTS=hiredis-test
LIBNAME=libhiredis LIBNAME=libhiredis
SSL_LIBNAME=libhiredis_ssl
PKGCONFNAME=hiredis.pc PKGCONFNAME=hiredis.pc
SSL_PKGCONFNAME=hiredis_ssl.pc
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
...@@ -36,71 +42,109 @@ endef ...@@ -36,71 +42,109 @@ 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 -Wno-missing-field-initializers
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
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=ar rcs $(STLIBNAME) SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=$(AR) rcs
# Platform-specific overrides # Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
USE_SSL?=0
# This is required for test.c only
ifeq ($(USE_SSL),1)
CFLAGS+=-DHIREDIS_TEST_SSL
endif
ifeq ($(uname_S),Linux)
SSL_LDFLAGS=-lssl -lcrypto
else
OPENSSL_PREFIX?=/usr/local/opt/openssl
CFLAGS+=-I$(OPENSSL_PREFIX)/include
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
endif
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)
ifeq ($(USE_SSL),1)
all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
endif
# Deps (use make dep to generate this) # Deps (use make dep to generate this)
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
dict.o: dict.c fmacros.h dict.h dict.o: dict.c fmacros.h dict.h
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h win32.h
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h
read.o: read.c fmacros.h read.h sds.h read.o: read.c fmacros.h read.h sds.h
sds.o: sds.c sds.h sds.o: sds.c sds.h
sockcompat.o: sockcompat.c sockcompat.h
ssl.o: ssl.c hiredis.h
test.o: test.c fmacros.h hiredis.h read.h sds.h test.o: test.c fmacros.h hiredis.h read.h sds.h
$(DYLIBNAME): $(OBJ) $(DYLIBNAME): $(OBJ)
$(DYLIB_MAKE_CMD) $(OBJ) $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
$(STLIBNAME): $(OBJ) $(STLIBNAME): $(OBJ)
$(STLIB_MAKE_CMD) $(OBJ) $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
$(SSL_DYLIBNAME): $(SSL_OBJ)
$(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
$(SSL_STLIBNAME): $(SSL_OBJ)
$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
dynamic: $(DYLIBNAME) dynamic: $(DYLIBNAME)
static: $(STLIBNAME) static: $(STLIBNAME)
ifeq ($(USE_SSL),1)
dynamic: $(SSL_DYLIBNAME)
static: $(SSL_STLIBNAME)
endif
# Binaries: # Binaries:
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) 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) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
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) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)
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) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
ifndef AE_DIR ifndef AE_DIR
hiredis-example-ae: hiredis-example-ae:
...@@ -117,7 +161,7 @@ hiredis-example-libuv: ...@@ -117,7 +161,7 @@ hiredis-example-libuv:
@false @false
else else
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)
endif endif
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
...@@ -134,38 +178,35 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) ...@@ -134,38 +178,35 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
endif endif
hiredis-example: examples/example.c $(STLIBNAME) hiredis-example: examples/example.c $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
examples: $(EXAMPLES) examples: $(EXAMPLES)
hiredis-test: test.o $(STLIBNAME) TEST_LIBS = $(STLIBNAME)
ifeq ($(USE_SSL),1)
TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread
endif
hiredis-test: test.o $(TEST_LIBS)
hiredis-%: %.o $(STLIBNAME) hiredis-%: %.o $(STLIBNAME)
$(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
test: hiredis-test test: hiredis-test
./hiredis-test ./hiredis-test
check: hiredis-test check: hiredis-test
@echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - TEST_SSL=$(USE_SSL) ./test.sh
$(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \
( kill `cat /tmp/hiredis-test-redis.pid` && false )
kill `cat /tmp/hiredis-test-redis.pid`
.c.o: .c.o:
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
clean: clean:
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
dep: dep:
$(CC) -MM *.c $(CC) $(CPPFLAGS) $(CFLAGS) -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..."
...@@ -180,9 +221,24 @@ $(PKGCONFNAME): hiredis.h ...@@ -180,9 +221,24 @@ $(PKGCONFNAME): hiredis.h
@echo Libs: -L\$${libdir} -lhiredis >> $@ @echo Libs: -L\$${libdir} -lhiredis >> $@
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
$(SSL_PKGCONFNAME): hiredis.h
@echo "Generating $@ for pkgconfig..."
@echo prefix=$(PREFIX) > $@
@echo exec_prefix=\$${prefix} >> $@
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
@echo >> $@
@echo Name: hiredis_ssl >> $@
@echo Description: SSL Support for hiredis. >> $@
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
@echo Requires: hiredis >> $@
@echo Libs: -L\$${libdir} -lhiredis_ssl >> $@
@echo Libs.private: -lssl -lcrypto >> $@
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)
......
...@@ -286,6 +286,7 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin ...@@ -286,6 +286,7 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin
```c ```c
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
``` ```
`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback.
### Sending commands and their callbacks ### Sending commands and their callbacks
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
...@@ -406,6 +407,6 @@ as soon as possible in order to prevent allocation of useless memory. ...@@ -406,6 +407,6 @@ as soon as possible in order to prevent allocation of useless memory.
## AUTHORS ## AUTHORS
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
Jan-Erik Rediger (janerik at fnordig dot com) Jan-Erik Rediger (janerik at fnordig dot com)
...@@ -34,48 +34,113 @@ ...@@ -34,48 +34,113 @@
#include "../hiredis.h" #include "../hiredis.h"
#include "../async.h" #include "../async.h"
#define REDIS_LIBEVENT_DELETED 0x01
#define REDIS_LIBEVENT_ENTERED 0x02
typedef struct redisLibeventEvents { typedef struct redisLibeventEvents {
redisAsyncContext *context; redisAsyncContext *context;
struct event *rev, *wev; struct event *ev;
struct event_base *base;
struct timeval tv;
short flags;
short state;
} redisLibeventEvents; } redisLibeventEvents;
static void redisLibeventReadEvent(int fd, short event, void *arg) { static void redisLibeventDestroy(redisLibeventEvents *e) {
((void)fd); ((void)event); free(e);
redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleRead(e->context);
} }
static void redisLibeventWriteEvent(int fd, short event, void *arg) { static void redisLibeventHandler(int fd, short event, void *arg) {
((void)fd); ((void)event); ((void)fd);
redisLibeventEvents *e = (redisLibeventEvents*)arg; redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleWrite(e->context); e->state |= REDIS_LIBEVENT_ENTERED;
#define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\
redisLibeventDestroy(e);\
return; \
}
if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
redisAsyncHandleTimeout(e->context);
CHECK_DELETED();
}
if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
redisAsyncHandleRead(e->context);
CHECK_DELETED();
}
if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
redisAsyncHandleWrite(e->context);
CHECK_DELETED();
}
e->state &= ~REDIS_LIBEVENT_ENTERED;
#undef CHECK_DELETED
}
static void redisLibeventUpdate(void *privdata, short flag, int isRemove) {
redisLibeventEvents *e = (redisLibeventEvents *)privdata;
const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL;
if (isRemove) {
if ((e->flags & flag) == 0) {
return;
} else {
e->flags &= ~flag;
}
} else {
if (e->flags & flag) {
return;
} else {
e->flags |= flag;
}
}
event_del(e->ev);
event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST,
redisLibeventHandler, privdata);
event_add(e->ev, tv);
} }
static void redisLibeventAddRead(void *privdata) { static void redisLibeventAddRead(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_READ, 0);
event_add(e->rev,NULL);
} }
static void redisLibeventDelRead(void *privdata) { static void redisLibeventDelRead(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_READ, 1);
event_del(e->rev);
} }
static void redisLibeventAddWrite(void *privdata) { static void redisLibeventAddWrite(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_WRITE, 0);
event_add(e->wev,NULL);
} }
static void redisLibeventDelWrite(void *privdata) { static void redisLibeventDelWrite(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_WRITE, 1);
event_del(e->wev);
} }
static void redisLibeventCleanup(void *privdata) { static void redisLibeventCleanup(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(e->rev); if (!e) {
event_del(e->wev); return;
free(e); }
event_del(e->ev);
event_free(e->ev);
e->ev = NULL;
if (e->state & REDIS_LIBEVENT_ENTERED) {
e->state |= REDIS_LIBEVENT_DELETED;
} else {
redisLibeventDestroy(e);
}
}
static void redisLibeventSetTimeout(void *privdata, struct timeval tv) {
redisLibeventEvents *e = (redisLibeventEvents *)privdata;
short flags = e->flags;
e->flags = 0;
e->tv = tv;
redisLibeventUpdate(e, flags, 0);
} }
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
...@@ -87,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { ...@@ -87,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
return REDIS_ERR; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisLibeventEvents*)malloc(sizeof(*e)); e = (redisLibeventEvents*)calloc(1, sizeof(*e));
e->context = ac; e->context = ac;
/* Register functions to start/stop listening for events */ /* Register functions to start/stop listening for events */
...@@ -96,13 +161,12 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { ...@@ -96,13 +161,12 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
ac->ev.addWrite = redisLibeventAddWrite; ac->ev.addWrite = redisLibeventAddWrite;
ac->ev.delWrite = redisLibeventDelWrite; ac->ev.delWrite = redisLibeventDelWrite;
ac->ev.cleanup = redisLibeventCleanup; ac->ev.cleanup = redisLibeventCleanup;
ac->ev.scheduleTimer = redisLibeventSetTimeout;
ac->ev.data = e; ac->ev.data = e;
/* Initialize and install read/write events */ /* Initialize and install read/write events */
e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e); e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e); e->base = base;
event_add(e->rev, NULL);
event_add(e->wev, NULL);
return REDIS_OK; return REDIS_OK;
} }
#endif #endif
...@@ -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 CFLAGS: -m32
TARGET_VARS: 32bit-vars CXXFLAGS: -m32
LDFLAGS: -m32
# 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,10 +17,8 @@ init: ...@@ -27,10 +17,8 @@ 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:
- 'echo building...' - 'echo building...'
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make LDFLAGS=$LDFLAGS CC=$CC $TARGET CFLAGS=$CFLAGS && make LDFLAGS=$LDFLAGS CC=$CC $TARGET_VARS hiredis-example"' - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \"Unix Makefiles\" && make VERBOSE=1"'
...@@ -32,7 +32,9 @@ ...@@ -32,7 +32,9 @@
#include "fmacros.h" #include "fmacros.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#ifndef _MSC_VER
#include <strings.h> #include <strings.h>
#endif
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
...@@ -40,22 +42,9 @@ ...@@ -40,22 +42,9 @@
#include "net.h" #include "net.h"
#include "dict.c" #include "dict.c"
#include "sds.h" #include "sds.h"
#include "win32.h"
#define _EL_ADD_READ(ctx) do { \ #include "async_private.h"
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
} while(0)
#define _EL_DEL_READ(ctx) do { \
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
} while(0)
#define _EL_ADD_WRITE(ctx) do { \
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
} while(0)
#define _EL_DEL_WRITE(ctx) do { \
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
} while(0)
#define _EL_CLEANUP(ctx) do { \
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
} while(0);
/* Forward declaration of function in hiredis.c */ /* Forward declaration of function in hiredis.c */
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
...@@ -126,6 +115,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) { ...@@ -126,6 +115,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
ac->ev.addWrite = NULL; ac->ev.addWrite = NULL;
ac->ev.delWrite = NULL; ac->ev.delWrite = NULL;
ac->ev.cleanup = NULL; ac->ev.cleanup = NULL;
ac->ev.scheduleTimer = NULL;
ac->onConnect = NULL; ac->onConnect = NULL;
ac->onDisconnect = NULL; ac->onDisconnect = NULL;
...@@ -150,56 +140,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) { ...@@ -150,56 +140,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) {
ac->errstr = c->errstr; ac->errstr = c->errstr;
} }
redisAsyncContext *redisAsyncConnect(const char *ip, int port) { redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
redisOptions myOptions = *options;
redisContext *c; redisContext *c;
redisAsyncContext *ac; redisAsyncContext *ac;
c = redisConnectNonBlock(ip,port); myOptions.options |= REDIS_OPT_NONBLOCK;
if (c == NULL) c = redisConnectWithOptions(&myOptions);
if (c == NULL) {
return NULL; return NULL;
}
ac = redisAsyncInitialize(c); ac = redisAsyncInitialize(c);
if (ac == NULL) { if (ac == NULL) {
redisFree(c); redisFree(c);
return NULL; return NULL;
} }
__redisAsyncCopyError(ac); __redisAsyncCopyError(ac);
return ac; return ac;
} }
redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port);
return redisAsyncConnectWithOptions(&options);
}
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); redisOptions options = {0};
redisAsyncContext *ac = redisAsyncInitialize(c); REDIS_OPTIONS_SET_TCP(&options, ip, port);
__redisAsyncCopyError(ac); options.endpoint.tcp.source_addr = source_addr;
return ac; return redisAsyncConnectWithOptions(&options);
} }
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); redisOptions options = {0};
redisAsyncContext *ac = redisAsyncInitialize(c); REDIS_OPTIONS_SET_TCP(&options, ip, port);
__redisAsyncCopyError(ac); options.options |= REDIS_OPT_REUSEADDR;
return ac; options.endpoint.tcp.source_addr = source_addr;
return redisAsyncConnectWithOptions(&options);
} }
redisAsyncContext *redisAsyncConnectUnix(const char *path) { redisAsyncContext *redisAsyncConnectUnix(const char *path) {
redisContext *c; redisOptions options = {0};
redisAsyncContext *ac; REDIS_OPTIONS_SET_UNIX(&options, path);
return redisAsyncConnectWithOptions(&options);
c = redisConnectUnixNonBlock(path);
if (c == NULL)
return NULL;
ac = redisAsyncInitialize(c);
if (ac == NULL) {
redisFree(c);
return NULL;
}
__redisAsyncCopyError(ac);
return ac;
} }
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
...@@ -328,7 +314,7 @@ void redisAsyncFree(redisAsyncContext *ac) { ...@@ -328,7 +314,7 @@ void redisAsyncFree(redisAsyncContext *ac) {
} }
/* Helper function to make the disconnect happen and clean up. */ /* Helper function to make the disconnect happen and clean up. */
static void __redisAsyncDisconnect(redisAsyncContext *ac) { void __redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
/* Make sure error is accessible if there is any */ /* Make sure error is accessible if there is any */
...@@ -336,16 +322,23 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { ...@@ -336,16 +322,23 @@ 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. */
c->flags |= REDIS_DISCONNECTING; c->flags |= REDIS_DISCONNECTING;
} }
/* cleanup event library on disconnect.
* this is safe to call multiple times */
_EL_CLEANUP(ac);
/* For non-clean disconnects, __redisAsyncFree() will execute pending /* For non-clean disconnects, __redisAsyncFree() will execute pending
* callbacks with a NULL-reply. */ * callbacks with a NULL-reply. */
__redisAsyncFree(ac); if (!(c->flags & REDIS_NO_AUTO_FREE)) {
__redisAsyncFree(ac);
}
} }
/* Tries to do a clean disconnect from Redis, meaning it stops new commands /* Tries to do a clean disconnect from Redis, meaning it stops new commands
...@@ -357,6 +350,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { ...@@ -357,6 +350,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
void redisAsyncDisconnect(redisAsyncContext *ac) { void redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
c->flags |= REDIS_DISCONNECTING; c->flags |= REDIS_DISCONNECTING;
/** unset the auto-free flag here, because disconnect undoes this */
c->flags &= ~REDIS_NO_AUTO_FREE;
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
} }
...@@ -364,6 +360,7 @@ void redisAsyncDisconnect(redisAsyncContext *ac) { ...@@ -364,6 +360,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 +384,28 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, ...@@ -387,16 +384,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 +419,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, ...@@ -410,7 +419,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 +501,34 @@ void redisProcessCallbacks(redisAsyncContext *ac) { ...@@ -492,22 +501,34 @@ 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. */ void redisAsyncRead(redisAsyncContext *ac) {
c->flags |= REDIS_CONNECTED; redisContext *c = &(ac->c);
if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
return REDIS_OK; if (redisBufferRead(c) == REDIS_ERR) {
__redisAsyncDisconnect(ac);
} else {
/* Always re-schedule reads */
_EL_ADD_READ(ac);
redisProcessCallbacks(ac);
}
} }
/* This function should be called when the socket is readable. /* This function should be called when the socket is readable.
...@@ -525,18 +546,29 @@ void redisAsyncHandleRead(redisAsyncContext *ac) { ...@@ -525,18 +546,29 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
return; return;
} }
if (redisBufferRead(c) == REDIS_ERR) { c->funcs->async_read(ac);
}
void redisAsyncWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
int done = 0;
if (redisBufferWrite(c,&done) == REDIS_ERR) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
} else { } else {
/* Always re-schedule reads */ /* Continue writing when not done, stop writing otherwise */
if (!done)
_EL_ADD_WRITE(ac);
else
_EL_DEL_WRITE(ac);
/* Always schedule reads after writes */
_EL_ADD_READ(ac); _EL_ADD_READ(ac);
redisProcessCallbacks(ac);
} }
} }
void redisAsyncHandleWrite(redisAsyncContext *ac) { void redisAsyncHandleWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
int done = 0;
if (!(c->flags & REDIS_CONNECTED)) { if (!(c->flags & REDIS_CONNECTED)) {
/* Abort connect was not successful. */ /* Abort connect was not successful. */
...@@ -547,18 +579,37 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) { ...@@ -547,18 +579,37 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
return; return;
} }
if (redisBufferWrite(c,&done) == REDIS_ERR) { c->funcs->async_write(ac);
__redisAsyncDisconnect(ac); }
} else {
/* Continue writing when not done, stop writing otherwise */
if (!done)
_EL_ADD_WRITE(ac);
else
_EL_DEL_WRITE(ac);
/* Always schedule reads after writes */ void __redisSetError(redisContext *c, int type, const char *str);
_EL_ADD_READ(ac);
void redisAsyncHandleTimeout(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redisCallback cb;
if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) {
/* Nothing to do - just an idle timeout */
return;
} }
if (!c->err) {
__redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout");
}
if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) {
ac->onConnect(ac, REDIS_ERR);
}
while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
__redisRunCallback(ac, &cb, NULL);
}
/**
* TODO: Don't automatically sever the connection,
* rather, allow to ignore <x> responses before the queue is clear
*/
__redisAsyncDisconnect(ac);
} }
/* Sets a pointer to the first argument and its length starting at p. Returns /* Sets a pointer to the first argument and its length starting at p. Returns
...@@ -583,6 +634,9 @@ static const char *nextArgument(const char *start, const char **str, size_t *len ...@@ -583,6 +634,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 +650,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ...@@ -596,6 +650,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 +667,18 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ...@@ -612,9 +667,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 +740,8 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv ...@@ -676,6 +740,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;
...@@ -685,3 +751,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ...@@ -685,3 +751,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
return status; return status;
} }
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
if (!ac->c.timeout) {
ac->c.timeout = calloc(1, sizeof(tv));
}
if (tv.tv_sec == ac->c.timeout->tv_sec &&
tv.tv_usec == ac->c.timeout->tv_usec) {
return;
}
*ac->c.timeout = tv;
}
...@@ -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;
...@@ -56,6 +57,7 @@ typedef struct redisCallbackList { ...@@ -56,6 +57,7 @@ typedef struct redisCallbackList {
/* Connection callback prototypes */ /* Connection callback prototypes */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
typedef void(redisTimerCallback)(void *timer, void *privdata);
/* Context for an async connection to Redis */ /* Context for an async connection to Redis */
typedef struct redisAsyncContext { typedef struct redisAsyncContext {
...@@ -80,6 +82,7 @@ typedef struct redisAsyncContext { ...@@ -80,6 +82,7 @@ typedef struct redisAsyncContext {
void (*addWrite)(void *privdata); void (*addWrite)(void *privdata);
void (*delWrite)(void *privdata); void (*delWrite)(void *privdata);
void (*cleanup)(void *privdata); void (*cleanup)(void *privdata);
void (*scheduleTimer)(void *privdata, struct timeval tv);
} ev; } ev;
/* Called when either the connection is terminated due to an error or per /* Called when either the connection is terminated due to an error or per
...@@ -92,6 +95,10 @@ typedef struct redisAsyncContext { ...@@ -92,6 +95,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;
...@@ -101,6 +108,7 @@ typedef struct redisAsyncContext { ...@@ -101,6 +108,7 @@ typedef struct redisAsyncContext {
} redisAsyncContext; } redisAsyncContext;
/* Functions that proxy to hiredis */ /* Functions that proxy to hiredis */
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnect(const char *ip, int port);
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
...@@ -108,12 +116,17 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, ...@@ -108,12 +116,17 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
redisAsyncContext *redisAsyncConnectUnix(const char *path); redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
void redisAsyncDisconnect(redisAsyncContext *ac); void redisAsyncDisconnect(redisAsyncContext *ac);
void redisAsyncFree(redisAsyncContext *ac); void redisAsyncFree(redisAsyncContext *ac);
/* Handle read/write events */ /* Handle read/write events */
void redisAsyncHandleRead(redisAsyncContext *ac); void redisAsyncHandleRead(redisAsyncContext *ac);
void redisAsyncHandleWrite(redisAsyncContext *ac); void redisAsyncHandleWrite(redisAsyncContext *ac);
void redisAsyncHandleTimeout(redisAsyncContext *ac);
void redisAsyncRead(redisAsyncContext *ac);
void redisAsyncWrite(redisAsyncContext *ac);
/* Command functions for an async context. Write the command to the /* Command functions for an async context. Write the command to the
* output buffer and register the provided callback. */ * output buffer and register the provided callback. */
......
/*
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis 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 __HIREDIS_ASYNC_PRIVATE_H
#define __HIREDIS_ASYNC_PRIVATE_H
#define _EL_ADD_READ(ctx) \
do { \
refreshTimeout(ctx); \
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
} while (0)
#define _EL_DEL_READ(ctx) do { \
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
} while(0)
#define _EL_ADD_WRITE(ctx) \
do { \
refreshTimeout(ctx); \
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
} while (0)
#define _EL_DEL_WRITE(ctx) do { \
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
} while(0)
#define _EL_CLEANUP(ctx) do { \
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
ctx->ev.cleanup = NULL; \
} while(0);
static inline void refreshTimeout(redisAsyncContext *ctx) {
if (ctx->c.timeout && ctx->ev.scheduleTimer &&
(ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) {
ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout);
// } else {
// printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout);
// if (ctx->c.timeout){
// printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec,
// ctx->c.timeout->tv_usec);
// }
}
}
void __redisAsyncDisconnect(redisAsyncContext *ac);
void redisProcessCallbacks(redisAsyncContext *ac);
#endif /* __HIREDIS_ASYNC_PRIVATE_H */
INCLUDE(FindPkgConfig)
# Check for GLib
PKG_CHECK_MODULES(GLIB2 glib-2.0)
if (GLIB2_FOUND)
INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS})
ADD_EXECUTABLE(example-glib example-glib.c)
TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES})
ENDIF(GLIB2_FOUND)
FIND_PATH(LIBEV ev.h
HINTS /usr/local /usr/opt/local
ENV LIBEV_INCLUDE_DIR)
if (LIBEV)
# Just compile and link with libev
ADD_EXECUTABLE(example-libev example-libev.c)
TARGET_LINK_LIBRARIES(example-libev hiredis ev)
ENDIF()
FIND_PATH(LIBEVENT event.h)
if (LIBEVENT)
ADD_EXECUTABLE(example-libevent example-libevent)
TARGET_LINK_LIBRARIES(example-libevent hiredis event)
ENDIF()
FIND_PATH(LIBUV uv.h)
IF (LIBUV)
ADD_EXECUTABLE(example-libuv example-libuv.c)
TARGET_LINK_LIBRARIES(example-libuv hiredis uv)
ENDIF()
IF (APPLE)
FIND_LIBRARY(CF CoreFoundation)
ADD_EXECUTABLE(example-macosx example-macosx.c)
TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})
ENDIF()
IF (ENABLE_SSL)
ADD_EXECUTABLE(example-ssl example-ssl.c)
TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)
ENDIF()
ADD_EXECUTABLE(example example.c)
TARGET_LINK_LIBRARIES(example hiredis)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <hiredis_ssl.h>
#include <async.h>
#include <adapters/libevent.h>
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
struct event_base *base = event_base_new();
if (argc < 5) {
fprintf(stderr,
"Usage: %s <key> <host> <port> <cert> <certKey> [ca]\n", argv[0]);
exit(1);
}
const char *value = argv[1];
size_t nvalue = strlen(value);
const char *hostname = argv[2];
int port = atoi(argv[3]);
const char *cert = argv[4];
const char *certKey = argv[5];
const char *caCert = argc > 5 ? argv[6] : NULL;
redisAsyncContext *c = redisAsyncConnect(hostname, port);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) {
printf("SSL Error!\n");
exit(1);
}
redisLibeventAttach(c,base);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
event_base_dispatch(base);
return 0;
}
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