Skip to content

Commit b4d1be1

Browse files
authored
Merge pull request #175 from pycompression/release_1.5.2
Release 1.5.2
2 parents fb34ac6 + b158a26 commit b4d1be1

File tree

10 files changed

+62
-39
lines changed

10 files changed

+62
-39
lines changed

.github/release_checklist.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Release checklist
55
- [ ] Set version to a stable number.
66
- [ ] Change current development version in `CHANGELOG.rst` to stable version.
77
- [ ] Change the version in `__init__.py`
8+
- [ ] Check if the address sanitizer does not find any problems using `tox -e asan`
89
- [ ] Merge the release branch into `main`.
910
- [ ] Created an annotated tag with the stable version number. Include changes
1011
from CHANGELOG.rst.

.readthedocs.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@ formats: [] # Do not build epub and pdf
33

44
python:
55
install:
6-
- method: pip
7-
path: .
8-
conda:
9-
environment: docs/conda-environment.yml
6+
- requirements: "requirements-docs.txt"
7+
- method: "pip"
8+
path: "."
9+
10+
sphinx:
11+
configuration: docs/conf.py
12+
13+
build:
14+
os: "ubuntu-22.04"
15+
tools:
16+
python: "3"
17+
apt_packages:
18+
- libisal-dev

CHANGELOG.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ Changelog
77
.. This document is user facing. Please word the changes in such a way
88
.. that users understand how the changes affect the new version.
99
10+
version 1.5.2
11+
-----------------
12+
+ Fix a bug where a filehandle remained opened when ``igzip_threaded.open``
13+
was used for writing with a wrong compression level.
14+
+ Fix a memory leak that occurred when an error was thrown for a gzip header
15+
with the wrong magic numbers.
16+
+ Fix a memory leak that occurred when isal_zlib.decompressobj was given a
17+
wrong wbits value.
18+
1019
version 1.5.1
1120
-----------------
1221
+ Fix a memory leak in the GzipReader.readall implementation.

docs/conda-environment.yml

Lines changed: 0 additions & 12 deletions
This file was deleted.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def build_isa_l():
135135

136136
setup(
137137
name="isal",
138-
version="1.5.1",
138+
version="1.5.2",
139139
description="Faster zlib and gzip compatible compression and "
140140
"decompression by providing python bindings for the ISA-L "
141141
"library.",

src/isal/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@
2727
"__version__"
2828
]
2929

30-
__version__ = "1.5.1"
30+
__version__ = "1.5.2"

src/isal/igzip_threaded.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import queue
1313
import struct
1414
import threading
15-
from typing import BinaryIO, List, Optional, Tuple
15+
from typing import List, Optional, Tuple
1616

1717
from . import igzip, isal_zlib
1818

@@ -56,20 +56,13 @@ def open(filename, mode="rb", compresslevel=igzip._COMPRESS_LEVEL_TRADEOFF,
5656
threads = multiprocessing.cpu_count()
5757
except: # noqa: E722
5858
threads = 1
59-
open_mode = mode.replace("t", "b")
60-
if isinstance(filename, (str, bytes)) or hasattr(filename, "__fspath__"):
61-
binary_file = builtins.open(filename, open_mode)
62-
elif hasattr(filename, "read") or hasattr(filename, "write"):
63-
binary_file = filename
64-
else:
65-
raise TypeError("filename must be a str or bytes object, or a file")
6659
if "r" in mode:
6760
gzip_file = io.BufferedReader(
68-
_ThreadedGzipReader(binary_file, block_size=block_size))
61+
_ThreadedGzipReader(filename, block_size=block_size))
6962
else:
7063
gzip_file = io.BufferedWriter(
7164
_ThreadedGzipWriter(
72-
fp=binary_file,
65+
filename,
7366
block_size=block_size,
7467
level=compresslevel,
7568
threads=threads
@@ -81,10 +74,20 @@ def open(filename, mode="rb", compresslevel=igzip._COMPRESS_LEVEL_TRADEOFF,
8174
return gzip_file
8275

8376

77+
def open_as_binary_stream(filename, open_mode):
78+
if isinstance(filename, (str, bytes)) or hasattr(filename, "__fspath__"):
79+
binary_file = builtins.open(filename, open_mode)
80+
elif hasattr(filename, "read") or hasattr(filename, "write"):
81+
binary_file = filename
82+
else:
83+
raise TypeError("filename must be a str or bytes object, or a file")
84+
return binary_file
85+
86+
8487
class _ThreadedGzipReader(io.RawIOBase):
85-
def __init__(self, fp, queue_size=2, block_size=1024 * 1024):
86-
self.raw = fp
87-
self.fileobj = igzip._IGzipReader(fp, buffersize=8 * block_size)
88+
def __init__(self, filename, queue_size=2, block_size=1024 * 1024):
89+
self.raw = open_as_binary_stream(filename, "rb")
90+
self.fileobj = igzip._IGzipReader(self.raw, buffersize=8 * block_size)
8891
self.pos = 0
8992
self.read_file = False
9093
self.queue = queue.Queue(queue_size)
@@ -193,15 +196,14 @@ class _ThreadedGzipWriter(io.RawIOBase):
193196
compressing and output is handled in one thread.
194197
"""
195198
def __init__(self,
196-
fp: BinaryIO,
199+
filename,
197200
level: int = isal_zlib.ISAL_DEFAULT_COMPRESSION,
198201
threads: int = 1,
199202
queue_size: int = 1,
200203
block_size: int = 1024 * 1024,
201204
):
202205
self.lock = threading.Lock()
203206
self.exception: Optional[Exception] = None
204-
self.raw = fp
205207
self.level = level
206208
self.previous_block = b""
207209
# Deflating random data results in an output a little larger than the
@@ -236,6 +238,7 @@ def __init__(self,
236238
self.running = False
237239
self._size = 0
238240
self._closed = False
241+
self.raw = open_as_binary_stream(filename, "wb")
239242
self._write_gzip_header()
240243
self.start()
241244

src/isal/isal_zlibmodule.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,7 @@ isal_zlib_decompressobj_impl(PyObject *module, int wbits, PyObject *zdict)
793793
err = wbits_to_flag_and_hist_bits_inflate(wbits, &hist_bits, &flag);
794794
if (err < 0) {
795795
PyErr_Format(PyExc_ValueError, "Invalid wbits value: %d", wbits);
796+
Py_DECREF(self);
796797
return NULL;
797798
}
798799
else if (err == 0) {
@@ -1683,9 +1684,11 @@ GzipReader_read_into_buffer(GzipReader *self, uint8_t *out_buffer, size_t out_bu
16831684

16841685
if (!(magic1 == 0x1f && magic2 == 0x8b)) {
16851686
Py_BLOCK_THREADS;
1687+
PyObject *magic_obj = PyBytes_FromStringAndSize((char *)current_pos, 2);
16861688
PyErr_Format(BadGzipFile,
16871689
"Not a gzipped file (%R)",
1688-
PyBytes_FromStringAndSize((char *)current_pos, 2));
1690+
magic_obj);
1691+
Py_DECREF(magic_obj);
16891692
return -1;
16901693
};
16911694
uint8_t method = current_pos[2];

tests/test_igzip_threaded.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def test_threaded_write_oversized_block_no_error(threads):
9999
@pytest.mark.parametrize("threads", [1, 3])
100100
def test_threaded_write_error(threads):
101101
f = igzip_threaded._ThreadedGzipWriter(
102-
fp=io.BytesIO(), level=3,
102+
io.BytesIO(), level=3,
103103
threads=threads, block_size=8 * 1024)
104104
# Bypass the write method which should not allow blocks larger than
105105
# block_size.
@@ -139,10 +139,11 @@ def test_writer_not_readable():
139139

140140

141141
def test_writer_wrong_level():
142-
with pytest.raises(ValueError) as error:
143-
igzip_threaded._ThreadedGzipWriter(io.BytesIO(), level=42)
144-
error.match("Invalid compression level")
145-
error.match("42")
142+
with tempfile.NamedTemporaryFile("wb") as tmp:
143+
with pytest.raises(ValueError) as error:
144+
igzip_threaded.open(tmp.name, mode="wb", compresslevel=42)
145+
error.match("Invalid compression level")
146+
error.match("42")
146147

147148

148149
def test_writer_too_low_threads():

tox.ini

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ commands =
2222
coverage html -i
2323
coverage xml -i
2424

25+
[testenv:asan]
26+
setenv=
27+
PYTHONDEVMODE=1
28+
PYTHONMALLOC=malloc
29+
CFLAGS=-lasan -fsanitize=address -fno-omit-frame-pointer
30+
allowlist_externals=bash
31+
commands=
32+
bash -c 'export LD_PRELOAD=$(gcc -print-file-name=libasan.so) && printenv LD_PRELOAD && python -c "from isal import isal_zlib" && pytest tests'
33+
2534
[testenv:compliance]
2635
deps=pytest
2736
commands=

0 commit comments

Comments
 (0)