From 8b14a16c57cc499abf4b4ee06a5ffadb20f0475c Mon Sep 17 00:00:00 2001 From: Balazs Gibizer Date: Thu, 29 Jan 2026 15:41:09 +0100 Subject: [PATCH] Fix full executor warning on noname executor The warning log assumed all executors has a name. Our centrally managed executors has but not the adhoc ones causing a stack trace in the compute manager power_sync periodics. Change-Id: I04620364439a6c377f5b8f8f68cbdd3c62c44562 Signed-off-by: Balazs Gibizer --- nova/tests/unit/compute/test_compute_mgr.py | 17 ++++++---- nova/tests/unit/test_utils.py | 35 +++++++++++++++++++++ nova/utils.py | 2 +- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index fab9a9719a..aba391af3a 100644 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -4139,15 +4139,20 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase, @mock.patch.object(objects.InstanceList, 'get_by_host') def test_sync_power_states(self, mock_get): + # Force an RLock as the test uses a sync executor as the task is run + # on the caller thread and both the caller and the task needs the lock + self.compute._syncs_in_progress_lock = threading.RLock() instance = mock.Mock() mock_get.return_value = [instance] - with mock.patch('nova.utils.spawn_on') as mock_spawn: + with mock.patch.object( + self.compute, '_query_driver_power_state_and_sync' + ) as mock_sync: self.compute._sync_power_states(mock.sentinel.context) - mock_get.assert_called_with(mock.sentinel.context, - self.compute.host, expected_attrs=[], - use_slave=True) - mock_spawn.assert_called_once_with( - self.compute._sync_power_executor, mock.ANY, instance) + + mock_get.assert_called_with(mock.sentinel.context, + self.compute.host, expected_attrs=[], + use_slave=True) + mock_sync.assert_called_once_with(mock.sentinel.context, instance) @mock.patch('nova.objects.InstanceList.get_by_host', new=mock.Mock()) @mock.patch('nova.compute.manager.ComputeManager.' diff --git a/nova/tests/unit/test_utils.py b/nova/tests/unit/test_utils.py index 46c109922d..55ee29b7fa 100644 --- a/nova/tests/unit/test_utils.py +++ b/nova/tests/unit/test_utils.py @@ -1559,6 +1559,41 @@ class SpawnOnTestCase(test.NoDBTestCase): 'nova.tests.unit.test_utils.SpawnOnTestCase.' 'test_spawn_on_warns_on_full_executor.cell_worker', task) + @mock.patch.object( + utils, 'concurrency_mode_threading', new=mock.Mock(return_value=True)) + @mock.patch.object(utils.LOG, 'warning') + def test_spawn_on_warns_on_full_executor_noname(self, mock_warning): + # Ensure we have executor for a single task only at a time + executor = utils.create_executor(max_workers=1) + + work = threading.Event() + started = threading.Event() + + # let the blocked tasks finish after the test case so that the leaked + # thread check is not triggered during cleanup + self.addCleanup(work.set) + + def task(): + started.set() + work.wait() + + # Start two tasks that will wait, the first will execute the second + # will wait in the queue + utils.spawn_on(executor, task) + utils.spawn_on(executor, task) + # wait for the first task to consume the single executor thread + started.wait() + # start one more task to trigger the fullness check. + utils.spawn_on(executor, task) + + # We expect that spawn_on will warn due to the second task being is + # waiting in the queue, and no idle worker thread exists. + mock_warning.assert_called_once_with( + 'The %s pool does not have free threads so the task %s will be ' + 'queued. If this happens repeatedly then the size of the pool is ' + 'too small for the load or there are stuck threads filling the ' + 'pool.', 'unknown', task) + class ExecutorStatsTestCase(test.NoDBTestCase): diff --git a/nova/utils.py b/nova/utils.py index 75ae131235..fb02373243 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -616,7 +616,7 @@ def spawn_on( "The %s pool does not have free threads so the task %s will be " "queued. If this happens repeatedly then the size of the pool is " "too small for the load or there are stuck threads filling the " - "pool.", executor.name, func) + "pool.", getattr(executor, "name", "unknown"), func) _context = common_context.get_current() profiler_info = _serialize_profile_info()