Skip to content

Commit

Permalink
Implement garbage collection support in Reader (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
oranav committed Apr 4, 2023
1 parent 8adb1b3 commit bd45034
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
* Implement pack_command that serializes redis-py command to the RESP bytes object.
* Implement garbage collection support in Reader (#162)

### 2.1.1 (2023-10-01)

Expand Down
19 changes: 14 additions & 5 deletions src/reader.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <assert.h>

static void Reader_dealloc(hiredis_ReaderObject *self);
static int Reader_traverse(hiredis_ReaderObject *self, visitproc visit, void *arg);
static int Reader_init(hiredis_ReaderObject *self, PyObject *args, PyObject *kwds);
static PyObject *Reader_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
static PyObject *Reader_feed(hiredis_ReaderObject *self, PyObject *args);
Expand Down Expand Up @@ -44,9 +45,9 @@ PyTypeObject hiredis_ReaderType = {
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
"Hiredis protocol reader", /*tp_doc */
0, /*tp_traverse */
(traverseproc)Reader_traverse,/*tp_traverse */
0, /*tp_clear */
0, /*tp_richcompare */
0, /*tp_weaklistoffset */
Expand Down Expand Up @@ -209,16 +210,24 @@ redisReplyObjectFunctions hiredis_ObjectFunctions = {
};

static void Reader_dealloc(hiredis_ReaderObject *self) {
PyObject_GC_UnTrack(self);
// we don't need to free self->encoding as the buffer is managed by Python
// https://docs.python.org/3/c-api/arg.html#strings-and-buffers
redisReaderFree(self->reader);
Py_XDECREF(self->protocolErrorClass);
Py_XDECREF(self->replyErrorClass);
Py_XDECREF(self->notEnoughDataObject);
Py_CLEAR(self->protocolErrorClass);
Py_CLEAR(self->replyErrorClass);
Py_CLEAR(self->notEnoughDataObject);

((PyObject *)self)->ob_type->tp_free((PyObject*)self);
}

static int Reader_traverse(hiredis_ReaderObject *self, visitproc visit, void *arg) {
Py_VISIT(self->protocolErrorClass);
Py_VISIT(self->replyErrorClass);
Py_VISIT(self->notEnoughDataObject);
return 0;
}

static int _Reader_set_exception(PyObject **target, PyObject *value) {
int callable;
callable = PyCallable_Check(value);
Expand Down
17 changes: 17 additions & 0 deletions tests/test_gc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import gc

import hiredis


def test_reader_gc():
class A:
def __init__(self):
self.reader = hiredis.Reader(replyError=self.reply_error)

def reply_error(self, error):
return Exception()

A()
gc.collect()

assert not any(isinstance(o, A) for o in gc.get_objects()), "Referent was not collected"

0 comments on commit bd45034

Please sign in to comment.