Conversation
Repeatedly loadrt -> addf -> delf -> unloadrt a component whose function busy-waits 20 us in a 100 us thread that keeps running. DELF_ITER (default 200) sets the number of cycles.
The realtime thread walks its funct_list without the HAL mutex. hal_del_funct_from_thread() and free_funct_struct() (unloadrt) unlinked the entry with list_remove_entry(), which points the entry's links at itself, and returned it to the free list at once. A thread standing on the entry then loops on it or follows a recycled link, and the following unloadrt dlclose()s code the thread may still run: rtapi_app dies or the thread hangs. Unlink the entry keeping its own links, then wait until the thread has completed two more passes (beatcnt, published with a release store) before the entry is freed. If the thread does not complete a pass within 1000 periods the entry is leaked and delf returns -ETIMEDOUT.
|
Interesting use case, just out of curiosity, what are you doing where you need swapping components with the thread running? |
|
2.9 backport, for reference: branch Differences from this PR:
Results, same setup as above (ubuntu:24.04 container, uspace without SCHED_FIFO,
If you'd rather have this in 2.9 as well, I can open a separate PR against 2.9. |
|
@yurc did you see my text? |
|
Hi Luca, sorry for the late reply, and thanks for the review!
We use LinuxCNC not only for machine tools but also as the controller for
automation cells: a rotary table, a conveyor, a palletizing robot, all on
the same engine.
Each cell's logic is an IEC 61131-3 program (SFC/ST) compiled by MATIEC
into a HAL component. Motion goes through PLCopen-style blocks
(MC_MoveAbsolute, MC_MoveVelocity, MC_Halt, MC_SetPosition). Cells talk to
each other through PackML tags over NATS.
The operator can swap a cell's program on a running station. The servo
thread with the EtherCAT CiA402 drives keeps running, the drives stay in OP
and hold position. So we load the new component and delf/unloadrt the old
one while the thread is running. Restarting HAL just to change a program is
not an option for us.
Today's example: a servo rotary table following a hand gesture from a
camera, with the angle coming in as a tag. That is how we hit the rtapi_app
crash.
The shellcheck warning (SC2034, unused loop variable) is fixed in
6af8e3b.
вс, 4 окт. 2026 г. в 11:43, Luca Toniolo ***@***.***>:
… *grandixximo* left a comment (LinuxCNC/linuxcnc#4630)
<#4630 (comment)>
Interesting use case, just out of curiosity, what are you doing where you
need swapping components with the thread running?
Ps:
test.sh fails shellcheck warning
—
Reply to this email directly, view it on GitHub
<#4630?email_source=notifications&email_token=AAU7VNU254YNFSWA25T3ROL5SIEULA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKOJXHAZDCMJYGQ4KM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-5978211848>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAU7VNRLMH5L367NARGRGMD5SIEULAVCNFSNUABDKJSXA33TNF2G64TZHMZTMNRSHEYDKO2JONZXKZJ3GU3DSNZVGUZTEMRXUF3AE>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
|
from no reply, to two in a row, nice ;-) |
|
what's your handover sequence, do you halt motion blocks before delf, or does new comp take over nets first? |
|
Thanks for the approval! We do break-before-make, and only from a stopped node. The new component never takes over nets while the old one is still on them.
The base layer stays up the whole time: EtherCAT/cia402, the |
Symptom
delfof a function from a running thread, followed byunloadrtof its component, killsrtapi_app(halcmd:recv_result 1 failed: Connection reset by peeronunloadrt) or leaves the thread spinning. With a function that takes a noticeable share of the period it happens within the first few cycles.Cause
The realtime thread walks
thread->funct_listwithout the HAL mutex (thread_task()).hal_del_funct_from_thread()andfree_funct_struct()(unloadrt) unlink the entry withlist_remove_entry(), which points the removed entry'snext/prevat itself (src/hal/hal_lib.c:2757). A thread standing on that entry keeps calling it.free_funct_entry_struct()), andunloadrtthendlclose()s the module (src/rtapi/uspace_rtapi_main.cc:630) while the thread may still execute its code.Fix
funct_entry_unlink(): take the entry out of the list but keep the entry's own links, so a thread standing on it continues to the rest of the list.thread_wait_quiescent(): with the mutex held, wait until the thread completed two more passes before the entry is freed. The pass counter is the existingbeatcnt, now published with a release store after the list walk.delfreturns-ETIMEDOUT.Waiting alone is not enough: with the wait but the old
list_remove_entry(), every run timed out because the thread looped on the self-linked entry.Test
tests/hal-delf-live: a component whose function busy-waits 20 µs in a 100 µs thread;loadrt→addf→delf→unloadrtrepeatedDELF_ITERtimes (default 200) with threads started, then checks thatt.threadbeatstill advances. Only halcmd/halcompile commands are used.Results
Built like the
rip-and-testCI job (ubuntu:24.04,--with-realtime=uspace), only this test, 10 runs × 200 cycles each:rtapi_appdies onunloadrt, cycles 1–7)threadbeat)unloadrt failed, cycles 1–5)Branch
Opened against master. 2.9 has the same bug (reproduced above), but the fix uses the pass counter that only exists since f9ea091 (threadbeat); a 2.9 backport needs a new field in
hal_thread_t. I can prepare it if you want it in 2.9.Why we need it
We swap HAL components in and out while the servo thread keeps running (one component's function is replaced in the same
rtapi_appthat runs motion and the EtherCAT master, without stopping the machine). That is where we hit this.Limits
init_funct_list(initf) is not touched.