diff --git a/.gitignore b/.gitignore index eef7a2f..025b031 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,4 @@ /kombu-5.3.0b2.tar.gz /kombu-5.3.0b3.tar.gz /kombu-5.3.1.tar.gz +/kombu-5.3.2.tar.gz diff --git a/3ad075a536d177170a7a5d7b04d81e94bd5bf404.patch b/3ad075a536d177170a7a5d7b04d81e94bd5bf404.patch new file mode 100644 index 0000000..94ef6a9 --- /dev/null +++ b/3ad075a536d177170a7a5d7b04d81e94bd5bf404.patch @@ -0,0 +1,97 @@ +From 3ad075a536d177170a7a5d7b04d81e94bd5bf404 Mon Sep 17 00:00:00 2001 +From: Peter Marheine +Date: Wed, 18 Oct 2023 20:45:10 +1100 +Subject: [PATCH] Create a lock on cached_property if not present (#1811) + +* Create a lock on cached_property if not present + +This fixes #1804 (fixing breakage caused by use of undocumented +implementation details of functools.cached_property) by ensuring a lock +is always present on cached_property attributes, which is required to +safely support setting and deleting cached values in addition to +computing them on demand. + +* Add a unit test for cached_property locking +--- + kombu/utils/objects.py | 11 ++++++++++- + t/unit/utils/test_objects.py | 32 ++++++++++++++++++++++++++++++++ + 2 files changed, 42 insertions(+), 1 deletion(-) + +diff --git a/kombu/utils/objects.py b/kombu/utils/objects.py +index 737a94529..862f914b3 100644 +--- a/kombu/utils/objects.py ++++ b/kombu/utils/objects.py +@@ -2,6 +2,8 @@ + + from __future__ import annotations + ++from threading import RLock ++ + __all__ = ('cached_property',) + + try: +@@ -25,10 +27,17 @@ def __init__(self, fget=None, fset=None, fdel=None): + # This is a backport so we set this ourselves. + self.attrname = self.func.__name__ + ++ if not hasattr(self, 'lock'): ++ # Prior to Python 3.12, functools.cached_property has an ++ # undocumented lock which is required for thread-safe __set__ ++ # and __delete__. Create one if it isn't already present. ++ self.lock = RLock() ++ + def __get__(self, instance, owner=None): + # TODO: Remove this after we drop support for Python<3.8 + # or fix the signature in the cached_property package +- return super().__get__(instance, owner) ++ with self.lock: ++ return super().__get__(instance, owner) + + def __set__(self, instance, value): + if instance is None: +diff --git a/t/unit/utils/test_objects.py b/t/unit/utils/test_objects.py +index b9f1484a5..e2d1619ec 100644 +--- a/t/unit/utils/test_objects.py ++++ b/t/unit/utils/test_objects.py +@@ -1,5 +1,7 @@ + from __future__ import annotations + ++from unittest import mock ++ + from kombu.utils.objects import cached_property + + +@@ -51,3 +53,33 @@ def foo(self, value): + assert x.xx == 10 + + del x.foo ++ ++ def test_locks_on_access(self): ++ ++ class X: ++ @cached_property ++ def foo(self): ++ return 42 ++ ++ x = X() ++ ++ # Getting the value acquires the lock, and may do so recursively ++ # on Python < 3.12 because the superclass acquires it. ++ with mock.patch.object(X.foo, 'lock') as mock_lock: ++ assert x.foo == 42 ++ mock_lock.__enter__.assert_called() ++ mock_lock.__exit__.assert_called() ++ ++ # Setting a value also acquires the lock. ++ with mock.patch.object(X.foo, 'lock') as mock_lock: ++ x.foo = 314 ++ assert x.foo == 314 ++ mock_lock.__enter__.assert_called_once() ++ mock_lock.__exit__.assert_called_once() ++ ++ # .. as does clearing the cached value to recompute it. ++ with mock.patch.object(X.foo, 'lock') as mock_lock: ++ del x.foo ++ assert x.foo == 42 ++ mock_lock.__enter__.assert_called_once() ++ mock_lock.__exit__.assert_called_once() diff --git a/6ef88c3445143dde9aeaef3a95c0fb399dcb1e20.patch b/6ef88c3445143dde9aeaef3a95c0fb399dcb1e20.patch deleted file mode 100644 index f3cae6a..0000000 --- a/6ef88c3445143dde9aeaef3a95c0fb399dcb1e20.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 6ef88c3445143dde9aeaef3a95c0fb399dcb1e20 Mon Sep 17 00:00:00 2001 -From: Cyril Roelandt -Date: Mon, 19 Jun 2023 17:38:38 +0200 -Subject: [PATCH] Python3.12: fix imports in kombu/utils/objects.py - -Consider the following piece of code, very similar to what can be found -in kombu/utils/objects.py: ----8<------------------------------------------------------------------ -$ cat /tmp/x.py -try: - from functools import _NOT_FOUND - from functools import cached_property as _cached_property -except ImportError: - from cached_property import threaded_cached_property as _cached_property - _NOT_FOUND = object() - -print("OK!") ----8<------------------------------------------------------------------ - -This works well in Python3.11: ----8<------------------------------------------------------------------ -$ podman run -it --rm -v /tmp:/tmp python:3.11.4 python /tmp/x.py -OK! ----8<------------------------------------------------------------------ - -But fails in Python3.12: ----8<------------------------------------------------------------------ -$ podman run -it --rm -v /tmp:/tmp python:3.12.0b2 python /tmp/x.py -Traceback (most recent call last): - File "/tmp/x.py", line 2, in - from functools import _NOT_FOUND -ImportError: cannot import name '_NOT_FOUND' from 'functools' (/usr/local/lib/python3.12/functools.py) - -During handling of the above exception, another exception occurred: - -Traceback (most recent call last): - File "/tmp/x.py", line 6, in - from cached_property import threaded_cached_property as _cached_property -ModuleNotFoundError: No module named 'cached_property' ----8<------------------------------------------------------------------ - -This is because Python3.12 removed functools._NOT_FOUND (see commit -056dfc71dce15f81887f0bd6da09d6099d71f979), which prevents -cached_property from being imported from functools in our code. If the -cached_property library is not installed, then the imports fail. - -We should be using two different try/except blocks, but since -functools._NOT_FOUND was defined as "object()" in the standard library -anyway, let's just not bother importing it. ---- - kombu/utils/objects.py | 3 +-- - 1 file changed, 1 insertion(+), 2 deletions(-) - -diff --git a/kombu/utils/objects.py b/kombu/utils/objects.py -index eb4dfc2a1..737a94529 100644 ---- a/kombu/utils/objects.py -+++ b/kombu/utils/objects.py -@@ -5,13 +5,12 @@ - __all__ = ('cached_property',) - - try: -- from functools import _NOT_FOUND - from functools import cached_property as _cached_property - except ImportError: - # TODO: Remove this fallback once we drop support for Python < 3.8 - from cached_property import threaded_cached_property as _cached_property - -- _NOT_FOUND = object() -+_NOT_FOUND = object() - - - class cached_property(_cached_property): diff --git a/7187021ecc9f65601c50754d0fc6ec6c5264eea1.patch b/7187021ecc9f65601c50754d0fc6ec6c5264eea1.patch new file mode 100644 index 0000000..b313242 --- /dev/null +++ b/7187021ecc9f65601c50754d0fc6ec6c5264eea1.patch @@ -0,0 +1,33 @@ +From 7187021ecc9f65601c50754d0fc6ec6c5264eea1 Mon Sep 17 00:00:00 2001 +From: Asif Saif Uddin +Date: Wed, 18 Oct 2023 16:14:16 +0600 +Subject: [PATCH] using assert_called_once() in est__pop_ready_uses_lock + (#1813) + +--- + t/unit/asynchronous/test_hub.py | 2 +- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/t/unit/asynchronous/test_hub.py b/t/unit/asynchronous/test_hub.py +index e46d338df..97551dd1d 100644 +--- a/t/unit/asynchronous/test_hub.py ++++ b/t/unit/asynchronous/test_hub.py +@@ -568,4 +568,4 @@ def test__pop_ready_pops_ready_items(self): + def test__pop_ready_uses_lock(self): + with patch.object(self.hub, '_ready_lock', autospec=True) as lock: + self.hub._pop_ready() +- assert lock.__enter__.called_once() ++ lock.__enter__.assert_called_once() +diff --git a/t/unit/asynchronous/test_hub.py b/t/unit/asynchronous/test_hub.py +index 27b048b94..e46d338df 100644 +--- a/t/unit/asynchronous/test_hub.py ++++ b/t/unit/asynchronous/test_hub.py +@@ -193,7 +193,7 @@ def test_call_soon_uses_lock(self): + callback = Mock(name='callback') + with patch.object(self.hub, '_ready_lock', autospec=True) as lock: + self.hub.call_soon(callback) +- assert lock.__enter__.called_once() ++ lock.__enter__.assert_called_once() + + def test_call_soon__promise_argument(self): + callback = promise(Mock(name='callback'), (1, 2, 3)) diff --git a/python-kombu.spec b/python-kombu.spec index 33b63d8..acddb55 100644 --- a/python-kombu.spec +++ b/python-kombu.spec @@ -2,7 +2,7 @@ %global srcname kombu # Packaging unstable? # %%global prerel b3 -%global general_version 5.3.1 +%global general_version 5.3.2 %global upstream_version %{general_version}%{?prerel} Name: python-%{srcname} @@ -16,8 +16,12 @@ License: BSD and Python URL: http://kombu.readthedocs.org/ Source0: https://github.com/celery/kombu/archive/v%{upstream_version}/%{srcname}-%{upstream_version}.tar.gz -# Python 3.12: https://github.com/celery/kombu/commit/6ef88c3445143dde9aeaef3a95c0fb399dcb1e20 -#Patch01: 6ef88c3445143dde9aeaef3a95c0fb399dcb1e20.patch +# Python 3.12: https://github.com/celery/kombu/commit/3ad075a536d177170a7a5d7b04d81e94bd5bf404 +Patch01: 3ad075a536d177170a7a5d7b04d81e94bd5bf404.patch + +# Python 3.12: https://github.com/celery/kombu/commit/7187021ecc9f65601c50754d0fc6ec6c5264eea1 +# and part of https://github.com/celery/kombu/commit/6c8e7e6b2815ae03eb77f7625c3c196e0390a749 +Patch02: 7187021ecc9f65601c50754d0fc6ec6c5264eea1.patch BuildArch: noarch @@ -36,9 +40,6 @@ Summary: %{summary} Requires: python3-amqp Requires: python3-vine -# Remove once https://github.com/celery/kombu/issues/1668 gets resolved -Requires: python3-cached_property - BuildRequires: python3-devel BuildRequires: python3-setuptools %if %{with tests} @@ -57,9 +58,6 @@ BuildRequires: python3-brotli BuildRequires: python3-hypothesis BuildRequires: python3-pytest-freezegun BuildRequires: python3-azure-identity - -# Remove once https://github.com/celery/kombu/issues/1668 gets resolved -BuildRequires: python3-cached_property %endif %description -n python3-%{srcname} @@ -83,10 +81,7 @@ also provide proven and tested solutions to common messaging problems. %check %if %{with tests} -# https://github.com/celery/kombu/issues/1765 -# Seems like test/tooling failures, not the actual code issues, eg. -# AttributeError: 'called_once' is not a valid assertion. Use a spec for the mock if 'called_once' is meant to be an attribute. -%pytest -k 'not test_entrypoints and not test_call_soon_uses_lock and not test__pop_ready_uses_lock' +%pytest %endif %files -n python3-%{srcname} diff --git a/sources b/sources index f99b9d5..ec161cd 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (kombu-5.3.1.tar.gz) = fc913a60a2eebb6d236b334e2ee484c416c17bbb894177ff7c3a1c9c4d75599bac20ad9fa894ded72d1b3a8f9a7001720e344f849ce334d1c6cca734ab8d1eff +SHA512 (kombu-5.3.2.tar.gz) = c7a760c1340a775ca204c04e5aa11b27c713c9d75b2160f9f7ef9f165f8df076f50cc01e70a471f94269c1613aec26547f07f60f4cdd2190395211c77c594632