Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d685795

Browse files
WIP: Experimental support to detect unconsumed data
This commit is an attempt at detecting unconsumed data on a socket when we pull it from the connection pool. Two new INI settings are introduced related to the changes: redis.pconnect.pool_detect_dirty: Value Explanation ----- ---------------------------------------------------------------- 0 Don't execute new logic at all. 1 Abort and close the socket if we find unconsumed bytes in the read buffer. 2 Seek to the end of our read buffer if we find unconsumed bytes and then poll the socket FD to detect if we're still readable in which case we fail and close the socket. redis.pconnect.pool_poll_timeout: The poll timeout to employ when checking if the socket is readable. This value is in milliseconds and can be zero. Review changes See #2013 Perform cheaper PHP liveness check first.
1 parent f841686 commit d685795

3 files changed

Lines changed: 97 additions & 4 deletions

File tree

common.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,17 @@ typedef enum {
133133
#define MULTI 1
134134
#define PIPELINE 2
135135

136+
#define PHPREDIS_DEBUG_LOGGING 0
137+
138+
#if PHPREDIS_DEBUG_LOGGING == 1
139+
#define redisDbgFmt(fmt, ...) \
140+
php_printf("%s:%d:%s(): " fmt "\n", __FILE__, __LINE__, __func__, __VA_ARGS__)
141+
#define redisDbgStr(str) phpredisDebugFmt("%s", str)
142+
#else
143+
#define redisDbgFmt(fmt, ...) ((void)0)
144+
#define redisDbgStr(str) ((void)0)
145+
#endif
146+
136147
#define IS_ATOMIC(redis_sock) (redis_sock->mode == ATOMIC)
137148
#define IS_MULTI(redis_sock) (redis_sock->mode & MULTI)
138149
#define IS_PIPELINE(redis_sock) (redis_sock->mode & PIPELINE)

library.c

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,6 +2159,82 @@ static int redis_stream_liveness_check(php_stream *stream) {
21592159
SUCCESS : FAILURE;
21602160
}
21612161

2162+
/* Try to get the underlying socket FD for use with poll/select.
2163+
* Returns -1 on failure. */
2164+
static php_socket_t redis_stream_fd_for_select(php_stream *stream) {
2165+
php_socket_t fd;
2166+
int flags;
2167+
2168+
flags = PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL;
2169+
if (php_stream_cast(stream, flags, (void*)&fd, 1) == FAILURE)
2170+
return -1;
2171+
2172+
return fd;
2173+
}
2174+
2175+
static int redis_detect_dirty_config(void) {
2176+
int val = INI_INT("redis.pconnect.pool_detect_dirty");
2177+
2178+
if (val >= 0 && val <= 2)
2179+
return val;
2180+
else if (val > 2)
2181+
return 2;
2182+
else
2183+
return 0;
2184+
}
2185+
2186+
static int redis_pool_poll_timeout(void) {
2187+
int val = INI_INT("redis.pconnect.pool_poll_timeout");
2188+
if (val >= 0)
2189+
return val;
2190+
2191+
return 0;
2192+
}
2193+
2194+
#define REDIS_POLL_FD_SET(_pfd, _fd, _events) \
2195+
(_pfd).fd = _fd; (_pfd).events = _events; (_pfd).revents = 0
2196+
2197+
/* Try to determine if the socket is out of sync (has unconsumed replies) */
2198+
static int redis_stream_detect_dirty(php_stream *stream) {
2199+
php_socket_t fd;
2200+
php_pollfd pfd;
2201+
int rv, action;
2202+
2203+
/* Short circuit if this is disabled */
2204+
if ((action = redis_detect_dirty_config()) == 0)
2205+
return SUCCESS;
2206+
2207+
/* Seek past unconsumed bytes if we detect them */
2208+
if (stream->readpos < stream->writepos) {
2209+
redisDbgFmt("%s on unconsumed buffer (%ld < %ld)",
2210+
action > 1 ? "Aborting" : "Seeking",
2211+
(long)stream->readpos, (long)stream->writepos);
2212+
2213+
/* Abort if we are configured to immediately fail */
2214+
if (action == 1)
2215+
return FAILURE;
2216+
2217+
/* Seek to the end of buffered data */
2218+
zend_off_t offset = stream->writepos - stream->readpos;
2219+
if (php_stream_seek(stream, offset, SEEK_CUR) == FAILURE)
2220+
return FAILURE;
2221+
}
2222+
2223+
/* Get the underlying FD */
2224+
if ((fd = redis_stream_fd_for_select(stream)) == -1)
2225+
return FAILURE;
2226+
2227+
/* We want to detect a readable socket (it shouln't be) */
2228+
REDIS_POLL_FD_SET(pfd, fd, PHP_POLLREADABLE);
2229+
rv = php_poll2(&pfd, 1, redis_pool_poll_timeout());
2230+
2231+
/* If we detect the socket is readable, it's dirty which is
2232+
* a failure. Otherwise as best we can tell it's good.
2233+
* TODO: We could attempt to consume up to N bytes */
2234+
redisDbgFmt("Detected %s socket", rv > 0 ? "readable" : "unreadable");
2235+
return rv == 0 ? SUCCESS : FAILURE;
2236+
}
2237+
21622238
static int
21632239
redis_sock_check_liveness(RedisSock *redis_sock)
21642240
{
@@ -2167,11 +2243,14 @@ redis_sock_check_liveness(RedisSock *redis_sock)
21672243
smart_string cmd = {0};
21682244
size_t len;
21692245

2170-
/* Short circuit if we detect the stream has gone bad or if the user has
2171-
* configured persistent connection "YOLO mode". */
2172-
if (redis_stream_liveness_check(redis_sock->stream) != SUCCESS) {
2246+
/* Short circuit if PHP detects the stream isn't live */
2247+
if (redis_stream_liveness_check(redis_sock->stream) != SUCCESS)
2248+
goto failure;
2249+
2250+
/* Short circuit if we detect the stream is "dirty", can't or are
2251+
configured not to try and fix it */
2252+
if (redis_stream_detect_dirty(redis_sock->stream) != SUCCESS)
21732253
goto failure;
2174-
}
21752254

21762255
redis_sock->status = REDIS_SOCK_STATUS_CONNECTED;
21772256
if (!INI_INT("redis.pconnect.echo_check_liveness")) {
@@ -2290,6 +2369,7 @@ PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock)
22902369
if (redis_sock_check_liveness(redis_sock) == SUCCESS) {
22912370
return SUCCESS;
22922371
}
2372+
22932373
p->nb_active--;
22942374
}
22952375

redis.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ PHP_INI_BEGIN()
9898
PHP_INI_ENTRY("redis.pconnect.pooling_enabled", "1", PHP_INI_ALL, NULL)
9999
PHP_INI_ENTRY("redis.pconnect.connection_limit", "0", PHP_INI_ALL, NULL)
100100
PHP_INI_ENTRY("redis.pconnect.echo_check_liveness", "1", PHP_INI_ALL, NULL)
101+
PHP_INI_ENTRY("redis.pconnect.pool_detect_dirty", "0", PHP_INI_ALL, NULL)
102+
PHP_INI_ENTRY("redis.pconnect.pool_poll_timeout", "0", PHP_INI_ALL, NULL)
101103
PHP_INI_ENTRY("redis.pconnect.pool_pattern", "", PHP_INI_ALL, NULL)
102104

103105
/* redis session */

0 commit comments

Comments
 (0)