mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-31 03:14:17 +09:00
enhance(websocket): improve client synchronization (#38847)
Keep notification counts and stopwatches in sync when tabs open or reconnect, closing the gap behind [this flake](https://github.com/go-gitea/gitea/actions/runs/31312525323/job/93242429394). 1. Send current notification counts and stopwatch state on each WebSocket connection, replacing the separate state fetches tabs made on every connect. 2. Tabs joining an already open connection fetch current state once, since its snapshot went out before they attached. 3. Send events queued during the handshake before the snapshot, so older events can't roll it back. 4. Wait for the Redis SUBSCRIBE acknowledgement, so the snapshot read after subscribing can't miss an event.
This commit is contained in:
1 parent
965d7522e0
commit
7308c5fb97
20 files changed
+268
-247
No files matched your search
@@ -26,8 +26,7 @@ export function initNotificationCount() {
|
||||
|
||||
let pollerStarted = false;
|
||||
onUserEvent('notification-count', (msg) => { receiveUpdateCount(msg.eventData.count) }); // no await
|
||||
// On each (re)connect, reconcile the count from the server to recover any push dropped during the connect gap.
|
||||
onUserEvent('worker-connected', () => { updateNotificationCount(); updateNotificationTable() }); // no await
|
||||
onUserEvent('worker-attached', () => { updateNotificationCount(); updateNotificationTable() }); // no await
|
||||
onUserEvent('worker-unavailable', () => {
|
||||
if (pollerStarted) return;
|
||||
pollerStarted = true;
|
||||
|
||||
@@ -76,8 +76,7 @@ export const initStopwatch = () => registerGlobalInitFunc('initActiveStopwatchNo
|
||||
|
||||
let pollerStarted = false;
|
||||
onUserEvent('stopwatches', (msg) => updateStopwatchData(msg.eventData));
|
||||
// On each (re)connect, reconcile stopwatch state from the server to recover any push dropped during the connect gap.
|
||||
onUserEvent('worker-connected', () => { updateStopwatch() }); // no await
|
||||
onUserEvent('worker-attached', () => { updateStopwatch() }); // no await
|
||||
onUserEvent('worker-unavailable', () => {
|
||||
if (pollerStarted) return;
|
||||
pollerStarted = true;
|
||||
|
||||
+3
-2
@@ -87,7 +87,7 @@ export type StopwatchData = {
|
||||
seconds: number,
|
||||
};
|
||||
|
||||
// keep in sync with services/websocket/events.go
|
||||
// keep in sync with services/websocket
|
||||
export type ServerUserEventMessage =
|
||||
{eventType: 'notification-count', eventData: {count: number}} |
|
||||
{eventType: 'stopwatches', eventData: Array<StopwatchData>} |
|
||||
@@ -97,7 +97,8 @@ export const serverUserEventTypes = ['notification-count', 'stopwatches', 'logou
|
||||
|
||||
export type UserEventMessage = ServerUserEventMessage |
|
||||
{eventType: 'worker-unavailable'} |
|
||||
{eventType: 'worker-connected'};
|
||||
{eventType: 'worker-connected'} |
|
||||
{eventType: 'worker-attached'}; // joined an open socket after its connect snapshot
|
||||
|
||||
export type UserEventType = UserEventMessage['eventType'];
|
||||
|
||||
|
||||
@@ -96,9 +96,6 @@ class WsSource {
|
||||
this.ws.addEventListener('open', () => {
|
||||
this.reconnectDelay = 1000;
|
||||
this.failuresWithoutConnect = 0;
|
||||
// Pushes fired while no client was subscribed (initial connect gap, or a
|
||||
// reconnect window) are dropped server-side, so tell clients to reconcile
|
||||
// their state from the server on every fresh connection.
|
||||
this.source.notifyClientsUserEvent({eventType: 'worker-connected'});
|
||||
});
|
||||
|
||||
@@ -191,6 +188,7 @@ const wsSourcesByUrl = new Map<string, WsSource>();
|
||||
const openWs = wsSourcesByUrl.get(url);
|
||||
if (openWs?.ws?.readyState === WebSocket.OPEN) {
|
||||
postUserEventMessage(port, {eventType: 'worker-connected'});
|
||||
postUserEventMessage(port, {eventType: 'worker-attached'});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user