Commit d1c0cbd63ab240ca0120434106b66dac91a03079 added support for disabling calls to reactor.pause() However, the commit message fails to explain why.
Why was this needed?
I have an extension, which executes some gcode. For this, it creates a status wrapper. The gcode that the extension executes is a gcode_shell_command, which calls reactor.pause() while waiting for the shell command to complete.
However, since the extension is executing a gcode_macro, the reactor is prohibiting pausing. When the support for disabling calls to reactor.pause() was added, how was the above pattern supposed to be changed?
So, how is Klipper supposed to execute macros on klippy:connect? If an extension is setup to execute a gcode template on that event, it will trigger the exception.
I am sorry. That was meant to be klipper:ready. However, Printer._connect() emits klipper:ready, which still cause the issue since the klipper:ready handlers are called with the pause counter incremented:
with self.reactor.assert_no_pause():
for cb in self.event_handlers.get("klippy:ready", []):
if self.state_message is not message_ready:
return
cb()
As mentioned above, the assert_no_pause() mechanism was added because we know that if some callbacks were to pause it could introduce subtle bugs that would be hard to diagnose.
The “ready” callback is an example of this - if one module were to pause then it is possible for a subset of modules to be informed that the printer is in a “ready” state, while another subset of modules has yet to be informed that the printer is “ready”, and then a new command could be executed while the software is “half in ready”. This “half way state” could cause incorrect and confusing behavior.
The typical solution is to create a new task from the ready callback - the creation of a task is itself non-blocking, and the new task is free to pause as it wishes. See klippy/extras/load_cell.py as an example: