mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-10-24 06:42:22 +00:00
fix(bridges): stop cleanly when the bridge disabled itself at startup
TelegramBridgeService returned from __init__ as soon as it found no api_token (or no HTTP library), before setting message_queues, send_times, http_session and _queue_processor_task. start() then returned early as intended, but stop() read _queue_processor_task and raised AttributeError, so every shutdown logged "Failed to stop service 'telegram_bridge'". DiscordBridgeService had the same gap on its no-HTTP-library path. Both now set that state before any early return. New tests start and stop each self-disabled bridge and check that only the reason it disabled itself is logged as an error; all three fail on dev.
This commit is contained in:
1 parent
c6500be0c3
commit
56a366ff7c
4 files changed
+70
-13
No files matched your search
@@ -111,6 +111,14 @@ class DiscordBridgeService(BaseServicePlugin):
|
||||
# Use bot's logger directly (inherited from BaseServicePlugin)
|
||||
# self.logger is already set by super().__init__(bot)
|
||||
|
||||
# State stop() relies on, set before the early return below so the
|
||||
# service still stops cleanly when it disables itself.
|
||||
self.channel_webhooks: dict[str, list[str]] = {}
|
||||
self.message_queues: dict[str, list[Any]] = {}
|
||||
self.send_times: dict[str, deque] = {}
|
||||
self.http_session: Optional[aiohttp.ClientSession] = None
|
||||
self._queue_processor_task: Optional[asyncio.Task] = None
|
||||
|
||||
# Check if HTTP library is available
|
||||
if not AIOHTTP_AVAILABLE and not REQUESTS_AVAILABLE:
|
||||
self.logger.error("Neither aiohttp nor requests library is available. Discord bridge requires one of these.")
|
||||
@@ -119,7 +127,6 @@ class DiscordBridgeService(BaseServicePlugin):
|
||||
|
||||
# Load channel mappings from config (bridge.* pattern)
|
||||
# Map MeshCore channel name → list of Discord webhook URLs
|
||||
self.channel_webhooks: dict[str, list[str]] = {}
|
||||
self._load_channel_mappings()
|
||||
|
||||
# NEVER bridge DMs (hardcoded for privacy)
|
||||
@@ -154,23 +161,17 @@ class DiscordBridgeService(BaseServicePlugin):
|
||||
|
||||
# Message queue per webhook to handle rate limits and retries
|
||||
# Using list instead of deque for easier removal of arbitrary items
|
||||
self.message_queues: dict[str, list[Any]] = {}
|
||||
self.max_retries = 5 # Maximum retry attempts per message
|
||||
self.retry_delay_base = 1.0 # Base delay in seconds for exponential backoff
|
||||
self.max_queue_age = 300 # Max age in seconds before dropping message (5 minutes)
|
||||
|
||||
# Proactive rate limiting: track send times per webhook
|
||||
# Discord allows 30 messages per 60 seconds, so we'll throttle to ~25/min for safety
|
||||
self.send_times: dict[str, deque] = {} # Track timestamps of sent messages (deque for efficient popleft)
|
||||
self.rate_limit_window = 60.0 # 60 second window
|
||||
self.rate_limit_max = 25 # Conservative limit (25/min instead of 30/min for safety)
|
||||
|
||||
# HTTP session for async requests
|
||||
self.http_session: Optional[aiohttp.ClientSession] = None
|
||||
|
||||
# Background task handles
|
||||
self._message_handler_task: Optional[asyncio.Task] = None
|
||||
self._queue_processor_task: Optional[asyncio.Task] = None
|
||||
|
||||
if not self.channel_webhooks:
|
||||
self.logger.warning("No Discord channel mappings configured. Discord bridge will not post any messages.")
|
||||
|
||||
@@ -101,6 +101,14 @@ class TelegramBridgeService(BaseServicePlugin):
|
||||
def __init__(self, bot: Any):
|
||||
super().__init__(bot)
|
||||
|
||||
# State stop() relies on, set before any early return so a bridge left
|
||||
# disabled by missing config still stops cleanly at shutdown.
|
||||
self.channel_chat_ids: dict[str, str] = {}
|
||||
self.message_queues: dict[str, list[QueuedMessage]] = {}
|
||||
self.send_times: dict[str, deque] = {}
|
||||
self.http_session: Optional[aiohttp.ClientSession] = None
|
||||
self._queue_processor_task: Optional[asyncio.Task] = None
|
||||
|
||||
if not AIOHTTP_AVAILABLE and not REQUESTS_AVAILABLE:
|
||||
self.logger.error(
|
||||
"Neither aiohttp nor requests available. Telegram bridge requires one of these."
|
||||
@@ -118,7 +126,6 @@ class TelegramBridgeService(BaseServicePlugin):
|
||||
self.enabled = False
|
||||
return
|
||||
|
||||
self.channel_chat_ids: dict[str, str] = {}
|
||||
self._load_channel_mappings()
|
||||
|
||||
# Optional settings
|
||||
@@ -145,16 +152,11 @@ class TelegramBridgeService(BaseServicePlugin):
|
||||
)
|
||||
|
||||
# Rate limiting: ~1 message per second per chat
|
||||
self.message_queues: dict[str, list[QueuedMessage]] = {}
|
||||
self.send_times: dict[str, deque] = {}
|
||||
self.rate_limit_min_interval = 1.0
|
||||
self.max_retries = 5
|
||||
self.retry_delay_base = 1.0
|
||||
self.max_queue_age = 300
|
||||
|
||||
self.http_session: Optional[aiohttp.ClientSession] = None
|
||||
self._queue_processor_task: Optional[asyncio.Task] = None
|
||||
|
||||
if not self.channel_chat_ids:
|
||||
self.logger.warning(
|
||||
"No Telegram channel mappings configured. "
|
||||
|
||||
Reference in new issue
Block a user