Fix races and stale things

This commit is contained in:
Jack Kingsman
2026-04-10 15:54:03 -07:00
parent 59601bb98e
commit 4f19e1ec9a
3 changed files with 16 additions and 8 deletions

View File

@@ -192,6 +192,9 @@ class RadioManager:
if not blocking:
if self._operation_lock.locked():
raise RadioOperationBusyError(f"Radio is busy (operation: {name})")
# In single-threaded asyncio the lock cannot be acquired between the
# check above and the await below (no other coroutine runs until we
# yield). The await returns immediately for an uncontested lock.
await self._operation_lock.acquire()
else:
await self._operation_lock.acquire()

View File

@@ -557,10 +557,11 @@ class MessageRepository:
@staticmethod
async def increment_ack_count(message_id: int) -> int:
"""Increment ack count and return the new value."""
await db.conn.execute("UPDATE messages SET acked = acked + 1 WHERE id = ?", (message_id,))
await db.conn.commit()
cursor = await db.conn.execute("SELECT acked FROM messages WHERE id = ?", (message_id,))
cursor = await db.conn.execute(
"UPDATE messages SET acked = acked + 1 WHERE id = ? RETURNING acked", (message_id,)
)
row = await cursor.fetchone()
await db.conn.commit()
return row["acked"] if row else 1
@staticmethod

View File

@@ -372,6 +372,8 @@ export function useConversationMessages(
const olderAbortControllerRef = useRef<AbortController | null>(null);
const newerAbortControllerRef = useRef<AbortController | null>(null);
const fetchingConversationIdRef = useRef<string | null>(null);
const activeConversationRef = useRef(activeConversation);
activeConversationRef.current = activeConversation;
const latestReconcileRequestIdRef = useRef(0);
const pendingReconnectReconcileRef = useRef(false);
const messagesRef = useRef<Message[]>([]);
@@ -664,9 +666,11 @@ export function useConversationMessages(
}, [activeConversation]);
const reconcileOnReconnect = useCallback(() => {
if (!isMessageConversation(activeConversation)) {
return;
}
// Read the current conversation from the ref rather than closing over
// activeConversation, so that a conversation switch during WS reconnect
// targets the right conversation instead of a stale capture.
const current = activeConversationRef.current;
if (!isMessageConversation(current)) return;
if (hasNewerMessagesRef.current) {
pendingReconnectReconcileRef.current = true;
@@ -677,8 +681,8 @@ export function useConversationMessages(
const controller = new AbortController();
const requestId = latestReconcileRequestIdRef.current + 1;
latestReconcileRequestIdRef.current = requestId;
reconcileFromBackend(activeConversation, controller.signal, requestId);
}, [activeConversation, reconcileFromBackend]);
reconcileFromBackend(current, controller.signal, requestId);
}, [reconcileFromBackend]);
useEffect(() => {
if (abortControllerRef.current) {