Alexito's World

A world of coding 💻, by Alejandro Martinez

It’s a trap! Catch OS signals in Swift.

In the script that I was recently writing, from where the post “The state of Swift scripting” emerged, I found that when the scrip is interrupted (Ctrl + C in the terminal) the process that the script started was not dying with it.

I was using NSTask to run the same script inside a repeat loop. It is a simple way to keep the script running for ever. But I didn’t want these child tasks to keep running when the main script stopped.

The solution I found is to use the operating system signal functions to catch the interrupt signal and kill the tasks.

First, an optional NSTask has to be defined in the global scope in order to access it in the signal handler. The handler has to be a C compatible function so it can’t capture the NSTask from the stack.

var taskToKill: NSTask?
Then, just store the task on that variable and setup the handler.

taskToKill = task defer { taskToKill = nil }
// signal(SIGINT, handler)
task.launch()

To avoid dealing with the specifics of the C functions I wrote a small wrapper that exposes a class function and an enum that defines the different signals.

Trap.handle(.interrupt) { taskToKill?.terminate() exit(EXIT_FAILURE) }

You can find Trap in GitHub. If someone knows a way in Foundation to link an NSTask to the parent process contact me ;) I would prefer to just do that and forget about handling signals.

If you liked this article please consider supporting me