jim800121chen 3c6971febd fix(local-tool): Review M1-M4 + m5 修復 — flash 生命週期 + store 隔離
Review 問題修復:

M1(寫已關閉 channel panic):
- flash service goroutine 改成先等 driver.Flash() 返回,再寫 error 訊息,最後 close
- driver.Flash 返回後保證不再寫 progressCh,消除 race condition

M2(FlashTask 永不清除 memory leak):
- service.go 新增 CleanupTask(taskID) 公開方法
- device_handler.go 的 goroutine 在 `for range progressCh` 結束後呼叫 CleanupTask

M3(同裝置重複 flash taskID 衝突):
- ProgressTracker.Create 改成:舊 task 未完成時返回 nil
- StartFlash 檢查 nil → 回傳 "flash already in progress" 錯誤

M4(前端 flash store 全域不區分 deviceId):
- flash-store.ts 新增 activeDeviceId 欄位
- updateProgress 改接 (deviceId, progress),比對 activeDeviceId 防止混裝
- use-flash-progress.ts 的 WebSocket callback 傳入 deviceId

m5(flash_ws.go 雙重 conn.Close):
- read pump goroutine 移除 defer conn.Close(),由外層 defer 統一關閉

額外修復(S4):
- modelPath 為空時直接回 error 而非傳無效路徑給 driver

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:16:24 +08:00

62 lines
1.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package flash
import (
"sync"
"visiona-local/server/internal/driver"
)
type FlashTask struct {
ID string
DeviceID string
ModelID string
ProgressCh chan driver.FlashProgress
Done bool
}
type ProgressTracker struct {
tasks map[string]*FlashTask
mu sync.Mutex
}
func NewProgressTracker() *ProgressTracker {
return &ProgressTracker{
tasks: make(map[string]*FlashTask),
}
}
// Create 建立新 flash task。如果同 taskID 已存在且未完成,回傳 nil 表示拒絕。
func (pt *ProgressTracker) Create(taskID, deviceID, modelID string) *FlashTask {
pt.mu.Lock()
defer pt.mu.Unlock()
// M3 fix: 防止同裝置重複 flash — 舊 task 未完成就拒絕
if existing, ok := pt.tasks[taskID]; ok && !existing.Done {
return nil
}
task := &FlashTask{
ID: taskID,
DeviceID: deviceID,
ModelID: modelID,
ProgressCh: make(chan driver.FlashProgress, 20),
Done: false,
}
pt.tasks[taskID] = task
return task
}
func (pt *ProgressTracker) Get(taskID string) (*FlashTask, bool) {
pt.mu.Lock()
defer pt.mu.Unlock()
t, ok := pt.tasks[taskID]
return t, ok
}
// Remove 清除已完成的 task釋放 map entry。
func (pt *ProgressTracker) Remove(taskID string) {
pt.mu.Lock()
defer pt.mu.Unlock()
delete(pt.tasks, taskID)
}