Just 2 weeks ago Patrick Lemke on the Kotlin Slack in the #kotlin-native channel asked an interesting question:
I was intrigued by this question. A SIGINT is the event sent to a process if you press Ctrl + C
in the shell, and that usually exits a process. So how would you actually handle events like SIGINT in Kotlin Native? π€
At first I thought this would be hard to implement. I tried to create a small example. On my way I not only managed to implement this, but discovered how Kotlin tooling and documentation can help you in solving similar problems. Onward!
How C/C++ handle SIGINT
At first it's probably a good idea to read about how these interrupts are actually created in C/C++. I searched for 'handle sigint c/c++' on Google and found a page on C++ Signal Handling. Basically, you define a function in C++ and you can register it as a signal handler by calling the signal
function (that's the only C++ code I'll show here):
void signalHandler(int signum) {
// your logic here when interrupt happens
}
int main () {
// register your handler function - can be SIGINT, SIGABRT, or others
signal(SIGINT, signalHandler);
// ever-running code here that can/will be interrupted
}
Calling signal
in Kotlin Native
Now let's find out how we can call that function in Kotlin Native! First, I opened IntelliJ, and created a new Kotlin Native project with Create New Project -> Kotlin -> Native | Gradle
. This automatically creates a sample main file. Let's see if it can find a signal
function. And jackpot - there it is, and it can be imported!
And SIGINT and the other constants can be found as well:
I think this is tooling by IntelliJ/Kotlin is pretty amazing. It looks simple, but there is actually a whole lot going on under the covers. The POSIX bindings are created automatically for you and the platform(s) you target, and they are already indexed, waiting for you to type-and-complete.
Now, moving to the second parameter: we already know this is a lambda. But what the heck is a CPointer<CFunction<(Int) β Unit>>?
- and how do I get one? I had no idea, because I've never done this before in Kotlin (although I know what a pointer is). So I googled for 'kotlin cpointer cfunction', and landed on this excellent Kotlin documentation: Mapping Function Pointers from C
And there it is - scrolling down I find exactly what I need: Passing Kotlin Function as C Function Pointer Using that, we now know that Kotlin provides a helper function called staticCFunction
. Let's use that!
So a trivial example might be that we print something forever. And then we will interrupt it. Here's my first, trivial implementation:
fun main() {
signal(SIGINT, staticCFunction<Int, Unit> {
println("Interrupt: $it")
})
while (true) {
println("running")
sleep(1)
}
}
When we compile this (by clicking the Build
button), it will create a binary with the name of our project and a .kexe
ending. For me, it created a file under build/bin/macos/debugExecutable/kotlin-native-signal.kexe
. I wanted to call the main method directly via the common 'green arrow run button', but that wasn't there. For now, we'll use the commandline - but I hope JetBrains continues to improve the tooling around Kotlin Native (see that section further down).
So let's call the program in a shell, and press Ctrl + C
after 2 seconds to interrupt it:
$ build/bin/macos/debugExecutable/kotlin-native-signal.kexe
running
running
^CInterrupt: 2
running
running
running
[program continues, does not exit]
Nice, it does handle the interrupt! π
But we didn't actually exit the program in our interrupt handler - so now the program will continue forever. Unless we kill it with killall
β¦ π
# in a second shell:
$ killall kotlin-native-signal.kexe
# in the first shell this will kill the program:
[1] 66077 terminated build/bin/macos/debugExecutable/kotlin-native-signal.kexe
To improve this program, we can call the (POSIX function!) exit
in the interrupt. Also, we can use staticCFunction
to create a CPointer reference to an existing Kotlin function as well. Here's the improved version, with imports added for reference:
import kotlinx.cinterop.staticCFunction
import platform.posix.SIGINT
import platform.posix.exit
import platform.posix.signal
import platform.posix.sleep
fun handleSignal(signalNumber: Int) {
println("Interrupt: $signalNumber")
exit(0)
}
fun main() {
signal(SIGINT, staticCFunction(::handleSignal))
while (true) {
println("running")
sleep(1)
}
}
Finally, when we interrupt the program with Ctrl + C
, it will run our handler code and exit.
IntelliJ: A tooling improvement suggestion
When I wrote this blogpost I wanted to run the main
method directly from the editor. But where usually I expect to find a green run icon of happiness there was just an empty grey area:
At first I tried the free community edition of IntelliJ. Next, I tried IntelliJ Ultimate. I opened the same project. Ultimate suggested I install a plugin, so I did that:
Nevertheless: even after doing that and restarting the IDE, Ultimate didn't show me the green run button. In the 'Run Configurations', I got a new entry 'Kotlin/Native Application' - but it didn't detect my Kotlin Native executable, so I couldn't select that.
Finally, I tried CLion. I installed an additional plugin for Kotlin Native. Still, no run icon to the left of the main
method. But at least I got a run configuration that way, and I could run & debug the Kotlin Native program from the IDE! π
So for tooling, I have the following wishes:
- I'd love to have that green run icon. It's there in C/C++ programs in CLion and in all Java projects, so why not here?
- IntelliJ Community could make the suggestion that run/debug is only supported in the commercial tools.
- IntelliJ Ultimate and CLion should behave consistently. What works in CLion should work in Ultimate as well.
Conclusion
This was a fun ride! It's so amazing that we can use POSIX APIs and more (iOS & MacOS libs, etc.) in Kotlin Native - and the coding tooling is great already. There are still some things that could be improved. Then again, Kotlin Native is still a relatively new technology. But it's already on a good way to becoming stable!
If you liked this post, please give it a β€οΈ and follow me on Twitter!
Top comments (0)