diff --git a/0001-make-data-collection-operations-thread-safe.patch b/0001-make-data-collection-operations-thread-safe.patch new file mode 100644 index 0000000..c01e98b --- /dev/null +++ b/0001-make-data-collection-operations-thread-safe.patch @@ -0,0 +1,140 @@ +From e36b42e2db46e892d9347ba0408c99b187ba8cb8 Mon Sep 17 00:00:00 2001 +From: Ned Batchelder +Date: Mon, 3 May 2021 07:56:05 -0400 +Subject: [PATCH] fix: make data collection operations thread-safe + +--- + CHANGES.rst | 3 +++ + coverage/sqldata.py | 20 ++++++++++++++++++++ + tests/test_data.py | 7 ++++++- + 3 files changed, 29 insertions(+), 1 deletion(-) + +#diff --git a/CHANGES.rst b/CHANGES.rst +#index 29af7340..3c65e5d8 100644 +#--- a/CHANGES.rst +#+++ b/CHANGES.rst +#@@ -26,6 +26,9 @@ Unreleased +# +# - Dropped support for Python 2.7, PyPy 2, and Python 3.5. +# +#+- Data collection is now thread-safe. There may have been rare instances of +#+ exceptions raised in multi-threaded programs. +#+ +# - Plugins (like the `Django coverage plugin`_) were generating "Already +# imported a file that will be measured" warnings about Django itself. These +# have been fixed, closing `issue 1150`_. +Index: coverage-5.5/coverage/sqldata.py +=================================================================== +--- coverage-5.5.orig/coverage/sqldata.py ++++ coverage-5.5/coverage/sqldata.py +@@ -8,12 +8,14 @@ + + import collections + import datetime ++import functools + import glob + import itertools + import os + import re + import sqlite3 + import sys ++import threading + import zlib + + from coverage import env +@@ -179,6 +181,10 @@ class CoverageData(SimpleReprMixin): + Data in a :class:`CoverageData` can be serialized and deserialized with + :meth:`dumps` and :meth:`loads`. + ++ The methods used during the coverage.py collection phase ++ (:meth:`add_lines`, :meth:`add_arcs`, :meth:`set_context`, and ++ :meth:`add_file_tracers`) are thread-safe. Other methods may not be. ++ + """ + + def __init__(self, basename=None, suffix=None, no_disk=False, warn=None, debug=None): +@@ -207,6 +213,8 @@ class CoverageData(SimpleReprMixin): + # Maps thread ids to SqliteDb objects. + self._dbs = {} + self._pid = os.getpid() ++ # Synchronize the operations used during collection. ++ self._lock = threading.Lock() + + # Are we in sync with the data file? + self._have_used = False +@@ -218,6 +226,15 @@ class CoverageData(SimpleReprMixin): + self._current_context_id = None + self._query_context_ids = None + ++ def _locked(method): # pylint: disable=no-self-argument ++ """A decorator for methods that should hold self._lock.""" ++ @functools.wraps(method) ++ def _wrapped(self, *args, **kwargs): ++ with self._lock: ++ # pylint: disable=not-callable ++ return method(self, *args, **kwargs) ++ return _wrapped ++ + def _choose_filename(self): + """Set self._filename based on inited attributes.""" + if self._no_disk: +@@ -381,6 +398,7 @@ class CoverageData(SimpleReprMixin): + else: + return None + ++ @_locked + def set_context(self, context): + """Set the current context for future :meth:`add_lines` etc. + +@@ -422,6 +440,7 @@ class CoverageData(SimpleReprMixin): + """ + return self._filename + ++ @_locked + def add_lines(self, line_data): + """Add measured line data. + +@@ -454,6 +473,7 @@ class CoverageData(SimpleReprMixin): + (file_id, self._current_context_id, linemap), + ) + ++ @_locked + def add_arcs(self, arc_data): + """Add measured arc data. + +@@ -498,6 +518,7 @@ class CoverageData(SimpleReprMixin): + ('has_arcs', str(int(arcs))) + ) + ++ @_locked + def add_file_tracers(self, file_tracers): + """Add per-file plugin information. + +Index: coverage-5.5/tests/test_data.py +=================================================================== +--- coverage-5.5.orig/tests/test_data.py ++++ coverage-5.5/tests/test_data.py +@@ -488,10 +488,14 @@ class CoverageDataTest(DataTestHelpers, + + def test_thread_stress(self): + covdata = CoverageData() ++ exceptions = [] + + def thread_main(): + """Every thread will try to add the same data.""" +- covdata.add_lines(LINES_1) ++ try: ++ covdata.add_lines(LINES_1) ++ except Exception as ex: ++ exceptions.append(ex) + + threads = [threading.Thread(target=thread_main) for _ in range(10)] + for t in threads: +@@ -500,6 +504,7 @@ class CoverageDataTest(DataTestHelpers, + t.join() + + self.assert_lines1_data(covdata) ++ assert exceptions == [] + + + class CoverageDataTestInTempDir(DataTestHelpers, CoverageTest): diff --git a/python-coverage.changes b/python-coverage.changes index dc73e24..362b314 100644 --- a/python-coverage.changes +++ b/python-coverage.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Tue May 11 10:44:16 UTC 2021 - Antonio Larrosa + +- Add a patch from upstream (slightly rebased) to make data + collection operations thread safe: + * 0001-make-data-collection-operations-thread-safe.patch + ------------------------------------------------------------------- Sun May 9 22:27:19 UTC 2021 - Matej Cepl diff --git a/python-coverage.spec b/python-coverage.spec index 7361f08..0bb734a 100644 --- a/python-coverage.spec +++ b/python-coverage.spec @@ -27,6 +27,8 @@ Source: https://files.pythonhosted.org/packages/source/c/coverage/covera # PATCH-FIX-UPSTREAM traced_file_absolute.patch gh#nedbat/coveragepy#1161 mcepl@suse.com # traced file names seem to be absolute now? Patch0: traced_file_absolute.patch +# PATCH-FIX-UPSTREAM 0001-make-data-collection-operations-thread-safe.patch alarrosa@suse.com -- Make data collection operations thread safe +Patch1: 0001-make-data-collection-operations-thread-safe.patch BuildRequires: %{python_module devel} BuildRequires: %{python_module flaky} BuildRequires: %{python_module hypothesis >= 4.57}