Summary
Direct patch: https://github.com/potatomushclient/potato/pull/535.patch
Potato can become temporarily unresponsive while a newly connected world sends its initial output burst. During the stall, world switching and input are delayed or ignored, and the world toolbar may visibly redraw or flicker.
The receive path currently reads only 10 bytes for each readable socket event. Every completed line then calls skinStatus and synchronously executes update idletasks. A banner or login screen therefore creates many tiny socket callbacks and repeated full UI/status passes while the connection is populating.
Reproduction
- Keep several worlds open so toolbar/world-switch behavior is visible.
- Connect or reconnect a world that sends a substantial login banner and initial room output.
- Immediately try switching between the new connection and an existing connection using the world toolbar or
Ctrl+<number>.
- Observe delayed/ignored actions and toolbar redraws until the initial output settles.
This was reproduced with Tcl/Tk 8.6.18 while connecting to Dev Asylum. An external PowerShell harness targeted Potato's HWND, sent alternating Ctrl+1 / Ctrl+6 commands during connection, and independently probed the Windows message loop with SendMessageTimeout(WM_NULL).
Measured behavior
Before the receive-loop change, a 25-second active trace recorded:
- 2 Windows-message timeouts at a 100 ms deadline
- 49 message-loop responses taking at least 50 ms
- only 19 of 40 scheduled world-switch attempts completed during the exercise window
- a multi-second interval where UI interaction could not be sampled normally
After the proposed change, the same reconnect and switch exercise recorded:
- 0 Windows-message timeouts
- 0 responses taking at least 50 ms
- all 40 of 40 world-switch attempts completed
- maximum message-loop response of 18.8 ms
CPU remained low during the original stalls, which is consistent with event-loop blocking/re-entrant UI work rather than CPU saturation.
Root cause
::potato::get_mushage reads only ten bytes per readable callback:
set disc [catch {::potato::ioRead $conn($c,id) 10} text]
::potato::get_mushageProcess then performs these operations for every completed line, regardless of whether connection/activity state changed:
skinStatus $c
update idletasks
skinStatus updates toolbar state and layout. update idletasks synchronously drains pending geometry and paint work from inside the receive callback. Together they amplify the callback flood and make input/window messages wait behind incoming data handling.
Proposed fix
- Read a bounded 4096-byte chunk per socket callback instead of 10 bytes.
- Remove unconditional per-line
skinStatus and update idletasks calls.
- Preserve the required status notification exactly where
get_mushageProcess directly changes conn($c,idle); the existing idle $c path already sends its own notification only on the first state transition.
This keeps socket reads bounded, allows Tk to return naturally to its event loop, and avoids rebuilding status UI when no state changed.
Validation
- live
/reload retained six connections and loaded only the affected procedures
- reconnect plus 40-action interaction trace completed without a timeout or slow message-loop sample
- local behavioral reliability suite: 24/24 passed
- focused clean-upstream assertion passed
git diff --check passed
- standalone patch applied cleanly and reproduced an exact diff against upstream commit
46d18387bd6b156b85176fa588a60991cbba6235
A focused pull request containing only potato.vfs/lib/potato.tcl will follow.
Summary
Direct patch: https://github.com/potatomushclient/potato/pull/535.patch
Potato can become temporarily unresponsive while a newly connected world sends its initial output burst. During the stall, world switching and input are delayed or ignored, and the world toolbar may visibly redraw or flicker.
The receive path currently reads only 10 bytes for each readable socket event. Every completed line then calls
skinStatusand synchronously executesupdate idletasks. A banner or login screen therefore creates many tiny socket callbacks and repeated full UI/status passes while the connection is populating.Reproduction
Ctrl+<number>.This was reproduced with Tcl/Tk 8.6.18 while connecting to Dev Asylum. An external PowerShell harness targeted Potato's HWND, sent alternating
Ctrl+1/Ctrl+6commands during connection, and independently probed the Windows message loop withSendMessageTimeout(WM_NULL).Measured behavior
Before the receive-loop change, a 25-second active trace recorded:
After the proposed change, the same reconnect and switch exercise recorded:
CPU remained low during the original stalls, which is consistent with event-loop blocking/re-entrant UI work rather than CPU saturation.
Root cause
::potato::get_mushagereads only ten bytes per readable callback:::potato::get_mushageProcessthen performs these operations for every completed line, regardless of whether connection/activity state changed:skinStatusupdates toolbar state and layout.update idletaskssynchronously drains pending geometry and paint work from inside the receive callback. Together they amplify the callback flood and make input/window messages wait behind incoming data handling.Proposed fix
skinStatusandupdate idletaskscalls.get_mushageProcessdirectly changesconn($c,idle); the existingidle $cpath already sends its own notification only on the first state transition.This keeps socket reads bounded, allows Tk to return naturally to its event loop, and avoids rebuilding status UI when no state changed.
Validation
/reloadretained six connections and loaded only the affected proceduresgit diff --checkpassed46d18387bd6b156b85176fa588a60991cbba6235A focused pull request containing only
potato.vfs/lib/potato.tclwill follow.