fix(service-party-app): joinSession 添加重试逻辑处理乐观锁冲突

问题:
- 多个参与方同时加入会话时会触发乐观锁冲突
- 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 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-29 13:47:51 -08:00
parent 6de545fcb9
commit 8c3a299714
1 changed files with 49 additions and 1 deletions

View File

@ -436,13 +436,54 @@ export class GrpcClient extends EventEmitter {
}
/**
*
*
*
*
*/
async joinSession(sessionId: string, partyId: string, joinToken: string): Promise<JoinSessionResponse> {
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<JoinSessionResponse> {
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<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
*
*/