Skip to content

Memory Leak Python 3.12.2 #628

@0x78f1935

Description

@0x78f1935

Summary

I conducted several tests with Python 3.11 with success and recently migrated to Python 3.12. I observed that on my Debian system, the SSH connection is lost after running my GitHub Actions pipeline. This issue consistently occurs after all tests have completed, and the results are about to be fetched.

Upon further investigation, I was able to reproduce this problem on a Windows system running Visual Studio Code.

As the repository is private, I will provide as much information as possible in this ticket to aid in troubleshooting.

image

Note: This issue persists until all available memory has been used.

The terminal is stuck at:

image

When I develop on my remote server and this happens, SSH simply times out. When I run this on my desktop, I can barely use my computer due to the memory leak, which prevents me from stopping the generation of the coverage.

During my investigation, I toggled various settings within my test suite and did the same with libraries. I removed xdist because I thought it might be related to multiprocessing, but this was not the case. What remained was:

django-coverage-plugin==3.1.0
    # via -r requirements.in
pytest==8.0.0
    # via
    #   pytest-cov
    #   pytest-django
    #   pytest-sugar
pytest-cov==4.1.0
    # via -r requirements.in
pytest-django==4.8.0
    # via -r requirements.in
pytest-sugar==1.0.0
    # via -r requirements.in
python-dotenv==1.0.1
    # via -r requirements.in

I'm utilizing pyproject.toml and use various flags to start my tests.

[tool.pytest.ini_options]
addopts = "--exitfirst -vs --junitxml htmlcov/pytest.xml --cov --cov-report html --cov-report xml --cov-report term"

which should results into: python -m pystest --exitfirst -vs --junitxml htmlcov/pytest.xml --cov --cov-report html --cov-report xml --cov-report term

Note: When running in Python3.11 this works fine.

When running on Python3.12 with only the flags python -m pystest --exitfirst -vs everything runs fine.
image

You might have noticed by now this is a django application. When running the same tests but with the django suite, everything works fine, even in Pyhton3.12. The command would look like python manage.py test --debug-mode --noinput --pythonpath backend
image

Note: The last two images do not generate coverage data. (Django test suite and Pytest without coverage)

Expected vs actual result

I expect my tests to run just like Python3.11 without memory issues.

Reproducer

I don't necesarely have a reproducable environment, but I do have a base image which I use for my Django application. All requirements are in there eventho I listed them already. This image doesn't include any tests.

Versions

django-coverage-plugin==3.1.0
    # via -r requirements.in
pytest==8.0.0
    # via
    #   pytest-cov
    #   pytest-django
    #   pytest-sugar
pytest-cov==4.1.0
    # via -r requirements.in
pytest-django==4.8.0
    # via -r requirements.in
pytest-sugar==1.0.0
    # via -r requirements.in
python-dotenv==1.0.1
    # via -r requirements.in

Python 3.12.2

Config

Do note the usage of -n 8, which was originally employed for xdist and has already been removed from my configuration to exclude xdist from the problem.

Original config

[tool.pytest.ini_options]
addopts = "-n 8 --exitfirst -vs --junitxml htmlcov/pytest.xml --cov --cov-report html --cov-report xml --cov-report term"
testpaths = [
    "tests.py",
    "test_*.py",
    "*_tests.py",
]
DJANGO_SETTINGS_MODULE = "backend.application.settings"
norecursedirs = "frontend/src/*"

[tool.coverage.run]
branch = true
command_line = "-m pytest"
concurrency = [
    "multiprocessing",
    "thread",
]
parallel = true
source = [
    "backend",
]
omit = [
    "*.html",
    "*.txt",
    "*.log",
    "*.js",
    "*.cjs",
    "*.jsx",
    "*.json",
    "*.ts",
    "*.tsx",
    "*.css",
    "*.sass",
    "backend/application/management/commands/clear_migrations.py",
    "backend/application/pagination.py",
    "backend/application/serializers.py",
]
plugins = ["django_coverage_plugin", ]

[tool.coverage.report]
fail_under = 90
ignore_errors = false
precision = 2
show_missing = true
skip_covered = false
skip_empty = true
sort = "Cover"

[tool.coverage.html]
directory = "htmlcov"
show_contexts = true
skip_covered = false
skip_empty = true
title = "Backend-code Coverage"

[tool.coverage.xml]
output = "htmlcov/coverage.xml"

[tool.coverage.django_coverage_plugin]
template_extensions = 'html, txt, tex, email'

Code

An example how my tests might look like:

# -*- mode: python ; coding: utf-8 -*-
"""
Unit Test: Application
----------------------
"""
from django.test import TestCase
from backend.application import get_logger
from uuid import uuid4


class LoggerTests(TestCase):
    def test_obtaining_logger(self):
        """
        Check if application is able to import the logger.
        """
        name = str(uuid4())[20:]
        logger = get_logger(name)
        self.assertEqual(logger.name, name)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions