Skip to content

Commit 043ce91

Browse files
authored
Backport fixes from main and bump version to 3.1.1 (#209)
* Fix memory leak in RESP3 map parsing (#204) * Fix memory leak in RESP3 map parsing Fix #175 * Add memray deps installation * Install deps only on ubuntu * Disable memray installation on Windows * Another attempt * Fix memray on old macOS versions * Disable memray for PyPy * Exclude FreeBSD * Revert * Another attempt for freebsd fix * Another attempt for freebsd fix (cherry picked from commit 7e77f22) * Version 3.1.1 * Run integration workflow on version branches
1 parent d72ff3d commit 043ce91

File tree

6 files changed

+74
-13
lines changed

6 files changed

+74
-13
lines changed

.github/workflows/freebsd.yaml

+4-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ jobs:
4141
curl -s https://bootstrap.pypa.io/get-pip.py -o get-pip.py
4242
python3.9 get-pip.py
4343
/usr/local/bin/pip install -U pip setuptools wheel
44-
/usr/local/bin/pip install -r dev_requirements.txt
44+
sed '5,6d' dev_requirements.txt > dev_requirements_without_memray.txt
45+
/usr/local/bin/pip install -r dev_requirements_without_memray.txt
4546
/usr/local/bin/python3.9 setup.py build_ext --inplace
46-
python -m pytest
47-
python3.9 setup.py bdist_wheel
47+
/usr/local/bin/python3.9 -m pytest
48+
/usr/local/bin/python3.9 setup.py bdist_wheel

.github/workflows/integration.yaml

+8-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ on:
88
- '**/*.md'
99
branches:
1010
- master
11-
- '[0-9].[0-9]'
11+
- 'v[0-9].[0-9]'
1212
pull_request:
1313
branches:
1414
- master
15-
- '[0-9].[0-9]'
15+
- 'v[0-9].[0-9]'
1616

1717
permissions:
1818
contents: read # to fetch code (actions/checkout)
@@ -40,7 +40,13 @@ jobs:
4040
python-version: ${{ matrix.python-version }}
4141
cache: 'pip'
4242
cache-dependency-path: dev_requirements.txt
43+
- name: Install memray deps
44+
if: ${{ matrix.os == 'ubuntu-latest' }}
45+
run: |
46+
sudo apt-get install build-essential python3-dev libdebuginfod-dev libunwind-dev liblz4-dev -y -qq
4347
- name: run tests
48+
env:
49+
MACOSX_DEPLOYMENT_TARGET: 10.14
4450
run: |
4551
pip install -U pip setuptools wheel
4652
pip install -r dev_requirements.txt

dev_requirements.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
black==24.3.0
22
flake8==4.0.1
33
isort==5.10.1
4-
pytest>=7.0.0
4+
pytest>=7.2.0
5+
memray==1.17.1 ; sys_platform != 'win32' and platform_python_implementation != 'PyPy'
6+
pytest-memray==1.7.0 ; sys_platform != 'win32' and platform_python_implementation != 'PyPy'
57
setuptools
68
tox==3.24.4
79
vulture>=2.3.0

hiredis/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.1.0"
1+
__version__ = "3.1.1"

src/reader.c

+19-6
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,19 @@ static void *tryParentize(const redisReadTask *task, PyObject *obj) {
7474
case REDIS_REPLY_MAP:
7575
if (task->idx % 2 == 0) {
7676
/* Set a temporary item to save the object as a key. */
77-
PyDict_SetItem(parent, obj, Py_None);
77+
int res = PyDict_SetItem(parent, obj, Py_None);
78+
Py_DECREF(obj);
79+
80+
if (res == -1) {
81+
return NULL;
82+
}
7883
} else {
7984
/* Pop the temporary item and set proper key and value. */
8085
PyObject *last_item = PyObject_CallMethod(parent, "popitem", NULL);
8186
PyObject *last_key = PyTuple_GetItem(last_item, 0);
8287
PyDict_SetItem(parent, last_key, obj);
88+
Py_DECREF(last_item);
89+
Py_DECREF(obj);
8390
}
8491
break;
8592
default:
@@ -359,18 +366,24 @@ static PyObject *Reader_feed(hiredis_ReaderObject *self, PyObject *args) {
359366

360367
static PyObject *Reader_gets(hiredis_ReaderObject *self, PyObject *args) {
361368
PyObject *obj;
362-
PyObject *err;
363-
char *errstr;
364369

365370
self->shouldDecode = 1;
366371
if (!PyArg_ParseTuple(args, "|i", &self->shouldDecode)) {
367372
return NULL;
368373
}
369374

370375
if (redisReaderGetReply(self->reader, (void**)&obj) == REDIS_ERR) {
371-
errstr = redisReaderGetError(self->reader);
372-
/* protocolErrorClass might be a callable. call it, then use it's type */
373-
err = createError(self->protocolErrorClass, errstr, strlen(errstr));
376+
PyObject *err = NULL;
377+
char *errstr = NULL;
378+
379+
// Checking if there is no error during the call to redisReaderGetReply
380+
// to avoid getting a SystemError.
381+
if (PyErr_Occurred() == NULL) {
382+
errstr = redisReaderGetError(self->reader);
383+
/* protocolErrorClass might be a callable. call it, then use it's type */
384+
err = createError(self->protocolErrorClass, errstr, strlen(errstr));
385+
}
386+
374387
if (err != NULL) {
375388
obj = PyObject_Type(err);
376389
PyErr_SetString(obj, errstr);

tests/test_reader.py

+39
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,45 @@ def test_dict(reader):
146146
reader.feed(b"%2\r\n+radius\r\n,4.5\r\n+diameter\r\n:9\r\n")
147147
assert {b"radius": 4.5, b"diameter": 9} == reader.gets()
148148

149+
@pytest.mark.limit_memory("50 KB")
150+
def test_dict_memory_leaks(reader):
151+
data = (
152+
b"%5\r\n"
153+
b"+radius\r\n,4.5\r\n"
154+
b"+diameter\r\n:9\r\n"
155+
b"+nested_map\r\n"
156+
b"%2\r\n"
157+
b"+key1\r\n+value1\r\n"
158+
b"+key2\r\n:42\r\n"
159+
b"+nested_array\r\n"
160+
b"*2\r\n"
161+
b"+item1\r\n"
162+
b"+item2\r\n"
163+
b"+nested_set\r\n"
164+
b"~2\r\n"
165+
b"+element1\r\n"
166+
b"+element2\r\n"
167+
)
168+
for i in range(10000):
169+
reader.feed(data)
170+
res = reader.gets()
171+
assert {
172+
b"radius": 4.5,
173+
b"diameter": 9,
174+
b"nested_map": {b"key1": b"value1", b"key2": 42},
175+
b"nested_array": [b"item1", b"item2"],
176+
b"nested_set": [b"element1", b"element2"],
177+
} == res
178+
179+
def test_dict_with_unhashable_key(reader):
180+
reader.feed(
181+
b"%1\r\n"
182+
b"%1\r\n+key1\r\n+value1\r\n"
183+
b":9\r\n"
184+
)
185+
with pytest.raises(TypeError):
186+
reader.gets()
187+
149188
def test_vector(reader):
150189
reader.feed(b">4\r\n+pubsub\r\n+message\r\n+channel\r\n+message\r\n")
151190
assert [b"pubsub", b"message", b"channel", b"message"] == reader.gets()

0 commit comments

Comments
 (0)