Skip to content

Commit a40749e

Browse files
julian-smith-artifex-comJorjMcKie
authored andcommitted
tests/: added test_4125(), for pixmap memory leak.
Addresses #4125.
1 parent 22f0aa3 commit a40749e

File tree

2 files changed

+105
-0
lines changed

2 files changed

+105
-0
lines changed

tests/resources/test_4125.pdf

166 KB
Binary file not shown.

tests/test_memory.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,108 @@ def rss():
115115
assert 0.93 <= r < 1.05, f'{r1=} {r2=} {r=}.'
116116
else:
117117
assert 0.95 <= r < 1.05, f'{r1=} {r2=} {r=}.'
118+
119+
120+
def show_tracemalloc_diff(snapshot1, snapshot2):
121+
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
122+
n = 0
123+
mem = 0
124+
for i in top_stats:
125+
n += i.count
126+
mem += i.size
127+
print(f'{n=}')
128+
print(f'{mem=}')
129+
print("Top 10:")
130+
for stat in top_stats[:10]:
131+
print(f' {stat}')
132+
snapshot_diff = snapshot2.compare_to(snapshot1, key_type='lineno')
133+
print(f'snapshot_diff:')
134+
count_diff = 0
135+
size_diff = 0
136+
for i, s in enumerate(snapshot_diff):
137+
print(f' {i}: {s.count=} {s.count_diff=} {s.size=} {s.size_diff=} {s.traceback=}')
138+
count_diff += s.count_diff
139+
size_diff += s.size_diff
140+
print(f'{count_diff=} {size_diff=}')
141+
142+
143+
144+
def test_4125():
145+
if os.environ.get('PYMUPDF_RUNNING_ON_VALGRIND') == '1':
146+
print(f'test_4125(): not running because PYMUPDF_RUNNING_ON_VALGRIND=1.')
147+
return
148+
if platform.system().startswith('MSYS_NT-'):
149+
print(f'test_4125(): not running on msys2 - psutil not available.')
150+
return
151+
152+
print('')
153+
print(f'test_4125(): {platform.python_version()=}.')
154+
155+
path = os.path.normpath(f'{__file__}/../../tests/resources/test_4125.pdf')
156+
import gc
157+
import psutil
158+
159+
root = os.path.normpath(f'{__file__}/../..')
160+
sys.path.insert(0, root)
161+
try:
162+
import pipcl
163+
finally:
164+
del sys.path[0]
165+
166+
process = psutil.Process()
167+
168+
class State: pass
169+
state = State()
170+
state.rsss = list()
171+
state.prev = None
172+
173+
def get_stat():
174+
rss = process.memory_info().rss
175+
if not state.rsss:
176+
state.prev = rss
177+
state.rsss.append(rss)
178+
drss = rss - state.prev
179+
state.prev = rss
180+
print(f'test_4125():'
181+
f' rss={pipcl.number_sep(rss)}'
182+
f' rss-rss0={pipcl.number_sep(rss-state.rsss[0])}'
183+
f' drss={pipcl.number_sep(drss)}'
184+
f'.'
185+
)
186+
187+
for i in range(10):
188+
with pymupdf.open(path) as document:
189+
for page in document:
190+
for image_info in page.get_images(full=True):
191+
xref, smask, width, height, bpc, colorspace, alt_colorspace, name, filter_, referencer = image_info
192+
pixmap = pymupdf.Pixmap(document, xref)
193+
if pixmap.colorspace != pymupdf.csRGB:
194+
pixmap2 = pymupdf.Pixmap(pymupdf.csRGB, pixmap)
195+
del pixmap2
196+
del pixmap
197+
pymupdf.TOOLS.store_shrink(100)
198+
pymupdf.TOOLS.glyph_cache_empty()
199+
gc.collect()
200+
get_stat()
201+
202+
# Check memory leak is as expected.
203+
for i in range(3, len(state.rsss)):
204+
drss = state.rsss[i] - state.rsss[i-1]
205+
pv = platform.python_version_tuple()
206+
pv = (int(pv[0]), int(pv[1]))
207+
if pv < (3, 11):
208+
# Python < 3.11 has less reliable memory usage so we exclude.
209+
print(f'test_4125(): Not checking on {platform.python_version_tuple()=} because < 3.11.')
210+
elif platform.system() == 'Darwin':
211+
print(f'test_4125(): Not checking on {platform.system()=} because RSS seems to be unreliable.')
212+
elif pymupdf.mupdf_version_tuple < (1, 25, 2):
213+
drss_expected = 4915200
214+
e = abs(1 - drss / drss_expected)
215+
print(f'test_4125(): {i=} {e=}')
216+
assert e < 0.15, f'{i=}: {drss_expected} {drss} {e=}.'
217+
else:
218+
# Bug fixed in MuPDF-1.26 and hopefully MuPDF-1.25.2. We still see
219+
# some fluctuations in RSS usage, perhaps worse for older Python
220+
# versions.
221+
e = 200*1000
222+
assert abs(drss) <= e, f'{i=}: {drss=} {e=}.'

0 commit comments

Comments
 (0)