back out tonight's socket changes
Reverts66a41d0andbc13450. Both were justified by reasoning that measurement then contradicted: the chat socket was never the fault. What actually fixed chat was tearing down and restarting the whole pm2 ecosystem, so the failure lived in process state, not in this hook. Leaves the tree identical to31ffe08— the pre-multi-server baseline Andre asked for — apart from docs/agent-git-identity.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,8 +15,6 @@ export const useChatWebSocket = ({ url, onMessage, onOpen }: UseChatWebSocketPar
|
||||
const retryRef = useRef(0);
|
||||
const retryTimeoutRef = useRef<number | null>(null);
|
||||
const isCleaningUpRef = useRef(false);
|
||||
/** A deferred teardown, cancelled when the effect re-runs — see the cleanup below. */
|
||||
const closeTimerRef = useRef<number | null>(null);
|
||||
const onMessageRef = useRef(onMessage);
|
||||
onMessageRef.current = onMessage;
|
||||
const onOpenRef = useRef(onOpen);
|
||||
@@ -24,16 +22,7 @@ export const useChatWebSocket = ({ url, onMessage, onOpen }: UseChatWebSocketPar
|
||||
|
||||
const connect = () => {
|
||||
if (isCleaningUpRef.current) return;
|
||||
// CONNECTING counts as ours, not just OPEN. The deferred teardown below keeps a remount's socket
|
||||
// alive mid-handshake, and this is what reclaims it: checking only OPEN meant an effect re-run
|
||||
// built a SECOND socket and orphaned the first, which then stayed open forever with its own `open`
|
||||
// handler bailing on the socketRef mismatch.
|
||||
//
|
||||
// This is per-instance and does NOT explain the two sockets a /chat/new load opens — measured with
|
||||
// a WebSocket-constructor counter, those come from two separate `useChat` instances mounting, each
|
||||
// with its own refs. Unresolved, and tracked separately; both connect, so it reads as healthy.
|
||||
const existing = socketRef.current;
|
||||
if (existing && (existing.readyState === WebSocket.OPEN || existing.readyState === WebSocket.CONNECTING)) return;
|
||||
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) return;
|
||||
|
||||
const socket = new WebSocket(url);
|
||||
socketRef.current = socket;
|
||||
@@ -70,41 +59,18 @@ export const useChatWebSocket = ({ url, onMessage, onOpen }: UseChatWebSocketPar
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// A pending teardown from a remount that is about to be undone — see below.
|
||||
if (closeTimerRef.current !== null) {
|
||||
clearTimeout(closeTimerRef.current);
|
||||
closeTimerRef.current = null;
|
||||
}
|
||||
isCleaningUpRef.current = false;
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
/**
|
||||
* Close LATER, not now.
|
||||
*
|
||||
* Closing here directly is correct for a real unmount and disastrous for a remount, and this hook
|
||||
* cannot tell them apart at the moment it runs. React's dev StrictMode double-invokes every effect
|
||||
* (mount → unmount → mount), and a subtree that is re-created — a resolved transcript, a parent key
|
||||
* change — does the same. Each time, the socket was closed while still CONNECTING, the browser
|
||||
* logged "closed before the connection is established", and the replacement was closed in turn, so
|
||||
* the view could churn forever and never hold a connection while sitting on Disconnected.
|
||||
*
|
||||
* Deferring by a tick makes the two distinguishable. A remount re-runs the effect immediately and
|
||||
* cancels this timer, so the live socket is kept and the handshake completes. A real unmount has
|
||||
* nobody to cancel it and the socket closes a frame later, which costs nothing.
|
||||
*/
|
||||
isCleaningUpRef.current = true;
|
||||
if (retryTimeoutRef.current !== null) {
|
||||
clearTimeout(retryTimeoutRef.current);
|
||||
retryTimeoutRef.current = null;
|
||||
}
|
||||
const socket = socketRef.current;
|
||||
closeTimerRef.current = window.setTimeout(() => {
|
||||
closeTimerRef.current = null;
|
||||
if (!isCleaningUpRef.current) return; // remounted: the effect above already reclaimed it
|
||||
if (socket) socket.close();
|
||||
if (socketRef.current === socket) socketRef.current = null;
|
||||
}, 0);
|
||||
if (socketRef.current) {
|
||||
socketRef.current.close();
|
||||
socketRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [url]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user