I’m using GLib 2.80.0 via GJS 1.80.2 on Linux Mint Cinnamon.
When launching a GLib.Subprocess which makes childs itself, then calling the method Subprocess.force_exit doesn’t affect the childs. How to make my program handle its Subprocess responsibly ?
MWE
Consider the following test.js program launching a GLib.Subprocess consisting of sh having a sleep child, then forcing it to exit 10 seconds after.
Prepare to watch what’s happening with gnome-system-monitor in typing sleep to search for the child process and its sh parent.
// test.js
const {Gio, GLib} = imports.gi;
const [_ok, argvp] = GLib.shell_parse_argv('sh -c "sleep 100"');
const proc = Gio.Subprocess.new(argvp, Gio.SubprocessFlags.NONE);
log("the Subprocess PID is " + proc.get_identifier());
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 10, () => proc.force_exit());
GLib.MainLoop.new(null, false).run(); // user has to send SIGINT (Ctrl+C in bash) to stop
Run the program from a terminal with the command gjs test.js.
Now you should see in gnome-system-monitor that there is an sh process with the same PID as printed in the terminal and a sleep process with the same PID+1.
After 10 seconds, the sh process should disappear and the issue is that the sleep child remains.
which removes any ability to send signals to all and only descendants of the Subprocess / spawned command (Subprocess uses spawn_command_[…] internally, so they behave the same).
I ended up using setsid prefixed to the command then using the command kill -<signal> -- -<PGID> to terminate/kill the Subprocess including any of its descendant.
The issue is that now no signal sent to gjs is forwarded to the Subprocess / spawned command.
I searched quite a lot about the issue and found that bash itself handles this issue correctly but it’s not straightforward how it does it (seems related to “job control”), which I think is why GLib hasn’t implemented it.
I can’t reproduce this on gjs-1.81.2, although I don’t recall anything relevant changing since 1.80.0.
I’m not sure if the example gjs -c 'imports.gi.GLib.spawn_command_line_sync("sh -c '\''sleep 100'\''");' will work in practice. Internally it probably still requires someone to iterate the main context.
I’m not sure if the example gjs -c 'imports.gi.GLib.spawn_command_line_sync("sh -c '\''sleep 100'\''");' will work in practice. Internally it probably still requires someone to iterate the main context.
There’s only one instruction, so why having a main context ? Anyway it’s this way only to maximize concision here. In my tests, it behaves exactly the same as with a main.
Try running your example under strace to see what syscalls are being made, and see if that brings any clues as to why the subprocess isn’t exiting.
You may also want to double-check that the subprocess hasn’t successfully exited but not been reaped yet, i.e. remains in the process list as a zombie.
I just tried the test.js on Fedora Workstation 40 in a VM and it appears that sh -c '<command>' behaves like sh -c ' exec <command>', which means there is no sh intermediate process in the end, which makes the thing working, but I’m not confident this is the classically expected behavior of this command and if that was maybe the difference on your setup.
Anyway in the source code of gspawn[…].c I have not seen anything related to PGID and the Subprocess.force_exit just send a SIGKILL to the PID of the Subprocess, so I see no chance the signal would be propagated.
To “spawn a command” seems maybe OK to me to not be responsible of it but having an encapsulated “subprocess” which pretends to force it to exit seems to me to ask more responsability, hence this topic.
But I can see the difficulty here as trying to see how bash does it I haven’t understand yet.