Skip to content

Don't send to the websocket from the caller's task - #100

Merged
luc-github merged 1 commit into
luc-github:masterfrom
ellensp:fix-s2s-nonblocking-write
Oct 24, 2026
Merged

luc-github merged 1 commit into
luc-github:masterfrom
ellensp:fix-s2s-nonblocking-write

Conversation

@ellensp

@ellensp ellensp commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Description

Serial_2_Socket::write() is called by Marlin, from Marlin's loop task. When the TX buffer filled, or 500 ms after the first byte, it called flush(), which sent the buffer with broadcastBIN() from that task.

Sending to a websocket client that has stopped reading blocks. With a full TCP send buffer, WiFiClient::write() waits up to 10 × 1 s in select(), and WebSockets::write() keeps retrying for up to WEBSOCKETS_TCP_TIMEOUT (5 s). Marlin's loop task stops for that time. Marlin bugfix-2.1.x watches that task with a 4 s task watchdog (USE_WATCHDOG, MarlinFirmware/Marlin#28725), so the board resets. A browser tab left open on a phone that has gone to sleep, or a client that dropped off the WiFi, can do this during a print.

This PR keeps all network sending in the ESP3DLib task:

  • write() only copies into the TX buffer, under a mutex. Output that doesn't fit in the buffer is dropped.
  • flush() only sets a flag.
  • handle_flush(), called from Web_Server::handle() in the ESP3DLib task, sends the buffer when the flag is set, when it is half full, or after FLUSHTIMEOUT. It copies the buffer out under the mutex, then sends to each connected client with sendBIN(). A client whose send fails is disconnected, so it can't stall every later send.

Testing

MKS TinyBee, Marlin bugfix-2.1.x (mks_tinybee, arduino-esp32 1.0.6), ESP3D_WIFISUPPORT, WEBSUPPORT, USE_WATCHDOG, station mode. A script sent M503 to /command every 0.3 s and M118 over USB serial every 0.5 s. A test websocket client on port 81 completed the handshake and then never read.

Before After
Stalled client + M503 flood, 60 s Task watchdog reset (loopTask) about 27 s in No reset; serial M118 answered in 0.06 s or less
Reading client alone, 40 rounds All M118 and M503 output received
Reading client + stalled client, 40 rounds 39/40 received. The one lost round falls in a single ~15 s stall of the ESP3DLib task while the stalled client is dropped; no further stalls after that

Serial_2_Socket::write runs in Marlin's loop task and sent the buffer
from there when it filled. Sending to a client that has stopped reading
waits up to about 15 s (WiFiClient write retries plus the WebSockets
TCP timeout), long enough to trip Marlin's task watchdog.

write() now only copies into the TX buffer, under a mutex, and drops
output that doesn't fit. flush() only sets a flag. The ESP3DLib task
sends the buffer from handle_flush(), to each client in turn, and
disconnects a client whose send fails so it can't stall later sends.
@luc-github
luc-github merged commit bfe08bf into luc-github:master Oct 24, 2026
1 check passed
@luc-github

Copy link
Copy Markdown
Owner

Merged - thank you

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.

2 participants