Refactor CompanionFrameServer for non-blocking message synchronization

- Changed the _get_companion_stats method to be asynchronous, improving responsiveness.
- Added a new async method _cmd_sync_next_message to handle message synchronization without blocking the event loop, enhancing performance during message processing.
- Introduced constants for response codes to streamline message handling in the CompanionFrameServer.
This commit is contained in:
Adam Gessaman
2026-02-25 15:39:18 -08:00
parent 789a2f27ea
commit c78133f175
2 changed files with 74 additions and 1 deletions
+73
View File
@@ -10,8 +10,16 @@ from __future__ import annotations
import asyncio
import logging
import struct
from typing import Optional
from pymc_core.companion.constants import (
RESP_CODE_CHANNEL_MSG_RECV,
RESP_CODE_CHANNEL_MSG_RECV_V3,
RESP_CODE_CONTACT_MSG_RECV,
RESP_CODE_CONTACT_MSG_RECV_V3,
RESP_CODE_NO_MORE_MESSAGES,
)
from pymc_core.companion.frame_server import CompanionFrameServer as _BaseFrameServer
from pymc_core.companion.models import QueuedMessage
@@ -83,6 +91,71 @@ class CompanionFrameServer(_BaseFrameServer):
path_len=msg_dict.get("path_len", 0),
)
# -----------------------------------------------------------------
# Non-blocking command overrides (keep event loop responsive)
# -----------------------------------------------------------------
async def _cmd_sync_next_message(self, data: bytes) -> None:
"""Sync next message; run persistence read in thread so SQLite does not block."""
msg = self.bridge.sync_next_message()
if msg is None:
msg = await asyncio.to_thread(self._sync_next_from_persistence)
if msg is None:
self._write_frame(bytes([RESP_CODE_NO_MORE_MESSAGES]))
return
if msg.is_channel:
path_len_byte = msg.path_len if msg.path_len < 256 else 0xFF
txt_type = 0
text_bytes = (msg.text or "").rstrip("\x00").encode("utf-8", errors="replace")
if self._app_target_ver >= 3:
frame = (
bytes(
[
RESP_CODE_CHANNEL_MSG_RECV_V3,
0,
0,
0,
msg.channel_idx,
path_len_byte,
txt_type,
]
)
+ struct.pack("<I", msg.timestamp)
+ text_bytes
)
else:
frame = bytes(
[
RESP_CODE_CHANNEL_MSG_RECV,
msg.channel_idx,
path_len_byte,
txt_type,
]
)
frame += struct.pack("<I", msg.timestamp) + text_bytes
else:
prefix = (
msg.sender_key[:6] if len(msg.sender_key) >= 6 else msg.sender_key.ljust(6, b"\x00")
)
path_len_byte = msg.path_len if msg.path_len < 256 else 0xFF
text_bytes = msg.text.encode("utf-8", errors="replace")
if self._app_target_ver >= 3:
frame = (
bytes([RESP_CODE_CONTACT_MSG_RECV_V3, 0, 0, 0])
+ prefix
+ bytes([path_len_byte, msg.txt_type])
+ struct.pack("<I", msg.timestamp)
+ text_bytes
)
else:
frame = (
bytes([RESP_CODE_CONTACT_MSG_RECV])
+ prefix
+ bytes([path_len_byte, msg.txt_type])
)
frame += struct.pack("<I", msg.timestamp) + text_bytes
self._write_frame(frame)
@staticmethod
def _contact_to_dict(c) -> dict:
"""Convert a Contact object to a persistence dict."""
+1 -1
View File
@@ -651,7 +651,7 @@ class RepeaterDaemon:
return stats
def _get_companion_stats(self, stats_type: int) -> dict:
async def _get_companion_stats(self, stats_type: int) -> dict:
"""Return stats dict for companion CMD_GET_STATS (format expected by frame_server + meshcore_py)."""
from repeater.companion.constants import (
STATS_TYPE_CORE,