Don't drop on back-pressure; add logging + de-duplication instead - #252
Merged
Merged
Conversation
rreichenbach
approved these changes
Sep 6, 2026
rreichenbach
left a comment
Collaborator
There was a problem hiding this comment.
Seems fine. I didn't test blend mode, but other functions seem right.
sastraxi
marked this pull request as ready for review
September 6, 2026 21:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
There's this back-pressure detector that aimed to figure out if MOD-UI was too slow to respond to our requests, mainly introduced for Blend Mode as there can be a ton of
param_setinterpolations depending on how many parameters you're driving with that feature.This feature was implemented by dropping new outbound WS messages if the queue was full.
Why we remove it
The number does not measure MOD-UI.
ws.transport.get_write_buffer_size()counts bytes in our own transport that we have not given to the kernel. MOD-UI can read all of them and still be slow.The threshold is never reached. We set it to 8KB. The
websocketslibrary pauses only at the 64KB high-water mark. Between 8KB and 64KB nothing is wrong, but the detector reported a problem.The flag could stay set for the rest of the session. Only the send loop set it and cleared it. A producer that saw the flag refused the message and did not queue it, so the queue could empty while the flag was true. The send loop then waited on
_wakeup, and nothing could wake it. This is the bug in #251: the parameter dialog opens, but the value does not change.It guarded the wrong failure. A reconnect empties the queue. A value sent while the socket was down returned true,
commitpainted it, and the reconnect deleted it. The LCD kept a value that MOD-UI never received.What replaces it
send_parameterandsend_bpmrefuse for one reason: there is no connection. The connect scope setsself.wsand clears it in afinally. The send path never writes it.Host.loadin MOD-UI is not a coroutine, so Tornado runs it on the ioloop and reads no socket while it runs. That is the only condition that fills our buffer, and MOD-UI already brackets it withloading_startandloading_end. Tornado also answers a protocol PING on that ioloop, sows.latencymeasures the delay directly. The worker pings every 5 seconds and logs peak latency each minute. There is no ping timeout: it would close the socket during a long board load, and the reconnect would empty the queue.Inbound,
coalesce_param_sets(ws_protocol.py) collapses each drain'sparam_setburst to the last per (instance, symbol), keeping the survivor at its original position — a fast scrub repaints once per tick instead of once per echo. Safe because the port is level-sampled and the feed is in-order;:bypassarrives asPluginBypassMessage, so bypass echoes are untouched.Commit / confirm semantics
A send that leaves confirms itself.
commitnow advances_confirmedon success; only an inbound echo could before. On the no-echo paths — a footswitch-less UI bypass (MOD-UI skips the origin socket; mod-host emits noparam_setfor a bypass it got from mod-ui), or any WS send, which mod-ui never echoes —_confirmedstayed frozen at the pre-edit value forever, so a later failed commit rolled the screen back to a value neither MOD-UI nor the player held. Rollback now targets the last confirmed value: echoed, or self-committed, whichever came last.A MIDI-learn sub-range change reclamps
_confirmed.set_binding_range/clear_binding_rangeclamp both the value and_confirmedinto the new extents (one_reclamphelper replacing the twin bodies), and fire committed observers — a keycap can no longer sit at a value the new extents exclude, and a failed commit cannot roll back outside them.MIDI CC sinks refuse the load window.
_publish_ccand_publish_switch_ccreturn False while_is_pedalboard_loading, matching_publish_plugin_param: a scrub mid-load no longer reaches mod-host while the equivalent WS edit is refused — and, with self-confirming commits, no longer poisons_confirmedeither. The audio card still always lands.The volume encoder commits. It wrote the card directly and only previewed the parameter — two stores that could drift, and it bypassed
audio_parameter_commit's input-gain VU recalibration. It is one commit through_sink_for→_publish_audionow.A bound analog control commits its parameter.
_handle_analogonly emitted CC; the parameter tracked the echo alone, and the LCD bar projected raw ADC while the parameter tracked mod-ui — drifting through every echo-latency window and any WS outage. A bound (non-external) pedal now commits: the CC-space value snaps onto the parameter's ownParameterStepsgrid, thencommitpublishes through_sink_for's newAnalogMidiControl→_publish_ccarm (external pedals keep the raw-CC-only path; their port owns the value).to_midi/bar_midi_valuemoved to the baseControllerso encoder and pedal share the CC lattice, and the LCD bar projects the parameter for bound controls, the ADC for everything else.Rename.
subscribe_settled→on_commit,_notify_settled→_notify_committed: the vocabulary now says what it does — values are previewed or committed, and an echo is just MOD-UI's commit.Other fixes
Panels did not use
_sink_for.PluginPanel._send_paramcalledws_bridgedirectly, so a footswitch- or encoder-bound parameter left as aparam_setfrom a panel and as a MIDI CC everywhere else. Panels now callparameter_value_commit.Two rollbacks could not run.
set_paramcalledreconcile, which writes_confirmed— the valuecommitreverts to. It now callspreview. Andmodhandler.py:414passednew_valuetodisplay_parameter_value, which previewed the refused value again one line after the rollback. It now passesparam.value.A refused send reverts everywhere. The audio did not change, so a screen that keeps the new value disagrees with what the player hears. The panel no longer retries.
_publish_plugin_paramand BPM refuse on a dead bridge or a board load;_publish_ccrefuses the board load (MIDI is fire-and-forget); the audio card always succeeds.BPM ignored the load window.
set_mod_tap_temposent during a pedalboard load, and MOD-UI overwrote the value. The guard is there, not in_publish_bpm, because the tap-tempo footswitch calls it from the callbacks map.Deleted
The retry in
_flush_param_queue. The RESTset_bpmfallback, which sent to the same server and port the WebSocket could not reach, with no timeout, on the 10ms loop.get_stats(), which had no callers.docs/backpressure-removal-plan.md, a plan for completed work; the facts worth keeping moved toarchitecture.md.What to check
The
finallyinwebsocket_bridge.pythat clearsself.ws. Everything depends on it, andtests/test_websocket_bridge.pycovers it. Inplugins/base.py,set_paramusespreviewand_flush_param_queueclears the queue._sink_forgains anAnalogMidiControlarm below the external bail. The commit/confirm fixes are pinned intests/v3/test_reactive_parameter.py(test_good_commit_advances_confirmed_without_echo,test_rollback_targets_the_last_confirmed_value),tests/v3/test_sink_routing.py(load-window CC refusal),tests/v3/test_encoder_dispatch.py(volume commits),tests/v3/test_analog_commit.py(bound pedal commits + bar projection; external pedal stays raw-CC), andtests/test_ws_protocol.py(coalesce_param_sets).