Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-oslo.messaging for 
openSUSE:Factory checked in at 2026-10-26 10:01:53
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-oslo.messaging (Old)
 and      /work/SRC/openSUSE:Factory/.python-oslo.messaging.new.2252680 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-oslo.messaging"

Mon Oct 26 10:01:53 2026 rev:40 rq:1392500 version:18.3.1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-oslo.messaging/python-oslo.messaging.changes  
    2026-09-08 16:58:01.511759085 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-oslo.messaging.new.2252680/python-oslo.messaging.changes
 2026-10-26 10:03:15.355900890 +0100
@@ -1,0 +2,9 @@
+Sun Oct 25 17:41:36 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 18.3.1:
+  * Fix QManager reply queue collision under cotyledon/threading
+  * Fix rabbit consumer reconnection loop
+  * Update TOX_CONSTRAINTS_FILE for stable/2026.2
+  * Update .gitreview for stable/2026.2
+
+-------------------------------------------------------------------

Old:
----
  oslo_messaging-18.3.0.tar.gz

New:
----
  oslo_messaging-18.3.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-oslo.messaging.spec ++++++
--- /var/tmp/diff_new_pack.UccplY/_old  2026-10-26 10:03:16.036929450 +0100
+++ /var/tmp/diff_new_pack.UccplY/_new  2026-10-26 10:03:16.038929534 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           python-oslo.messaging
-Version:        18.3.0
+Version:        18.3.1
 Release:        0
 Summary:        OpenStack oslo.messaging library
 License:        Apache-2.0

++++++ oslo_messaging-18.3.0.tar.gz -> oslo_messaging-18.3.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo_messaging-18.3.0/AUTHORS 
new/oslo_messaging-18.3.1/AUTHORS
--- old/oslo_messaging-18.3.0/AUTHORS   2026-08-24 16:00:30.821021800 +0200
+++ new/oslo_messaging-18.3.1/AUTHORS   2026-10-19 13:24:06.831040400 +0200
@@ -122,12 +122,14 @@
 Juan Antonio Osorio Robles <[email protected]>
 Juan Larriba <[email protected]>
 Julien Danjou <[email protected]>
+Julien Le Jeune <[email protected]>
 Kenneth Giusti <[email protected]>
 Kevin Benton <[email protected]>
 Kirill Bespalov <[email protected]>
 Komei Shimamura <[email protected]>
 Konstantin Kalin <[email protected]>
 Kui Shi <[email protected]>
+Kushnarenko Nikita <[email protected]>
 LIU Yulong <[email protected]>
 Lance Bragstad <[email protected]>
 Li Ma <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo_messaging-18.3.0/ChangeLog 
new/oslo_messaging-18.3.1/ChangeLog
--- old/oslo_messaging-18.3.0/ChangeLog 2026-08-24 16:00:30.655008000 +0200
+++ new/oslo_messaging-18.3.1/ChangeLog 2026-10-19 13:24:06.662030700 +0200
@@ -1,6 +1,14 @@
 CHANGES
 =======
 
+18.3.1
+------
+
+* Fix QManager reply queue collision under cotyledon/threading
+* Fix rabbit consumer reconnection loop
+* Update TOX\_CONSTRAINTS\_FILE for stable/2026.2
+* Update .gitreview for stable/2026.2
+
 18.3.0
 ------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo_messaging-18.3.0/PKG-INFO 
new/oslo_messaging-18.3.1/PKG-INFO
--- old/oslo_messaging-18.3.0/PKG-INFO  2026-08-24 16:00:30.983034800 +0200
+++ new/oslo_messaging-18.3.1/PKG-INFO  2026-10-19 13:24:07.000049800 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: oslo.messaging
-Version: 18.3.0
+Version: 18.3.1
 Summary: Oslo Messaging API
 Author-email: OpenStack <[email protected]>
 Project-URL: Homepage, https://docs.openstack.org/oslo.messaging
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo_messaging-18.3.0/oslo.messaging.egg-info/PKG-INFO 
new/oslo_messaging-18.3.1/oslo.messaging.egg-info/PKG-INFO
--- old/oslo_messaging-18.3.0/oslo.messaging.egg-info/PKG-INFO  2026-08-24 
16:00:30.827022000 +0200
+++ new/oslo_messaging-18.3.1/oslo.messaging.egg-info/PKG-INFO  2026-10-19 
13:24:06.836040500 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: oslo.messaging
-Version: 18.3.0
+Version: 18.3.1
 Summary: Oslo Messaging API
 Author-email: OpenStack <[email protected]>
 Project-URL: Homepage, https://docs.openstack.org/oslo.messaging
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo_messaging-18.3.0/oslo.messaging.egg-info/SOURCES.txt 
new/oslo_messaging-18.3.1/oslo.messaging.egg-info/SOURCES.txt
--- old/oslo_messaging-18.3.0/oslo.messaging.egg-info/SOURCES.txt       
2026-08-24 16:00:30.905028600 +0200
+++ new/oslo_messaging-18.3.1/oslo.messaging.egg-info/SOURCES.txt       
2026-10-19 13:24:06.921045500 +0200
@@ -111,6 +111,7 @@
 oslo_messaging/tests/test_utils.py
 oslo_messaging/tests/utils.py
 oslo_messaging/tests/drivers/__init__.py
+oslo_messaging/tests/drivers/test_amqpdriver.py
 oslo_messaging/tests/drivers/test_impl_kafka.py
 oslo_messaging/tests/drivers/test_impl_rabbit.py
 oslo_messaging/tests/drivers/test_pool.py
@@ -164,6 +165,7 @@
 releasenotes/notes/drop-python27-support-5ef2f365d8930483.yaml
 releasenotes/notes/enforce_fips_mode-07dd259eb8a73c2b.yaml
 releasenotes/notes/fix-access_policy-deafult-a6954a147cb002b0.yaml
+releasenotes/notes/fix-qmanager-pgid-setsid-race-0a1b2c3d4e5f6a7b.yaml
 releasenotes/notes/fix-queue-manager-with-podman-36078437037fa198.yaml
 releasenotes/notes/get-rpc-client-0b4aa62160864b29.yaml
 releasenotes/notes/get-rpc-helpers-cls-8911826ac08aef2a.yaml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo_messaging-18.3.0/oslo.messaging.egg-info/pbr.json 
new/oslo_messaging-18.3.1/oslo.messaging.egg-info/pbr.json
--- old/oslo_messaging-18.3.0/oslo.messaging.egg-info/pbr.json  2026-08-24 
16:00:30.844023500 +0200
+++ new/oslo_messaging-18.3.1/oslo.messaging.egg-info/pbr.json  2026-10-19 
13:24:06.853041600 +0200
@@ -1 +1 @@
-{"git_version": "3b02f8ab", "is_release": true}
\ No newline at end of file
+{"git_version": "9df3fda5", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo_messaging-18.3.0/oslo_messaging/_drivers/amqpdriver.py 
new/oslo_messaging-18.3.1/oslo_messaging/_drivers/amqpdriver.py
--- old/oslo_messaging-18.3.0/oslo_messaging/_drivers/amqpdriver.py     
2026-08-24 15:59:42.959014700 +0200
+++ new/oslo_messaging-18.3.1/oslo_messaging/_drivers/amqpdriver.py     
2026-10-19 13:23:14.348776600 +0200
@@ -63,21 +63,46 @@
         self.file_name = '/dev/shm/{}_{}_qmanager'.format(  # nosec
             self.hostname,
             self.processname)
-        # We use the process group to restart the counter on service restart
-        self.pg = os.getpgrp()
 
-        # We need to also handle containerized deployments, so let's
-        # parse start time (in jiffies) since system boot
+    @staticmethod
+    def _service_identity():
+        # The identity of the current *generation* of the service (the master
+        # and all its workers), used to decide whether the shared counter must
+        # be reset (the service restarted) or incremented.
         #
+        # It MUST be evaluated when the queue name is built, NOT captured at
+        # object construction time, because the process group can change
+        # *after* the oslo.messaging objects already exist: any supervisor
+        # that calls os.setsid() once its objects are instantiated moves the
+        # process into a new group. This is what happens with the cotyledon
+        # supervisor, whose master calls os.setsid() after the default
+        # transport is created (before the service starts serving) and then
+        # forks the workers that actually call get(). A QManager built in the
+        # master before setsid
+        # captures the *pre-setsid* process group (the container init group),
+        # while the workers that actually call get() run *after* setsid in the
+        # master's own group. As every QManager of the process (reply queue,
+        # per-cell transports, per-connection fanout queues) shares the same
+        # /dev/shm counter, mixing a master-born identity with a worker-born
+        # one makes them reset the counter for one another: several processes
+        # then build the same reply queue name (reply_<host>:<proc>:1) and
+        # steal each other's RPC replies.
+        # See https://bugs.launchpad.net/oslo.messaging/+bug/2110957
+        pg = os.getpgrp()
+
+        # Disambiguate a recycled pid across a restart with the start time (in
+        # jiffies since system boot) of that process.
         # https://www.man7.org/linux/man-pages//man5/proc_pid_stat.5.html
         #
-        # We fallback to process id here when process group id is 0, as /proc/0
-        # does not exist. This is only hit in an edge case with the crun
-        # container runtime (default for podman), see issue for more details:
+        # We fall back to our own pid when the process group id is 0, as
+        # /proc/0 does not exist. This is only hit in an edge case with the
+        # crun container runtime (default for podman) where a "podman exec"
+        # process gets no process group of its own, see
         # https://github.com/containers/crun/issues/1642
-        proc_id = self.pg or os.getpid()
+        proc_id = pg or os.getpid()
         with open(f'/proc/{proc_id}/stat') as f:
-            self.start_time = int(f.read().split()[21])
+            start_time = int(f.read().split()[21])
+        return pg, start_time
 
     def get(self):
         lock_name = 'oslo_read_shm_{}_{}'.format(
@@ -85,6 +110,11 @@
 
         @lockutils.synchronized(lock_name, external=True)
         def read_from_shm():
+            # Identity of the current service generation (the process group
+            # shared by the master and all its workers), re-evaluated on every
+            # call -- see _service_identity for why it must not be cached.
+            cur_pg, cur_start_time = self._service_identity()
+
             # Grab the counter from shm
             # This function is thread and process safe thanks to lockutils
             try:
@@ -94,12 +124,12 @@
                     counter = int(counter)
                     start_time = int(start_time)
             except (FileNotFoundError, ValueError):
-                pg = self.pg
+                pg = cur_pg
                 counter = 0
-                start_time = self.start_time
+                start_time = cur_start_time
 
             # Increment the counter
-            if pg == self.pg and start_time == self.start_time:
+            if pg == cur_pg and start_time == cur_start_time:
                 counter += 1
             else:
                 # The process group is changed, or start time since system boot
@@ -109,8 +139,8 @@
 
             # Write the new counter
             with open(self.file_name, 'w') as f:
-                f.write(str(self.pg) + ':' + str(counter) + ':' +
-                        str(self.start_time))
+                f.write(str(cur_pg) + ':' + str(counter) + ':' +
+                        str(cur_start_time))
             return counter
 
         counter = read_from_shm()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo_messaging-18.3.0/oslo_messaging/_drivers/impl_rabbit.py 
new/oslo_messaging-18.3.1/oslo_messaging/_drivers/impl_rabbit.py
--- old/oslo_messaging-18.3.0/oslo_messaging/_drivers/impl_rabbit.py    
2026-08-24 15:59:42.971978700 +0200
+++ new/oslo_messaging-18.3.1/oslo_messaging/_drivers/impl_rabbit.py    
2026-10-19 13:23:14.353781200 +0200
@@ -1117,6 +1117,10 @@
             # NOTE(gsantomaggio): we must reraise this without
             # trigger error_callback
             raise
+        except rpc_common.Timeout:
+            # NOTE must reraise this without
+            # trigger error_callback
+            raise
         except Exception as exc:
             error_callback and error_callback(exc)
             self._set_current_channel(None)
@@ -1372,11 +1376,17 @@
             LOG.error('Failed to consume message from queue: %s', exc)
 
         def _consume():
-            # NOTE(sileht): in case the acknowledgment or requeue of a
-            # message fail, the kombu transport can be disconnected
-            # In this case, we must redeclare our consumers, so raise
-            # a recoverable error to trigger the reconnection code.
+            # NOTE: Kombu transport can be interrupted if the acknowledgement
+            # or requeue of a message fails. We need to ensure the connection
+            # and re-declare consumers.
+            # Kombu's ensure_connection utilises a failover strategy, instead
+            # of retring to connect to the same host.
             if not self.connection.connected:
+                # NOTE(bug/2126768): When a rabbit host is dead, reconnecting
+                # to the same host would loop forever until the node is fixed.
+                # Instead switch to the next host given by the failover
+                # strategy and raise a recoverable connection error.
+                self.connection.maybe_switch_next()
                 raise self.connection.recoverable_connection_errors[0]
 
             while self._new_tags:
@@ -1391,13 +1401,18 @@
                 if self._consume_loop_stopped:
                     return
 
-                if self._heartbeat_supported_and_enabled():
-                    self._heartbeat_check()
-
                 try:
                     self.connection.drain_events(timeout=poll_timeout)
                     return
                 except TimeoutError:
+                    # Note(lajoskatona): Heartbeat check is only needed
+                    # on timeout (idle connection). When drain_events
+                    # returns successfully, the connection is clearly
+                    # alive, as kombu's own ConsumerMixin behavior works
+                    # 
https://github.com/celery/kombu/blob/3633f916e455ad4d8835ee23abc0b526f5920ed7/kombu/mixins.py#L197-L198
+                    if self._heartbeat_supported_and_enabled():
+                        self._heartbeat_check()
+
                     poll_timeout = timer.check_return(
                         _raise_timeout, maximum=self._poll_timeout)
                 except self.connection.channel_errors as exc:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo_messaging-18.3.0/oslo_messaging/tests/drivers/test_amqpdriver.py 
new/oslo_messaging-18.3.1/oslo_messaging/tests/drivers/test_amqpdriver.py
--- old/oslo_messaging-18.3.0/oslo_messaging/tests/drivers/test_amqpdriver.py   
1970-01-01 01:00:00.000000000 +0100
+++ new/oslo_messaging-18.3.1/oslo_messaging/tests/drivers/test_amqpdriver.py   
2026-10-19 13:23:14.365792500 +0200
@@ -0,0 +1,145 @@
+# Copyright 2026 OVHcloud
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import os
+from unittest import mock
+
+import fixtures
+from oslo_concurrency import lockutils
+
+from oslo_messaging._drivers import amqpdriver
+from oslo_messaging.tests import utils as test_utils
+
+
+class QManagerTestCase(test_utils.BaseTestCase):
+    """Tests for the reply/fanout queue name manager (use_queue_manager).
+
+    Regression coverage for
+    https://bugs.launchpad.net/oslo.messaging/+bug/2110957 : the per-process
+    identity used to key the shared /dev/shm counter must be evaluated when the
+    queue name is built (get()), not cached at __init__ time, otherwise a
+    QManager created in the cotyledon master before os.setsid() and a QManager
+    created later in a worker disagree on the identity, keep resetting the
+    shared counter, and hand out duplicate reply queue names.
+    """
+
+    def setUp(self):
+        super().setUp()
+        # get() takes an external (file) lock via lockutils; give it a path.
+        lock_path = self.useFixture(fixtures.TempDir()).path
+        lockutils.set_defaults(lock_path=lock_path)
+
+    def _make_qmanager(self):
+        tmp = self.useFixture(fixtures.TempDir()).path
+        qm = amqpdriver.QManager(hostname='host', processname='conductor')
+        # Do not touch the real /dev/shm.
+        qm.file_name = os.path.join(tmp, 'host_conductor_qmanager')
+        return qm
+
+    def test_get_increments_for_a_stable_identity(self):
+        qm = self._make_qmanager()
+        with mock.patch.object(qm, '_service_identity',
+                               return_value=(7, 111)):
+            self.assertEqual('host:conductor:1', qm.get())
+            self.assertEqual('host:conductor:2', qm.get())
+            self.assertEqual('host:conductor:3', qm.get())
+
+    def test_get_resets_when_identity_changes(self):
+        # A different (pg, start_time) means the service was restarted: the
+        # counter must start over so queue names get reused.
+        qm = self._make_qmanager()
+        with mock.patch.object(qm, '_service_identity',
+                               return_value=(7, 111)):
+            self.assertEqual('host:conductor:1', qm.get())
+            self.assertEqual('host:conductor:2', qm.get())
+        with mock.patch.object(qm, '_service_identity',
+                               return_value=(9, 222)):
+            self.assertEqual('host:conductor:1', qm.get())
+
+    def test_get_resets_on_restart_with_constant_pid(self):
+        # Containerised service running as a constant pid/pgid across restarts
+        # (bug 2078935): the process group is unchanged but the start_time (in
+        # jiffies since boot) differs, so the restart must still be detected
+        # and the counter reset. This is why start_time is part of the key.
+        qm = self._make_qmanager()
+        with mock.patch.object(qm, '_service_identity',
+                               return_value=(1, 111)):
+            self.assertEqual('host:conductor:1', qm.get())
+            self.assertEqual('host:conductor:2', qm.get())
+        # Same pgid (1), different start_time -> restarted.
+        with mock.patch.object(qm, '_service_identity',
+                               return_value=(1, 999)):
+            self.assertEqual('host:conductor:1', qm.get())
+
+    def test_identity_evaluated_at_get_time_not_at_init(self):
+        # The bug: object built in the master (pre-setsid pgid) but used by a
+        # worker (post-setsid pgid). With the identity resolved at get() time,
+        # what matters is the *calling* process, not the constructing one.
+        qm = self._make_qmanager()  # "constructed in the master"
+
+        # Now the worker (pgid 7) uses it: the stored identity must be the
+        # worker's, so the reply queue is stable and no longer keyed on the
+        # pre-setsid process group of the master.
+        with mock.patch.object(qm, '_service_identity',
+                               return_value=(7, 111)):
+            self.assertEqual('host:conductor:1', qm.get())
+            self.assertEqual('host:conductor:2', qm.get())
+
+    def test_no_collision_between_master_born_and_worker_born_managers(self):
+        # Two QManagers sharing the same shm file (e.g. the reply-queue
+        # manager inherited from the master and a fanout manager created in
+        # the worker).
+        # Before the fix their cached self.pg differed (1 vs 7) and they reset
+        # the counter for one another, both landing on ":1". Now both resolve
+        # the identity at get() time -> same worker identity -> the counter
+        # keeps growing and names stay unique.
+        reply_qm = self._make_qmanager()          # master-born
+        fanout_qm = self._make_qmanager()         # worker-born
+        fanout_qm.file_name = reply_qm.file_name  # same shared counter
+
+        worker_identity = (7, 111)
+        with mock.patch.object(reply_qm, '_service_identity',
+                               return_value=worker_identity), \
+                mock.patch.object(fanout_qm, '_service_identity',
+                                  return_value=worker_identity):
+            first = reply_qm.get()
+            second = fanout_qm.get()
+
+        self.assertNotEqual(first, second)
+        self.assertEqual(['host:conductor:1', 'host:conductor:2'],
+                         [first, second])
+
+    def test_service_identity_uses_process_group(self):
+        stat = ('7 (nova-conductor) S 1 7 7 0 -1 0 0 0 0 0 0 0 0 0 20 0 2 0 '
+                '424242 0 0')
+        with mock.patch.object(amqpdriver.os, 'getpgrp', return_value=7), \
+                mock.patch('builtins.open', mock.mock_open(read_data=stat)):
+            pg, start_time = amqpdriver.QManager._service_identity()
+        self.assertEqual(7, pg)
+        self.assertEqual(424242, start_time)
+
+    def test_service_identity_falls_back_to_pid_when_pgrp_zero(self):
+        # crun edge case (https://github.com/containers/crun/issues/1642):
+        # os.getpgrp() returns 0 and /proc/0 does not exist, so the start_time
+        # is read from our own pid instead. This is evaluated here at call
+        # time, no longer relying on the identity cached at construction. The
+        # returned pgid stays 0, which is shared across the exec'd processes.
+        stat = ('123 (nova-conductor) S 1 123 123 0 -1 0 0 0 0 0 0 0 0 0 20 0 '
+                '2 0 424242 0 0')
+        with mock.patch.object(amqpdriver.os, 'getpgrp', return_value=0), \
+                mock.patch.object(amqpdriver.os, 'getpid', return_value=123), \
+                mock.patch('builtins.open', mock.mock_open(read_data=stat)):
+            pg, start_time = amqpdriver.QManager._service_identity()
+        self.assertEqual(0, pg)
+        self.assertEqual(424242, start_time)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo_messaging-18.3.0/oslo_messaging/tests/drivers/test_impl_rabbit.py 
new/oslo_messaging-18.3.1/oslo_messaging/tests/drivers/test_impl_rabbit.py
--- old/oslo_messaging-18.3.0/oslo_messaging/tests/drivers/test_impl_rabbit.py  
2026-08-24 15:59:42.991923300 +0200
+++ new/oslo_messaging-18.3.1/oslo_messaging/tests/drivers/test_impl_rabbit.py  
2026-10-19 13:23:14.366793600 +0200
@@ -486,6 +486,64 @@
                 # Ensure a new channel have been setuped
                 self.assertNotEqual(channel, conn.connection.channel)
 
+    def test_consume_switches_host_when_disconnected(self):
+        transport = oslo_messaging.get_transport(self.conf,
+                                                 'kombu+memory:////')
+        self.addCleanup(transport.cleanup)
+        with transport._driver._get_connection(
+                driver_common.PURPOSE_LISTEN) as conn:
+            with mock.patch('kombu.connection.Connection.connected',
+                            new_callable=mock.PropertyMock,
+                            return_value=False), \
+                 mock.patch('kombu.connection.Connection.'
+                            'recoverable_connection_errors',
+                            new_callable=mock.PropertyMock,
+                            return_value=(driver_common.Timeout,)), \
+                 mock.patch.object(
+                     conn.connection.connection,
+                     'maybe_switch_next') as mock_switch:
+                # The recoverable error is raised repeatedly until the
+                # consume timeout expires and Timeout is re-raised.
+                self.assertRaises(driver_common.Timeout,
+                                  conn.consume, timeout=0.01)
+                # We failed over to the next host at least once ...
+                mock_switch.assert_called()
+
+    def test_heartbeat_check_only_on_timeout(self):
+        transport = oslo_messaging.get_transport(self.conf,
+                                                 'kombu+memory:////')
+        self.addCleanup(transport.cleanup)
+        with transport._driver._get_connection(
+                driver_common.PURPOSE_LISTEN) as conn:
+            with mock.patch.object(
+                    conn.connection, '_heartbeat_check') as mock_hb:
+                conn.connection.connection.drain_events = mock.Mock(
+                    return_value=None)
+                conn.consume(timeout=0.01)
+                # heartbeat_check should NOT be called when
+                # drain_events succeeds
+                mock_hb.assert_not_called()
+
+    def test_heartbeat_check_called_on_drain_events_timeout(self):
+        transport = oslo_messaging.get_transport(self.conf,
+                                                 'kombu+memory:////')
+        self.addCleanup(transport.cleanup)
+        with transport._driver._get_connection(
+                driver_common.PURPOSE_LISTEN) as conn:
+            with mock.patch.object(
+                    conn.connection, '_heartbeat_check') as mock_hb, \
+                 mock.patch.object(
+                    conn.connection,
+                    '_heartbeat_supported_and_enabled',
+                    return_value=True):
+                conn.connection.connection.drain_events = mock.Mock(
+                    side_effect=TimeoutError)
+                self.assertRaises(driver_common.Timeout,
+                                  conn.consume, timeout=0.01)
+                # _heartbeat_check is only called when timeout raised,
+                # otherwise it is unnecessary as the connection is alive.
+                mock_hb.assert_called()
+
 
 class TestRabbitTransportURL(test_utils.BaseTestCase):
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo_messaging-18.3.0/releasenotes/notes/fix-qmanager-pgid-setsid-race-0a1b2c3d4e5f6a7b.yaml
 
new/oslo_messaging-18.3.1/releasenotes/notes/fix-qmanager-pgid-setsid-race-0a1b2c3d4e5f6a7b.yaml
--- 
old/oslo_messaging-18.3.0/releasenotes/notes/fix-qmanager-pgid-setsid-race-0a1b2c3d4e5f6a7b.yaml
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/oslo_messaging-18.3.1/releasenotes/notes/fix-qmanager-pgid-setsid-race-0a1b2c3d4e5f6a7b.yaml
    2026-10-19 13:23:14.303733800 +0200
@@ -0,0 +1,17 @@
+---
+fixes:
+  - |
+    Fixed a reply queue name collision with ``use_queue_manager = True``. The
+    ``QManager`` used to only rely on the process-group identity captured at
+    object construction time to key the shared ``/dev/shm`` counter. When the
+    process group changes after some oslo.messaging objects already exist --
+    as happens under the cotyledon supervisor, whose master calls
+    ``os.setsid()`` after creating them -- a manager born in the master and a
+    manager born in a worker disagreed on that identity and kept resetting the
+    shared counter, so several processes built the same reply
+    queue name (``reply_<host>:<proc>:1``) and stole each other's RPC replies
+    (leading to ``No calling threads waiting for msg_id`` and
+    ``MessagingTimeout``). The identity is now re-evaluated when the queue
+    name is built. When the process group id is 0 (crun edge case) it falls
+    back to the process id, as before. See
+    `bug 2110957 <https://bugs.launchpad.net/oslo.messaging/+bug/2110957>`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo_messaging-18.3.0/tox.ini 
new/oslo_messaging-18.3.1/tox.ini
--- old/oslo_messaging-18.3.0/tox.ini   2026-08-24 15:59:43.053770500 +0200
+++ new/oslo_messaging-18.3.1/tox.ini   2026-10-19 13:23:14.378805000 +0200
@@ -8,7 +8,7 @@
   ZUUL_CACHE_DIR
   REQUIREMENTS_PIP_LOCATION
 constraints =
-  
{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+  
{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2026.2}
 deps =
   -r{toxinidir}/test-requirements.txt
 commands =

Reply via email to