Skip to content

Commit 182e593

Browse files
authored
Merge pull request #158 from XrXr/select-exception-safety
Fix connection corruption when rb_thread_fd_select() raises
2 parents 983c296 + aace186 commit 182e593

File tree

1 file changed

+33
-4
lines changed

1 file changed

+33
-4
lines changed

hiredis-client/ext/redis_client/hiredis/hiredis_connection.c

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,19 @@ static int hiredis_wait_readable(int fd, const struct timeval *timeout, int *iss
467467
return 0;
468468
}
469469

470+
struct fd_select {
471+
rb_fdset_t *write_fds;
472+
struct timeval *timeout;
473+
int max;
474+
int return_value;
475+
};
476+
477+
static VALUE protected_writable_select(VALUE _args) {
478+
struct fd_select *args = (struct fd_select *)_args;
479+
args->return_value = rb_thread_fd_select(args->max, NULL, args->write_fds, NULL, args->timeout);
480+
return Qnil;
481+
}
482+
470483
static int hiredis_wait_writable(int fd, const struct timeval *timeout, int *isset) {
471484
struct timeval to;
472485
struct timeval *toptr = NULL;
@@ -483,7 +496,18 @@ static int hiredis_wait_writable(int fd, const struct timeval *timeout, int *iss
483496
toptr = &to;
484497
}
485498

486-
if (rb_thread_fd_select(fd + 1, NULL, &fds, NULL, toptr) < 0) {
499+
struct fd_select args = {
500+
.max = fd + 1,
501+
.write_fds = &fds,
502+
.timeout = toptr,
503+
};
504+
int status;
505+
506+
(void)rb_protect(protected_writable_select, (VALUE)&args, &status);
507+
508+
// Error in case an exception arrives in rb_thread_fd_select()
509+
// (e.g. from Thread.raise)
510+
if (status || args.return_value < 0) {
487511
rb_fd_term(&fds);
488512
return -1;
489513
}
@@ -501,7 +525,6 @@ static VALUE hiredis_connect_finish(hiredis_connection_t *connection, redisConte
501525
redisFree(context);
502526
rb_raise(rb_eRuntimeError, "HiredisConnection is already connected, must be a bug");
503527
}
504-
connection->context = context;
505528

506529
if (context->err) {
507530
redis_raise_error_and_disconnect(context, rb_eRedisClientCannotConnectError);
@@ -536,6 +559,7 @@ static VALUE hiredis_connect_finish(hiredis_connection_t *connection, redisConte
536559

537560
context->reader->fn = &reply_functions;
538561
redisSetPushCallback(context, NULL);
562+
connection->context = context;
539563
return Qtrue;
540564
}
541565

@@ -601,9 +625,14 @@ static VALUE hiredis_reconnect(VALUE self, VALUE is_unix, VALUE ssl_param) {
601625
return Qfalse;
602626
}
603627

604-
hiredis_reconnect_nogvl(connection->context);
628+
// Clear context on the connection, since the nogvl call can raise an
629+
// exception after the redisReconnect() call succeeds and leave the
630+
// connection in a half-initialized state.
631+
redisContext *context = connection->context;
632+
connection->context = NULL;
633+
hiredis_reconnect_nogvl(context);
605634

606-
VALUE success = hiredis_connect_finish(connection, connection->context);
635+
VALUE success = hiredis_connect_finish(connection, context);
607636

608637
if (RTEST(success)) {
609638
if (!RTEST(is_unix)) {

0 commit comments

Comments
 (0)