mirror of
https://github.com/penpot/penpot.git
synced 2026-08-01 02:46:20 +00:00
🐛 Preserve established plugin connection when rejecting a duplicate #10961
In multi-user mode, rejecting a second plugin WebSocket connection for an already-registered user token performed the full removeConnection cleanup for the newcomer. Since the token-keyed cleanup is keyed by token rather than by socket, this deleted the clientsByToken entry and the Redis request-channel subscription of the established, healthy connection. That connection then remained open and heartbeating but was unroutable, so every subsequent MCP tool call for the user failed although a valid plugin connection existed. removeConnection now performs the token-keyed cleanup only if the removed connection actually owns the token registration, so rejecting a duplicate releases only the resources the newcomer itself registered. AI-assisted-by: claude-fable-5
This commit is contained in:
parent
1ae9334064
commit
b4659df5b2
@ -133,9 +133,10 @@ export class PluginBridge {
|
|||||||
/**
|
/**
|
||||||
* Removes a client connection and releases all resources associated with it.
|
* Removes a client connection and releases all resources associated with it.
|
||||||
*
|
*
|
||||||
* Clears the per-connection keep-alive interval and removes the connection
|
* Clears the per-connection keep-alive interval and removes the connection from the
|
||||||
* from both the socket-keyed and token-keyed indexes. Safe to call with a
|
* socket-keyed index. The token-keyed index entry (and, in multi-instance mode, the
|
||||||
* socket that is not (or no longer) registered.
|
* token's Redis task subscription) is removed only if it is owned by the given
|
||||||
|
* connection. Safe to call with a socket that is not (or no longer) registered.
|
||||||
*
|
*
|
||||||
* @param ws - The WebSocket whose connection state should be removed
|
* @param ws - The WebSocket whose connection state should be removed
|
||||||
*/
|
*/
|
||||||
@ -147,12 +148,18 @@ export class PluginBridge {
|
|||||||
clearInterval(connection.pingInterval);
|
clearInterval(connection.pingInterval);
|
||||||
this.connectedClients.delete(ws);
|
this.connectedClients.delete(ws);
|
||||||
if (connection.userToken) {
|
if (connection.userToken) {
|
||||||
this.clientsByToken.delete(connection.userToken);
|
// Perform the token-keyed cleanup only if this connection owns the token registration.
|
||||||
|
// A connection rejected as a duplicate carries the same token but must not remove token associations.
|
||||||
|
if (this.clientsByToken.get(connection.userToken) !== connection) {
|
||||||
|
this.logger.debug("Removed connection does not own its token registration; skipping token cleanup");
|
||||||
|
} else {
|
||||||
|
this.clientsByToken.delete(connection.userToken);
|
||||||
|
|
||||||
if (this.redisBridge) {
|
if (this.redisBridge) {
|
||||||
this.redisBridge
|
this.redisBridge
|
||||||
.unsubscribeFromTasks(connection.userToken)
|
.unsubscribeFromTasks(connection.userToken)
|
||||||
.catch((error) => this.logger.error(error, "Failed to unsubscribe from Redis task channel"));
|
.catch((error) => this.logger.error(error, "Failed to unsubscribe from Redis task channel"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user