From 8c3a299714514a1451b9fc9aa0e527f83fc37377 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 29 Dec 2025 13:47:51 -0800 Subject: [PATCH] =?UTF-8?q?fix(service-party-app):=20joinSession=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=87=8D=E8=AF=95=E9=80=BB=E8=BE=91=E5=A4=84?= =?UTF-8?q?=E7=90=86=E4=B9=90=E8=A7=82=E9=94=81=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: - 多个参与方同时加入会话时会触发乐观锁冲突 - server-party 有重试逻辑可以成功重试 - service-party-app (Electron) 没有重试逻辑,直接失败 - 导致外部参与方无法成功加入 co_managed_keygen 会话 修复: - joinSession 方法添加最多 3 次重试 - 支持重试的错误类型:optimistic lock、UNAVAILABLE、DEADLINE_EXCEEDED - 使用指数退避 + 随机抖动避免重试风暴 - 抽取 doJoinSession 内部方法和 sleep 辅助方法 Generated with Claude Code Co-Authored-By: Claude Opus 4.5 --- .../electron/modules/grpc-client.ts | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/backend/mpc-system/services/service-party-app/electron/modules/grpc-client.ts b/backend/mpc-system/services/service-party-app/electron/modules/grpc-client.ts index 81b58ca9..40ce6a99 100644 --- a/backend/mpc-system/services/service-party-app/electron/modules/grpc-client.ts +++ b/backend/mpc-system/services/service-party-app/electron/modules/grpc-client.ts @@ -436,13 +436,54 @@ export class GrpcClient extends EventEmitter { } /** - * 加入会话 + * 加入会话(带重试逻辑) + * + * 支持重试以处理乐观锁冲突等瞬态错误 */ async joinSession(sessionId: string, partyId: string, joinToken: string): Promise { if (!this.client) { throw new Error('Not connected'); } + const maxRetries = 3; + const baseDelayMs = 500; + let lastError: Error | null = null; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const result = await this.doJoinSession(sessionId, partyId, joinToken); + return result; + } catch (err) { + lastError = err as Error; + const errorMessage = lastError.message || ''; + + // 判断是否为可重试的错误 + const isRetryable = + errorMessage.includes('optimistic lock') || + errorMessage.includes('modified by another transaction') || + errorMessage.includes('UNAVAILABLE') || + errorMessage.includes('DEADLINE_EXCEEDED'); + + if (!isRetryable || attempt >= maxRetries) { + console.error(`[gRPC] joinSession failed after ${attempt} attempt(s):`, errorMessage); + throw lastError; + } + + // 计算退避延迟(指数退避 + 随机抖动) + const delay = baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 100; + console.log(`[gRPC] joinSession attempt ${attempt}/${maxRetries} failed (${errorMessage}), retrying in ${Math.round(delay)}ms...`); + + await this.sleep(delay); + } + } + + throw lastError || new Error('joinSession failed after retries'); + } + + /** + * 内部方法:执行单次 joinSession 调用 + */ + private doJoinSession(sessionId: string, partyId: string, joinToken: string): Promise { return new Promise((resolve, reject) => { (this.client as grpc.Client & { joinSession: (req: unknown, callback: (err: Error | null, res: JoinSessionResponse) => void) => void }) .joinSession( @@ -462,6 +503,13 @@ export class GrpcClient extends EventEmitter { }); } + /** + * 辅助方法:异步等待 + */ + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + /** * 订阅会话事件(带自动重连) */