Discard what a failed remote transfer still sends, and don't block on flush by kfox · Pull Request #40 · SensoriumEmbedded/TeensyROM · GitHub
Skip to content

Discard what a failed remote transfer still sends, and don't block on flush - #40

Merged
SensoriumEmbedded merged 8 commits into
SensoriumEmbedded:mainfrom
kfox:fw-transfer-deadlines
Oct 23, 2026
Merged

SensoriumEmbedded merged 8 commits into
SensoriumEmbedded:mainfrom
kfox:fw-transfer-deadlines

Conversation

@kfox

@kfox kfox commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Split out of #37 and independent of the extension work. Based on main.

The problem.

  • A failed receive leaves the rest of the payload in the command stream. When a file upload or a WriteC64Mem hits the per-byte timeout (the PC pauses for more than 500 mS, for example), the bytes the peer still sends are parsed as the next commands.
  • Some flushes wait forever. EthernetClient::flush() and the USB host serial's flush() wait for the peer to acknowledge, with no deadline, so a peer that disappears mid-transfer can hang loop(). The writes themselves can block too (see the follow-ups).
  • A failed upload left its file behind. The truncated or corrupt file stayed on the card, where it could be launched as a broken cart or program, and the client's retry got "File already exists."
  • The drain's ceiling counted from the start of the command. A transfer that averaged under 2 bytes/mS and failed late was already past it, so the drain returned at once and the rest of the payload ran as commands.

The fix.

  • Draining after a failed receive. Receiving a file and WriteC64Mem now discard what the peer is still owed through the existing DiscardCmdInput, which is itself bounded. It and SpansCeilingmS move out of #ifdef Fab04_FullDMACapable so the plain TR build has them too.
  • The drain counts from the failure. Its callers pass millis() and the bytes still owed, so the ceiling is the remainder's time at 2 bytes/mS plus the slack.
  • A failed upload is removed. PostFileCommand removes the file when ReceiveFileData fails, on a timeout or a checksum failure. GetFileStream only creates new files, so it cannot delete anything that was there before.
  • Non-blocking flushes. The flushes on these paths go through FlushCmdChannel, which does not block.

Merging alongside the others

Measured on this branch

  • --target tr-plus and --target tr both build clean. npm test passes 167/167.
  • Measured on a TR+ over USB, at 18e299f7. A WriteC64Mem and a PostFile each declared 1000 bytes. Each sent 100 bytes, stalled 0.7 s, then sent the rest as VersionInfo tokens.
    • With this PR: each answered Timeout! with its failure, then nothing, and the next command was answered once.
    • On a build without this PR: every one of those tokens ran as a command, about 450 version banners per stall.

Not measured: the Ethernet and USB-host channels, and the no-deadline flush() claim against those libraries' source. The upload removal and the drain-from-failure change (the commits after 18e299f7) are built and unit-tested only; no hardware run yet, including a plain TR.

Proposed follow-ups

  • Writes that block with no deadline. EthernetClass::socketSend waits for room in the send buffer, and USBSerialBase::write in USBHost_t36 does the same, so a reply larger than the free buffer can still hang loop() when the peer has vanished. This belongs with the overall deadline below.
  • An overall transfer deadline. A peer that trickles one byte just inside the per-byte timeout can hold loop(), and with it the C64 menu, for 65535 × 500 mS (9.1 hours) on a WriteC64Mem, and longer for a file. Left out because no real client has been seen doing it, and a peer that can open the command channel can already launch files, write C64 memory and flash firmware. A per-channel version of this was measured in an earlier revision of this PR: 1000 bytes at one byte every 300 mS answered Too slow, 18 of 1000 bytes after about 5.4 s.

@SensoriumEmbedded

Copy link
Copy Markdown
Owner

@kfox
kfox marked this pull request as draft September 29, 2026 15:21
@kfox

kfox commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

This may be rewritten or closed later. A small subset of the changes in this PR will be added to another PR as a requirement to support the PR#43 changes.

@MetalHexx

Copy link
Copy Markdown
Contributor

Will wait for @MetalHexx to review, may delay until next FW release

@kfox Why don't you rebase this branch with main and I will do some testing. Seems you have some conflicts to resolve at the moment.

A transfer's only bound was the per-byte timeout, so a peer trickling one
byte just inside it held loop() -- and with it the C64 menu -- for as long as
it liked: 65535 x 500 mS for a WriteC64Mem, and longer for a file. Receiving
and sending a file and WriteC64Mem now stop at TransferCeilingmS(len), a floor
plus the length at a rate well under what each channel really does, and
answer FailToken with how far they got.

After a failed receive, DrainCmdChannel discards what the peer is still
sending, bounded itself, so the rest of the payload is not read as the next
command. And the flushes on these paths go through FlushCmdChannel, which
does not block: EthernetClient::flush() and the USB host serial's flush wait
with no deadline for a peer to acknowledge.
@kfox
kfox force-pushed the fw-transfer-deadlines branch from a18d4a1 to 08c44a5 Compare October 9, 2026 03:29
kfox added 4 commits October 8, 2026 22:32
No peer has been seen trickling bytes just under the per-byte timeout, and
one that could already has full control of the board. A failed receive now
discards the rest of its payload through the existing DiscardCmdInput
instead of a second drain helper.
@kfox
kfox marked this pull request as ready for review October 22, 2026 03:15
@SensoriumEmbedded

Copy link
Copy Markdown
Owner

kfox added 3 commits October 22, 2026 12:34
…ommand start

DiscardCmdInput stops 2 S past SpansCeilingmS(Bytes), measured from Began.
ReceiveFileData and WriteC64Mem passed the command's start and its whole
length, so a transfer averaging under 2 bytes/mS that failed late was
already past the ceiling: the drain returned at once and the rest of the
payload ran as commands. They now pass millis() and the bytes still owed,
so the ceiling is the remainder's time at 2 bytes/mS from the failure.
The no-memory-to-stage drain in WriteC64Mem is the same shape and gets
the same arguments.

The comments over SpansCeilingmS and DiscardCmdInput described a command
deadline that only WriteC64Spans still has.
ReceiveFileData closes the file and returns false on a timeout and on a
checksum failure, leaving a truncated or corrupt file on the card.
GetFileStream refuses a file that already exists, so the client's retry
got "File already exists", and the broken file could be launched as a
cart or program. PostFileCommand now removes it on either failure.
GetFileStream only ever creates a new file, so this cannot delete
anything the user already had.
@kfox kfox changed the title Put a deadline on remote file and memory transfers Discard what a failed remote transfer still sends, and don't block on flush Oct 22, 2026
@SensoriumEmbedded
SensoriumEmbedded merged commit 96daba1 into SensoriumEmbedded:main Oct 23, 2026
4 checks passed
SensoriumEmbedded added a commit that referenced this pull request Oct 24, 2026
The responses posted on PR #39 and PR #40, copied from GitHub, as
references for the shape of a draft. Both predate the current tone
rules, so the skill says the rules win where an example differs.

The tone rules change with them:
- The first response thanks the author for the work itself, not
  only its testing.
- Memory improvements are requested changes before merging, each its
  own bullet (the lever, what it saves, why it's safe).
- The hardware-testing heading carries the framing: for one of us to
  possibly get to before release, not gates, with anything unlisted
  covered or fine. The "everything else" line and the offer of a
  hand are gone.
- When one author gets responses on several related PRs at once, the
  shared lines go only in the first.
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.

3 participants