Thread overview
How to wait for a shell process to finish on ctrl+c before exiting?
Nov 24, 2019
aliak
Nov 24, 2019
mipri
Nov 25, 2019
aliak
Nov 25, 2019
aliak
November 24, 2019
I'm writing some command line tooling stuff, and one of the command spins up a docker compose file (which in short, spins up some services and aggregates the output of each service to stdout).

When a user presses ctrl+c, i would like to pass on the ctrl+c to the spawned process and wait till it handles ctrl+c and then let go of the current process.

So far I have this:

int spawnedPid;

extern(C) int kill(int pid, int sig) nothrow @nogc @system;

extern(C) void interruptHandler(int sig) nothrow @nogc @system {
    kill(spawnedPid, sig);
}

int spawnProcessAndWait(string[] cmd) {
    auto pid = spawnProcess(cmd, stdin, stdout, stderr);
    spawnedPid = pid.processID;
    signal(SIGINT, &interruptHandler);
    int result = wait(pid);
    return wait(pid);
}

It doesn't work. I think the call to kill doesn't wait? Is there a way to make it wait?

I can't call kill(Pid) or wait(Pid) inside the interrupt handler because those are not @nogc [0].

[0]: https://forum.dlang.org/thread/mtikzznfaahiltguvybw@forum.dlang.org
November 24, 2019
On Sunday, 24 November 2019 at 15:44:00 UTC, aliak wrote:
> I'm writing some command line tooling stuff, and one of the command spins up a docker compose file (which in short, spins up some services and aggregates the output of each service to stdout).
>
> When a user presses ctrl+c, i would like to pass on the ctrl+c to the spawned process and wait till it handles ctrl+c and then let go of the current process.

This might be useful:
---
#! /usr/bin/env rdmd
import std;
import core.stdc.signal;

int spawnedPid;

extern(C) int kill(int pid, int sig) nothrow @nogc @system;
extern (C) int waitpid(int pid, int* status, int options) nothrow @nogc @system;

extern(C) void interruptHandler(int sig) nothrow @nogc @system {
    kill(spawnedPid, SIGTERM);
    waitpid(spawnedPid, null, 0);
}

int spawnProcessAndWait(string[] cmd) {
    auto pid = spawnProcess(cmd, stdin, stdout, stderr);
    spawnedPid = pid.processID;
    signal(SIGINT, &interruptHandler);
    int result = wait(pid);
    return result;
}

void main() {
    spawnProcessAndWait(["perl", "-le", "$SIG{INT} = sub { print 'Ignoring interrupt' }; $SIG{TERM} = sub { print 'Got'; sleep 2; print 'Term' }; print 'Go'; sleep 10"]);
}
---

I don't just using this code, but running this and seeing how
it behaves. For example, you might be surprised by the output
if you hit control-C as soon as you see the "Go":

---
Go
^CIgnoring interrupt
Got
Term
---
with a 2s delay after 'Got'.

Or this output if you remove the Perl $SIG{INT} assignment:

---
Go
^C
---
with you dropped right back to the shell.


It's also possible to cause this code to exit with an exception
from the wait(), because there's no process for it wait for.
November 24, 2019
On 11/24/19 10:44 AM, aliak wrote:
> I'm writing some command line tooling stuff, and one of the command spins up a docker compose file (which in short, spins up some services and aggregates the output of each service to stdout).
> 
> When a user presses ctrl+c, i would like to pass on the ctrl+c to the spawned process and wait till it handles ctrl+c and then let go of the current process.
> 
> So far I have this:
> 
> int spawnedPid;
> 
> extern(C) int kill(int pid, int sig) nothrow @nogc @system;
> 
> extern(C) void interruptHandler(int sig) nothrow @nogc @system {
>      kill(spawnedPid, sig);
> }
> 
> int spawnProcessAndWait(string[] cmd) {
>      auto pid = spawnProcess(cmd, stdin, stdout, stderr);
>      spawnedPid = pid.processID;
>      signal(SIGINT, &interruptHandler);
>      int result = wait(pid);
>      return wait(pid);
> }
> 
> It doesn't work. I think the call to kill doesn't wait? Is there a way to make it wait?

Hm.. are you sure that ctrl-c isn't also sending the signal to your child process? I thought it did.

-Steve
November 25, 2019
On Sunday, 24 November 2019 at 16:05:14 UTC, mipri wrote:
> On Sunday, 24 November 2019 at 15:44:00 UTC, aliak wrote:
>> [...]
>
> This might be useful:
> ---
> #! /usr/bin/env rdmd
> import std;
> import core.stdc.signal;
>
> [...]

waitpid, of course! Thanks agin :)
November 25, 2019
On Sunday, 24 November 2019 at 17:04:49 UTC, Steven Schveighoffer wrote:
> On 11/24/19 10:44 AM, aliak wrote:
>> [...]
>
> Hm.. are you sure that ctrl-c isn't also sending the signal to your child process? I thought it did.
>
> -Steve

Yesh, you're right. That extra kill is unnecessary and was actually causing problems. So thanks for that!
November 25, 2019
On 11/25/19 3:55 AM, aliak wrote:
> On Sunday, 24 November 2019 at 17:04:49 UTC, Steven Schveighoffer wrote:
>> On 11/24/19 10:44 AM, aliak wrote:
>>> [...]
>>
>> Hm.. are you sure that ctrl-c isn't also sending the signal to your child process? I thought it did.
>>
> 
> Yesh, you're right. That extra kill is unnecessary and was actually causing problems. So thanks for that!

As a general rule though, I would say you should do as LITTLE as possible inside your signal handler. Being inside a signal handler is unlike any other function, because you can be called from anywhere. Locks can be held that are not normally, etc.

Most of the time, I just set flags in signal handlers and handle them asynchronously outside in my main loop.

-Steve