Have you ever needed to handle signal events within your ZX script? For example, what happens if you need to handle a graceful shutdown of your long-running or infinite-running script? Or what happens when the user forcefully stops the script?
These signal events are typically “SIGTERM”, “SIGINT”, and similar events. When using a script language like Bash, these events are most commonly captured with trap
commands, but what happens when we’re using ZX?
In this tutorial we’ll explore how to use simple JavaScript to capture signal events in a modern ZX script.
You won’t need much to be successful with this short and sweet tutorial. Here’s what you’ll need:
If you’ve made it this far, you probably already have ZX installed because signal events generally aren’t the first thing you look into. However, if you don’t have it installed, get it installed by doing the following from a command prompt:
npm install -g zx
The above command will use Node.js to install ZX which allows you to execute ZX scripts in a similar fashion to what you’d do with Bash scripts.
The first half of this tutorial will probably work fine for Windows, but when it comes to sending signal events from the host, you may have to do some research on what Windows expects versus what macOS and Linux expects.
To kick things off, we’re going to build a very simple script. Somewhere on your computer create an example.mjs file and include the following JavaScript:
#!/usr/bin/env zx
process.on("SIGTERM", () => {
console.log("SIGTERM");
process.exit();
});
process.on("SIGINT", () => {
console.log("SIGINT");
});
$`echo 'Hello World'`
try {
await $`sleep 100`
} catch (error) {
echo(error.message)
}
Before we walk through what the above file is doing, make sure you give this file execute permissions. This can be done with the following command from the command line:
chmod +x example.mjs
So what is happening in the above file?
Ignoring the star of the show for a moment, we are first printing output and then sleeping for one hundred seconds. We are doing the sleep to simulate a long-running or infinite-running process.
This leads us to our signal event listeners.
Traditionally you’d be using the trap
command in a bash script. This might look something like the following:
trap 'echo SIGTERM; exit' SIGTERM
trap 'echo SIGINT; exit' SIGINT
While you could mess around with the trap
command in a ZX script, it is a whole lot easier to leverage JavaScript and the process
listeners.
In no particular order, we listen for a “SIGTERM” event:
process.on("SIGTERM", () => {
console.log("SIGTERM");
process.exit();
});
This event happens during a graceful shutdown request. It is the most ideal because you can add your shutdown and cleanup logic to it rather than a hard stop.
We also listen for the “SIGINT” event:
process.on("SIGINT", () => {
console.log("SIGINT");
});
The above event is triggered when the user presses control+c on the keyboard.
So how can we simulate this ZX script to make sure the signal events are working?
This is where the macOS and Linux related commands come into play. Let’s first play around with the “SIGINT” event.
Run the script by entering the following in a command prompt:
./example.mjs
The script will automatically end after one hundred seconds which is plenty of time to test things.
Press control+c on the keyboard. It should print “SIGINT” before exiting. It will also print an error because sleep
will have thrown an exception which is normal.
So let’s try to trigger the “SIGTERM” event. Run the ZX script once more.
In a separate command prompt, enter the following commands:
ps -a
kill -s SIGTERM <PID>
The first command will list your currently running processes. Look for a process that is using the example.mjs file and copy the “PID”. Using the “PID”, enter it into the kill
command. In the kill
command we are specifying the signal event to send.
If everything went well, “SIGTERM” should be output from your ZX script and it should gracefully terminate.
You just saw how to handle signal events in a ZX script. ZX is more or less a wrapper to Bash so you can use the trap
commands if you really wanted to, but the process
listeners work so much easier.
Using the process
listeners to handle “SIGINT” and “SIGTERM” work great if you’re using ZX to run a Node.js application or if you’re pairing it with Docker. You can use this to handle the stopping of Docker containers for example.
A video version of this tutorial can be found below.