Skip to content

Improve websocketd_RxPutC semaphore locking#21

Merged
terjeio merged 1 commit intogrblHAL:masterfrom
bclbruno:master
May 6, 2026
Merged

Improve websocketd_RxPutC semaphore locking#21
terjeio merged 1 commit intogrblHAL:masterfrom
bclbruno:master

Conversation

@bclbruno
Copy link
Copy Markdown
Contributor

@bclbruno bclbruno commented May 6, 2026

  • The mutex now strictly guards the rxbuf update, matching the pattern already used by streamGetC, streamRxFlush, and websocketd_RxCancel.
  • enqueue_realtime_command is a function pointer that any plugin can override via streamSetRtHandler. rx_mux is created with xSemaphoreCreateMutex() (a non-recursive FreeRTOS mutex). If a custom handler ever calls back into websocketd_RxPutC (directly, or indirectly via stream code), the old version would attempt to take a mutex already held by the same task and block forever. The new version eliminates that hazard entirely.
  • websocketd_RxPutC runs from the lwIP/TCPIP task context (called from websocket_msg_parse inside websocket_recv). Holding rx_mux across an arbitrary, plugin-supplied callback meant the lwIP task could be blocked behind any work that handler did. The grbl protocol task also takes rx_mux in streamGetC to drain bytes — long handler execution under the lock would directly stall both the network RX path and grbl input. Shrinking the critical section to a few buffer-pointer updates removes that stall window and reduces the chance of a higher-priority task being held off by a lower-priority one.

Behavior preservation

  • Return value semantics (ok && !overflow) are identical on the reachable path.
  • Realtime commands are still consumed before being buffered (same precedence as before).
  • Overflow detection and the streambuffers.rxbuf.overflow flag continue to work unchanged.
  • No public API or call-site changes are required elsewhere.

- The mutex now strictly guards the rxbuf head/tail/data update, matching the pattern already used by streamGetC, streamRxFlush, and websocketd_RxCancel.
-  enqueue_realtime_command is a function pointer that any plugin can override via streamSetRtHandler (e.g. MPG, keypad, macros, sender shims). rx_mux is created with xSemaphoreCreateMutex()  (a non-recursive FreeRTOS mutex). If a custom handler ever calls back into websocketd_RxPutC (directly, or indirectly via stream code), the old version would attempt to take a mutex already held by the same task and block forever. The new version eliminates that hazard entirely.
- websocketd_RxPutC runs from the lwIP/TCPIP task context (called from websocket_msg_parse inside websocket_recv). Holding rx_mux across an arbitrary, plugin-supplied callback meant the lwIP task could be blocked behind any work that handler did. The grbl protocol task also takes rx_mux in streamGetC to drain bytes — long handler execution under the lock would directly stall both the network RX path and grbl input. Shrinking the critical section to a few buffer-pointer updates removes that stall window and reduces the chance of a higher-priority task being held off by a lower-priority one.
@terjeio
Copy link
Copy Markdown
Contributor

terjeio commented May 6, 2026

Did you manage to enter such a deadlock from your own custom code?

@bclbruno
Copy link
Copy Markdown
Contributor Author

bclbruno commented May 6, 2026

Yes, I tested it using a ESP32.
Send CMD_JOG_CANCEL using a websocket stream.
It will call hal.stream.cancel_read_buffer() which will attempt to re-acquire the semaphore.

But that's not my custom code, the protocol_enqueue_realtime_command from protocol.c has the issue

@terjeio
Copy link
Copy Markdown
Contributor

terjeio commented May 6, 2026

Yes, I tested it using a ESP32.

Thank you.

@terjeio terjeio merged commit 910d1fa into grblHAL:master May 6, 2026
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