Skip to content

Faster ui-smoke and workplane-retain: wait on the GUI, and two shutdown waits that never ended - #4782

Draft
grandixximo wants to merge 2 commits into
LinuxCNC:masterfrom
grandixximo:ui-smoke-speed
Draft

grandixximo wants to merge 2 commits into
LinuxCNC:masterfrom
grandixximo:ui-smoke-speed

Conversation

@grandixximo

@grandixximo grandixximo commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Based on #4778: the first commit is that PR's, so this stays a draft until it merges. It also needs #4801 and #4802 to run without homing retries in gmoccapy. The ui-smoke driver resets the estop and turns the machine on back to back, and without #4778 qtdragon and gmoccapy then keep showing the estop set in the confirm shots.

Follow-up to #4770, for the tests on Bertho's list that are mine: ui-smoke axis, touchy and gmoccapy, and workplane-retain.

Two commits fix waits outside the tests that every shutdown paid:

  • gladevcp ignored halcmd unload. The SIGTERM becomes a KeyboardInterrupt, and PyGObject swallows it when it lands in a GLib callback. A panel was still alive 5 s after an unload, 3 times in 3; it now exits within 0.2 s and releases its component.
  • linuxcnc's Cleanup waited its full 2 s for user components whose process is gone. gmoccapy never frees its component, and touchy and any GUI killed by a signal leave theirs. It now waits only for components that can still leave.

The ui-smoke driver no longer pauses 3 s for the GUI, half a second after each state and mode change, or 0.4 s after opening the program:

  • It waits for the GUI's HAL component to be ready and, for the X GUIs, for its main window to be viewable. gmoccapy reports ready early in its constructor and then sends startup commands of its own; a jog stop among them aborted a homing begun meanwhile, which cost 15 s per run.
  • An attempt a GUI undoes is retried as soon as the driver can tell: the machine left ON, homing was aborted, the mode left AUTO, or a newer command switched the mode back (qtdragon and gmoccapy switch to MANUAL once they see all-homed).
  • The program is opened after homing, once the GUIs have opened their own startup files.
  • The 2 s pause after homing stays for gmoccapy only: it offers AUTO only after it has processed all-homed, and shows nothing to wait on.
  • Before the confirm shot it waits for the LinuxCNC splash to close. The AXIS manual toolchanger note stays in the axis shot; it is up at that point in every run, loaded or not.

The confirm-shot settle check is unchanged. The committed reference images were taken by the old driver, later in the run, so axis's lacks the toolchanger note and the others differ in small ways; I will add a commit with new references from this PR's CI artifacts.

workplane-retain is split into retain/ and clear/, like abort/g64.

Times in seconds; loaded is 6 copies of each at -j 12 with 4 busy loops, all passing twice:

test master this PR master, loaded this PR, loaded
ui-smoke/axis 15.2 8.2 17.6 14.2
ui-smoke/touchy 14.0 8.1 16.3 12.1
ui-smoke/gmoccapy 18.9 13.2 38.0 21.3
ui-smoke/qtdragon 13.7 8.3
workplane-retain 19.3 10.1 each

Full suite 355/355 at -j 4 in 177 s. The slowest test left is mdi-while-queuebuster-waitflag, about 22 s here.

GStat emitted state-estop-reset only for ESTOP => ESTOP_RESET.  A client
that resets the estop and turns the machine on between two polls goes
from ESTOP straight to ON, and qtdragon and gmoccapy kept showing the
estop set on a machine that was on.
@BsAtHome

BsAtHome commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

On my machine, still slow, but improving:

test master this PR
tests/ui-smoke/axis 36.73 29.98
tests/ui-smoke/gmoccapy 38.14 32.16
tests/ui-smoke/gmoccapy-quit 11.32 7.40
tests/ui-smoke/qtdragon 17.66 12.53
tests/ui-smoke/qtdragon-quit 9.25 8.43
tests/ui-smoke/touchy 36.05 30.04
tests/ui-smoke/touchy-fit 11.55 9.29
tests/ui-smoke/touchy-quit 10.45 7.43

Don't know why those full GUI tests are so terribly slow. The CPUs are not really loaded when it runs. When I disable the screenshot in the ui-smoke/axis (immediately return in screenshot_grab()), then it is 10 seconds faster. Task is running about 13500 cycles (that would be about 13.5 seconds or just over). The screenshot_grab_settled() call takes 19..20 seconds.

@BsAtHome

BsAtHome commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Just discovered that the real reason is that it keeps retrying capturing images until UI_SMOKE_SETTLE_TRIES runs out.
The while loop in screenshot_grab_settled() never breaks. This is en echo of settle_tries and settle_ae just after the _screenshow_ae() call:

screenshot_grab_settled
24: settle_ae: 362.205 (0.000174675)
23: settle_ae: 0 (0)
...
1: settle_ae: 0 (0)

The problem is that the loop always takes the first case ''|*[!0-9]*) settle_stable=0 ;;, regardless. No sure what the intention has been here.

@grandixximo

Copy link
Copy Markdown
Contributor Author

Thanks, that's it. Your ImageMagick prints the AE as 362.205 (0.000174675); mine (7.1.1-43) prints a bare 362, so here the loop settled after three grabs and I never saw it. The *[!0-9]* case was meant for an error message from compare, which counts as "not settled"; with the new format every frame fell into it, and all 25 tries ran.

I pushed a fix that keeps the whole pixels of the first number. With a wrapper that makes my ImageMagick print your format, axis and touchy go from 23.4 and 23.6 s to 8.2 and 8.0 s, the same as with mine. Could you rerun on your machine?

@BsAtHome

BsAtHome commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Ah, yes,...

$ magick --version
Version: ImageMagick 7.1.2-35 Q16-HDRI x86_64 24992 https://imagemagick.org

Much better:

test master this PR
tests/ui-smoke/axis 36.73 12.70
tests/ui-smoke/gmoccapy 38.14 14.36
tests/ui-smoke/gmoccapy-quit 11.32 7.40
tests/ui-smoke/qtdragon 17.66 12.00
tests/ui-smoke/qtdragon-quit 9.25 9.42
tests/ui-smoke/touchy 36.05 12.12
tests/ui-smoke/touchy-fit 11.55 9.22
tests/ui-smoke/touchy-quit 10.45 7.41

All values should be +/-0.25s or so.

@BsAtHome

BsAtHome commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Maybe squash and mark ready?

@BsAtHome

BsAtHome commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Failed test in package-arch bookworm (x86).
Is this again a race condition?

--- expected-telnet-output	2026-11-07 00:54:58.912340726 +0000
+++ /dev/fd/63	2026-11-07 01:03:07.284239214 +0000
@@ -67,7 +67,7 @@
 SET MDI ACK
 SET PROBE NAK
 ERROR Start point too close to probe point
-SET PROBE ACK
+SET PROBE NAK
 ERROR G38.2 move finished without making contact.
 PROBE_TRIPPED NO
 SET PROBE_CLEAR ACK
*** ./tests/linuxcncrsh: FAIL: checkresult exited with 1

@BsAtHome

BsAtHome commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

The last slow test is tests/mdi-while-queuebuster-waitflag, which takes about 30 seconds. It starts LCNC 20 times sequentially and breaks if a "Segmentation fault" is detected. The README tells something about a race, but a race and a segfault are two quite different things.
It was introduced in #165 and it is a bit vague on the actual condition. It says that an MDI could begin execution while another is still in progress. But how this is a "race" and detectable by "Segmentation fault" is beyond me. It seems that such fault would be uncovered in the Python code, and that should never happen. Any segmentation fault is a serious logic error. So, if you can hit it, then the code must be fixed to recover from that situation or "nicely" abort with an indicative message what is wrong.

@grandixximo You have been over the interpreter code more often. Maybe you can share some insights?

@grandixximo

Copy link
Copy Markdown
Contributor Author

Yes, the linuxcncrsh failure is a race, and not from this PR. A G38.2 that ends without contact makes task report ERROR for about one cycle, then EXEC and DONE (I logged it: ERROR at about 64 ms, DONE at about 66 ms). In wait mode "done", linuxcncrsh polls with a doubling backoff, and one poll lands at about 63 ms: usually it just misses the ERROR and answers ACK, sometimes it sees it and answers NAK. A slow runner widens the window; that bookworm job logged task main loops of 0.15 s. Every client that waits for completion can miss a failure reported that way, so I think task should keep reporting ERROR for a failed command until the next one arrives. I'll look at that separately.

On mdi-while-queuebuster-waitflag, the condition is in task, not in Python. When a remap yields INTERP_EXECUTE_FINISH, task sets the plan wait and queues a PLAN_SYNCH; mdi_execute_hook() clears the wait once the queue is empty and execState is DONE. In MDI mode, emcTaskPlan() handles an incoming command before it calls the hook. So for one task cycle (after the SYNCH has run, before the hook clears the wait) an incoming MDI saw an empty queue and no pending command, and was issued straight to the interpreter while the remap was still suspended. John's fix, 7e640f7 (2016), added emcTaskPlanIsWait() == 0 to that check, so such an MDI is queued instead. It is still there.

What an MDI in that window does today: I forced one in with a temporary hack in the hook. The interpreter does not read a new line while a Python handler is suspended (read() skips it on purpose, so the handler can be re-entered), so the MDI text is dropped and its execute() just resumes the remap. No segfault, but a silently lost MDI, which is what the condition prevents. read() already skipped like that in 2016, so I can't tell from today's code why it crashed then; John's commit only says "a lot of bad stuff happens later". The crash was the symptom, and that condition is the fix.

The test cannot reach that cycle any more. The MDIs after M400 arrive about 10 ms later (linuxcnc.command waits for task to receive each one, polling every 10 ms), and by then the remap has finished. During a longer wait, task holds the PLAN_SYNCH, so they are queued anyway. With the condition removed from current master, I started it 100 times, 80 of them under load, and never saw a crash or a wrong order. So its 20 starts cost about 27 s and test nothing; a test of that one cycle would need a hook inside task. I'd cut it to a single start that checks the print order and the exit, as a smoke test of MDIs queued behind a yielding remap. OK with you?

@BsAtHome

BsAtHome commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Alright, will look forward to yet another race fix :-)
Didn't think it was from this PR. It is usually just a symptom when it fails in CI anyway.

Changing the (very slow) non-testing test with something that actually tests something (a lot faster) is a Good Thing™.
Yes, if you can check the ordering of the yielding remap, then that should be fine.

gladevcp ignored halcmd unload.  hal turns the SIGTERM into a
KeyboardInterrupt, which PyGObject swallows when it lands in a GLib
callback such as the status polls, so the panel stayed up: touchy's
touchy_test panel was still alive 5 seconds after an unload, 3 times in
3.  Without a panel handler of its own, gladevcp now quits the main loop
on SIGTERM, exits within 0.2 seconds and releases its component.

linuxcnc's Cleanup waited its full 2 seconds for user components whose
process is gone.  gmoccapy leaves its component on every exit, and
touchy and any GUI killed by a signal do the same.  It now waits only
for realtime components and for user components whose process is still
alive.

The ui-smoke driver paused 3 seconds for the GUI to settle, half a
second after every state and mode change to see whether a GUI undid it,
2 seconds after homing and 0.4 seconds after opening the program.

- Wait for the GUI's HAL component to be ready (--gui-comp) and, for
  the X GUIs, for its main window to be viewable (--gui-window).
  gmoccapy reports ready early in its constructor and then sends its own
  startup commands; a jog stop among them aborts a homing begun meanwhile.
- No stability pause: an attempt that a GUI undoes is retried, and gives
  up as soon as the machine leaves ON, homing is aborted or the mode
  leaves AUTO, instead of sitting out its timeout.  If the machine goes
  off after homing, home again from unhomed; a run that a GUI aborts by
  leaving AUTO is run again.  A mode request that a newer command undid
  (qtdragon switches to MANUAL once it sees all-homed) is repeated at
  once, not after 3 seconds.
- Open the program after homing, when the GUIs have opened their own
  startup files (an earlier open collides with axis's).
- The 2 second pause after homing stays for gmoccapy only
  (--post-home-settle): it offers AUTO only once it has processed
  all-homed and shows nothing to wait on.  The pause also lets it see
  the program while the interp is idle.
- Before the confirm shot, wait for the LinuxCNC splash to close itself,
  so it does not cover touchy.  The note of the AXIS manual toolchanger
  stays in the axis shot: it is up at that point in every run, loaded
  or not, and the shot shows it if it ever goes.
- Read the AE count newer ImageMagick prints as "362.205 (0.000174675)"
  rather than a bare count.  The settle loop took anything but digits as
  "not settled" and ran all 25 grabs, about 15 seconds more per GUI
  test.  Found by Bertho (BsAtHome).
- Poll for linuxcnc's exit every 0.1 second, not every second.

The reference images are the confirm shots of this change's CI run; the
old ones no longer matched master's CI shots either.

workplane-retain started linuxcnc twice, with RETAIN_WORK_PLANE and
without, about 20 seconds in all.  Each setting is now a test of its
own, like abort/g64: retain/ and clear/ hold the [RS274NGC] section and
include the shared settings in workplane.inc.

Measured by Bertho on his machine: axis 36.7 => 12.7, gmoccapy 38.1 =>
14.4, touchy 36.1 => 12.1, qtdragon 17.7 => 12.0 seconds.  Here, with 6
copies of each of axis, touchy and gmoccapy at -j 12 and 4 busy loops:
axis 17.6 => 14.2, touchy 16.3 => 12.1, gmoccapy 38.0 => 21.3, 36 of 36
passing; workplane-retain 19.3 => 10.1 for each half.
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