Skip to content

Fix GUI startup ENOMEM on small-RAM machines with an OpenMP OpenBLAS - #4733

Merged
andypugh merged 2 commits into
LinuxCNC:masterfrom
grandixximo:gui-spawn-no-fork
Oct 28, 2026
Merged

andypugh merged 2 commits into
LinuxCNC:masterfrom
grandixximo:gui-spawn-no-fork

Conversation

@grandixximo

Copy link
Copy Markdown
Contributor

NTULINUX reported on Discord that on a Gentoo VM with 4 GB, sim axis lathe, gmoccapy lathe and qtdragon lathe fail to start, while plain axis starts:

__vm_enough_memory: pid: 2586, comm: axis, bytes: 10737418240 not enough memory for the allocation
OSError: [Errno 12] Cannot allocate memory

I reproduced it in his VM image. It is not a leak. Gentoo's OpenBLAS (OpenMP build, loaded through flexiblas) reserves 128 MiB buffers when it loads, about 1 GiB of private writable memory per CPU: 4.3 GB of VmSize with 4 CPUs, 8.5 GB with 8. Resident memory stays around 36 MB. The preview imports numpy, which loads it, so every GUI with a preview holds that reservation. With the default overcommit mode a fork() has to account it again, and when it is larger than RAM plus swap the kernel refuses. In his VM python3 -c 'import numpy, os; os.fork()' fails on its own.

The GUIs that fail are the ones that fork after numpy is loaded:

  • axis, gmoccapy and qtvcp run POSTGUI_HALFILE through os.spawnvp(), which forks the GUI. Plain axis has no postgui file, so it never forks.
  • qtdragon lathe also forks inside QtWebEngine (Chromium's sandbox setup), which LinuxCNC does not control.

It surfaced now because the preview started importing numpy with the OpenGL 3.3 rewrite.

Two commits:

  1. scripts/linuxcnc sets OMP_NUM_THREADS=1 unless the user already set it. That drops the reservation to 1.2 GB at 8 CPUs and fixes all three GUIs, qtdragon included. LinuxCNC does no BLAS work that needs threads. OPENBLAS_NUM_THREADS=1 does not help with this build.
  2. axis, gmoccapy, gscreen and qtvcp run the postgui HAL files with subprocess.call() instead of os.spawnvp(). subprocess starts the child with vfork/posix_spawn, which shares the parent's memory until exec, so the GUI's size no longer matters. The return value is the same (exit status, or minus the signal). On its own it fixes axis and gmoccapy but not qtdragon, so it does not replace the first commit; it just stops copying a large GUI for every postgui file.

Tested in his Gentoo VM (Python 3.14, OpenBLAS 0.3.34, 4 GB, 8 CPUs), lathe sims of axis, gmoccapy and qtdragon:

as shipped OMP_NUM_THREADS=1 subprocess only
axis Errno 12 starts starts
gmoccapy Errno 12 starts starts
qtdragon QtWebEngine aborts starts QtWebEngine aborts

On Debian 13 the patched axis runs the sim lathe postgui file and links its pyvcp pins as before.

The OpenBLAS reservation itself is a Gentoo packaging matter; any numpy program that forks on a small machine hits it. Until this lands, export OMP_NUM_THREADS=1 before starting LinuxCNC works around it.

An OpenMP build of OpenBLAS reserves 128 MiB buffers at load, about
1 GiB of private writable memory per CPU. The preview imports numpy,
which loads it, so every GUI with a preview holds that reservation.
With the default overcommit mode a fork() must account it again, and
on a machine with less RAM than that the kernel refuses:

  __vm_enough_memory: pid: 2586, comm: axis, bytes: 10737418240 not
  enough memory for the allocation
  OSError: [Errno 12] Cannot allocate memory

The GUIs that fork after the import fail to start: axis, gmoccapy and
qtvcp for the POSTGUI_HALFILE, and qtdragon also in QtWebEngine.
Reported by NTULINUX.

OMP_NUM_THREADS=1 drops the reservation (8.5 GB to 1.2 GB VmSize at
8 CPUs). OPENBLAS_NUM_THREADS=1 does not. LinuxCNC does no BLAS work
that needs threads. A value the user set is kept.
axis, gmoccapy, gscreen and qtvcp ran POSTGUI_HALFILE and
POSTGUI_HALCMD through os.spawnvp(), which forks the whole GUI
process. A fork must account the GUI's private writable memory again,
and with the preview's numpy that can be gigabytes of reservation
(see the OMP_NUM_THREADS commit). subprocess.call() starts the child
with vfork/posix_spawn, which shares the parent's memory until exec
and accounts nothing.

The return value is the same: the exit status, or minus the signal.

gmoccapy's comment on SIGTERM lost in a fork hook no longer points at
the postgui spawns; the check stays for any other fork.
@andypugh
andypugh merged commit 6f90560 into LinuxCNC:master Oct 28, 2026
19 checks passed
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