Skip to content

Commit 5eb8af0

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Fix unbounded thread pool in OVN MaintenanceThread" into stable/2026.1
2 parents 61f100d + 461e7ac commit 5eb8af0

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ def start(self):
156156
if self._thread is None:
157157
self._worker = periodics.PeriodicWorker(
158158
self._callables,
159-
executor_factory=futurist.ThreadPoolExecutor)
159+
executor_factory=lambda: futurist.ThreadPoolExecutor(
160+
max_workers=1))
160161
self._thread = threading.Thread(target=self._worker.start)
161162
self._thread.daemon = True
162163
self._thread.start()

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# License for the specific language governing permissions and limitations
1414
# under the License.
1515

16+
import threading
17+
import time
1618
from unittest import mock
1719

1820
from futurist import periodics
@@ -35,6 +37,49 @@
3537
from neutron.tests.unit import testlib_api
3638

3739

40+
class TestMaintenanceThread(base.BaseTestCase):
41+
42+
def test_start_bounds_thread_pool_executor(self):
43+
"""Verify MaintenanceThread runs periodic tasks with one worker."""
44+
num_tasks = 5
45+
run_counter = [0] * num_tasks
46+
active_threads = []
47+
target_runs = 5
48+
49+
class _PeriodicTask:
50+
def __init__(self, idx):
51+
self.idx = idx
52+
53+
@periodics.periodic(spacing=0.05)
54+
def trivial_task(self):
55+
run_counter[self.idx] += 1
56+
active_threads.append(threading.active_count())
57+
time.sleep(0)
58+
if run_counter[self.idx] >= target_runs:
59+
raise periodics.NeverAgain
60+
61+
mt = maintenance.MaintenanceThread()
62+
for idx in range(num_tasks):
63+
mt.add_periodics(_PeriodicTask(idx))
64+
65+
threads_before = threading.active_count()
66+
mt.start()
67+
threads_after_start = threading.active_count()
68+
self.assertGreater(threads_after_start, threads_before)
69+
70+
executor = mt._worker._executor_factory()
71+
# Let some tasks be executed.
72+
time.sleep(2)
73+
threads_after_wait = threading.active_count()
74+
self.assertEqual(threads_after_wait, threads_after_start + 1)
75+
76+
executor.shutdown(wait=False)
77+
mt._thread.join(timeout=3)
78+
threads_end = threading.active_count()
79+
self.assertEqual(threads_end, threads_after_wait)
80+
self.assertTrue(all(target_runs == _c for _c in run_counter))
81+
82+
3883
class TestHasLockPeriodicDecorator(base.BaseTestCase):
3984

4085
def test_decorator_no_limit_have_lock(self):

0 commit comments

Comments
 (0)