状态:根因已定位并实锤,修复进行中(未完成)。 影响服务:
fwh-toolbox-server(cgroup 内存上限 2GB)。 测试站点:10060001A00F22520081(时区 America/Chicago,-0500/-0600 且有 DST)。
时间分片 periods 构建死循环 → periods 切片无限 append → OOM。
位置:
service/repositories/api/fwh_pro/fwh_eventAlarmLog.go 的 QueryEventOrAlarmListOnTimeRangeservice/repositories/api/fwh_pro/fwh_mqttLog.go 的 QueryMqttLogListOnTimeRange两函数内「按天拆分时间段」的游标推进逻辑:
currTime = dayEnd.Add(time.Second).Truncate(24 * time.Hour) // Truncate 按 UTC 零点对齐
currTime = time.Date(currTime.Year(), currTime.Month(), currTime.Day(), 0,0,0,0, currTime.Location())
当传入的 startTime/endTime 带非 UTC 时区(如 America/Chicago,-0500)时:
dayEnd = 2026-09-19T23:59:59-0500,.Add(1s) = 2026-09-20T00:00:00-0500 = 2026-09-20T05:00:00 UTC;.Truncate(24h) 按 UTC 零点截断 → 2026-09-20T00:00:00 UTC = 2026-09-19T19:00:00-0500(回退到 19 号晚上);time.Date(...,-0500) → 2026-09-19T00:00:00-0500;currTime 从 09-19 变回 09-19 00:00,未前进 → currTime.Before(endTime) 恒成立 → 死循环。验证日志实锤(每 500 个 period 打一次):
periods: 1443000 curr: 2026-09-19T00:00:00-0500 end: 2026-09-19T23:59:59-0500 ← curr 停滞不前
(数秒内 periods 涨到 144 万+,仍在增长)
QueryEventOrAlarmListOnTimeRange / QueryMqttLogListOnTimeRange 是 fwh_pro 共享函数,多个模块调用(fwhTaskSiteAnalysis、fwhTaskBatchDownload、dataDownloadService、mcp/fwh_api、fwhQueueOld)。fwhTaskBatchDownload.go:55) 用 time.Parse(time.DateOnly, ...) 解析日期 → 结果是 UTC 时间。函数内 Truncate(24h) 也按 UTC 对齐,两者一致,游标正常前进,不触发。dataDownloadService.go ExecuteStructuredDataDownloadItem) 用 time.ParseInLocation(time.DateOnly, item.Date, loc) 解析,loc = 站点本地时区(Chicago)→ 带非 UTC 时区 → 触发 Truncate(24h) 回退死循环。结论:时间分片本不该带时区概念(就是「某段日期」的字面值)。BatchDownload 传 UTC 字面值恰好规避;DataDownload 传本地时区时间踩中共享函数里 Truncate(24h) 的 UTC 对齐陷阱。
方向:不改被多模块共用的 fwh_pro 查询函数逻辑;在 DataDownload 调用侧把传给上游查询的时间剥离时区(转为同字面值的 UTC),与 BatchDownload 行为一致。
理由:上游查询只把时间 Format("2006-01-02 15:04:05") 成字符串发给 FWH;UTC 与 Chicago 解析出的 2026-09-14 00:00:00 字面值相同,查询语义不变,但避开 Truncate(24h) 时区陷阱。
注意:ExecuteStructuredDataDownloadItem 里的 start/end(本地时区)仍用于 Frozen(D+2) 判断(!time.Now().In(loc).Before(start.AddDate(0,0,2))),不能改动它们本身;只新增一对「字面值 UTC」时间专用于上游查询。
已完成(service/services/dataDownloadService.go ExecuteStructuredDataDownloadItem):
start/end 定义后新增 startQuery := stripTimeLocation(start) / endQuery := stripTimeLocation(end)(含说明注释);start,end 改为 startQuery,endQuery。待完成(下次接续):
补上 stripTimeLocation 辅助函数(当前编译会因未定义而失败)。建议实现:把时间按字面 y/m/d/h/m/s 重建为 UTC:
func stripTimeLocation(t time.Time) time.Time {
if t.IsZero() { return t }
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC)
}
放在 parseRemoteTime(约 422 行)附近。
在两个共享查询函数上补注释(限制条件),避免再次踩坑:说明「startTime/endTime 必须为不含时区(UTC)的字面时间;内部按天拆分用 time.Truncate(24h) 按 UTC 对齐,传入非 UTC 时区会导致 periods 死循环 OOM」。
去掉临时验证日志(periods 持续增长 Warn),保留「periods 构建完成」Debug(可选)。
go build ./...(相关包)+ go test ./services 验证。
清理毒任务(Mongo 活动态 item/task 置 cancelled + 清 asynq data-download:* 队列与 data-download:* 锁),重新部署 fwh-toolbox-server,重测 event&告警 + mqtt。
dataDownloadBinaryService.go 用 strconv.Atoi("EmsRunLog") 失败。已加 resolveEmsLogType(字符串枚举→数字映射,兼容数字),与 BatchDownload 的 mapEmsLogType 一致。selectIotFirmwareOrderDetailList 对某 orderId 返回 500,导致整个 update_log item 失败重试。建议单个 order detail 失败时跳过并记 Warn,不整体失败。worker Concurrency: 8(dataDownloadQueue.go)。periods 死循环下,多个 item 并发只是让 OOM 更快;根因修复后并发不再是问题。是否降并发可后续评估,非必须。