From d3f9154ce213a6587569c14f3b52aa2a8f820c99 Mon Sep 17 00:00:00 2001 From: Balazs Gibizer Date: Sun, 9 Nov 2025 11:47:20 +0100 Subject: [PATCH] Make image upload tpool usage conditional When running in eventlet mode we keep the original eventlet.tpool usage but when running in threading mode we call the functions directly on the thread of the caller. The original tpool_execute() call made the upload call running in a native tread as it has parts that are blocking and therefore running them in the current greenthread would make the other greenthreads starved. After this patch we preserve the same effect but with different syntax. We use tpool_wrap to wrap the function into a tpool.Proxy object and then call the proxy object. That proxy ensures that any call on the proxy object is run in a native thread. This change is useful for us in native threaded mode. There the tpool_wrap returns the function unchanged. So upload is executed in the caller's native thread. This is OK as in native threaded mode any concurrent tasks are also in native threads and native threads are preempted when needed. So other tasks will not be starved. Change-Id: Iddb8b317b7a883c6fd144a93aca862a792fcd1af Signed-off-by: Balazs Gibizer --- nova/image/glance.py | 9 ++++----- nova/tests/unit/image/test_glance.py | 11 ++++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/nova/image/glance.py b/nova/image/glance.py index 3af555978d..262624a60a 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -585,11 +585,10 @@ class GlanceImageServiceV2(object): _reraise_translated_exception() def _upload_data(self, context, image_id, data): - # NOTE(aarents) offload upload in a native thread as it can block - # coroutine in busy environment. - utils.tpool_execute(self._client.call, - context, 2, 'upload', - args=(image_id, data)) + # NOTE(aarents) In eventlet mode offload upload in a native thread as + # it can block coroutine in busy environment. + utils.tpool_wrap( + self._client.call)(context, 2, 'upload', args=(image_id, data)) return self._client.call(context, 2, 'get', args=(image_id,)) diff --git a/nova/tests/unit/image/test_glance.py b/nova/tests/unit/image/test_glance.py index 769d797a05..25f0750765 100644 --- a/nova/tests/unit/image/test_glance.py +++ b/nova/tests/unit/image/test_glance.py @@ -1914,13 +1914,12 @@ class TestCreate(test.NoDBTestCase): class TestUpdate(test.NoDBTestCase): """Tests the update method of the GlanceImageServiceV2.""" - @mock.patch('nova.utils.tpool_execute', - side_effect=nova.utils.tpool_execute) + @mock.patch('nova.utils.tpool_wrap') @mock.patch('nova.image.glance.GlanceImageServiceV2.show') @mock.patch('nova.image.glance._translate_from_glance') @mock.patch('nova.image.glance._translate_to_glance') def test_update_success_v2( - self, trans_to_mock, trans_from_mock, show_mock, texec_mock): + self, trans_to_mock, trans_from_mock, show_mock, twrap_mock): image = { 'id': mock.sentinel.image_id, 'name': mock.sentinel.name, @@ -1937,6 +1936,7 @@ class TestUpdate(test.NoDBTestCase): trans_from_mock.return_value = mock.sentinel.trans_from client = mock.MagicMock() client.call.return_value = mock.sentinel.image_meta + twrap_mock.return_value = client.call ctx = mock.sentinel.ctx show_mock.return_value = { 'image_id': mock.sentinel.image_id, @@ -1969,8 +1969,9 @@ class TestUpdate(test.NoDBTestCase): data=mock.sentinel.data) self.assertEqual(3, client.call.call_count) - texec_mock.assert_called_once_with( - client.call, ctx, 2, 'upload', + twrap_mock.assert_called_once_with(client.call) + twrap_mock.return_value.assert_any_call( + ctx, 2, 'upload', args=(mock.sentinel.image_id, mock.sentinel.data))