Skip to content

macOS Chrome Web Serial loses bytes during bulk 0x84/0x8A reads (affects SD Download + live previews) #153

Description

@mbreiser

Summary

On macOS, Chrome's Web Serial API silently loses bytes during bulk 0x84/0x8A reads from the G6 controller. This affects the pre-existing SD-table Download button and the live pattern previews re-enabled by #151. It is a host-side (Chromium/macOS) limitation, not a firmware or #151 regression: the same controller sustains flawless full-speed downloads over pyserial on the same machine (the firmware repo's full HIL suite passes), and the firmware recovers cleanly from every aborted transfer (the reiserlab/LED-Display_G6_Firmware_Arena#16 fix behaving as designed).

Environment

Evidence

All with a 21,338-byte pattern (002_grating_sq.pat); byte counts from arena-link.js's own bulk-read timeout diagnostics:

  1. Default Web Serial receive buffer (255 bytes, no bufferSize passed in port.open): every bulk read stalled early — bulk-read data timeout: got 5650/21338 bytes. Preview spinner timed out; Download button failed.
  2. With bufferSize: 1 MiB (local patch, below): the first bulk read after a fresh port-open completes reliably — the fix(console+studio): re-enable live 0x84 pattern preview #151 thumbnail rendered end-to-end (20-frame badge, hover animation). Subsequent bulk reads still fail intermittently with variable loss points: got 20625/21338, then got 17048/21338 on the next attempt.
  3. The missing bytes are lost, not delayed: after a failed read, the next framed command returns a clean, correctly-framed reply with no stray body bytes flushing out first.
  4. No JS exceptions, no read-loop error, no Chrome console errors — the loss is silent.
  5. Same controller, same cable, same Mac, via pyserial: sustained multi-MB downloads at ~8 MB/s pass repeatedly (firmware HIL suite, 81/81 green).

Known upstream issue

This matches the reported Chromium Web Serial behavior for CDC devices under bulk transfer:

Mitigation (proposed, one line + comment)

js/arena-link.js currently opens with { baudRate } only. Passing a large receive buffer takes the failure mode from "always fails a few KB in" to "first-transfer-after-open reliably works; later transfers intermittently lose tail bytes":

// bufferSize: Web Serial's default receive buffer is only 255 bytes.
// On macOS that is too small for 0x84/0x8A bulk downloads — the CDC
// read pipeline stalls a few KB into a fast burst, the controller
// rightly treats the dead host as stalled (issue #16 fix 1, ~2 s bound)
// and aborts the transfer. 1 MiB absorbs a whole transfer's burstiness.
await this._port.open({ baudRate, bufferSize: 1 << 20 });

(Currently applied locally on the bench Mac; happy to push to a branch/PR.)

Possible further directions (untested)

  • Retry-with-reopen in sendBulkRead: the strong first-transfer-after-open pattern suggests closing/reopening the port (or cancelling and re-acquiring the reader) between bulk reads may reset whatever state accumulates. Ugly but automatable inside ArenaLink.
  • Application-level integrity + retry: the bulk read already knows the expected byte count; on shortfall, retry the 0x84 once or twice before surfacing an error (previews are idempotent; the firmware recovers cleanly between attempts).
  • Firmware-side host pacing (last resort): a host-requested throttle on serial bulk streaming would sidestep the burst overrun, at the cost of firmware churn right after fix: bound and de-block SET/GET_PATTERN_FILE transfers on stalled hosts (#16) LED-Display_G6_Firmware_Arena#27.

Impact

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions