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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions