From 983af306b0758acf8f03d219d2d769d4d588d317 Mon Sep 17 00:00:00 2001 From: Bartok9 Date: Sat, 25 Jul 2026 01:54:13 -0400 Subject: [PATCH] fix(communication): require positive callback_max_workers ThreadPoolExecutor rejects non-positive max_workers with a generic error. Validate callback_max_workers as a positive int up front on BaseConnector. Signed-off-by: Bartok9 --- .../rai/communication/base_connector.py | 10 +++++ .../test_base_connector_workers.py | 43 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 tests/communication/test_base_connector_workers.py diff --git a/src/rai_core/rai/communication/base_connector.py b/src/rai_core/rai/communication/base_connector.py index 51fd2f044..64590b1ea 100644 --- a/src/rai_core/rai/communication/base_connector.py +++ b/src/rai_core/rai/communication/base_connector.py @@ -72,6 +72,16 @@ class ParametrizedCallback(BaseModel, Generic[T]): class BaseConnector(Generic[T]): def __init__(self, callback_max_workers: int = 4): + if not isinstance(callback_max_workers, int) or isinstance( + callback_max_workers, bool + ): + raise ValueError( + f"callback_max_workers must be a positive int, got {callback_max_workers!r}" + ) + if callback_max_workers <= 0: + raise ValueError( + f"callback_max_workers must be a positive int, got {callback_max_workers!r}" + ) self.callback_max_workers = callback_max_workers self.logger = logging.getLogger(self.__class__.__name__) self.registered_callbacks: Dict[str, Dict[str, ParametrizedCallback[T]]] = ( diff --git a/tests/communication/test_base_connector_workers.py b/tests/communication/test_base_connector_workers.py new file mode 100644 index 000000000..01390e144 --- /dev/null +++ b/tests/communication/test_base_connector_workers.py @@ -0,0 +1,43 @@ +# Copyright (C) 2025 Robotec.AI +# +# 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 pytest +from rai.communication.base_connector import BaseConnector, BaseMessage + + +class _Msg(BaseMessage): + pass + + +class _Conn(BaseConnector[_Msg]): + def send_message(self, message, target, **kwargs): + pass + + def receive_message(self, source, timeout_sec, **kwargs): + pass + + def service_call(self, message, target, timeout_sec, **kwargs): + pass + + +@pytest.mark.parametrize("bad", [0, -1, 1.5, True, "4", None]) +def test_callback_max_workers_rejects_non_positive(bad): + with pytest.raises(ValueError, match="callback_max_workers must be a positive int"): + _Conn(callback_max_workers=bad) # type: ignore[arg-type] + + +def test_callback_max_workers_accepts_positive(): + c = _Conn(callback_max_workers=2) + assert c.callback_max_workers == 2 + c.callback_executor.shutdown(wait=False)