Skip to content

Commit

Permalink
Merge pull request #157 from redis-rb/fix-gc-bug
Browse files Browse the repository at this point in the history
Fix a GC bug in hiredis-client
  • Loading branch information
casperisfine authored Dec 20, 2023
2 parents 0f72a4a + 0cbbb79 commit 983c296
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- Fixed a GC bug that could cause crashes in `hiredis-client`.

# 0.19.0

- Revalidate connection in `RedisClient#connected?`
Expand Down
15 changes: 10 additions & 5 deletions hiredis-client/ext/redis_client/hiredis/hiredis_connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ static void *reply_append(const redisReadTask *task, VALUE value) {
if (task->idx % 2) {
VALUE key = rb_ary_pop(state->stack);
rb_hash_aset(parent, key, value);
RB_GC_GUARD(key);
} else {
rb_ary_push(state->stack, value);
}
Expand All @@ -163,8 +164,10 @@ static void *reply_append(const redisReadTask *task, VALUE value) {
rb_bug("[hiredis] Unexpected task parent type %d", task->parent->type);
break;
}
RB_GC_GUARD(parent);
}
rb_ary_store(state->stack, task_index, value);
RB_GC_GUARD(value);
return (void*)value;
}

Expand Down Expand Up @@ -693,8 +696,9 @@ static int hiredis_read_internal(hiredis_connection_t *connection, VALUE *reply)
// We use that to avoid having to have a `mark` function with write barriers.
// Not that it would be too hard, but if we mark the response objects, we'll likely end up
// promoting them to the old generation which isn't desirable.
VALUE stack = rb_ary_new();
hiredis_reader_state_t reader_state = {
.stack = rb_ary_new(),
.stack = stack,
.task_index = &connection->context->reader->ridx,
};
connection->context->reader->privdata = &reader_state;
Expand Down Expand Up @@ -759,10 +763,10 @@ static int hiredis_read_internal(hiredis_connection_t *connection, VALUE *reply)

/* Set reply object */
if (reply != NULL) {
*reply = (VALUE)redis_reply;
*reply = rb_ary_entry(stack, 0);
}

RB_GC_GUARD(reader_state.stack);
RB_GC_GUARD(stack);

return 0;
}
Expand All @@ -789,6 +793,7 @@ static VALUE hiredis_read(VALUE self) {
// See reply_create_bool
reply = Qfalse;
}
RB_GC_GUARD(self);
return reply;
}

Expand All @@ -806,7 +811,7 @@ static inline double diff_timespec_ms(const struct timespec *time1, const struct
}

static inline int timeval_to_msec(struct timeval duration) {
return duration.tv_sec * 1000 + duration.tv_usec / 1000;
return (int)(duration.tv_sec * 1000 + duration.tv_usec / 1000);
}

typedef struct {
Expand Down Expand Up @@ -895,8 +900,8 @@ RUBY_FUNC_EXPORTED void Init_hiredis_connection(void) {
redisInitOpenSSL();

id_parse = rb_intern("parse");
Redis_Qfalse = rb_obj_alloc(rb_cObject);
rb_global_variable(&Redis_Qfalse);
Redis_Qfalse = rb_obj_alloc(rb_cObject);

VALUE rb_cRedisClient = rb_const_get(rb_cObject, rb_intern("RedisClient"));

Expand Down
23 changes: 23 additions & 0 deletions test/redis_client_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,27 @@ def test_password
client = new_client(**{ password: password })
assert_equal password, client.password
end

if GC.respond_to?(:auto_compact)
def test_gc_safety
gc_stress_was = GC.stress
gc_auto_compact_was = GC.auto_compact

GC.stress = true
GC.auto_compact = true

client = new_client
client.call("PING")

list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(&:to_s)
client.call("LPUSH", "list", list)

3.times do
assert_equal list, client.call("LRANGE", "list", 0, -1).reverse
end
ensure
GC.stress = gc_stress_was
GC.auto_compact = gc_auto_compact_was
end
end
end

0 comments on commit 983c296

Please sign in to comment.