Skip to content

Don't send to network clients from the caller's task - #101

Open
ellensp wants to merge 1 commit into
luc-github:3.0.beta1from
ellensp:fix-s2s-queue-3.0
Open

ellensp wants to merge 1 commit into
luc-github:3.0.beta1from
ellensp:fix-s2s-queue-3.0

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. On each newline (and when the buffer filled or timed out) it called flush(), which ran esp3d_commands.process() on the line in that task. So dispatching to the websocket and other clients ran in Marlin's task, and WebSocket_Server::writeBytes() calls broadcastBIN() when its own buffer fills.

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 watches that task with a 4 s task watchdog (USE_WATCHDOG, MarlinFirmware/Marlin#28725), so the board resets. A web UI 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 the processing in the ESP3D task:

  • write() and flush() turn the TX buffer into a message as before, but put it on a FreeRTOS queue (S2S_TXQUEUESIZE, 32) instead of processing it. If the queue is full, the message is dropped.
  • handle(), called from the ESP3D task, processes the queued messages after handle_flush().
  • WebSocket_Server::flushTXbuffer() sends to each connected client with sendBIN() instead of broadcastBIN(), and disconnects a client whose send fails, so it can't stall every later send.

Testing

MKS TinyBee, Marlin bugfix-2.1.x with arduino-esp32 3.0.4 (pioarduino 51.03.04), ESP3D_WIFISUPPORT, WEBSUPPORT, USE_WATCHDOG, station mode. A script sent M503 to /command?cmd= every 0.3 s and M118 over USB serial every 0.5 s. A test websocket client (webui-v3, port 81) completed the handshake and then never read.

Before After
Stalled client + M503 flood, 60 s Task watchdog reset (loopTask) 7.7 s in No reset; serial M118 answered in 0.06 s or less
Reading client alone, 20 rounds All output received All output received
New reading client straight after a stalled-client run, 20 rounds All output received

Serial_2_Socket::write runs in Marlin's loop task. On each newline it
processed the line there, so dispatching to the websocket and other
clients ran in Marlin's task. Sending to a websocket 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() and flush() now put each message on a queue (dropping it if the
queue is full), and handle(), in the ESP3D task, processes the queue.
WebSocket_Server sends to each client in turn and disconnects a client
whose send fails, so it can't stall later sends.
@luc-github

luc-github commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Thank you, Yes the processing should be in ESP3DLib task on core 0 - interresting that I never got such issue - but the WDT is handled differently in my fork as far I can remember - I will review it
I keep the PR as planned and will sync once Upstream is ready to avoid mixed code with my fork like the other one and a sync with latest ESP3D code update, actual ESP3DLib is based on 3.0.0.7b , when ESP3D is now 3.1.3 ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants