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:
parent
6de545fcb9
commit
8c3a299714
|
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅会话事件(带自动重连)
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue