Fun with threads and POSIX signals

Adrian Dogar
5 min readApr 17, 2021

These last days, I got to assist a Perl newbie in his attempt to modify a library with ugly and evil code. Something that you’ll want to deal with as much as you’d like to deal with Dark Lord of the Sith himself. During that time, I got to say this words more than once: “Este codigo ten o demo dentro!”. A good translation of that Galician sentence would sound like: “Satan lives in these lines!”.

I saw three lines compressed in one unintelligible “sausage string”. I saw one variable resolved in 3 lines to compensate the previous point. Also there were some double and even triple negations, variable names that were holding the opposite information as their name stated and also a one-line function, used only in one place through out the library. Condensed syntaxes and spaceless strings appeared in my nightmares during that week… Just imagine!

As a good practices says: first, try to understand what that code does, and after that you can change it. (and I highlight that for the newbie, I know he hangs out here and he might read me :) So we were reading line by line, writing down notes and even isolate code blocks and had them run in disposable sandboxes.

Even so, we learned some stuff, and have spent some good moments trying to figure out how some blocks were handling POSIX signals. That’s something you don’t have to do each day.

In a very very succinct form, I could say that the script was supposed to do a task after a time delay. And sleep wasn't the solution. The script should perform a monitoring during that time window, and for certain situations, there was no need for the final task, so it could break out the delay and proceed forward. The sleep just put's the execution on hold, and there are no parallel threads where you can have other tasks running in the meantime. Unless, of course, you manually create that thread yourself.

SIGALRM

So, there it was. The one-line function. Used only once in the entire code library. The declaration and it’s usage were separated by some 400 lines.

sub generate_alarm {
$buzz++;
}

Never really got my attention. That is really stupid simple code. Even though it was using terrible names (my version is 100 times prettier), was still easy to understand its role.

The story got exciting when I saw how it gets to be called: was passed as a reference to a $SIG{ALRM} special variable.

$SIG{'ALRM'} = \&generate_alarm;

Even so, It didn’t bothered me much. But the next thing really caught my attention: an infinite loop, a few lines lower, that was exiting only if the $buzz variable was true. And the $buzz was set to 0 right before the infinite loop.

my $buzz = 0;LOOP: while (1) {
# do some things
if ($some_var > 20) {
print "Condition was meet.\n";
last LOOP;
};
sleep 1; if ($buzz){
print "Time's up!\n";
last LOOP;
};
# do some more things
}

It took me a while to understand that the main piece that made all these bricks stick to getter was a short line, locate a few lines above the infinite loop:

alarm 10;

When you want to build something similar, the first thing the comes in mind is having a separate thread. And you’d probably use fork(), or spawn() for that purpose. But that was just too much trouble for creating a 10 seconds timer.

Doctors aren’t able to count seconds and pulsations on the same time. Neither is a Perl script. That’s why they look at their wrist watch while they count the heart beats. Same principle here. Put someone else in charge of your timer. alarm 10 tell's system to make this ten seconds timer, and meanwhile the script itself... executes some prints, perform some checks, prints logs, maybe has some coffee (sleep 1), but always has an ear for the alarm clock, in case the buzzer goes on.

And it does, using the ALRM signal. When the system reaches at the end of the timer, it sends back the SIGALRM signal, that during the life of this script, is pointing to the one-line function that does the simple $buzz++ needed to take the script out of the infinite loop.

So now you know. You can make a loop that counts the total loops, or you can make one that counts the seconds. And you can set the velocity of these iteration by pausing the script the time you need. Of course, both of these loops can benefit the next and last special features.

Funny example

Here is a fun implementation. Random values are expected during the 10 seconds window. And what can be random and useful at the same time? CPU usage peaks (in percentages).

  • If one value is greater than the threshold ($hi_pcpu_proc > 15 in %), it will break out the loop.
  • If the condition wasn’t meet, it stops after 10 seconds.
#!/usr/bin/perl

sub trigger_alarm {
$buzz++;
print "Beep! Beeeeeep! BEEEEEEEEEP!\n";
}
$buzz = 0;
$SIG{ALRM} = \&trigger_alarm;

alarm 10;

LOOP: while (1) {
# do some things my $hi_pcpu_proc = `ps -er -o pcpu= | head -1`;
chomp $hi_pcpu_proc;
$hi_pcpu_proc =~ s/[\s%]//g;

if ($hi_pcpu_proc > 15) {
print "One proc had a CPU peak! [$hi_pcpu_proc]\n";
last LOOP;
};

sleep 1;
if ($buzz){
print "Time's up!\n";
last LOOP;
};
}
exit;

Recap the logic

Basically, a POSIX signal is a messenger that helps processes communicate with each other. Of course, you are not supposed to communicate each thought you may have, so there is a list of predefined signals, each with a unique role.

The most known ones are SIGQUIT, SIGKILL, SIGTERM, SIGINT and so on, which are used to tell a process it must die.

SIGALRM instead, is a signal that is sent to who ever asks for it. And this happens after a countdown. It runs with a timer. Perl uses the alarm 10 feature to tell the system: "Yo, I am waiting for a wake-up call in 10 seconds. Start counting now!".

Meanwhile the script has the thread for himself, to use it as he see’s fit. When the timer ends, the sistem will send the SIGALRM call to the Perl process. %SIG is the special hash that Perl uses to store signals in. Just like %ENV is for environment variables.

And when the above script receives it, will trigger the generate_alarm() function, that increments the $buzz by one.

Luckily, Perl allows us to point references to functions. That’s why the mad guy who created the script, pointed the $SIG{ALRM}, to the one-line function. And that did the trick.

--

--

Adrian Dogar
0 Followers

Backend Dev, Sys Dev, Sys Ops, CD, Php, Perl, Python, Ant.