@@ -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