Skip to content

fix(serial): 高占空比下接收帧合并不切分 + 总计字节数恒为 0 (#9) - #14

Merged
Gyanano merged 1 commit into
developfrom
bugfix/rx-frame-merge-high-duty-cycle
Sep 2, 2026
Merged

fix(serial): 高占空比下接收帧合并不切分 + 总计字节数恒为 0 (#9)#14
Gyanano merged 1 commit into
developfrom
bugfix/rx-frame-merge-high-duty-cycle

Conversation

@Gyanano

@Gyanano Gyanano commented Sep 2, 2026

Copy link
Copy Markdown
Owner

问题

用户在 V1.4.0 实测:115200 波特率 + 2560 字节 / 250ms 周期自收发,发送计数涨到 30 时接收帧数停在 1。

根因

  1. 帧合并不切分(主因):帧切分为 Timeout 模式(默认 10ms)。上述工况下线缆占用 222ms,帧间空闲仅 ~28ms,小于读超时 50ms——read() 直接带着下一段数据返回,永远不会走 TimedOut 分支,而 flush_if_idle 只在空读/超时分支里调用。段内缓冲持续增长,直到 64KiB 硬上限才切一帧(30 次发送 = 76.8KB → 恰好 1 帧,与现象完全吻合)。字节没有丢失(raw 落盘与统计在分帧之前),只是帧边界消失、界面看起来「接收停了」。
  2. 总计字节数恒为 0(顺带发现的显示回归):Step 4 事件化后,事件来源的 LogEntry 按设计 data 为空,而 totalBytes 仍按 data.length 求和。

修复

  • 读循环在 feed()\) 之前先 flush_if_idle(now)`:只要观察到距上一段数据的空闲间隔超过切分超时,就先闭合待切帧——Timeout 语义不再受读超时粒度限制。
  • FrameDto.len 保留为 LogEntry.byte_len,总计按 byte_len ?? data.length 求和。

测试

  • 新增 ScriptEvent::DelayedBytes:read() 阻塞 30ms 后返回下一段(忠实模拟 POSIX read 在等待中收到数据的行为)。
  • timeout_mode_cuts_frame_when_read_returns_after_idle_gap:30ms 间隔 > 10ms 切分超时 → 两帧(修复前合并为 1 帧,测试红)。
  • timeout_mode_merges_when_gap_stays_below_timeout:背靠背到达仍合并为一帧(防止过度切分)。
  • 全套 42 个 Rust 测试绿;前端 typecheck + build 绿。

待人工复测

115200 + 2560B / 250ms 自收发:接收帧数应与发送帧数同步增长(约 4 帧/秒),总计字节数正常累计。

Closes #9


Note

Medium Risk
Touches core serial read/framing behavior on every successful read; regression risk is mitigated by new golden tests, but high-throughput framing edge cases deserve manual retest.

Overview
Fixes Issue #9, where high-duty-cycle traffic made the RX frame count look stuck while sends kept climbing. In default Timeout segmentation, frame boundaries relied on idle flushes mostly on empty reads or TimedOut; when the next chunk arrived within the 50 ms read timeout but after the segmentation timeout (~28 ms gaps at 115200/2560 B/250 ms), chunks merged until the 64 KiB cap—so one giant RX frame matched “30 sends, 1 receive.”

The reader now calls flush_if_idle before feed whenever a read returns bytes, so an elapsed idle gap still closes the pending frame even without a timeout read. Golden harness adds DelayedBytes (blocked read then data) plus tests that a 30 ms gap splits into two frames and back-to-back chunks still merge.

Separately, event-driven log rows carry empty data but FrameDto.len; the UI adds byte_len on LogEntry, maps it in useSerialLogs, and the footer total bytes sums byte_len ?? data.length so the counter is no longer always zero.

Reviewed by Cursor Bugbot for commit 6fd7df7. Bugbot is set up for automated code reviews on this repo. Configure here.

At high duty cycles (e.g. 115200 baud, 2560 B every 250 ms) the
inter-frame gap (~28 ms) never reaches the 50 ms read timeout, so
read() returns the next chunk directly and flush_if_idle was never
consulted — frames merged invisibly until the 64 KiB hard cap, making
the RX frame counter appear stalled while TX kept climbing.

Run the idle check before feed() on every data read: an observed gap
longer than the segmentation timeout now closes the pending frame.
Bytes were never lost (raw tap and stats are pre-framing); this
restores correct frame boundaries.

Regression pinned with a scripted DelayedBytes read that blocks 30 ms
then delivers the next chunk, plus a companion test pinning that
back-to-back chunks still merge.

Also fix the log viewer's total-bytes counter: event-sourced entries
carry empty data by design (Step 4), so the sum always showed 0. The
frame DTO's len is now kept as byte_len and used for the total.
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_bf5209a4-f698-4193-818c-9b52369ac866)

@Gyanano
Gyanano merged commit 83f238c into develop Sep 2, 2026
3 checks passed
@Gyanano
Gyanano deleted the bugfix/rx-frame-merge-high-duty-cycle branch September 2, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(serial): 高负载持续接收时驱动层疑似丢字节(读线程被轮询克隆饿死)

1 participant