Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions lib/python/common/hal_glib.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,8 +615,9 @@ def update(self):
state_new = self.old['state']
if state_new != state_old:

# set machine estop/clear
if state_old == linuxcnc.STATE_ESTOP and state_new == linuxcnc.STATE_ESTOP_RESET:
# set machine estop/clear; a reset and machine on between two
# polls go from ESTOP straight to ON and still clear the estop
if state_old == linuxcnc.STATE_ESTOP:
self.emit('state-estop-reset')
self.emit('interp-idle')
elif state_new == linuxcnc.STATE_ESTOP:
Expand Down
18 changes: 15 additions & 3 deletions scripts/linuxcnc.in
Original file line number Diff line number Diff line change
Expand Up @@ -694,9 +694,21 @@ function Cleanup() {
# Unused variable 'i', just a counter
# shellcheck disable=SC2034
for i in $(seq 10); do
# (the one component is the halcmd itself; a thread is listed as
# a __<name> pseudo component, which stays until realtime stops)
if [ "$($HALCMD list comp | tr -s ' ' '\n' | grep -c -v -e '^__' -e '^$')" = 1 ]; then break; fi
# Wait only for components that can still leave: a thread is
# listed as a __<name> pseudo component, which stays until
# realtime stops, and a user component whose process is gone
# (a GUI that exited without hal_exit) never leaves. The halcmd
# that listed them has exited by the time its pid is checked.
COMPS=$($HALCMD -s show comp)
BUSY=0
while read -r _ TYPE NAME PID _; do
case "$TYPE:$NAME" in
RT:__*) ;;
RT:*) BUSY=1 ;;
User:*) if "$KILL" -0 "$PID" 2>/dev/null; then BUSY=1; fi ;;
esac
done <<< "$COMPS"
if [ "$BUSY" = 0 ]; then break; fi
sleep .2
done

Expand Down
5 changes: 5 additions & 0 deletions src/hal/user_comps/gladevcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,11 @@ def main():
LOG.debug("Register callback '%s' for SIGINT and SIGTERM" %(signal_func))
signal.signal(signal.SIGTERM, handlers[signal_func])
signal.signal(signal.SIGINT, handlers[signal_func])
else:
# halcmd unload sends SIGTERM. The KeyboardInterrupt hal makes of
# it is lost when it lands in a GLib callback (the status polls),
# so quit from the main loop instead.
GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, Gtk.main_quit)

try:
Gtk.main()
Expand Down
10 changes: 7 additions & 3 deletions tests/mdi-while-queuebuster-waitflag/README
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
This test tries to hit a race condition where an MDI command is
executed when a queue buster is waiting to be restarted after
INTERP_EXEC_FINISH.
MDI lines sent behind a remapped M400 that yields INTERP_EXECUTE_FINISH
wait for it and run in order. Task sets its plan wait while the remap is
suspended, and an MDI arriving then is queued instead of being handed to
the interpreter, which would drop it (7e640f7b08). The test checks the
order the lines and the remap report in, through the operator messages,
and that nothing crashed. It cannot hit the one task cycle the wait flag
guards on its own: linuxcnc.command waits for task to take each line.
3 changes: 2 additions & 1 deletion tests/mdi-while-queuebuster-waitflag/checkresult
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#!/bin/sh
exit 0 # test failure is indicated by test.sh exit value
# the MDI lines and the remap ran, in the order they were sent
grep -q "^in order$" "$1"
7 changes: 2 additions & 5 deletions tests/mdi-while-queuebuster-waitflag/remap.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@ def init_stdglue(self):
def M400(self,**words):

self.execute("M66 E0 L0")
# self.execute("M66 P0 L1 Q1")
print("self.input_flag pre = %s" % self.input_flag)
emccanon.MESSAGE("M400 before the yield")
yield interpreter.INTERP_EXECUTE_FINISH

print("self.input_flag post = %s" % self.input_flag)
# optionally,
# yield interpreter.INTERP_OK
emccanon.MESSAGE("M400 after the yield")


39 changes: 26 additions & 13 deletions tests/mdi-while-queuebuster-waitflag/test-ui.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
#!/usr/bin/env python3
# MDI lines queued behind a remap that yields INTERP_EXECUTE_FINISH run
# after it, in the order they were sent: see README.

import linuxcnc, hal
import linuxcnc
import sys
import time

# Initialization
c = linuxcnc.command()
s = linuxcnc.stat()
e = linuxcnc.error_channel()

c.state(linuxcnc.STATE_ESTOP_RESET)
c.state(linuxcnc.STATE_ON)
c.mode(linuxcnc.MODE_MDI)

c.mdi('(print,pre 1)')
c.mdi('(print,pre 2)')
c.mdi('M400')
c.mdi('(print,post 1)')
c.mdi('(print,post 2)')
c.mdi('(print,post 3)')
c.mdi('(print,post 4)')
c.mdi('(print,post 5)')
c.mdi('(print,post 6)')
# each line reports through the operator messages, which task sends in the
# order it runs them; the remap reports before and after its yield
posts = ["post %d" % n for n in range(1, 7)]
for cmd in ["(debug,pre 1)", "(debug,pre 2)", "M400"] + ["(debug,%s)" % p for p in posts]:
c.mdi(cmd)
want = ["pre 1", "pre 2", "M400 before the yield", "M400 after the yield"] + posts

# Shutdown
got = []
deadline = time.time() + 30
while len(got) < len(want) and time.time() < deadline:
m = e.poll()
if not m:
time.sleep(0.01)
continue
print("channel:", m)
if m[0] in (linuxcnc.NML_ERROR, linuxcnc.OPERATOR_ERROR):
break
got.append(m[1].strip())
c.wait_complete()
sys.exit(0)

print("ran:", ", ".join(got))
print("in order" if got == want else "not in order, wanted: " + ", ".join(want))
sys.exit(0)
13 changes: 4 additions & 9 deletions tests/mdi-while-queuebuster-waitflag/test.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
#!/bin/bash -e

# Variable 'i' is unused, just a loop count
# shellcheck disable=SC2034
for i in $(seq 20); do
linuxcnc -r test.ini
if grep -q '^[^+].*Segmentation fault' stderr; then
exit 1
fi
done
linuxcnc -r test.ini
if grep -q '^[^+].*Segmentation fault' stderr; then
exit 1
fi
2 changes: 1 addition & 1 deletion tests/ui-smoke/_lib/checkresult.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
# Pass if the driver printed UI_SMOKE_OK and did not print UI_SMOKE_FAIL.
# The driver only emits UI_SMOKE_OK after a successful NML round-trip
# (linuxcnc task ready and stat.poll() still alive after settle), so
# (linuxcnc task ready and stat.poll() still alive once the GUI is up), so
# this is sufficient evidence that the GUI booted. We do not grep for
# generic crash markers like "Segmentation fault" or "Traceback":
# linuxcnc's own scripts/linuxcnc Cleanup may emit shutdown-side
Expand Down
Loading
Loading