From e40dced0e2daa759ea94f66dacc9d3357a345708 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:02:37 +1100 Subject: [PATCH] task: undo read-ahead modes when an error aborts a program (#4715) When motion rejects a move (a soft limit, for one) or motion or io report an error, task aborts the program. The interpreter has by then read ahead, often to the M2 that resets G54 and G17, and kept those modes although the machine never reached them. The coordinate system and plane set before the program were lost, and #5220 saved at exit kept G54. An operator abort already brings the interpreter back to the modes of the last executed move with emcTaskStateRestore(). Call it on the two error paths too, before the ON_ABORT_COMMAND routine runs, as on that abort. tests/abort/exec-error-modes sets G55 G18, runs into a soft limit with a G56 G19 and an M2 read ahead, and checks G55 G18 remain. --- src/emc/task/emctaskmain.cc | 5 + tests/abort/exec-error-modes/.gitignore | 1 + tests/abort/exec-error-modes/README | 6 + tests/abort/exec-error-modes/checkresult | 2 + tests/abort/exec-error-modes/core_sim.hal | 12 ++ tests/abort/exec-error-modes/simpockets.tbl | 1 + tests/abort/exec-error-modes/test-ui.py | 80 ++++++++++++++ tests/abort/exec-error-modes/test.ini | 115 ++++++++++++++++++++ tests/abort/exec-error-modes/test.ngc | 5 + tests/abort/exec-error-modes/test.sh | 4 + 10 files changed, 231 insertions(+) create mode 100644 tests/abort/exec-error-modes/.gitignore create mode 100644 tests/abort/exec-error-modes/README create mode 100755 tests/abort/exec-error-modes/checkresult create mode 100644 tests/abort/exec-error-modes/core_sim.hal create mode 100644 tests/abort/exec-error-modes/simpockets.tbl create mode 100755 tests/abort/exec-error-modes/test-ui.py create mode 100644 tests/abort/exec-error-modes/test.ini create mode 100644 tests/abort/exec-error-modes/test.ngc create mode 100755 tests/abort/exec-error-modes/test.sh diff --git a/src/emc/task/emctaskmain.cc b/src/emc/task/emctaskmain.cc index 246d200c23d..56c03b2f8d6 100644 --- a/src/emc/task/emctaskmain.cc +++ b/src/emc/task/emctaskmain.cc @@ -2593,6 +2593,9 @@ static int emcTaskExecute(void) // clear out pending command emcTaskCommand = 0; interp_list.clear(); + // the interpreter read ahead of the move that failed; bring back the + // modes of the last executed move, as an operator abort does + emcTaskStateRestore(); emcAbortCleanup(EMC_ABORT_TASK_EXEC_ERROR); emcStatus->task.currentLine = 0; @@ -3524,6 +3527,8 @@ int main(int argc, char *argv[]) interp_list.clear(); emcStatus->task.currentLine = 0; + // as for an operator abort, undo the modes read ahead + emcTaskStateRestore(); emcAbortCleanup(EMC_ABORT_MOTION_OR_IO_RCS_ERROR); // clear out the interpreter state diff --git a/tests/abort/exec-error-modes/.gitignore b/tests/abort/exec-error-modes/.gitignore new file mode 100644 index 00000000000..b4bf49eb08f --- /dev/null +++ b/tests/abort/exec-error-modes/.gitignore @@ -0,0 +1 @@ +sim.var* diff --git a/tests/abort/exec-error-modes/README b/tests/abort/exec-error-modes/README new file mode 100644 index 00000000000..dd8d27fc26e --- /dev/null +++ b/tests/abort/exec-error-modes/README @@ -0,0 +1,6 @@ +Issue #4715: a move that motion rejects (here a soft limit) aborts the +program with an error, but the interpreter had already read ahead past it, +here to a G56 G19 and the M2 that resets to G54 G17. The abort left those +modes active although the machine never reached them. This sets G55 G18 in +MDI, runs test.ngc and checks that G55 G18 are active after the error, as +after an operator abort. diff --git a/tests/abort/exec-error-modes/checkresult b/tests/abort/exec-error-modes/checkresult new file mode 100755 index 00000000000..24dc9aa53e3 --- /dev/null +++ b/tests/abort/exec-error-modes/checkresult @@ -0,0 +1,2 @@ +#!/bin/sh +exit 0 # test failure is indicated by test.sh exit value diff --git a/tests/abort/exec-error-modes/core_sim.hal b/tests/abort/exec-error-modes/core_sim.hal new file mode 100644 index 00000000000..f33a3ccf7f4 --- /dev/null +++ b/tests/abort/exec-error-modes/core_sim.hal @@ -0,0 +1,12 @@ +loadrt [KINS]KINEMATICS +loadrt [EMCMOT]EMCMOT base_period_nsec=[EMCMOT]BASE_PERIOD servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS +addf motion-command-handler servo-thread +addf motion-controller servo-thread + +net Xpos joint.0.motor-pos-cmd => joint.0.motor-pos-fb +net Ypos joint.1.motor-pos-cmd => joint.1.motor-pos-fb +net Zpos joint.2.motor-pos-cmd => joint.2.motor-pos-fb + +net estop-loop iocontrol.0.user-enable-out iocontrol.0.emc-enable-in +net tool-prepare-loop iocontrol.0.tool-prepare iocontrol.0.tool-prepared +net tool-change-loop iocontrol.0.tool-change iocontrol.0.tool-changed diff --git a/tests/abort/exec-error-modes/simpockets.tbl b/tests/abort/exec-error-modes/simpockets.tbl new file mode 100644 index 00000000000..1371faeee58 --- /dev/null +++ b/tests/abort/exec-error-modes/simpockets.tbl @@ -0,0 +1 @@ +T1 P1 ; diff --git a/tests/abort/exec-error-modes/test-ui.py b/tests/abort/exec-error-modes/test-ui.py new file mode 100755 index 00000000000..a91409ef702 --- /dev/null +++ b/tests/abort/exec-error-modes/test-ui.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +import linuxcnc +import linuxcnc_util +import time +import sys + + +l = linuxcnc_util.LinuxCNC() +c, s = l.command, l.status +l.wait_for_linuxcnc_startup() +e = linuxcnc.error_channel() + +c.state(linuxcnc.STATE_ESTOP_RESET) +c.state(linuxcnc.STATE_ON) +c.mode(linuxcnc.MODE_MDI) +c.wait_complete() +c.mdi('G55 G18') +c.wait_complete() +l.wait_for_interp_state(linuxcnc.INTERP_IDLE) + + +def modes(): + s.poll() + return s.gcodes[3], s.gcodes[8] # plane, coordinate system + + +if modes() != (180, 550): + print('FAIL: MDI did not set G55 G18: %s' % (modes(),)) + sys.exit(1) + +c.mode(linuxcnc.MODE_AUTO) +c.wait_complete() +c.program_open('test.ngc') +c.wait_complete() +c.auto(linuxcnc.AUTO_RUN, 0) + +# Wait for the program to start (no fixed delay: a loaded machine may take a while). +start = time.time() +while time.time() - start < 10: + s.poll() + if s.interp_state != linuxcnc.INTERP_IDLE: + break + time.sleep(0.005) +else: + print('FAIL: the program did not start within 10 s') + sys.exit(1) + +errors = [] +start = time.time() +while time.time() - start < 30: + s.poll() + msg = e.poll() + if msg: + errors.append(msg[1]) + if s.interp_state == linuxcnc.INTERP_IDLE: + break + time.sleep(0.005) +else: + print('FAIL: the program did not stop within 30 s') + sys.exit(1) + +time.sleep(0.5) +while True: + msg = e.poll() + if not msg: + break + errors.append(msg[1]) +for m in errors: + print('error channel: %s' % (m,)) + +if not any('would exceed' in m for m in errors): + print('FAIL: motion did not reject the move beyond the limit') + sys.exit(1) + +print('after the error: plane %d, coordinate system %d' % modes()) +if modes() != (180, 550): + print('FAIL: the error left the modes the interpreter read ahead') + sys.exit(1) +sys.exit(0) diff --git a/tests/abort/exec-error-modes/test.ini b/tests/abort/exec-error-modes/test.ini new file mode 100644 index 00000000000..d6d15ef0394 --- /dev/null +++ b/tests/abort/exec-error-modes/test.ini @@ -0,0 +1,115 @@ +# Simulated machine for tests/abort/exec-error-modes (issue #4715), based on tests/program-units. + +[EMC] +# The version string for this INI file. +VERSION = 1.1 + +DEBUG = 0 +RCS_DEBUG = 0x05400f01 + +[DISPLAY] +DISPLAY = ./test-ui.py + +[FILTER] +#No Content + +[RS274NGC] +PARAMETER_FILE = sim.var + +[EMCMOT] +EMCMOT = motmod +COMM_TIMEOUT = 4.0 +BASE_PERIOD = 0 +SERVO_PERIOD = 1000000 + +[TASK] +TASK = milltask +CYCLE_TIME = 0.001 + +[HAL] +HALFILE = core_sim.hal + +[TRAJ] + +NO_FORCE_HOMING=1 +AXES = 3 +COORDINATES = X Y Z +HOME = 0 0 0 +LINEAR_UNITS = inch +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 1.2 +MAX_LINEAR_ACCELERATION = 123.45 +MAX_LINEAR_VELOCITY = 45.67 + +[EMCIO] +EMCIO = io +TOOL_TABLE = simpockets.tbl +TOOL_CHANGE_QUILL_UP = 1 +RANDOM_TOOLCHANGER = 0 + + + +[KINS] +KINEMATICS = trivkins +#This is a best-guess at the number of joints, it should be checked +JOINTS = 3 + +[AXIS_X] +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 + +[JOINT_0] + +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +FERROR = 0.050 +MIN_FERROR = 0.010 + +[AXIS_Y] +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 + +[JOINT_1] + +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +FERROR = 0.050 +MIN_FERROR = 0.010 + +[AXIS_Z] +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 + +[JOINT_2] + +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +FERROR = 0.050 +MIN_FERROR = 0.010 diff --git a/tests/abort/exec-error-modes/test.ngc b/tests/abort/exec-error-modes/test.ngc new file mode 100644 index 00000000000..f4b79f50e69 --- /dev/null +++ b/tests/abort/exec-error-modes/test.ngc @@ -0,0 +1,5 @@ +(the moves start on line 2: a state tag from line 1 is never restored) +G1 X1 F20 (about 3 s: the interpreter reads past it to the G56 G19 and the M2) +G56 G19 +G0 X100 (beyond MAX_LIMIT: motion rejects it while the move above runs) +M2 diff --git a/tests/abort/exec-error-modes/test.sh b/tests/abort/exec-error-modes/test.sh new file mode 100755 index 00000000000..6edf34d77ca --- /dev/null +++ b/tests/abort/exec-error-modes/test.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +rm -f sim.var +linuxcnc -r test.ini