/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
lib/tests/exception_capturing_thread.py
124 строки
4 KB
Ken McGrady
Update copyright header to 2026 (#13491)
02 янв 2026, 16:21
Не верифицирован
02 янв 2026, 16:21
374540a
Код
Авторство
О чём код?
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026) # # 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. from __future__ import annotations import threading from typing import TYPE_CHECKING, Any from streamlit.runtime.forward_msg_queue import ForwardMsgQueue from streamlit.runtime.fragment import MemoryFragmentStorage from streamlit.runtime.memory_uploaded_file_manager import MemoryUploadedFileManager from streamlit.runtime.pages_manager import PagesManager from streamlit.runtime.scriptrunner import ScriptRunContext, add_script_run_ctx from streamlit.runtime.state import SafeSessionState, SessionState if TYPE_CHECKING: from collections.abc import Callable def call_on_threads( func: Callable[[int], Any], num_threads: int, timeout: float | None = 0.25, attach_script_run_ctx: bool = True, ) -> None: """Call a function on multiple threads simultaneously and assert that no thread raises an unhandled exception. The function must take single `int` param, which will be the index of the thread it's being called on. Note that a passing multi-threaded test does not generally guarantee that the tested code is thread safe! Because threading issues tend to be non-deterministic, a flaky test that fails only occasionally is a good indicator of an underlying issue. Parameters ---------- func The function to call on each thread. num_threads The number of threads to create. timeout If the thread runs for longer than this amount of time, raise an Exception. attach_script_run_ctx If True, attach a mock ScriptRunContext to each thread before starting. """ threads = [ ExceptionCapturingThread(name=f"Thread {ii}", target=func, args=[ii]) for ii in range(num_threads) ] if attach_script_run_ctx: for ii in range(num_threads): ctx = ScriptRunContext( session_id=f"Thread{ii}_Session", _enqueue=ForwardMsgQueue().enqueue, query_string="", session_state=SafeSessionState(SessionState(), lambda: None), uploaded_file_mgr=MemoryUploadedFileManager("/mock/upload"), main_script_path="", user_info={"email": "test@example.com"}, fragment_storage=MemoryFragmentStorage(), pages_manager=PagesManager(""), ) thread = threads[ii] add_script_run_ctx(thread, ctx) for thread in threads: thread.start() for thread in threads: thread.join(timeout=timeout) thread.assert_no_unhandled_exception() class ExceptionCapturingThread(threading.Thread): """Thread subclass that captures unhandled exceptions.""" def __init__( self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None ): super().__init__( group=group, target=target, name=name, args=args, kwargs=kwargs, daemon=daemon, ) self._unhandled_exception: BaseException | None = None @property def unhandled_exception(self) -> BaseException | None: """The unhandled exception raised by the thread's target, if it raised one.""" return self._unhandled_exception def assert_no_unhandled_exception(self) -> None: """If the thread target raised an unhandled exception, re-raise it. Otherwise no-op. """ if self._unhandled_exception is not None: raise RuntimeError( f"Unhandled exception in thread '{self.name}'" ) from self._unhandled_exception def run(self) -> None: try: super().run() except Exception as e: self._unhandled_exception = e