diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index acdbe9f7..c1669130 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -11,6 +11,8 @@ Changelog - Added email authorization support: ``send_email_address`` and ``send_email_code``. - ``stop`` no longer blocks forever when tdlib does not answer, and no longer leaves the client running when tdlib answers with an error. It now waits up to 5 seconds for the session to close, which can be changed with ``stop(close_timeout=...)``. - Calling a method after ``stop`` raises ``ClientDestroyedError`` instead of crashing the process. The destroyed tdlib handle was passed to the C library as a ``NULL`` pointer. +- An exception raised by an update handler no longer stops the worker thread. The error is logged and the worker keeps processing the queue. +- When the handler queue is full, the update is dropped and an error is logged instead of raising ``queue.Full``. [0.19.0] - 2024-06-23 --------------------- diff --git a/tests/test_telegram_methods.py b/tests/test_telegram_methods.py index b62f8d00..fc7a8f5c 100644 --- a/tests/test_telegram_methods.py +++ b/tests/test_telegram_methods.py @@ -9,6 +9,7 @@ from telegram.client import MESSAGE_HANDLER_TYPE, AuthorizationState, Telegram from telegram.text import Spoiler from telegram.utils import AsyncResult +from telegram.worker import SimpleWorker API_ID = 1 API_HASH = "hash" @@ -90,7 +91,7 @@ def test_parse_text_entities(self, telegram): telegram._tdjson.send.assert_called_once_with(exp_data) def test_send_phone_number_or_bot_token(self, telegram): - # check that the dunction calls _send_phone_number or _send_bot_token + # check that the function calls _send_phone_number or _send_bot_token with patch.object(telegram, "_send_phone_number"), patch.object(telegram, "_send_bot_token"): telegram.phone = "123" telegram.bot_token = None @@ -604,30 +605,33 @@ def test_login_raises_on_state_without_action(self, telegram): class TestWorkerExceptionHandling: def test_worker_thread_survives_handler_exception(self): - import time - from queue import Queue - - from telegram.worker import SimpleWorker - - q = Queue() + q = queue.Queue() worker = SimpleWorker(queue=q) - worker.run() results = [] + good_handler_called = threading.Event() def bad_handler(update): raise RuntimeError("boom") def good_handler(update): results.append(update) + good_handler_called.set() + + with patch.object(q, "task_done", wraps=q.task_done) as task_done: + worker.run() + + q.put((bad_handler, {"@type": "test"})) + q.put((good_handler, {"@type": "test"})) - q.put((bad_handler, {"@type": "test"})) - q.put((good_handler, {"@type": "test"})) + assert good_handler_called.wait(timeout=5), "the worker stopped after the failing handler" - time.sleep(0.5) - worker.stop() + # `stop` joins the thread, so both updates are fully processed after it returns + worker.stop() assert results == [{"@type": "test"}] + # a failing handler must still mark its update as done, otherwise `Queue.join` blocks forever + assert task_done.call_count == 2 class TestStop: