Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Alarm::Concurrent

by bbfu (Curate)
on May 04, 2001 at 01:30 UTC ( [id://77787]=perlcraft: print w/replies, xml ) Need Help??

   1: #
   2: # Third of a three part post of modules that implement
   3: # an extension of Perl's built-in alarm functionality.
   4: #
   5: # See [Alarm::_TieSIG] and [Alarm::Queued]
   6: # for the other two parts.
   7: #
   8: # Updated!  Fixed one or two minor issues and fixed
   9: # the documentation a bit.
  10: #
  11: package Alarm::Concurrent;
  12: 
  13: use strict;
  14: 
  15: =head1 NAME
  16: 
  17: Alarm::Concurrent - Allow multiple, concurrent alarms.
  18: 
  19: =head1 DESCRIPTION
  20: 
  21: This module is an attempt to enhance Perl's built-in
  22: alarm/C<$SIG{ALRM}> functionality.
  23: 
  24: This function, and its associated signal handler, allow you
  25: to arrange for your program to receive a SIGALRM signal,
  26: which you can then catch and deal with appropriately.
  27: 
  28: Unfortunately, due to the nature of the design of these
  29: signals (at the OS level), you can only have one alarm
  30: and handler active at any given time.  That's where this
  31: module comes in.
  32: 
  33: This module allows you to define multiple alarms, each
  34: with an associated handler.  These alarms are sequenced
  35: (in a queue) but concurrent, which means that their order
  36: is preserved but they always go off as their set time
  37: expires, regardless of the state of the other alarms.
  38: (If you'd like to have the alarms only go off in the order
  39: you set them, see Alarm::Queued.)
  40: 
  41: To set an alarm, call the C<setalarm()> function with the
  42: set time of the alarm and a reference to the subroutine
  43: to be called when the alarm goes off.  You can then go on
  44: with your program and the alarm will be called after the
  45: set time has passed.
  46: 
  47: It is also possible to set an alarm that does
  48: not have a handler associated with it using
  49: C<Alarm::Concurrent::alarm()>.  (This function can also
  50: be imported into your namespace, in which case it will
  51: replace Perl's built-in alarm for your package only.)
  52: 
  53: If an alarm that does not have a handler associated
  54: with it goes off, the default handler, pointed to by
  55: C<$Alarm::Concurrent::DEFAULT_HANLDER>, is called.  You can
  56: change the default handler by assigning to this variable.
  57: 
  58: The default C<$Alarm::Concurrent::DEFAULT_HANDLER> simply
  59: dies with the message "Alarm clock!\n".
  60: 
  61: =head1 IMPORT/EXPORT
  62: 
  63: No methods are exported by default but you can import any
  64: of the functions in the L<FUNCTIONS|"FUNCTIONS"> section.
  65: 
  66: You can also import the special tag C<:ALL> which will import
  67: all the functions in the L<FUNCTIONS|"FUNCTIONS"> section
  68: (except C<Alarm::Concurrent::restore()>).
  69: 
  70: =head1 OVERRIDE
  71: 
  72: If you import the special tag C<:OVERRIDE>, this module
  73: will override Perl's built-in alarm function for
  74: B<every namespace> and it will take over Perl's magic
  75: C<%SIG> variable, changing any attempts to read or
  76: write C<$SIG{ALRM}> into calls to C<gethandler()> and
  77: C<sethandler()>, respectively (reading and writing to
  78: other keys in C<%SIG> is unaffected).
  79: 
  80: This can be useful when you are calling code that tries to
  81: set its own alarm "the old fashioned way."  It can also,
  82: however, be dangerous.  Overriding alarm is documented
  83: and should be stable but taking over C<%SIG> is more risky
  84: (see L<CAVEATS|"CAVEATS">).
  85: 
  86: Note that if you do I<not> override alarm and
  87: C<%SIG>, any code you use that sets "legacy alarms"
  88: will disable all of your concurrent alarms.  You can
  89: call C<Alarm::Concurrent::restore()> to reinstall the
  90: Alarm::Concurrent handler.  This function can not be
  91: imported.
  92: 
  93: =cut
  94: 
  95: # In case they want to take over $SIG{ALRM}.
  96: use Alarm::_TieSIG;
  97: use Carp;
  98: 
  99: use Exporter;
 100: use vars qw( @ISA @EXPORT_OK %EXPORT_TAGS );
 101: @ISA = qw(Exporter);
 102: @EXPORT_OK = qw(
 103:   setalarm
 104:   clearalarm
 105:   alarm
 106:   sethandler
 107:   gethandler
 108: );
 109: %EXPORT_TAGS = (
 110:   ALL => [@EXPORT_OK],
 111: );
 112: 
 113: #
 114: # Exporter doesn't allow hooks for handling
 115: # special tags.  So, we have to do it ourselves.
 116: #
 117: sub import {
 118:   my $thispkg = shift;
 119: 
 120:   # Look for and remove special :OVERRIDE tag.
 121:   my $override = 0;
 122:   @_ = grep { ($_ eq ':OVERLOAD') ? ($override = 1, 0) : 1 } @_;
 123: 
 124:   if($override) {
 125:     $thispkg->export('CORE::GLOBAL', 'alarm');
 126:     Alarm::_TieSIG::tiesig(); # ALL YOUR %SIG ARE BELONG TO US!!!
 127:   };
 128: 
 129:   $thispkg->export_to_level(1, $thispkg, @_); # export the rest
 130: }
 131: 
 132: # Called for an alarm with no defined handler.
 133: sub _default_handler {
 134:   die "Alarm clock!\n";
 135: }
 136: 
 137: use vars '$DEFAULT_HANDLER';
 138: $DEFAULT_HANDLER = \&_default_handler; # Overeridable.
 139: 
 140: #
 141: # Each element of @ALARM_QUEUE should be a pointer
 142: # to an array containing exactly four elements:
 143: #
 144: #  0) The duration of the alarm in seconds
 145: #  1) The time at which the alarm was set
 146: #  2) A pointer to a subroutine that should be called
 147: #     when the alarm goes off.
 148: #
 149: use vars qw( @ALARM_QUEUE $ACTIVE_ALARM );
 150: @ALARM_QUEUE = ();
 151: $ACTIVE_ALARM = -1;
 152: 
 153: # Install our alarm handler. (& is to avoid prototype warning)
 154: &restore(1);
 155: 
 156: # Custom alarm handler.
 157: sub _alrm {
 158:   return unless(@ALARM_QUEUE);
 159: 
 160:   # Call handler for this alarm and remove it from the queue.
 161:   my $handler = splice(@ALARM_QUEUE, $ACTIVE_ALARM, 1)->[2];
 162:   $handler ||= $DEFAULT_HANDLER;
 163:   $handler->();
 164:   $ACTIVE_ALARM = -1;
 165: 
 166:   # Have to use a C-style loop because we modify
 167:   # the index variable inside the loop. :-(
 168:   for(my $i = 0; $i < @ALARM_QUEUE; ++$i) {
 169:     my $time_remaining = $ALARM_QUEUE[$i][1]+$ALARM_QUEUE[$i][0]-time;
 170: 
 171:     my $active_time;
 172:     $active_time  = $ALARM_QUEUE[$ACTIVE_ALARM][1];
 173:     $active_time += $ALARM_QUEUE[$ACTIVE_ALARM][0];
 174:     $active_time -= time;
 175: 
 176:     if($time_remaining <= 0) {
 177: 
 178:       # Note the -- on $i.  When we splice out an alarm, all the ones
 179:       # after it shift down by one.  We have to account for this.
 180:       $handler = splice(@ALARM_QUEUE, $i--, 1)->[2] || $DEFAULT_HANDLER;
 181:       $handler->(); # Call handler for this alarm.
 182: 
 183:     } elsif($ACTIVE_ALARM == -1 or $time_remaining < $active_time) {
 184:       $ACTIVE_ALARM = $i;
 185:       CORE::alarm($time_remaining);
 186:     }
 187:   }
 188: }
 189: 
 190: 
 191: #********************************************************************#
 192: 
 193: =head1 FUNCTIONS
 194: 
 195: The following functions are available for use.
 196: 
 197: =over 4
 198: 
 199: =item setalarm SECONDS CODEREF
 200: 
 201: Sets a new alarm and associates a handler with it.
 202: The handler is called when the specified number of seconds
 203: have elapsed.  See L<DESCRIPTION|"DESCRIPTION"> for more
 204: information.
 205: 
 206: =cut
 207: sub setalarm($$) {
 208:   my ($alarm, $code) = @_;
 209: 
 210:   unless(not defined($code) or UNIVERSAL::isa($code, 'CODE')) {
 211:     croak("Alarm handler must be CODEREF");
 212:   }
 213: 
 214:   push( @ALARM_QUEUE, [ $alarm, time(), $code ] );
 215: 
 216:   my $time_remaining;
 217:   $time_remaining  = $ALARM_QUEUE[$ACTIVE_ALARM][1];
 218:   $time_remaining += $ALARM_QUEUE[$ACTIVE_ALARM][0];
 219:   $time_remaining -= time;
 220: 
 221:   if($ACTIVE_ALARM == -1 or $alarm < $time_remaining) {
 222:     $ACTIVE_ALARM = $#ALARM_QUEUE;
 223:     CORE::alarm($alarm);
 224:   }
 225: }
 226: 
 227: =item clearalarm INDEX LENGTH
 228: 
 229: =item clearalarm INDEX
 230: 
 231: =item clearalarm
 232: 
 233: Clears one or more previously set alarms.  The index is
 234: an array index, with 0 being the currently active alarm
 235: and -1 being the last (most recent) alarm that was set.
 236: 
 237: INDEX defaults to 0 and LENGTH defaults to 1.
 238: 
 239: =cut
 240: sub clearalarm(;$$) {
 241:   my $index  = shift || 0;
 242:   my $length = shift || 1;
 243: 
 244:   splice @ALARM_QUEUE, $index, $length;
 245:   if(($index < 0 ? $#ALARM_QUEUE+$index : $index) == $ACTIVE_ALARM) {
 246:     $ACTIVE_ALARM = -1;
 247:     CORE::alarm(0);
 248:   }
 249: 
 250:   # Have to use a C-style loop because we modify the index
 251:   # variable inside the loop. :-(
 252:   for(my $i = 0; $i < @ALARM_QUEUE; ++$i) {
 253:     my $time_remaining = $ALARM_QUEUE[$i][1]+$ALARM_QUEUE[$i][0]-time;
 254: 
 255:     my $active_time;
 256:     $active_time  = $ALARM_QUEUE[$ACTIVE_ALARM][1];
 257:     $active_time += $ALARM_QUEUE[$ACTIVE_ALARM][0];
 258:     $active_time -= time;
 259: 
 260:     if($time_remaining <= 0) {
 261:       # Note the -- on $i.  When we splice out an alarm, all the ones
 262:       # after it shift down by one.  We have to account for this.
 263:       my $handler = splice(@ALARM_QUEUE,$i--,1)->[2];
 264:       $handler ||= $DEFAULT_HANDLER;
 265:       $handler->(); # Call handler for this alarm.
 266:     } elsif($ACTIVE_ALARM == -1 or $time_remaining < $active_time) {
 267:       $ACTIVE_ALARM = $i;
 268:       CORE::alarm($time_remaining);
 269:     }
 270:   }
 271: }
 272: 
 273: =item alarm SECONDS
 274: 
 275: =item alarm
 276: 
 277: Creates a new alarm with no handler.  A handler can
 278: later be set for it via sethandler() or C<$SIG{ALRM}>,
 279: if overridden.
 280: 
 281: For the most part, this function behaves exactly like
 282: Perl's built-in alarm function, except that it sets up a
 283: concurrent alarm instead.  Thus, each call to alarm does
 284: not disable previous alarms unless called with a set time
 285: of 0.
 286: 
 287: Calling C<alarm()> with a set time of 0 will disable the
 288: last alarm set.
 289: 
 290: If SECONDS is not specified, the value stored in C<$_>
 291: is used.
 292: 
 293: =cut
 294: sub alarm(;$) {
 295:   my $alarm = @_ ? shift : $_;
 296: 
 297:   if($alarm == 0) {
 298:     clearalarm(-1);
 299:   } else {
 300:     push( @ALARM_QUEUE, [ $alarm, time(), undef ] );
 301:     
 302:     my $time_remaining;
 303:     $time_remaining  = $ALARM_QUEUE[$ACTIVE_ALARM][1];
 304:     $time_remaining += $ALARM_QUEUE[$ACTIVE_ALARM][0];
 305:     $time_remaining -= time;
 306: 
 307:     if($ACTIVE_ALARM == -1 or $alarm < $time_remaining) {
 308:       $ACTIVE_ALARM = $#ALARM_QUEUE;
 309:       CORE::alarm($alarm);
 310:     }
 311:   }
 312: }
 313: 
 314: =item sethandler INDEX CODEREF
 315: 
 316: =item sethandler CODEREF
 317: 
 318: Sets a handler for the alarm found at INDEX in the queue.
 319: This is an array index, so negative values may be used to
 320: indicate position relative to the end of the queue.
 321: 
 322: If INDEX is not specified, the handler is set for the
 323: last alarm in the queue that doesn't have one associated
 324: with it.  This means that if you set multiple alarms
 325: using C<alarm()>, you should arrange their respective
 326: C<sethandler()>'s in the I<opposite> order.
 327: 
 328: =cut
 329: sub sethandler($;$) {
 330: 
 331:   unless(not defined($_[-1]) or UNIVERSAL::isa($_[-1], 'CODE')) {
 332:     croak("Alarm handler must be CODEREF");
 333:   }
 334: 
 335:   if(@_ == 2) {
 336:     $ALARM_QUEUE[$_[0]]->[2] = $_[1];
 337:   } else {
 338:     foreach my $alarm (reverse @ALARM_QUEUE) {
 339:       if(not defined $alarm->[2]) {
 340:         $alarm->[2] = shift();
 341:         last;
 342:       }
 343:     }
 344:   }
 345: }
 346: 
 347: =item gethandler INDEX
 348: 
 349: =item gethandler
 350: 
 351: Returns the handler for the alarm found at INDEX in the queue.
 352: This is an array index, so negative values may be used.
 353: 
 354: If INDEX is not specified, returns the handler for the currently
 355: active alarm.
 356: 
 357: =cut
 358: sub gethandler(;$) {
 359:   my $index = shift || $ACTIVE_ALARM;
 360:   return(
 361:     ($index < @ALARM_QUEUE and $index > -1)
 362:       ?
 363:     $ALARM_QUEUE[$index][2]
 364:       :
 365:     undef
 366:   );
 367: }
 368: 
 369: =item restore FLAG
 370: 
 371: =item restore
 372: 
 373: This function reinstalls the Alarm::Concurrent alarm handler
 374: if it has been replaced by a "legacy alarm handler."
 375: 
 376: If FLAG is present and true, C<restore()> will save the
 377: current handler by setting it as a new concurrent alarm (as
 378: if you had called C<setalarm()> for it).
 379: 
 380: This function may not be imported.
 381: 
 382: Note:  Do B<not> call this function if you have imported
 383: the C<:OVERLOAD> symbol.  It can have unpredictable results.
 384: 
 385: =cut
 386: sub restore(;$) {
 387:   return if(defined($SIG{ALRM}) and $SIG{ALRM} == \&_alrm);
 388: 
 389:   my $oldalrm = CORE::alarm(0);
 390: 
 391:   if($oldalrm and shift) {
 392:     # Save legacy alarm.
 393:     setalarm($oldalrm, $SIG{ALRM});
 394:   }
 395: 
 396:   # Install our alarm handler.
 397:   $SIG{ALRM} = \&_alrm;
 398: }
 399: 
 400: =head1 CAVEATS
 401: 
 402: =over 4
 403: 
 404: =item *
 405: 
 406: C<%SIG> is Perl magic and should probably not be messed
 407: with, though I have not witnessed any problems in the
 408: (admittedly limited) testing I've done.  I would be
 409: interested to hear from anyone who performs extensive
 410: testing, with different versions of Perl, of the
 411: reliability of doing this.
 412: 
 413: Moreover, since there is no way to just take over
 414: C<$SIG{ALRM}>, the entire magic hash is usurped and any
 415: other C<%SIG}> accesses are simply passed through to the
 416: original magic hash.  This means that if there I<are> any
 417: problems, they will most likely affect all other signal
 418: handlers you have defined, including C<$SIG{__WARN__}>
 419: and C<$SIG{__DIE__}> and others.
 420: 
 421: In other words, if you're going to use the C<:OVERRIDE>
 422: option, you do so at your own risk (and you'd better be
 423: pretty damn sure of yourself, too).
 424: 
 425: =item *
 426: 
 427: The default C<$DEFAULT_HANDLER> simply dies with the
 428: message "Alarm clock!\n".
 429: 
 430: =item *
 431: 
 432: All warnings about alarms possibly being off by up to a full
 433: second still apply.  See the documentation for alarm for more
 434: information.
 435: 
 436: =item *
 437: 
 438: The alarm handling routine does not make any allowances
 439: for systems that clear the alarm handler before it is
 440: called.  This may be changed in the future.
 441: 
 442: =item *
 443: 
 444: According to L<perlipc/"Signals">, doing just about I<anything>
 445: in signal handling routines is dangerous because it might
 446: be called during a non-re-entrant system library routines
 447: which could cause a memory fault and core dump.
 448: 
 449: The Alarm::Concurrent alarm handling routine does quite a bit.
 450: 
 451: You have been warned.
 452: 
 453: =back
 454: 
 455: =head1 AUTHOR
 456: 
 457: Written by Cory Johns (c) 2001.
 458: 
 459: =cut
 460: 
 461: 1;

Replies are listed 'Best First'.
(bbfu) Test/Demonstration script for Alarm::Concurrent
by bbfu (Curate) on May 04, 2001 at 01:35 UTC

    And so you can see it in action, here's a test/demonstration script for this module.

    #!/usr/bin/perl -w # # Test of Alarm::Concurrent - Written by Cory Johns (c) 2001 # ++$|; BEGIN { # This will be saved and activated when # Alarm::Concurrent is imported. alarm(1); $SIG{ALRM} = sub { print "Legacy alarm." }; } use Alarm::Concurrent qw( :OVERLOAD :ALL ); setalarm(6, sub { print "BONG!!!" }); alarm(2); sethandler(sub { print "bing" }); MyPack::doalarm(); # Alarms set for the same time go # off in the order you set them. setalarm(4, sub { print "!" }); package MyPack; sub doalarm { # Alarm is overridden in _all_ namespaces. alarm(4); # $SIG{ALRM} has been taken over. # It now calls sethandler(). $SIG{ALRM} = sub { print "bong" }; } #*******************************************# package main; for(1..6) { print "Second $_... "; sleep 1; print "\n"; }

    bbfu
    Seasons don't fear The Reaper.
    Nor do the wind, the sun, and the rain.
    We can be like they are.

Re: Alarm::Concurrent
by Starky (Chaplain) on May 31, 2001 at 08:58 UTC
    Well kibble my bits and call me red, but a developer I work with on an open source project (Peep: The Network Auralizer) just implemented about the same thing for a dire need we had. This is something that when you need, you really need.

    Are you planning on submitting your Alarm::* packages to the CPAN? You should.

    One additional feature that I implemented in his code (which you may find useful to implement) is to have repeated events triggered; that is, if you need to have something go off every 5 seconds rather than just once 5 seconds from now, you can do that.

    The way I did it was rather cheesy, but works: Each handler has a callback (typically a closure) and some associated data structure with metadata about the callback. If it is a repeated event, the handler just reschedules itself before it invokes the callback.

    I really enjoyed reading your submission!

    -Collin

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (4)
As of 2024-04-25 16:43 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found