WebAssembly and Signal Handling
You can think of signal’s like user space interrupts. Essentially the running application is interrupted, and a signal handler function is invoked. This function is passed some data to describe why it is being interrupted. Once the function is invoked it can process a response to this “signal”. You’ve probably seen this on Linux. Geeks for Geeks has a great description of Signals. They are effectively used to communicate some information to the running process, a common use case is sending a signal to notify a process that is about to be terminated, thus allowing it to clean up resources, closing file handles, etc.
The Signal Handler
When a process receives a signal the existing execution is effectively paused, and if configured a specific signal handler function is invoked. This allows an application to receive information from other processes or the operating system itself. Often these signals notify an application that it is about to be terminated, when you press ctrl-c
at the command prompt, your application will received a SIGINT
message.
While configuring a signal handler in native code is pretty straight forward, writing a signal handler in WebAssembly is hard. This is because to do this, we’d need to pause execution, preserving whatever state the application is in, then create space to execute a brand new function. This is going to entail creating additional space of the function stack. These concepts don’t exist in WebAssembly, once a function is invoked there is no standard way to halt its execution it will continue to execute until it reaches a conclusion…. At least not yet..
There are proposals for the introduction of a stack switching mechanic which would allow this to happen, but at the time of writing this, they don’t exist at the moment. So, we’re stuck. We can’t handle the signal.
The Error Message
Let’s look at an example of a signal handler and how it works natively, and then we can walk through what happens when we try to build it for Windows.
Here is an example application, let’s call it signal.c
.
#include <stdio.h>
#include <signal.h>
#include <stdbool.h>
#include <unistd.h>
bool gStop = false;
void signal_handler(int sig) {
printf("Signal handler called - Signal %d !\n", sig);
gStop = true;
}
int main(void) {
printf("registering signal handler \n");
signal(SIGINT, signal_handler);
while(!gStop) {
printf("still running..\n");
sleep(1);
}
printf(" and terminating politely now...\n");
return 0;
}
We can compile this on linux using gcc
just like this :
gcc -o sig ./signal.c
If we execute it, let it run for a little bit, and then terminal the application using ctrl-c
we’ll get the following:
chris@NUC:~/signal$ ls
sig signal.c
chris@NUC:~/signal$ ./sig
registering signal handler
still running..
still running..
still running..
still running..
^CSignal handler called - Signal 2 !
and terminating politely now...
chris@NUC:~/signal$
You can see that when the ctrl-c
key press was detected, SIGINT
was triggered and the signal_handler
function was invoked. It set the boolean value gStop
to false, and that intern caused the while
loop to exit and the application terminated gracefully with it’s polite message.
Now, let’s try the same thing with WebAssembly. We can attempt to compile the application to WebAssembly with the following command line, note that this uses the WASI SDK:
/opt/wasi-sdk/bin/clang -o sig.wasm ./signal.c
This will return the following error message:
chris@NUC:~/signal$ /opt/wasi-sdk/bin/clang -o sig.wasm ./signal.c
In file included from ./signal.c:2:
/opt/wasi-sdk/bin/../share/wasi-sysroot/include/signal.h:2:2: error: "wasm lacks signal support; to enable minimal signal emulation, compile with -D_WASI_EMULATED_SIGNAL and link with -lwasi-emulated-signal"
#error "wasm lacks signal support; to enable minimal signal emulation, \
^
./signal.c:16:2: error: call to undeclared function 'signal'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
signal(SIGINT, signal_handler);
^
./signal.c:16:9: error: use of undeclared identifier 'SIGINT'
signal(SIGINT, signal_handler);
^
3 errors generated.
chris@NUC:~/signal$
Umm… this isn’t what we want….
The Solution
However it is possible to work around this. The command line error messages, in fairness tell us what we need to do to compile the code - Turn on the pre-processor macro for signal handling _WASI_EMULATED_SIGNAL
and then make sure to link against the emulated signal library -lwasi-emulated-signal
.
/opt/wasi-sdk/bin/clang -D_WASI_EMULATED_SIGNAL -lwasi-emulated-signal -o sig.wasm ./signal.c
Note that this actually compiles and we now have a wasm file… (yay!)
chris@NUC:~/signal$ ls
sig signal.c sig.wasm
Now what happens when we try to run it? - Let’s give this a go. We can execute the wasm with iwasm
the WAMR interpreter…
chris@NUC:~/signal$ ls
sig signal.c sig.wasm
chris@NUC:~/signal$ iwasm ./sig.wasm
registering signal handler
still running..
still running..
still running..
^C
chris@NUC:~/signal$
You can see that while the code executed, when ctrl-c
was invoked the signal_handler
function was never invoked. We try with wasmtime to see if that runtime offers an alternative…
chris@NUC:~/signal$ wasmtime ./sig.wasm
registering signal handler
still running..
still running..
^C
chris@NUC:~/signal$
Nope, the signal handler wasn’t invoked, here either and we have the same output.. so what about wasm3 ?
chris@NUC:~/signal$ wasm3 ./sig.wasm
registering signal handler
still running..
still running..
still running..
^C
chris@NUC:~/signal$
It’s the same output, an ignored signal and the same sort of termination we’d have if the signal wasn’t processed.
What do you need Signals for Anyway?
Signals are used to convey important information, they can be used with signal and multi-threaded applications. For a lot of single threaded applications the signal handler, mostly just sets a flag that allows the event to be processed in some form of loop, similar to the sample here.
We could extend our runtime embedded and add a native signal handler that traps all signals and sets a value, inside a running WebAssembly application, allowing it to handle the signal gracefully. But that would be a story for another day. For now we’ve covered signals and how’ they can be compiled, and by default ignored in WebAssembly.