0

My program has a main function that will execute a series of tests, which will include some processes that needed to be terminated if the user Ctrl-C.

The following code is the correct workflow

- (void)run {
   [process1 start];
   [process2 start];
   Running...
   [process1 stop];
   [process2 stop];
}

But the user may use Ctrl-C to stop my program, and I don't want to leave process1 and process2 running because they are dangerous. How can stop them properly? Kill command cannot be used because I don't know the dynamic process names, and the only way is to call stop on those two ObjC objects.

Patroclus
  • 1,163
  • 13
  • 31

1 Answers1

2

Technically inside your Objective-C method you can't catch a SIGINT signal (generated by user upon pressing CTRL-C) - your program gets interrupted. The control of flow at this point is handled by kernel. The state of cpu registers valid during execution of your objective-c run method are inevitably lost. Hence you can't continue executing your run method anymore.

What you can do is register a custom SIGINT handler using C API.

#import <signal.h>
void sigHandler(int sig) {
    //as noted by @bbum list of functions with guaranteed behavior in sighandler is very limited
    //obj-c runtime is definitely NOT on the list
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
       [process1 stop];
       [process2 stop];
    });
    //give extra time for the async part to run
    usleep(50);
}

int main(int argc, const char * argv[]) {
    signal(SIGINT, sigHandler);
    @autoreleasepool {
        //your normal program launch goes here  
    }
    return 0;
}

If you have a console app alternatively you could consider route here so basically disable CTRL-C firing SIGINT and asynchronously handle the keyboard reading for CTRL-C yourself. The main drawback is if your program would hit an endless loop it cannot be interrupted with CTRL-C. So to minimise this effect you could set this special terminal mode at the beginning of your run method and restore it once you're done.

Kamil.S
  • 5,205
  • 2
  • 22
  • 51
  • But those two objects are declared inside run method, and I don't want to make them static variables, which makes it harder. – Patroclus Jul 26 '19 at 17:54
  • Check my updated answer v2 – Kamil.S Jul 26 '19 at 18:32
  • 1
    You don't want to run basically any code at all in that signal handler and certainly not objc. Instead, use dispatch_after() to schedule a block on a queue. Get out of the signal handler as early as possible. The underlying issue is that the runtime state is indeterminate at the time the handler is fired. – bbum Jul 26 '19 at 19:17
  • 1
    Greg Parker goes into detail here: https://stackoverflow.com/questions/24051879/what-can-i-do-in-a-signal-handler/24068104 – bbum Jul 26 '19 at 19:17
  • 1
    @bbum thanks for input. I also found this: https://stackoverflow.com/questions/25959056/launchd-sleep-in-gcd-managed-signal-handler I think giving extra time in the thread running the `sighandler` would make sense, before the process gets terminated. – Kamil.S Jul 26 '19 at 19:43
  • 1
    @bbum So I should call dispatch_after() in the sighandler? But how long? Is this safe? – Patroclus Jul 26 '19 at 20:17
  • 1
    @FrankJoe It really doesn't matter; very very very short is OK. The goal is to get out of the signal handler because you have no idea what the runtime state is when the handler fired. Once you return from the handler, then the runtime state becomes predictable. Depending on the work to be done, a global async queue is fine. If you need to do main thread only work, schedule it on the main queue and it'll be processed on the next pass through the run loop. – bbum Jul 27 '19 at 20:46