NAME
IO::SocketAlarm - Perform asynchronous actions when a socket changes status
SYNOPSIS
When the client goes away, send SIGALRM
use IO::SocketAlarm qw( socketalarm );
...
unless (eval {
local $SIG{ALRM}= sub { die "got alarm"; };
my $alarm= socketalarm($socket);
long_running_function();
$alarm->cancel; # stop watching socket
1;
}) {
warn "Interrupted long_running_task" if $@ =~ /^got alarm/;
...
}
In a more extreme example, when HTTP client goes away, terminate the current worker process and also kill the mysql query
# get MySQL connection ID
$con_id= $dbh->selectcol_arrayref("SELECT CONNECTION_ID()")->[0];
# If $socket closes, kill this whole worker, and also kill
# the MySQL query from the server-side.
$alarm= socketalarm $s, [ exec => 'mysql','-e',"kill $con_id" ];
DESCRIPTION
Sometimes you have a blocking system call, or blocking library, and it prevents you from checking whether the initiator of this request (like a http client) is still waiting for the answer. The right way to solve the problem is an event loop, where you are waiting both for the long-tunning task and also watching for events on the client connection and your program can respond to either of them. Perl has several great event loops, like Mojo::IOLoop, AnyEvent, or IO::Async with which you can build a properly engineered solution. But... if you don't have the luxury of refactoring your whole project to be event-driven, and you'd really just like a way to kill the current HTTP worker when the client is lost and you're blocking in a long-running database call, this module is for you.
This module operates by creating a second C-level thread (regardless of whether your perl was compiled with threading support) and having that thread monitor the status of your socket.
First caveat: The background thread is limited in the types of actions it can take. For example, you definitely can't run perl code in response to the status change, but it can send a signal to the main thread, or other process-global actions like completely exiting and executing mysql -e "kill $conn_id"
.
Second caveat: This module's design isn't 100% portable beyond Linux and FreeBSD. On Windows, MacOS, and OpenBSD there is no way (that I've found) to poll for TCP 'FIN' status. This module will probably still work for a HTTP worker behind a reverse proxy; see EVENT_EOF.
Third caveat: While the module is thread-safe, per-se, it does introduce the sorts of confusion caused by concurrency, like checking $alarm->triggered
and having that status change before the very next line of code in your script.
Fourth caveat: The signals you send to yourself won't take effect until control returns to the perl interpreter. If you are blocking in a C library or XS, it might be that the only way to wake it up is to close the file handles it is using. For DBD::mysql and libmysql, that doesn't even work because of mysql_auto_reconnect, and besides which, mysql servers don't notice that clients are gone until the current query ends. Stopping a long-running mysql query can (seemingly) only be accomplished by running SQL on the server.
EXPORTS
This module exports everything from IO::SocketAlarm::Util. Of particular note:
socketalarm
$alarm= socketalarm($socket); # sends SIGALRM when EVENT_SHUT
$alarm= socketalarm($socket, @actions);
$alarm= socketalarm($socket, $event_mask, @actions);
This creates a new alarm on $socket
, waiting for $event_mask
to occur, and if it does, a background thread will run @actions
. It is a shortcut for new as follows:
$alarm= IO::SocketAlarm->new(
socket => $socket,
events => $event_mask,
actions => \@actions,
);
$alarm->start;
ALARM OBJECT
An Alarm object represents the scope of the alarm. You can undefine it or call $alarm->cancel
to disable the alarm, but beware that you might have a race condition between letting it go out of scope and letting your local signal handler go out of scope, so use the same precautions that you would use when using alarm()
.
When triggered, the alarm only runs its actions once.
Constructor
new
$alarm= IO::SocketAlarm->new(%attributes);
Accepts attributes 'socket', 'events', and 'actions'. Note that actions
will get translated a bit from how you specify them to what you see in the attribute afterward.
Attributes
socket
The $socket
must be an operating system level socket (having a 'fileno', as opposed to a Perl virtual handle of some sort), and still be open.
events
This is a bit-mask of which events to trigger on. Combine them with the bitwise-or operator:
# the default on Linux/FreeBSD:
events => EVENT_SHUT,
# the default on Windows/Mac/OpenBSD
events => EVENT_SHUT|EVENT_EOF,
actions
# the default:
actions => [ [ sig => SIGALRM ] ],
The @actions
are an array of one or more action specifications. When the $events
are detected, this list will be performed in sequence. The actions are described as simple lisp-like arrayrefs. (you can't just specify a coderef for an action because they run in a separate C thread that isn't able to touch the perl interpreter.)
The available actions are:
- sig
-
[ sig => $signal ],
Send yourself a signal. The signal constants come from
use POSIX ':signal_h';
. - kill
-
[ kill => $signal, $pid ],
Send a signal to any process. Note the order of arguments: this is the same as Perl and bash, but the opposite of the C library, and a mixup can be bad!
- close
-
[ close => 5, ... ] [ close => $fh, ... ] [ close => pack_sockaddr_in($port, inet_aton("localhost")), ... ]
Close one or more file descriptors or socket names. This could have uses like killing database connections when you know the file handle number or host:port of the database server.
If the parameter is an integer, it is assumed to be a raw file descriptor number like you get from
fileno
. If the parameter is an IO::Handle, it callsfileno
for you, and croaks if that handle isn't backed by a real file descriptor. The parameter can also be a byte string as per thegetpeername
orpack_sockaddr_in
functions; in this case all sockets connected to that peer name will be closed. - shut_r, shut_w, shut_rw
-
[ shut_r => $fd_or_sockname, ... ], [ shut_w => $fd_or_sockname, ... ], [ shut_rw => $fd_or_sockname, ... ],
Like
close
, but instead of callingclose(fd)
it calls the socket functionshutdown(fd, $how)
where$how
is one ofSHUT_RD
,SHUT_RW
,SHUT_RDWR
. This leaves the socket open, but causes reads or writes to fail, which may give a more graceful cancellation of whatever was happening over that socket. - run
-
[ run => @argv ],
Fork (twice) and exec an external program. The program shares your STDOUT and STDERR, but is connected to /dev/null on STDIN. The double-fork (and reap of first forked child) allows the (grand)child process to run independently from the current process, and get reaped by
init
, and not tangle up whatever you might be doing withwaitpid
. If theexec
fails, it is reported onSTDERR
, but the current process has no way to inspect the outcome of theexec
or the exit status of the program it runs. - exec
-
[ exec => @argv ],
Replace the current running process with a different process, just like
exec
. This completely aborts the main perl script and loses any work, without calling 'atexit' or any other cleanup your perl script might have intended to do. Sometimes, this is what you want, though. This can fail if$argv[0]
isn't found in the PATH, in which case your program just immediatelyexit
s. - sleep
-
[ sleep => $seconds ],
Wait before running the next action.
action_count
Shortcut for scalar @actions
, but avoids inflating the arrayref of actions.
cur_action
Returns -1 if the alarm is not yet triggered, else the number of the action being executed, ending with the integer beyond the max element of "actions". Note that by the time your script reads this attribute, it may already have changed.
triggered
Shortcut for $cur_action >= 0
finished
Shortcut for $cur_action > $#actions
Methods
start
Begin listening for the alarm events. Returns a boolean of whether the alarm was inactive prior to this call. (i.e. whether the call changed the state of the alarm)
cancel
Stop listening for the alarm events. Returns a boolean of whether the alarm was active prior to this call. (i.e. whether the call changed the state of the alarm)
stringify
Render the alarm as user-readable text, for diagnosis and logging.
VERSION
version 0.002
AUTHOR
Michael Conrad <mike@nrdvana.net>
COPYRIGHT AND LICENSE
This software is copyright (c) 2024 by IntelliTree Solutions.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.