How to make a `GLib.Subprocess` having childs exit responsibly?

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.

Doing the following:

  • In a bash terminal (gnome-terminal) :

    • gjs -c 'imports.gi.GLib.spawn_command_line_sync("sh -c '\''sleep 100'\''");'
  • In another bash terminal :

    • ps -o comm,sid,pgid,pid -p $(pgrep -d',' -f 'bash')
    • ps -o comm,sid,pgid,pid -p $(pgrep -d',' -f 'sleep')

yields the following IDs hierarchy:

COMMAND SID PGID PID
bash a a a
gjs a b b
sh a b c
sleep a b d

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.

What doesn’t work ?

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.

Everything works for me. I can’t reproduce the problem you’re experiencing.

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.

1 Like

Thank you for testing.

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.

I may have to try with a fork/clone.

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.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.