rwadurian/backend/mpc-system/services
hailin 6dda30c528 fix(android): 实现统一的 Job 管理器,防止协程泄漏 [P0-3]
【架构安全修复 - 防止协程泄漏和内存泄漏】

## 问题背景

TssRepository 原有 4 个独立的 Job 变量:
- messageCollectionJob: 消息路由任务
- sessionEventJob: 会话事件订阅任务
- sessionStatusPollingJob: 会话状态轮询任务
- progressCollectionJob: 进度收集任务

每个 Job 需要手动取消,容易在以下场景导致协程泄漏:
1. Activity 销毁时某个 Job 忘记取消 → 后台协程继续运行 → 内存泄漏 → OOM
2. 快速重启连接时旧 Job 未取消 → 多个 Job 并行运行 → 资源竞争
3. 异常路径中某个 Job 未取消 → 僵尸协程 → 内存累积

## 修复方案

### 1. 创建 JobManager 统一管理类
```kotlin
private inner class JobManager {
    private val jobs = mutableMapOf<String, Job>()

    fun launch(name: String, block: suspend CoroutineScope.() -> Unit): Job {
        jobs[name]?.cancel()  // 自动取消同名旧 Job
        val job = repositoryScope.launch(block = block)
        jobs[name] = job
        return job
    }

    fun cancel(name: String) { ... }
    fun isActive(name: String): Boolean { ... }
    fun cancelAll() { ... }  // 一键清理所有 Job
}
```

### 2. 定义 Job 名称常量
```kotlin
companion object {
    const val JOB_MESSAGE_COLLECTION = "message_collection"
    const val JOB_SESSION_EVENT = "session_event"
    const val JOB_SESSION_STATUS_POLLING = "session_status_polling"
    const val JOB_PROGRESS_COLLECTION = "progress_collection"
}
```

### 3. 迁移所有 Job 使用方式

**启动 Job:**
```kotlin
// BEFORE:
messageCollectionJob?.cancel()
messageCollectionJob = repositoryScope.launch { ... }

// AFTER:
jobManager.launch(JOB_MESSAGE_COLLECTION) { ... }
// 自动取消旧 Job,无需手动 cancel
```

**取消 Job:**
```kotlin
// BEFORE:
messageCollectionJob?.cancel()

// AFTER:
jobManager.cancel(JOB_MESSAGE_COLLECTION)
```

**检查 Job 状态:**
```kotlin
// BEFORE:
if (messageCollectionJob == null || messageCollectionJob?.isActive != true)

// AFTER:
if (!jobManager.isActive(JOB_MESSAGE_COLLECTION))
```

**清理所有 Job:**
```kotlin
// BEFORE (需要手动取消每个 Job,容易遗漏):
fun cleanup() {
    messageCollectionJob?.cancel()
    sessionEventJob?.cancel()
    sessionStatusPollingJob?.cancel()
    progressCollectionJob?.cancel()  // 如果漏了这个 → 内存泄漏
    repositoryScope.cancel()
}

// AFTER (一键清理,永不遗漏):
fun cleanup() {
    jobManager.cancelAll()
    repositoryScope.cancel()
}
```

## 修复的崩溃场景

### 场景 1: Activity 快速销毁重建
- **原问题**: Activity 销毁时如果某个 Job 未取消,后台协程继续持有 Activity/Context 引用
- **后果**: 内存泄漏,多次重建后 OOM 崩溃
- **修复**: JobManager.cancelAll() 确保所有 Job 都被取消

### 场景 2: 网络重连时资源竞争
- **原问题**: disconnect() 后 reconnect() 启动新 Job,但旧 Job 未取消
- **后果**: 多个 messageCollectionJob 并行运行,消息重复处理,状态混乱
- **修复**: JobManager.launch() 自动取消同名旧 Job

### 场景 3: 异常路径中 Job 未清理
- **原问题**: try-catch 中异常发生后,cleanup 逻辑被跳过
- **后果**: 僵尸协程累积,内存持续增长
- **修复**: JobManager 集中管理,即使部分清理失败,cancelAll() 仍能清理全部

## 影响范围

### 修改的函数 (共 11 个):
1. disconnect() - 使用 jobManager.cancelAll()
2. cleanup() - 使用 jobManager.cancelAll()
3. startSessionEventSubscription() - 使用 jobManager.launch(JOB_SESSION_EVENT)
4. ensureSessionEventSubscriptionActive() - 使用 jobManager.isActive(JOB_SESSION_EVENT)
5. startProgressCollection() - 使用 jobManager.launch(JOB_PROGRESS_COLLECTION)
6. stopProgressCollection() - 使用 jobManager.cancel(JOB_PROGRESS_COLLECTION)
7. startSessionStatusPolling() - 使用 jobManager.launch(JOB_SESSION_STATUS_POLLING)
8. stopSessionStatusPolling() - 使用 jobManager.cancel(JOB_SESSION_STATUS_POLLING)
9. startMessageRouting() - 使用 jobManager.launch(JOB_MESSAGE_COLLECTION)
10. cancelSession() - 使用 jobManager.cancel() 取消多个 Job
11. 多个签名/密钥生成完成后的清理逻辑 - 使用 jobManager.cancel(JOB_MESSAGE_COLLECTION)

### 删除的变量:
- messageCollectionJob: Job?
- sessionEventJob: Job?
- sessionStatusPollingJob: Job?
- progressCollectionJob: Job?

### 新增代码:
- JobManager 内部类 (110 行,含详细注释)
- 4 个 Job 名称常量

## 测试验证

编译状态:  BUILD SUCCESSFUL in 2m 10s
- 无编译错误
- 仅有警告 (unused parameters),不影响功能

## 后续优化建议

可以进一步优化:
1. 添加 Job 超时检测 (避免永久运行的僵尸协程)
2. 添加 Job 异常处理回调 (统一的错误处理)
3. 添加 Job 启动/取消日志 (已在 JobManager 中实现)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-26 21:38:03 -08:00
..
account Revert "fix(co-keygen): convert threshold at storage time to match tss-lib convention" 2025-12-31 10:24:25 -08:00
message-router fix(message-router): prevent subscription race condition on gRPC reconnect 2026-01-01 10:04:11 -08:00
server-party fix(participate_signing): 恢复 Execute 方法的 UserShareData 分支 2026-01-26 19:00:52 -08:00
server-party-api fix(context): use parent context instead of Background() to allow proper cancellation 2025-12-06 06:36:34 -08:00
server-party-co-managed fix(co-managed): 使用数据库中的 PartyIndex 而非循环索引 2026-01-26 20:24:32 -08:00
service-party-android fix(android): 实现统一的 Job 管理器,防止协程泄漏 [P0-3] 2026-01-26 21:38:03 -08:00
service-party-app fix(tss): 修复备份恢复后签名失败的问题 2026-01-20 00:39:05 -08:00
session-coordinator feat(session): broadcast participant_joined event via gRPC for real-time UI updates 2026-01-01 08:34:47 -08:00
tss-wasm feat(tss): add real-time round progress from msg.Type() parsing 2026-01-01 22:41:51 -08:00