Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 507c19f

Browse files
committed
fix: don't leak in-memory databases. #2138
1 parent 23ad769 commit 507c19f

3 files changed

Lines changed: 40 additions & 3 deletions

File tree

CHANGES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ upgrading your version of coverage.py.
2323
Unreleased
2424
----------
2525

26+
- Fix: `issue 2138`_ describes a memory leak that happened when repeatedly
27+
using the Coverage API with in-memory data. This is now fixed.
28+
2629
- Fix: the markdown-formatted coverage report didn't fully escape special
2730
characters in file paths (`issue 2141`_). This would be very unlikely to
2831
cause a problem, but now it's done properly, thanks to `Ellie Ayla
2932
<pull 2142_>`_.
3033

34+
.. _issue 2138: https://github.com/coveragepy/coveragepy/issues/2138
3135
.. _issue 2141: https://github.com/coveragepy/coveragepy/issues/2141
3236
.. _pull 2142: https://github.com/coveragepy/coveragepy/pull/2142
3337

coverage/sqldata.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,7 @@ def _choose_filename(self) -> None:
298298

299299
def _reset(self) -> None:
300300
"""Reset our attributes."""
301-
if not self._no_disk:
302-
self.close()
301+
self.close(force=True)
303302
self._file_map = {}
304303
self._have_used = False
305304
self._current_context_id = None

tests/test_data.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from __future__ import annotations
77

8+
9+
import gc
810
import glob
911
import os
1012
import os.path
@@ -24,6 +26,7 @@
2426
from coverage.files import PathAliases, canonical_filename
2527
from coverage.types import FilePathClasses, FilePathType, TArc, TLineNo
2628

29+
from tests import osinfo
2730
from tests.coveragetest import CoverageTest
2831
from tests.helpers import DebugControlString, assert_count_equal
2932

@@ -745,7 +748,7 @@ def test_debug_output_with_debug_option(self) -> None:
745748

746749
assert re.search(
747750
r"^"
748-
+ r"Closing dbs, force=False: {}\n"
751+
+ r"Closing dbs, force=True: {}\n"
749752
+ r"Erasing data file '.*\.coverage' \(does not exist\)\n"
750753
+ r"Opening data file '.*\.coverage' \(does not exist\)\n"
751754
+ r"Initing data file '.*\.coverage' \(0 bytes, modified [-:. 0-9]+\)\n"
@@ -1093,3 +1096,34 @@ def test_updating(self) -> None:
10931096
b = CoverageData(no_disk=True)
10941097
b.update(a)
10951098
assert b.measured_files() == {"foo.py"}
1099+
1100+
@pytest.mark.flaky
1101+
def test_erase_doesnt_leak_memory(self) -> None:
1102+
# https://github.com/coveragepy/coveragepy/issues/2138
1103+
# Repeated erase cycles on no_disk data should not leak memory
1104+
# from unclosed in-memory database connections.
1105+
1106+
# Keep references to erased data objects, simulating how
1107+
# Coverage._data_to_close accumulates them.
1108+
old_data = []
1109+
1110+
# Warmup
1111+
covdata = CoverageData(no_disk=True)
1112+
covdata.add_lines({"foo.py": list(range(1, 1000))})
1113+
covdata.erase()
1114+
old_data.append(covdata)
1115+
1116+
gc.collect()
1117+
baseline = osinfo.process_ram()
1118+
1119+
for _ in range(200):
1120+
covdata = CoverageData(no_disk=True)
1121+
covdata.add_lines({"foo.py": list(range(1, 1000))})
1122+
covdata.erase()
1123+
old_data.append(covdata)
1124+
1125+
gc.collect()
1126+
growth = osinfo.process_ram() - baseline
1127+
# Before fixing 2138, 200 cycles leaked ~44MB from unclosed in-memory
1128+
# SQLite databases. With the fix, growth is well under 1MB.
1129+
assert growth < 4_000_000, f"Memory grew by {growth:,} bytes over 200 erase cycles"

0 commit comments

Comments
 (0)