feat(matrix): add configurable graceful sync shutdown

This commit is contained in:
Alexander Minges
2026-02-10 12:32:17 +01:00
parent 227251649f
commit 2642009792
2 changed files with 27 additions and 5 deletions
+20 -1
View File
@@ -49,9 +49,28 @@ class MatrixChannel(BaseChannel):
self._sync_task = asyncio.create_task(self._sync_loop()) self._sync_task = asyncio.create_task(self._sync_loop())
async def stop(self) -> None: async def stop(self) -> None:
"""Stop the Matrix channel with graceful sync shutdown."""
self._running = False self._running = False
if self.client:
# Request sync_forever loop to exit cleanly.
self.client.stop_sync_forever()
if self._sync_task: if self._sync_task:
self._sync_task.cancel() try:
await asyncio.wait_for(
asyncio.shield(self._sync_task),
timeout=self.config.sync_stop_grace_seconds,
)
except asyncio.TimeoutError:
self._sync_task.cancel()
try:
await self._sync_task
except asyncio.CancelledError:
pass
except asyncio.CancelledError:
pass
if self.client: if self.client:
await self.client.close() await self.client.close()
+7 -4
View File
@@ -1,6 +1,7 @@
"""Configuration schema using Pydantic.""" """Configuration schema using Pydantic."""
from pathlib import Path from pathlib import Path
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
@@ -53,6 +54,8 @@ class MatrixConfig(BaseModel):
access_token: str = "" access_token: str = ""
user_id: str = "" # @bot:matrix.org user_id: str = "" # @bot:matrix.org
device_id: str = "" device_id: str = ""
# Max seconds to wait for sync_forever to stop gracefully before cancellation fallback.
sync_stop_grace_seconds: int = 2
allow_from: list[str] = Field(default_factory=list) allow_from: list[str] = Field(default_factory=list)
class ChannelsConfig(BaseModel): class ChannelsConfig(BaseModel):
@@ -137,12 +140,12 @@ class Config(BaseSettings):
providers: ProvidersConfig = Field(default_factory=ProvidersConfig) providers: ProvidersConfig = Field(default_factory=ProvidersConfig)
gateway: GatewayConfig = Field(default_factory=GatewayConfig) gateway: GatewayConfig = Field(default_factory=GatewayConfig)
tools: ToolsConfig = Field(default_factory=ToolsConfig) tools: ToolsConfig = Field(default_factory=ToolsConfig)
@property @property
def workspace_path(self) -> Path: def workspace_path(self) -> Path:
"""Get expanded workspace path.""" """Get expanded workspace path."""
return Path(self.agents.defaults.workspace).expanduser() return Path(self.agents.defaults.workspace).expanduser()
def _match_provider(self, model: str | None = None) -> tuple["ProviderConfig | None", str | None]: def _match_provider(self, model: str | None = None) -> tuple["ProviderConfig | None", str | None]:
"""Match provider config and its registry name. Returns (config, spec_name).""" """Match provider config and its registry name. Returns (config, spec_name)."""
from nanobot.providers.registry import PROVIDERS from nanobot.providers.registry import PROVIDERS
@@ -175,7 +178,7 @@ class Config(BaseSettings):
"""Get API key for the given model. Falls back to first available key.""" """Get API key for the given model. Falls back to first available key."""
p = self.get_provider(model) p = self.get_provider(model)
return p.api_key if p else None return p.api_key if p else None
def get_api_base(self, model: str | None = None) -> str | None: def get_api_base(self, model: str | None = None) -> str | None:
"""Get API base URL for the given model. Applies default URLs for known gateways.""" """Get API base URL for the given model. Applies default URLs for known gateways."""
from nanobot.providers.registry import find_by_name from nanobot.providers.registry import find_by_name
@@ -190,7 +193,7 @@ class Config(BaseSettings):
if spec and spec.is_gateway and spec.default_api_base: if spec and spec.is_gateway and spec.default_api_base:
return spec.default_api_base return spec.default_api_base
return None return None
class Config: class Config:
env_prefix = "NANOBOT_" env_prefix = "NANOBOT_"
env_nested_delimiter = "__" env_nested_delimiter = "__"