Commit Graph

498 Commits

Author SHA1 Message Date
not-a-robot
36f6d25e54 Auto merge of #367 - rangan337:unstable, r=badboy
Fix libuv adapter to handle polling errors

Currently, when an async connection is attached to a *libuv* event loop using the libuv adapter, a libuv poll handle is initialized using the connection's file descriptor.

All subsequent async reads / writes are done by polling the file descriptor associated with the poll handle (using *uv_poll_start()* and *uv_poll_stop()*).

When an event is available, the **redisLibuvPoll()** function is invoked as the callback with **status** set to 0 and the detected event on the **events** parameter.

In the event of a polling error, the **redisLibuvPoll()** callback function is invoked with **status < 0** (exact error status depends on the reason the polling failed). Currently, this function returns if an error status is encountered without taking any action.

This can be problematic in various error scenarios, two of which are detailed below -

The steps involved in creating an async connection are -
1. Call *redisAsyncConnect()*
2. Call *redisLibuvAttach()* to attach the async connection to the event loop
3. Set connect / disconnect callbacks for the async connection using *redisAsyncSetConnectCallback()* and *redisAsyncSetDisconnectCallback()*
4. Run the event loop to check if the connection was successful or not

In step 4, if the connection was successful, the callback set in *redisAsyncSetConnectCallback()* is called.

**However** if the connection is not successful, an error is generated in polling the file descriptor and no action is taken.

If the Redis server crashes, the connection breaks resulting in a polling error. Now subsequent Redis commands issued using the <b>redisAsyncCommand()</b> family of functions would internally follow the steps below -
1. Call *__redisAsyncCommand()* helper function
2. Call
```
EL_ADD_WRITE(ac);
```
which expands to calling *redisLibuvAddWrite()*
3. Call *redisLibuvPoll()* which returns without taking any action

As a result, their callbacks would never be invoked and writes will get queued up indefinitely on this broken connection **unless** the connection is disconnected by a call to some other function in hiredis.c (which you really can't rely on to happen in all use cases)

To address the above scenarios and similar ones, if a polling error is encountered, we mark the *err* and *errstr* fields with appropriate error flag and error message respectively.

We also invoke *redisAsyncHandleRead()* so that
1. If a connection is not successful, *__redisAsyncHandleConnect()* can be called
2. If any other error occurs on a valid connection, (like scenario 2 above) *redisBufferRead()* would return REDIS_ERR and *__redisAsyncDisconnect()* will be called.

Note that either *redisAsyncHandleRead()* or *redisAsyncHandleWrite()* could be called when an error was encountered, but I went with *redisAsyncHandleRead()* as it seemed to be cleaner (no *done* variable)

Example Gist Link - https://gist.github.com/rangan337/11a5e893ec41977b901b

This is a simple example that assumes that a Redis server is not running on the PORT it is querying.

* **Prior to the PR,** no connection errors are reported.
* **After the PR,** the error that the client failed to connect to the server is reported.

Example Gist Link (With Sample Output) - https://gist.github.com/rangan337/77da6f7069d0fb29d443

This example basically :

1. Creates an async connection to a Redis server and attaches it to an event loop (running on its own thread)
2. Following that, the server is populated with some data.
3. At this point, on a parallel thread a **DEBUG SEGFAULT** is sent to crash the server.
4. While 3 is going on, in parallel requests are sent over the async connection by a set of 20 or so threads. Note that -
  * While 20 *run command* events are fired on the event loop, as there is no guarantee that each *fire* will trigger the event handler, its quite possible that < 20 commands are actually sent to the server
  * As this is in parallel with the segfault, some commands will be executed before and some after the segfault
5. Sleep the main thread for 2 seconds to allow all the other threads to do their job
6. Print the number of pending queued callbacks on the async connection.
  * **Prior to the PR,** this value would be >=0.
  * **After the PR,** this value will always be 0 (as when the connection is disconnected all pending callbacks are executed with a NULL reply)
7. Send another wave of parallel requests over the connection to the server.
  * **Prior to the PR,** this will silently queue up the callbacks on the broken connection.
  * Note that nothing is done in the **after PR** case as when the connection is disconnected in the step above, it is freed and set to NULL.
8. Lastly, print out the queued callback count (this is just to validate that they get queued up prior to the PR).

Note that for this example, the gist also has the sample output of a run attached.
2016-04-20 15:31:18 +02:00
Rangan
2ac6bdf784 Fix libuv adapter to handle polling errors
Introduction

Currently, when an async connection is attached to a libuv event loop using the libuv adapter, a libuv poll handle is initialized using the connection's file descriptor.

All subsequent async reads / writes are done by polling the file descriptor associated with the poll handle (using uv_poll_start() and uv_poll_stop()).

When an event is available, the redisLibuvPoll() function is invoked as the callback with status set to 0 and the detected event on the events parameter.

Issue

In the event of a polling error, the redisLibuvPoll() callback function is invoked with status < 0 (exact error status depends on the reason the polling failed). Currently, this function returns if an error status is encountered without taking any action.

This can be problematic in various error scenarios, two of which are detailed below -

Scenario 1 - When a connection fails

The steps involved in creating an async connection are -
1. Call redisAsyncConnect()
2. Call redisLibuvAttach() to attach the async connection to the event loop
3. Set connect / disconnect callbacks for the async connection using redisAsyncSetConnectCallback() and redisAsyncSetDisconnectCallback()
4. Run the event loop to check if the connection was successful or not

In step 4, if the connection was successful, the callback set in redisAsyncSetConnectCallback() is called.

However if the connection is not successful, an error is generated in polling the file descriptor and no action is taken.

Scenario 2 - Server crashes and the connection is broken

If the Redis server crashes, the connection breaks resulting in a polling error. Now subsequent Redis commands issued using the redisAsyncCommand() family of functions would internally follow the steps below -
1. Call __redisAsyncCommand() helper function
2. Call

EL_ADD_WRITE(ac);
which expands to calling redisLibuvAddWrite()
3. Call redisLibuvPoll() which returns without taking any action

As a result, their callbacks would never be invoked and writes will get queued up indefinitely on this broken connection unless the connection is disconnected by a call to some other function in hiredis.c (which you really can't rely on to happen in all use cases)

Fix / Resolution

To address the above scenarios and similar ones, if a polling error is encountered, we mark the err and errstr fields with appropriate error flag and error message respectively.

We also invoke redisAsyncHandleRead() so that
1. If a connection is not successful, __redisAsyncHandleConnect() can be called
2. If any other error occurs on a valid connection, (like scenario 2 above) redisBufferRead() would return REDIS_ERR and __redisAsyncDisconnect() will be called.

Note that either redisAsyncHandleRead() or redisAsyncHandleWrite() could be called when an error was encountered, but I went with redisAsyncHandleRead() as it seemed to be cleaner (no done variable)

Examples

Scenario 1 - When a connection fails

Example Gist Link - https://gist.github.com/rangan337/11a5e893ec41977b901b

This is a simple example that assumes that a Redis server is not running on the PORT it is querying.

Prior to the PR, no connection errors are reported.
After the PR, the error that the client failed to connect to the server is reported.
Scenario 2 - Server crashes and the connection is broken

Example Gist Link (With Sample Output) - https://gist.github.com/rangan337/77da6f7069d0fb29d443

This example basically :

Creates an async connection to a Redis server and attaches it to an event loop (running on its own thread)
Following that, the server is populated with some data.
At this point, on a parallel thread a DEBUG SEGFAULT is sent to crash the server.
While 3 is going on, in parallel requests are sent over the async connection by a set of 20 or so threads. Note that -
While 20 run command events are fired on the event loop, as there is no guarantee that each fire will trigger the event handler, its quite possible that < 20 commands are actually sent to the server
As this is in parallel with the segfault, some commands will be executed before and some after the segfault
Sleep the main thread for 2 seconds to allow all the other threads to do their job
Print the number of pending queued callbacks on the async connection.
Prior to the PR, this value would be >=0.
After the PR, this value will always be 0 (as when the connection is disconnected all pending callbacks are executed with a NULL reply)
Send another wave of parallel requests over the connection to the server.
Prior to the PR, this will silently queue up the callbacks on the broken connection.
Note that nothing is done in the after PR case as when the connection is disconnected in the step above, it is freed and set to NULL.
Lastly, print out the queued callback count (this is just to validate that they get queued up prior to the PR).
Note that for this example, the gist also has the sample output of a run attached.
2015-10-01 14:05:51 -04:00
Jan-Erik Rediger
3c3a406ec4 Change date format in CHANGELOG
Easier to read, easier to sort.
2015-09-14 15:50:38 +02:00
Jan-Erik Rediger
d323b5ef9c Revert "redisBufferRead: Clear REDIS_CONNECTED flag when server closed connection"
This reverts commit 1db17f257b.

If the `REDIS_CONNECTED` flag is cleared,
the async onDisconnect callback function will never be called.
This causes problems as the disconnect is never reported back to the user.

Closes #359
2015-09-14 14:58:05 +02:00
Jan-Erik Rediger
53c32439bf Release version 0.13.2 2015-08-25 14:16:20 +02:00
Jerry Jacobs
1db17f257b redisBufferRead: Clear REDIS_CONNECTED flag when server closed connection 2015-08-24 17:03:16 +02:00
Jan-Erik Rediger
8e6d8195a9 Merge branch 'soname-stable' into unstable 2015-08-24 16:54:48 +02:00
Jan-Erik Rediger
a9a0af1d35 Use container-based Travis by installing packages through the addon 2015-07-28 00:21:24 +02:00
Jan-Erik Rediger
36a9802c54 Prevent crash on pending replies in async code
Fixes #335.
2015-07-27 23:35:02 +02:00
Jan-Erik Rediger
ded5374763 Fix a typo in the Mac OSX example 2015-07-27 23:19:41 +02:00
Dmitry Bakhvalov
c18a564818 Added MacOS X addapter and corresponding example.
Added MacOS X support via CoreFoundation run loop.
2015-07-27 23:19:14 +02:00
Pietro Cerutti
4a632a6038 Make sure to disconnect the adapter in the destructor 2015-07-27 23:17:41 +02:00
Pietro Cerutti
9069b147b0 Fix typo 2015-07-27 23:17:41 +02:00
Pietro Cerutti
1984b309b8 Add hooks for read/write/cleanup 2015-07-27 23:17:41 +02:00
Pietro Cerutti
8ef7d595ac Add Qt adapter and relative example. 2015-07-27 23:17:41 +02:00
Gergely Nagy
3b153cbf9d Add an Ivykis adapter
This adds a new adapter and an example for using hiredis with the ivykis
async I/O library.

Signed-off-by: Gergely Nagy <algernon@madhouse-project.org>
2015-07-27 22:48:05 +02:00
Jan-Erik Rediger
9644a96a48 Use stable soname version 2015-07-27 22:14:03 +02:00
Jan-Erik Rediger
2fc31e74b1 Merge pull request #341 from Cylix/glib_adapter_cpp_compilation
Use explicit casts for void* pointer in order to compile in C++
2015-06-25 17:10:02 +02:00
Jan-Erik Rediger
b9cc0add2c Merge pull request #333 from w359405949/w359405949-patch-1
undefined reference to `clock_gettime'
2015-06-24 15:35:35 +02:00
Simon Ninon
1c884ec75b Use explicit casts for void* pointer in order to compile in C++ 2015-06-22 14:44:57 +02:00
w359405949
485d0a148f Update Makefile
fix link error while run "make hiredis-example-libuv":

undefined reference to `clock_gettime'
undefined reference to `clock_getres'
2015-05-30 09:45:20 +08:00
Jan-Erik Rediger
f58dd249d6 Release version 0.13.1 2015-05-03 22:58:11 +02:00
Jan-Erik Rediger
26999505d6 Make sure to compile example to trigger edge-cases in compiling 2015-05-03 22:33:39 +02:00
Jan-Erik Rediger
8999750f12 Revert "Always compile with C99 standard."
This reverts commit d8145d79ce.
2015-05-03 22:32:42 +02:00
Alex Balashov
d132d676e9 Renamed redisContext struct member 'unix' to 'unix_sock' to avoid encountering defined constant 'unix' in GNU C environment (see commit d8145d79ce).
Not all code using hiredis can compile using '-std=c99', and/or not all users are able to easily make that change to the build process of various open-source projects, so it is more pragmatic to choose a different identifier that does not impose this requirement.
2015-04-30 15:01:31 -04:00
Jan-Erik Rediger
b9f907fb4c Merge pull request #324 from redis/fix-spontaneous-reply-leak
Fix memory leak in async spontaneous reply handling
2015-04-29 14:31:37 +02:00
antirez
2fc39eb4c3 Fix memory leak in async spontaneous reply handling
When an asynchronous hiredis connection subscribes to a Pub/Sub channel
and gets an error, and in other related conditions, the function
redisProcessCallbacks() enters a code path where the link is
disconnected, however the function returns before freeing the allocated
reply object. This causes a memory leak. The memory leak was trivial to
trigger in Redis Sentinel, which uses hiredis, every time we tried to
subscribe to an instance that required a password, in case the Sentinel
was configured either with the wrong password or without password at
all. In this case, the -AUTH error caused the leaking code path to be
executed.
2015-04-28 22:00:48 +02:00
Jan-Erik Rediger
d8145d79ce Always compile with C99 standard.
Turns out: gnu9x defines `unix` to 1, making it unusable as a variable
name.
2015-04-16 22:51:32 +02:00
Jan-Erik Rediger
31436c33ac Release version 0.13.0 2015-04-16 21:30:43 +02:00
Jan-Erik Rediger
4b30b5812d Add current maintainer to README 2015-04-16 21:29:47 +02:00
Jan-Erik Rediger
af598dbce5 Change copyright date and add copyright holder 2015-04-16 21:29:41 +02:00
Jan-Erik Rediger
b676007253 Document reconnect method 2015-04-16 21:01:00 +02:00
Jan-Erik Rediger
d9e0b0f6ab Implement a reconnect method for the client context
Originally implemented by @abedra as part of #306.

In case a write or read times out, we force an error state, because we
can't guarantuee that the next read will get the right data.
Instead we need to reconnect to have a clean-state connection, which is
now easily possible with this method.
2015-04-16 21:00:30 +02:00
Jan-Erik Rediger
b872919463 Make this work on Redis 3.0 2015-04-16 18:24:50 +02:00
Jan-Erik Rediger
27d4dcb6f0 Merge pull request #318 from neonquill/fix-install-target
Add PKGCONFNAME to install dependencies.
2015-03-29 13:20:17 +02:00
David Watson
dc13bc8627 Add PKGCONFNAME to install dependencies.
Attempting to use the install target before the make target works fine,
except for the missing pkgconfig file.  Adding that file to the
dependencies for the install target to make sure it gets created first.
2015-03-28 12:17:11 -04:00
Jan-Erik Rediger
421e0f33f4 Merge pull request #314 from tzickel/master
Added support for compiling the parser code with Microsoft Visual C compiler.
2015-03-27 14:28:47 +01:00
Jan-Erik Rediger
30814af63c Correct escaping for prefix in pkgconf file 2015-03-19 09:21:25 +01:00
Jan-Erik Rediger
5c12fa4ce4 Merge pull request #316 from boardwalk/master
Fix hiredis.pc generation.
2015-03-19 09:20:31 +01:00
Dan Skorupski
97364ed59a Fix hiredis.pc generation. 2015-03-18 18:56:11 -05:00
Jan-Erik Rediger
796305a307 Merge pull request #315 from badboy/travis-fix
Update apt repos first
2015-03-18 12:50:19 +01:00
Jan-Erik Rediger
0f6ad5c460 Update apt repos first 2015-03-18 10:39:45 +01:00
tzickel
ec229678c2 Added support for compiling the parser code with Microsoft Visual C compiler.
For hiredis-py and others support on windows.
2015-03-13 15:58:23 +02:00
Dominique Leuenberger
37c06facda Fix pkgconf file: escaping needed
Due to the various processors going over the command, we need more
escaping.

1) Make parses it, so $${libdir} becomes ${libdir}
2) 'shell' parses it for the 'echo command', whereas echo ${libdir}
would be an empty string; escape it as \${libdir} to ensure we get what
we want.

Closes #312
2015-03-03 08:12:49 -08:00
Matt Stancliff
27076a3e6d Fix tests when assert() undefined
Closes #309

(such as when -DNDEBUG disables all assert() macros)

Inspired by keith-bennett-gbg, but re-rewritten to be more concise.
2015-02-18 14:45:03 -05:00
Matt Stancliff
6b122d43f9 Fix pkgconf when used with DESTDIR
Closes #302
2015-02-12 13:58:14 -05:00
Matt Stancliff
9be3a07d8a Release hiredis 0.12.1
Major fix:
  - `make install` now works properly

Minor fix:
  - `make test` now works after `make 32bit` on a 64-bit platform
  - added more automated travis tests
2015-01-26 10:08:40 -05:00
Matt Stancliff
d3fb491b85 Add more travis tests
Adds travis testing for 32bit builds as well as compile warnings
on 64 bit and 32 bit builds.
2015-01-26 10:08:40 -05:00
Matt Stancliff
2b2b512dca Build test binary by default
This is the only way to force a 32-bit build of the test binary
2015-01-26 10:08:12 -05:00
Matt Stancliff
74f53e30db Fix pkgconf build dependency
We need to re-gen pkgconf when the version changes, and the version
is kept in hiredis.h, so make pkgconf depend on hiredis.h.
2015-01-26 09:41:11 -05:00