2025-11-25
This commit is contained in:
278
Untitled.md
Normal file
278
Untitled.md
Normal file
@@ -0,0 +1,278 @@
|
||||
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant HTTPServer
|
||||
participant WebSocketServer
|
||||
participant ClientMQWebsocketAdapter as ClientMQAdapter
|
||||
participant ClientMQ
|
||||
participant ClientMQWorker
|
||||
participant MQWorker
|
||||
participant PingInterval as Ping Interval<br/>(20s)
|
||||
|
||||
Note over Client,ClientMQWorker: === Connection Establishment ===
|
||||
activate Client
|
||||
Client->>HTTPServer: HTTP Upgrade Request
|
||||
activate HTTPServer
|
||||
HTTPServer->>WebSocketServer: upgrade event
|
||||
deactivate HTTPServer
|
||||
|
||||
activate WebSocketServer
|
||||
WebSocketServer->>WebSocketServer: handleUpgrade()
|
||||
WebSocketServer->>WebSocketServer: ...
|
||||
WebSocketServer->>ClientMQAdapter: connectDelegate(socket, headers)
|
||||
deactivate WebSocketServer
|
||||
|
||||
activate ClientMQAdapter
|
||||
ClientMQAdapter->>ClientMQ: emit('connect', connectionId)
|
||||
deactivate ClientMQAdapter
|
||||
|
||||
activate ClientMQ
|
||||
ClientMQ->>ClientMQ: Register adapter in _adaptersById
|
||||
ClientMQ->>ClientMQWorker: emit('connect', connectionId)
|
||||
deactivate ClientMQ
|
||||
|
||||
activate ClientMQWorker
|
||||
rect rgb(255, 255, 200)
|
||||
Note over ClientMQWorker: 🔢 CONNECTION COUNTER MUTATION
|
||||
ClientMQWorker->>ClientMQWorker: _connectionsTotal++<br/>_connectionsOpen++
|
||||
end
|
||||
deactivate ClientMQWorker
|
||||
deactivate Client
|
||||
|
||||
Note over Client,ClientMQWorker: === Heartbeat/Ping Flow ===
|
||||
loop Every 20 seconds
|
||||
activate PingInterval
|
||||
PingInterval->>ClientMQAdapter: Ping interval trigger
|
||||
deactivate PingInterval
|
||||
|
||||
activate ClientMQAdapter
|
||||
ClientMQAdapter->>ClientMQAdapter: Iterate all sockets
|
||||
alt Previous ping still pending
|
||||
ClientMQAdapter->>ClientMQAdapter: Skip ping, log warning
|
||||
else Socket has ping() method
|
||||
alt No pong received (lastPong < lastPing)
|
||||
ClientMQAdapter->>ClientMQAdapter: Detect timeout
|
||||
ClientMQAdapter->>ClientMQ: emit('timeout', connectionId)
|
||||
deactivate ClientMQAdapter
|
||||
|
||||
activate ClientMQ
|
||||
ClientMQ->>ClientMQWorker: emit('timeout', connectionId)
|
||||
deactivate ClientMQ
|
||||
|
||||
activate ClientMQWorker
|
||||
ClientMQWorker->>ClientMQ: close(connectionId, 3334, 'timeout')
|
||||
deactivate ClientMQWorker
|
||||
|
||||
Note over ClientMQAdapter: See "Server-Initiated Close" below
|
||||
else Pong received
|
||||
ClientMQAdapter->>Client: ping()
|
||||
activate Client
|
||||
Client->>WebSocketServer: pong()
|
||||
deactivate Client
|
||||
|
||||
activate WebSocketServer
|
||||
WebSocketServer->>ClientMQAdapter: pongDelegate(socket)
|
||||
deactivate WebSocketServer
|
||||
|
||||
ClientMQAdapter->>ClientMQAdapter: Set __lastPong = Date.now()<br/>Calculate RTT
|
||||
ClientMQAdapter->>MQWorker: processRoundTripTimeMeasurement(...)
|
||||
end
|
||||
else Socket has no ping() method
|
||||
alt Last ping > 2x heartbeatInterval ago
|
||||
ClientMQAdapter->>ClientMQAdapter: Detect timeout
|
||||
ClientMQAdapter->>ClientMQ: emit('timeout', connectionId)
|
||||
Note over ClientMQAdapter: See timeout flow above
|
||||
end
|
||||
end
|
||||
end
|
||||
deactivate ClientMQAdapter
|
||||
|
||||
Note over Client,ClientMQWorker: === Server-Initiated Close ===
|
||||
activate ClientMQWorker
|
||||
alt Close reason: timeout
|
||||
ClientMQWorker->>ClientMQ: close(connectionId, 3334, 'timeout')
|
||||
deactivate ClientMQWorker
|
||||
activate ClientMQ
|
||||
ClientMQ->>ClientMQAdapter: close(connectionId, code, reason)
|
||||
deactivate ClientMQ
|
||||
activate ClientMQAdapter
|
||||
else Close reason: drain
|
||||
ClientMQWorker->>ClientMQ: stop(3333, 'drain')
|
||||
deactivate ClientMQWorker
|
||||
activate ClientMQ
|
||||
loop For each connection
|
||||
ClientMQ->>ClientMQAdapter: close(connectionId, code, reason)
|
||||
activate ClientMQAdapter
|
||||
Note over ClientMQAdapter: Full close processing happens<br/>for each connection in loop
|
||||
alt Socket not found
|
||||
ClientMQAdapter->>ClientMQAdapter: Log warning, return false
|
||||
else Connection already closed (>5s ago)
|
||||
ClientMQAdapter->>ClientMQAdapter: Force disconnect
|
||||
ClientMQAdapter->>ClientMQAdapter: Set __forcefullyClosed = true
|
||||
ClientMQAdapter->>ClientMQ: emit('disconnect-after-forcefull-close')
|
||||
else Connection valid
|
||||
ClientMQAdapter->>ClientMQAdapter: Set __connectionEnded = now()
|
||||
ClientMQAdapter->>WebSocketServer: socket.close(code, reason)
|
||||
activate WebSocketServer
|
||||
WebSocketServer->>Client: Send Close Frame
|
||||
activate Client
|
||||
deactivate Client
|
||||
activate Client
|
||||
Client->>WebSocketServer: Send Close Frame (acknowledgment)
|
||||
deactivate Client
|
||||
WebSocketServer->>WebSocketServer: 'close' event fires
|
||||
WebSocketServer->>ClientMQAdapter: disconnectDelegate(socket, code, description)
|
||||
deactivate WebSocketServer
|
||||
ClientMQAdapter->>ClientMQAdapter: Remove from _socketsById
|
||||
ClientMQAdapter->>ClientMQ: emit('disconnect', connectionId, code, description)
|
||||
activate ClientMQ
|
||||
ClientMQ->>ClientMQWorker: emit('disconnect', connectionId, code, description)
|
||||
deactivate ClientMQ
|
||||
activate ClientMQWorker
|
||||
ClientMQWorker->>ClientMQWorker: _connectionsOpen--
|
||||
ClientMQWorker->>MQWorker: publish('pcast.ConnectionDisconnected', ...)
|
||||
activate MQWorker
|
||||
deactivate MQWorker
|
||||
deactivate ClientMQWorker
|
||||
end
|
||||
deactivate ClientMQAdapter
|
||||
end
|
||||
deactivate ClientMQ
|
||||
|
||||
activate ClientMQAdapter
|
||||
Note over ClientMQAdapter: All connections processed in loop above<br/>Processing code below will be no-op (socket not found)
|
||||
else Close reason: connection limit
|
||||
ClientMQWorker->>ClientMQ: close(connectionId, 1013, 'concurrency-limit-exceeded')
|
||||
deactivate ClientMQWorker
|
||||
activate ClientMQ
|
||||
ClientMQ->>ClientMQAdapter: close(connectionId, code, reason)
|
||||
deactivate ClientMQ
|
||||
activate ClientMQAdapter
|
||||
else Close reason: heartbeat terminate
|
||||
ClientMQWorker->>ClientMQ: close(connectionId, 3335, 'unexpected-close')
|
||||
deactivate ClientMQWorker
|
||||
activate ClientMQ
|
||||
ClientMQ->>ClientMQAdapter: close(connectionId, code, reason)
|
||||
deactivate ClientMQ
|
||||
activate ClientMQAdapter
|
||||
else Close reason: send error
|
||||
ClientMQWorker->>ClientMQ: close(connectionId, 3335, 'unexpected-close')
|
||||
deactivate ClientMQWorker
|
||||
activate ClientMQ
|
||||
ClientMQ->>ClientMQAdapter: close(connectionId, code, reason)
|
||||
deactivate ClientMQ
|
||||
activate ClientMQAdapter
|
||||
end
|
||||
Note over ClientMQAdapter: Processing for single connection close<br/>(ClientMQAdapter active for non-drain cases only)
|
||||
alt Socket not found
|
||||
ClientMQAdapter->>ClientMQAdapter: Log warning, return false
|
||||
deactivate ClientMQAdapter
|
||||
else Connection already closed (>5s ago)
|
||||
ClientMQAdapter->>ClientMQAdapter: Force disconnect
|
||||
ClientMQAdapter->>ClientMQAdapter: Set __forcefullyClosed = true
|
||||
ClientMQAdapter->>ClientMQ: emit('disconnect-after-forcefull-close')
|
||||
deactivate ClientMQAdapter
|
||||
activate ClientMQ
|
||||
deactivate ClientMQ
|
||||
else Connection valid
|
||||
ClientMQAdapter->>ClientMQAdapter: Set __connectionEnded = now()
|
||||
rect rgb(200, 255, 200)
|
||||
Note over ClientMQAdapter,Client: 📤 SERVER SENDS CLOSE FRAME
|
||||
ClientMQAdapter->>WebSocketServer: socket.close(code, reason)
|
||||
activate WebSocketServer
|
||||
WebSocketServer->>Client: Send Close Frame (code, reason)
|
||||
activate Client
|
||||
Note over Client: Client receives server's close frame
|
||||
deactivate Client
|
||||
end
|
||||
rect rgb(255, 200, 200)
|
||||
Note over Client,WebSocketServer: 📥 CLIENT MUST RESPOND WITH CLOSE FRAME<br/>(WebSocket Protocol Requirement)
|
||||
alt Client sends close frame
|
||||
activate Client
|
||||
Client->>WebSocketServer: Send Close Frame (acknowledgment)
|
||||
deactivate Client
|
||||
Note over WebSocketServer: Platform receives client's close frame
|
||||
else Client does NOT send close frame
|
||||
Note over ClientMQAdapter: ⏱️ After 5s timeout (closeTimeout)<br/>Connection marked as forcefully closed
|
||||
Note over ClientMQAdapter: Next disconnect will use<br/>disconnect-after-forcefull-close path
|
||||
end
|
||||
end
|
||||
rect rgb(200, 200, 255)
|
||||
Note over WebSocketServer,ClientMQWorker: 🔄 CLOSE EVENT & DISCONNECT PROCESSING
|
||||
WebSocketServer->>WebSocketServer: 'close' event fires<br/>(reasonCode, description)
|
||||
WebSocketServer->>ClientMQAdapter: disconnectDelegate(socket, code, description)
|
||||
deactivate WebSocketServer
|
||||
alt Multiple close events
|
||||
ClientMQAdapter->>ClientMQAdapter: Log warning, return early<br/>(_connectionsOpen NOT decremented)
|
||||
deactivate ClientMQAdapter
|
||||
else Socket already disconnected
|
||||
alt Previously forcefully closed
|
||||
ClientMQAdapter->>ClientMQ: emit('disconnect-after-forcefull-close')<br/>(_connectionsOpen NOT decremented)
|
||||
deactivate ClientMQAdapter
|
||||
activate ClientMQ
|
||||
deactivate ClientMQ
|
||||
else Already disconnected
|
||||
ClientMQAdapter->>ClientMQ: emit('disconnect-already-disconnected')<br/>(_connectionsOpen NOT decremented)
|
||||
deactivate ClientMQAdapter
|
||||
activate ClientMQ
|
||||
deactivate ClientMQ
|
||||
end
|
||||
else Normal disconnect
|
||||
ClientMQAdapter->>ClientMQAdapter: Remove from _socketsById
|
||||
ClientMQAdapter->>ClientMQAdapter: Set __connected = false
|
||||
ClientMQAdapter->>ClientMQAdapter: Calculate connection duration
|
||||
ClientMQAdapter->>ClientMQ: emit('disconnect', connectionId, code, description)
|
||||
deactivate ClientMQAdapter
|
||||
activate ClientMQ
|
||||
ClientMQ->>ClientMQ: Remove from _adaptersById
|
||||
ClientMQ->>ClientMQWorker: emit('disconnect', connectionId, code, description)
|
||||
deactivate ClientMQ
|
||||
activate ClientMQWorker
|
||||
rect rgb(255, 255, 200)
|
||||
Note over ClientMQWorker: 🔢 CONNECTION COUNTER MUTATION<br/>⚠️ Only happens after receiving client's close frame
|
||||
ClientMQWorker->>ClientMQWorker: _connectionsOpen--
|
||||
end
|
||||
ClientMQWorker->>ClientMQWorker: Clean up room subscriptions
|
||||
ClientMQWorker->>ClientMQWorker: Clean up conversation subscriptions
|
||||
ClientMQWorker->>MQWorker: publish('pcast.ConnectionDisconnected', ...)
|
||||
activate MQWorker
|
||||
deactivate MQWorker
|
||||
deactivate ClientMQWorker
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Note over Client,ClientMQWorker: === Error Handling ===
|
||||
alt Socket error
|
||||
activate Client
|
||||
Client->>WebSocketServer: error event
|
||||
deactivate Client
|
||||
activate WebSocketServer
|
||||
WebSocketServer->>WebSocketServer: Log error
|
||||
deactivate WebSocketServer
|
||||
else Handler error
|
||||
activate WebSocketServer
|
||||
WebSocketServer->>WebSocketServer: Log error, continue
|
||||
deactivate WebSocketServer
|
||||
else Send error (not opened)
|
||||
activate ClientMQWorker
|
||||
ClientMQWorker->>ClientMQ: close(connectionId, 3335, 'unexpected-close')
|
||||
deactivate ClientMQWorker
|
||||
activate ClientMQ
|
||||
deactivate ClientMQ
|
||||
activate ClientMQWorker
|
||||
ClientMQWorker->>MQWorker: request('pcast.ConnectionDisconnected', ...)
|
||||
activate MQWorker
|
||||
deactivate MQWorker
|
||||
deactivate ClientMQWorker
|
||||
Note over ClientMQAdapter: See "Server-Initiated Close" above
|
||||
else Send error (not found/closed)
|
||||
activate ClientMQWorker
|
||||
ClientMQWorker->>ClientMQWorker: Return {status: 'closed'}
|
||||
deactivate ClientMQWorker
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user