From 1cd8585b2992564c9069a59d5646807531c86215 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jul 10 2025 15:48:13 +0000 Subject: [PATCH 1/3] rollback on IntegrityError in get_next_build --- diff --git a/kojihub/kojihub.py b/kojihub/kojihub.py index d48a549..67db4ec 100644 --- a/kojihub/kojihub.py +++ b/kojihub/kojihub.py @@ -4859,9 +4859,11 @@ def get_next_build(build_info): return new_build(build_info) build_info['release'] = get_next_release(build_info) for try_no in range(2, 10): + savepoint = Savepoint('get_next_build_pre_insert') try: return new_build(build_info) except IntegrityError: + savepoint.rollback() build_info['release'] = get_next_release(build_info, try_no) # otherwise raise koji.GenericError("Can't find available release") From bf27153e1e5b909bdf27a20914825e061fad14a2 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jul 10 2025 15:48:13 +0000 Subject: [PATCH 2/3] unit test --- diff --git a/tests/test_hub/test_get_next_build.py b/tests/test_hub/test_get_next_build.py index ecbaa68..7661386 100644 --- a/tests/test_hub/test_get_next_build.py +++ b/tests/test_hub/test_get_next_build.py @@ -12,6 +12,7 @@ class TestGetNextBuild(unittest.TestCase): self.get_next_release = mock.patch('kojihub.kojihub.get_next_release').start() self.new_build = mock.patch('kojihub.kojihub.new_build').start() self._dml = mock.patch('kojihub.kojihub._dml').start() + self.Savepoint = mock.patch('kojihub.kojihub.Savepoint').start() self.binfo = {'name': 'name', 'version': 'version'} def tearDown(self): From ecfa7766b5cc76d79db03bd8233ab65d11a2d4ea Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jul 10 2025 15:48:13 +0000 Subject: [PATCH 3/3] fix a separate race case In a very close race, one side can first see the duplicate nvr during the get_build check in new_build, which leads to an attempt to recycle, which fails because the duplicate is in the BUILDING state. --- diff --git a/kojihub/kojihub.py b/kojihub/kojihub.py index 67db4ec..f62949b 100644 --- a/kojihub/kojihub.py +++ b/kojihub/kojihub.py @@ -4861,8 +4861,9 @@ def get_next_build(build_info): for try_no in range(2, 10): savepoint = Savepoint('get_next_build_pre_insert') try: - return new_build(build_info) - except IntegrityError: + # using strict so we don't try to recycle + return new_build(build_info, strict=True) + except (IntegrityError, koji.GenericError): savepoint.rollback() build_info['release'] = get_next_release(build_info, try_no) # otherwise