Skip to content

Proper and safe evaluation of realtime capability#4132

Merged
grandixximo merged 8 commits into
LinuxCNC:masterfrom
hdiethelm:halcmd_getrt
Jun 24, 2026
Merged

Proper and safe evaluation of realtime capability#4132
grandixximo merged 8 commits into
LinuxCNC:masterfrom
hdiethelm:halcmd_getrt

Conversation

@hdiethelm

@hdiethelm hdiethelm commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Intended to be used with:
#4107

Will fix is_sim / is_rt which is broken: #4129

Two new functionality's for two use-cases:

  1. Not running to see if the system is actually capable of running RT
  2. Running to query the real time capability and type

realtime status can be used to check if realtime is running.

The realtime script is extended with the verify command returning 0 if RT capable, 1 if not.
It is intended to use when not running and running.

  • RTAI: Returns always 0
  • uspace: calls rtapi_app getrt and returns the state
    • Not running: rtapi_app performs all the checks and returns immediately
    • Running: rtapi_app calls the master for the real time capability and returns the state

There is the new function hal_get_realtime_type() returning the type of the actually running realtime system trough the hal.

is_sim / is_rt use realtime verify at init.

rtapi_is_realtime() is deprecated: It works only in real time context since #3964 and was never 100% reliable, also according to the doc.

@BsAtHome

BsAtHome commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

I do think it to be very problematic that getting the RT status is so involved. There should be a simple test that does not involve instantiating larger parts of infrastructure.

@hdiethelm

hdiethelm commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

:-D The CI has no realtime:
ERROR MOTION: no realtime detected.

It is somewhat a chicken and egg problem. Not involving the RT infrastructure needs separate code and this can always not be in sync with RT. Involving RT runs a lot of stuff. Let's think about this. Exactly the issue here: #4129

What do you think about a parameter / pin? So you can use getp / gets to ask for realtime?

Or I could add a new command path to halcmd / rtapi_app that does not start the hal if it is not yet running.

@BsAtHome

BsAtHome commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Running non-RT is not an error per se (see CI). Therefore, you cannot and must not "simply" or "blindly" force one or the other.

There are use-cases where you want to know the RT status and that does not always mean that you will or will not be running either. Finding out what the RT status is or will be must be lightweight and may be different from where you ask. Doing it in a component or from the cmd-line may be different, depending how you ask and with what intention you ask.

@grandixximo

grandixximo commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Both CI failures are small:

  • rip-and-test-clang: has_setuid_root is unused on the default uspace build (the detect_* callers are all behind USPACE_RTAI/XENOMAI ifdefs, so the stubs never call it), and -Werror=unused-function kills it. [[maybe_unused]] or moving it inside the ifdefs fixes it.
  • rip-rtai: hal.h:240 extern rtapi_realtime_status_t hal_realtime_status(); needs (void). The RTAI kernel-module build uses -Werror=strict-prototypes, so the empty () fails (clang on the uspace build let it through). Same on the definition in hal_lib.c.

Two runtime things I noticed while reading:

  • rtapi_realtime_status() returns LXRT for detect_rtai(), but makeApp() only handles RTAI, so a uspace RTAI build hits the final else and sets app = nullptr.
  • If can_set_sched_fifo() succeeds but the kernel string matches none of the markers, it falls through to NONE and runs SCHED_OTHER. The old code treated SCHED_FIFO success alone as realtime, so this is a regression on plain-PREEMPT/generic kernels; the fallback there probably wants to stay RT-capable.

Minor: REALTIME_STATUS_PREEMT_* is missing a P (PREEMPT), and detect_preempt_dynamic() tests the same string on both sides of the ||.

@hdiethelm hdiethelm force-pushed the halcmd_getrt branch 2 times, most recently from b73daaf to 5dfd303 Compare June 6, 2026 12:22
@hdiethelm

Copy link
Copy Markdown
Contributor Author

Thanks. Fixed.

One quirk: RTAI in userspace is called LXRT. I should rename this consistently.
https://www.rtai.org/userfiles/documentation/magma/html/api/group__lxrt.html#details

Comment thread src/hal/halmodule.cc Outdated
Comment thread src/hal/halmodule.cc Outdated
Comment thread src/rtapi/rtapi.h Outdated
@hdiethelm hdiethelm force-pushed the halcmd_getrt branch 3 times, most recently from 7f6e60d to fbf2b81 Compare June 6, 2026 13:18
@hdiethelm

Copy link
Copy Markdown
Contributor Author

Hmm, time for a break, to many force pushes, sorry. I will continue tomorrow.

So you are OK with the general concept? Then I will polish it up, update the doc and do some more testing in different combinations.

Open:

  • Python enum
  • Python is_sim / is_rt: I would prefer just removing them
    • Alternative: Dynamic property, but this is also halve breaking due to unknown has to be handled. I could throw an error in this case.

Deferred:

  • rtapi_app start / stop behaivour

@grandixximo

Copy link
Copy Markdown
Contributor

The CI fixes and the LXRT / fallback handling look good now.

On naming, with @BsAtHome's #4099 in mind: rtapi_get_realtime_type() is consistent with the existing rtapi_get_* getters. The hal-side hal_get_realtime_type() is the one I'd reconsider, since #4099 is standardizing hal_get_<datatype>(ref) (e.g. hal_get_si32(ref), hal_get_bool(ref)) where the suffix is a HAL data type and it takes a typed ref. A parameterless hal_get_realtime_type() reads off-pattern in that family; something like hal_realtime_type() would keep hal_get_* reserved for the value accessors. @BsAtHome owns that convention, so up to him.

For the two open how-tos:

  • Dynamic is_rt/is_sim without breaking the API: lib/python/hal.py already wraps _hal (from _hal import *), so a PEP 562 module __getattr__ there gives live values: return get_realtime_type() > 0 for is_rt and <= 0 for is_sim. Existing callers (pncconf, stepconf) keep working; the one change is that before rtapi_app runs the value is -1, so is_rt reads False / is_sim True.
  • Exposing the enum to Python: one PyModule_AddIntConstant(m, "REALTIME_TYPE_NONE", REALTIME_TYPE_NONE) per value, consistent with the existing HAL_BIT etc. constants.

@BsAtHome

BsAtHome commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

something like hal_realtime_type() would keep hal_get_* reserved for the value accessors. @BsAtHome owns that convention, so up to him.

Well, I don't own it. However, I agree with the argument to leave the get/set moniker to the hal pin/param data access.

@hdiethelm

hdiethelm commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

The last few commits are still figuring out ways to solve all issues, not ready for code style review yet.

@grandixximo Thanks a lot for the hints. Helps a lot not to have to search everything.

  • hal_get_realtime_type: There is also hal_get_param_value_by_name and so on, so also consistent.
  • Python enum: Done
  • is_rt / is_sim That one was annoying: First, i need to create a component if there is none yet and a lot of error handling: bdd3ef9 And then I discovered that stepconf.py / stepconf.py need is_sim before realtime is up and running. So roll back and just call realtime verify at init: cdbace2 However, now this is fully backwards compatible, no side effects expected.
  • I left the python is_initialized function from ^ in, might be this is useful. Many functions can only be used if there is already a component. So you can check if there is already one and create one if needed. hal.component_exists() needs also a component to succeed. I tried... ;-)

@hdiethelm hdiethelm force-pushed the halcmd_getrt branch 2 times, most recently from 6477a97 to af73e08 Compare June 6, 2026 22:13
@hdiethelm

Copy link
Copy Markdown
Contributor Author

And of course, after debian package install, the realtime script is not any more in path. And there was also no define for the path where it is. af73e08 adds one.

For scripts, REALTIME=@REALTIME@ is used, so it is available there.

@hdiethelm

Copy link
Copy Markdown
Contributor Author

Due to error handling is annoying not knowing when rtapi_app is running or not:
579f7ed
Let's see how much fall out this generates...

It now works exactly the same as RTAI. realtime start is now needed before every halcmd involving realtime.
Everything else including halrun / linuxcnc usw. works as before what I tested so far.

And it generated a new ToDo: Cleanup the realtime script. It is a mess. There are a lot of RTAI only parts executed in uspace mode. Like loading an empty list of modules and so on. So far, I just added the things I needed.

@grandixximo

Copy link
Copy Markdown
Contributor

Two notes after the latest push.

Naming: you're right, I'll withdraw my concern. hal_get_realtime_type() matches the existing hal_get_lock(), which is already a parameterless global-state getter, so hal_get_* isn't exclusively the typed-ref family. Leave it as is.

CI / auto-start: the two failures (raster, hal-show) come from dropping the uspace auto-start of rtapi_app on first loadrt. raster runs halcmd -f raster.hal (a loadrt with no realtime start) and now gets No master found. That breaks standalone halcmd -f *.hal scripts in general, so I'd suggest not hard-breaking it: keep auto-start working but emit a one-time deprecation warning pointing at realtime start. That keeps existing scripts working and gives a migration path. Then migrate our own scripts/configs/tests to call realtime start explicitly so the tree models the new idiom (and update the expected outputs, since a stderr warning will otherwise trip output-comparison tests like hal-show).

Separately: is dropping the auto-start actually needed for the RT-status goal, or is it a cleanup that could be its own PR? Keeping them decoupled would let the getrt/realtime_type work land without the broader behavior change.

@hdiethelm

Copy link
Copy Markdown
Contributor Author

If these test fails due to a missing realtime start, they most probably fail also with RTAI, i have to test it.

It is not needed in this PR. However, as soon you like to use hal_realtime_type() in many places, it will help a lot, duet to realtime needs to be started before.

I will move it in a separate PR as initialy planned, this one gets already big.

@hdiethelm

Copy link
Copy Markdown
Contributor Author

Rebased to master and created a package.

Now you can use:

from lcnc import realtime

print("realtime.verify " + str(realtime.verify()))
print("realtime.status " + str(realtime.status()))

or

import lcnc

print("realtime.verify " + str(lcnc.realtime.verify()))
print("realtime.status " + str(lcnc.realtime.status()))

I am seeing quite a lot of random files from linuxcnc installed just in top of dist-packages:

/usr/lib/python3/dist-packages/raster.py
/usr/lib/python3/dist-packages/preview_helpers.py
/usr/lib/python3/dist-packages/propertywindow.py
/usr/lib/python3/dist-packages/pyhal.py
/usr/lib/python3/dist-packages/pyngcgui.py
/usr/lib/python3/dist-packages/hal.py
/usr/lib/python3/dist-packages/hal_glib.py
/usr/lib/python3/dist-packages/hershey.py
/usr/lib/python3/dist-packages/lineardeltakins.cpython-313-x86_64-linux-gnu.so
/usr/lib/python3/dist-packages/linux_event.py
/usr/lib/python3/dist-packages/linuxcnc.cpython-313-x86_64-linux-gnu.so
/usr/lib/python3/dist-packages/linuxcnc_util.py
/usr/lib/python3/dist-packages/multifilebuilder.py
/usr/lib/python3/dist-packages/common
/usr/lib/python3/dist-packages/common/colored_formatter.py
/usr/lib/python3/dist-packages/common/hal_glib.py
/usr/lib/python3/dist-packages/common/iniinfo.py
/usr/lib/python3/dist-packages/common/logger.py
...

^^ looks to me like really bad practice, even if I have basically no clue of python.
It would probably make sense to move them all to a lcnc (or linuxcnc?) python package to not collide with other packages now or in the future. However, that's a whole new project fixing all the imports if you do that.

@grandixximo Do you work more with python? Any insight? Makes my package sens the way I am doing it?

At least after installing the deb, I can run the above code directly with python and stepconf also passes the realtime check, so it is can not be to far... ;-)

Otherwise, I could also just roll back the package creation and stick to lcnc_realtime.py / realtime.py instead of starting a whole new concept of creating a proper linuxcnc python package.

It is not worse than the existing code. realtime.py will never collide with a python implementation of the realtime script, due to it will live in scripts/ or bin/ in the source and under /usr/lib/linuxcnc/realtime after install. The realtime.py module lives under lib/python/realtime.py and under /usr/lib/python3/dist-packages/realtime.py after install.

@grandixximo

Copy link
Copy Markdown
Contributor

Your instinct on the pollution is right: SITEPY is the global /usr/lib/python3/dist-packages, and we dump ~30 generically-named flat modules (hal.py, gremlin.py, nf.py, raster.py) plus generic dirs (common/, touchy/) straight into the shared namespace. A single branded package is the correct long-term fix, and a bare realtime.py there would be a real collision hazard, so you're not wrong to want a package.

That said, for this PR I'd go with the flat lcnc_realtime.py and not the package. Two reasons:

  1. The package makes a project-wide namespace decision inside a realtime-status PR. "What is LinuxCNC's official python namespace" deserves its own thread, the naming alone already took a week here.
  2. We already carry three brand names: emc2/emc in the build and env layer (EMC2_HOME, EMC2VERSION, and the EMC2_REALTIME define this very PR adds), linuxcnc in the package and user layer, and the linuxcnc C extension .so. lcnc would be a fourth. So picking a python namespace is really a chance to consolidate, not just add one more name, and that consolidation shouldn't be decided by default in a status PR. The cleanest target is linuxcnc, but the .so squats it, and turning that into a linuxcnc/__init__ + inner .so is the real fix, which is exactly the bigger project you don't want to take on here. Starting lcnc now risks a second migration later.

So: ship lcnc_realtime.py (flat, collision-safe name, matches today's convention, unblocks this and #4107), and open a separate PR/issue for a proper namespace where the dist-packages cleanup gets the attention it deserves, ideally aimed at linuxcnc. If you and @BsAtHome would rather land the package now, I'm fine with it too, just get an explicit "lcnc is our namespace going forward" on record first so it isn't decided by default.

One thing that matters more than the packaging: hal.is_sim / hal.is_rt were documented public attributes (in python-hal-interface.adoc, which this PR deletes). Hard-removing them breaks any external user HAL/python config or custom GUI reading them. I'd keep a thin deprecation shim, the names computed via the new path with a warning, rather than removing them outright.

And if the package does stay: drop the eager from . import realtime in __init__.py so import lcnc doesn't pull in every future submodule; use subprocess.run([REALTIME, "verify"], stdin=DEVNULL, stdout=DEVNULL) instead of os.system (no shell, covers BsAtHome's stdin point); README is missing a trailing newline.

@hdiethelm

hdiethelm commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

That said, for this PR I'd go with the flat lcnc_realtime.py and not the package. ...

Agreed, that's a different project. Adding lcnc_realtime.py and migrating it later to lcnc/realtime.py does adds only minimal overhead to creating a package later.

I just created an issue: #4198

So: ship lcnc_realtime.py (flat, collision-safe name, matches today's convention, unblocks this and #4107), and open a separate PR/issue for a proper namespace where the dist-packages cleanup gets the attention it deserves, ideally aimed at linuxcnc. If you and @BsAtHome would rather land the package now, I'm fine with it too, just get an explicit "lcnc is our namespace going forward" on record first so it isn't decided by default.

At least for me, I rather do it the old fashioned way (lcnc_realtime.py) and do the package right in a different PR.

One thing that matters more than the packaging: hal.is_sim / hal.is_rt were documented public attributes (in python-hal-interface.adoc, which this PR deletes). Hard-removing them breaks any external user HAL/python config or custom GUI reading them. I'd keep a thin deprecation shim, the names computed via the new path with a warning, rather than removing them outright.

That would mean digging out the PEP 562 module __getattr__ again but instead of doing it in there, throw a deprecation message and call lcnc_realtime.verify(), right? I can do that.

And if the package does stay: drop the eager from . import realtime in __init__.py so import lcnc doesn't pull in every future submodule; use subprocess.run([REALTIME, "verify"], stdin=DEVNULL, stdout=DEVNULL) instead of os.system (no shell, covers BsAtHome's stdin point); README is missing a trailing newline.

Thanks, I will do that.

Just a side question. How can I have the package work the following way without an import in __init__.py? Dropping the package again, I won't need it anyway.

import lcnc

print("realtime.verify " + str(lcnc.realtime.verify()))
print("realtime.status " + str(lcnc.realtime.status()))

@grandixximo

Copy link
Copy Markdown
Contributor

Sounds good, and thanks for opening #4198.

On the shim: yes, exactly. PEP 562 __getattr__ in lib/python/hal.py (the wrapper that does from _hal import *), on access to is_rt / is_sim emit a DeprecationWarning and return lcnc_realtime.verify() for is_rt and not lcnc_realtime.verify() for is_sim. Keeping it in hal.py rather than the C extension is the right spot since that's where the old names lived for importers.

On the side question: with a genuinely empty __init__.py, import lcnc; lcnc.realtime.verify() will not work. A submodule isn't bound as an attribute of the package until something imports it (same reason import xml; xml.etree... fails until you import xml.etree). So the supported forms are from lcnc import realtime or import lcnc.realtime (then lcnc.realtime.verify()).

If you ever did want the import lcnc; lcnc.realtime form without eager-importing every submodule, the clean way is a lazy PEP 562 __getattr__ in __init__.py:

def __getattr__(name):
    if name == "realtime":
        import importlib
        return importlib.import_module(".realtime", __name__)
    raise AttributeError(name)

That imports on first access only. But since you're dropping the package for now, none of that applies, lcnc_realtime.verify() is all you need.

@BsAtHome

Copy link
Copy Markdown
Contributor

Right, it seems that we've settled on the (color)name of the shed ;-)

@grandixximo

Copy link
Copy Markdown
Contributor

Till we decide to rename it tomorrow...

@hdiethelm

hdiethelm commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

So, I reverted the package, added doc and added the deprecation of is_sim / is_rt.

I'd like to stick with lcnc_realtime.py. lcncrealtime.py is hard on my eye and there are also other modules with underscore like hal_glib.py.

Anything more except final testing?

@hdiethelm hdiethelm force-pushed the halcmd_getrt branch 2 times, most recently from d9528f4 to 2951ecc Compare June 24, 2026 08:45
@grandixximo

Copy link
Copy Markdown
Contributor

Coming out of draft?

@hdiethelm

Copy link
Copy Markdown
Contributor Author

Yes, why not. I tested stepconf / pnyconf and retested a few parts. So I call it ready.

@hdiethelm hdiethelm marked this pull request as ready for review June 24, 2026 10:53
@hdiethelm

Copy link
Copy Markdown
Contributor Author

Of course, my own test complained, fixed.

Processing	file:///home/runner/work/linuxcnc/linuxcnc/docs/build/html/uk/remap/remap.html


List of broken links and other issues:
file:///home/runner/work/linuxcnc/linuxcnc/docs/build/html/uk/config/python-lcnc_realtime.html	
  Line: 157
  Code: 404 File `/home/runner/work/linuxcnc/linuxcnc/docs/build/html/uk/config/python-lcnc_realtime.html' does not exist
 To do: The link is broken. Double-check that you have not made any typo,
	or mistake in copy-pasting. If the link points to a resource that
	no longer exists, you may want to remove or fix the link.

Comment thread src/rtapi/uspace_rtapi_main.cc Outdated
Comment thread lib/python/hal.py Outdated
The same checks are performed always the same now. If something is not
properly checked, makeApp() fill fail instead of just chosing a
different RT implementation by itsself.

New function: rtapi_realtime_type_t rtapi_get_realtime_type(void)
rtapi_is_realtime() was always unreliable and broken for user components
for some time. It is fixed but only available for rt components now.

hal_get_realtime_type() returns the true running realtime type
through the HAL for user and realtime components.

This function is also exposed through python hal.

rtapi_app now unloads hal_lib at exit, so everything is cleaned up
properly and realtime_type is set back to REALTIME_TYPE_UNINITIALIZED.
REALTIME_TYPE_UNKNOWN / REALTIME_TYPE_PREEMPT_DYNAMIC need
LINUXCNC_FORCE_REALTIME=1 to be set.

These two options are most likely not desired and should not be selected
automatically.
lcnc_realtime.verify() should be used instead of hal.is_rt
Instead use FutureWarning which is normaly visible for users
@grandixximo grandixximo merged commit 64e7321 into LinuxCNC:master Jun 24, 2026
16 checks passed
@grandixximo

grandixximo commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Now back to #4107 what does the "you are not running realtime" need look like in the UI?

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.

3 participants