Skip to content

Commit be405d1

Browse files
authored
Merge pull request #949 from suhr25/fix-logging-handler-leak
fix: prevent logging handler accumulation in LoggingHelper
2 parents b613d65 + da120af commit be405d1

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

malariagen_data/util.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,12 @@ def __init__(
899899
handler = logging.FileHandler(out)
900900
self._handler = handler
901901

902+
# Remove any pre-existing handlers from the singleton logger to prevent
903+
# accumulation (and FileHandler FD leaks) on repeated instantiation.
904+
for existing_handler in logger.handlers[:]:
905+
logger.removeHandler(existing_handler)
906+
existing_handler.close()
907+
902908
# configure handler
903909
if handler is not None:
904910
if debug:

tests/anoph/test_base.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import io
2+
import logging
3+
14
import numpy as np
25
import pandas as pd
36
import pytest
@@ -8,6 +11,7 @@
811
from malariagen_data import ag3 as _ag3
912
from malariagen_data import adir1 as _adir1
1013
from malariagen_data.anoph.base import AnophelesBase
14+
from malariagen_data.util import LoggingHelper
1115

1216

1317
@pytest.fixture
@@ -258,6 +262,32 @@ def test_lookup_study(fixture, api):
258262
api.lookup_study("foobar")
259263

260264

265+
def test_logging_helper_no_handler_accumulation():
266+
# Regression test: repeated LoggingHelper construction on the same logger
267+
# name must not accumulate handlers (StreamHandler leak, FileHandler FD leak).
268+
logger_name = "test_logging_helper_no_handler_accumulation"
269+
for _ in range(10):
270+
LoggingHelper(name=logger_name, out=io.StringIO())
271+
logger = logging.getLogger(logger_name)
272+
assert (
273+
len(logger.handlers) <= 1
274+
), f"Handler leak: {len(logger.handlers)} handlers after 10 instantiations"
275+
276+
277+
def test_logging_helper_no_duplicate_output():
278+
# Regression test: a message emitted after N instantiations must appear
279+
# exactly once in the output stream.
280+
logger_name = "test_logging_helper_no_duplicate_output"
281+
out = io.StringIO()
282+
for _ in range(5):
283+
helper = LoggingHelper(name=logger_name, out=out)
284+
helper.info("sentinel")
285+
output = out.getvalue()
286+
assert (
287+
output.count("sentinel") == 1
288+
), f"Duplicate log output: 'sentinel' appeared {output.count('sentinel')} times"
289+
290+
261291
def _strip_terms_of_use_from_manifest(manifest_path):
262292
"""Rewrite a manifest TSV file without terms-of-use columns."""
263293
df = pd.read_csv(manifest_path, sep="\t")

0 commit comments

Comments
 (0)