Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add Reflex::Role::SigCatcher and Reflex::SigCatcher to reify it.

  • Loading branch information...
commit 51d8dc11fc83733843d9c5d99c0e5766cdf52ffa 1 parent 53ab166
Rocco Caputo authored
8 eg/eg-39-signals.pl
@@ -4,18 +4,18 @@
4 4 use strict;
5 5 use lib qw(../lib);
6 6
7   -use Reflex::Signal;
  7 +use Reflex::SigCatcher;
8 8 use Reflex::Callbacks qw(cb_coderef);
9 9 use ExampleHelpers qw(eg_say);
10 10
11 11 eg_say("Process $$ is waiting for SIGUSR1 and SIGUSR2.");
12 12
13   -my $usr1 = Reflex::Signal->new(
14   - name => "USR1",
  13 +my $usr1 = Reflex::SigCatcher->new(
  14 + signal => "USR1",
15 15 on_signal => cb_coderef { eg_say("Got SIGUSR1.") },
16 16 );
17 17
18   -my $usr2 = Reflex::Signal->new( name => "USR2" );
  18 +my $usr2 = Reflex::SigCatcher->new( signal => "USR2" );
19 19 while ($usr2->next()) {
20 20 eg_say("Got SIGUSR2.");
21 21 }
3  lib/Reflex/Role/Connecting.pm
@@ -40,6 +40,9 @@ role {
40 40 handle => $socket,
41 41 };
42 42
  43 + # Work around a Moose edge case.
  44 + sub BUILD {}
  45 +
43 46 after BUILD => sub {
44 47 my ($self, $args) = @_;
45 48
2  lib/Reflex/Role/Reactive.pm
@@ -64,7 +64,7 @@ my $singleton_session_id = POE::Session->create(
64 64 ### Signals.
65 65
66 66 signal_happened => sub {
67   - Reflex::Signal->deliver(@_[ARG0..$#_]);
  67 + Reflex::Role::SigCatcher->deliver(@_[ARG0..$#_]);
68 68 },
69 69
70 70 ### Cross-session emit() is converted into these events.
6 lib/Reflex/Role/Readable.pm
@@ -64,11 +64,17 @@ role {
64 64 $POE::Kernel::poe_kernel->select_read($self->$h(), undef);
65 65 };
66 66
  67 + # Work around a Moose edge case.
  68 + sub BUILD {}
  69 +
67 70 after BUILD => sub {
68 71 my ($self, $arg) = @_;
69 72 $self->$setup_name($arg);
70 73 };
71 74
  75 + # Work around a Moose edge case.
  76 + sub DEMOLISH {}
  77 +
72 78 # Turn off watcher during destruction.
73 79 after DEMOLISH => sub {
74 80 my $self = shift;
250 lib/Reflex/Role/SigCatcher.pm
... ... @@ -0,0 +1,250 @@
  1 +package Reflex::Role::SigCatcher;
  2 +use MooseX::Role::Parameterized;
  3 +use Reflex::Util::Methods qw(method_name emit_an_event);
  4 +
  5 +use Scalar::Util qw(weaken);
  6 +
  7 +parameter signal => (
  8 + isa => 'Str',
  9 + default => 'signal',
  10 +);
  11 +
  12 +parameter active => (
  13 + isa => 'Str',
  14 + default => 'active',
  15 +);
  16 +
  17 +parameter cb_signal => method_name("on", "signal", "caught");
  18 +parameter method_start => method_name("start", "signal", undef);
  19 +parameter method_stop => method_name("stop", "signal", undef);
  20 +parameter method_pause => method_name("pause", "signal", undef);
  21 +parameter method_resume => method_name("resume", "signal", undef);
  22 +
  23 +# A session may only watch a distinct signal once.
  24 +# So we must map each distinct signal to all the interested objects.
  25 +# This is class scoped data.
  26 +#
  27 +# TODO - We could put this closer to the POE::Session and obviate the
  28 +# need for the deliver() redirector.
  29 +
  30 +my %callbacks;
  31 +my %signal_param_names;
  32 +
  33 +sub _register_signal_params {
  34 + my ($class, @names) = @_;
  35 + $signal_param_names{$class->meta->get_attribute("signal")->default()} = \@names;
  36 +}
  37 +
  38 +sub deliver {
  39 + my ($class, $signal_name, @signal_args) = @_;
  40 +
  41 + # If nobody's watching us, then why did we do it in the road?
  42 + # TODO - Diagnostic warning/error?
  43 + return unless exists $callbacks{$signal_name};
  44 +
  45 + # Calculate the event arguments based on the signal name.
  46 + my %event_args = ( signal => $signal_name );
  47 + if (exists $signal_param_names{$signal_name}) {
  48 + my $i = 0;
  49 + %event_args = (
  50 + map { $_ => $signal_args[$i++] }
  51 + @{$signal_param_names{$signal_name}}
  52 + );
  53 + }
  54 +
  55 + # Deliver the signal.
  56 + # TODO - map() magic to speed this up?
  57 +
  58 + foreach my $callback_recs (values %{$callbacks{$signal_name}}) {
  59 + foreach my $callback_rec (values %$callback_recs) {
  60 + my ($object, $method) = @$callback_rec;
  61 + $object->$method(\%event_args);
  62 + }
  63 + }
  64 +}
  65 +
  66 +# The role itself.
  67 +
  68 +role {
  69 + my $p = shift;
  70 +
  71 + my $signal = $p->signal();
  72 + my $active = $p->active();
  73 + my $cb_signal = $p->cb_signal();
  74 +
  75 + my $method_start = $p->method_start();
  76 + my $method_stop = $p->method_stop();
  77 + my $method_pause = $p->method_pause();
  78 + my $method_resume = $p->method_resume();
  79 +
  80 + # Work around a Moose edge case.
  81 + sub BUILD {};
  82 +
  83 + after BUILD => sub {
  84 + return unless $active;
  85 + shift()->$method_start();
  86 + return;
  87 + };
  88 +
  89 + method $method_start => sub {
  90 + my $self = shift;
  91 +
  92 + my $sig_name = $self->$signal();
  93 +
  94 + # Register this object with that signal.
  95 + $callbacks{$sig_name}->{$self->session_id()}->{$self} = [
  96 + $self, $cb_signal
  97 + ];
  98 + weaken $callbacks{$sig_name}->{$self->session_id()}->{$self}->[0];
  99 +
  100 + # First time this object is watching that signal? Start the
  101 + # watcher. Otherwise, a watcher should already be going.
  102 +
  103 + return if (
  104 + (scalar keys %{$callbacks{$sig_name}->{$self->session_id()}}) > 1
  105 + );
  106 +
  107 + $self->$method_resume();
  108 + };
  109 +
  110 + method $method_pause => sub {
  111 + my $self = shift;
  112 +
  113 + # Be in the session associated with this object.
  114 + return unless $self->call_gate($method_pause);
  115 +
  116 + $POE::Kernel::poe_kernel->sig($self->$signal(), undef);
  117 + };
  118 +
  119 + method $method_resume => sub {
  120 + my $self = shift;
  121 +
  122 + # Be in the session associated with this object.
  123 + return unless $self->call_gate($method_resume);
  124 +
  125 + $POE::Kernel::poe_kernel->sig($self->$signal(), "signal_happened");
  126 + };
  127 +
  128 + method $method_stop => sub {
  129 + my $self = shift;
  130 +
  131 + my $sig_name = $self->$signal();
  132 +
  133 + # Unregister this object with that signal.
  134 + my $sw = $callbacks{$sig_name}->{$self->session_id()};
  135 + delete $sw->{$self};
  136 +
  137 + # Deactivate the signal watcher if this was the last object.
  138 + unless (scalar keys %$sw) {
  139 + delete $callbacks{$sig_name}->{$self->session_id()};
  140 +
  141 + delete $callbacks{$sig_name} unless (
  142 + scalar keys %{$callbacks{$sig_name}}
  143 + );
  144 +
  145 + $self->$method_pause();
  146 + }
  147 + };
  148 +
  149 + method $cb_signal => emit_an_event("signal");
  150 +};
  151 +
  152 +__END__
  153 +
  154 +
  155 +sub stop_watching {
  156 +}
  157 +
  158 +
  159 +sub DEMOLISH {
  160 + my $self = shift;
  161 +
  162 + return unless defined $self->name();
  163 +
  164 + my $sw = $session_watchers{$self->name()}->{$self->session_id()};
  165 + delete $sw->{$self};
  166 +
  167 + unless (scalar keys %$sw) {
  168 + delete $session_watchers{$self->name()};
  169 +
  170 + delete $session_watchers{$self->name()} unless (
  171 + scalar keys %{$session_watchers{$self->name()}}
  172 + );
  173 +
  174 + $self->stop_watching();
  175 + }
  176 +}
  177 +
  178 +1;
  179 +
  180 +__END__
  181 +
  182 +=head1 NAME
  183 +
  184 +Reflex::Signal - Generic signal watcher and base class for specific ones.
  185 +
  186 +=head1 SYNOPSIS
  187 +
  188 +As a callback:
  189 +
  190 + use Reflex::Signal;
  191 + use Reflex::Callbacks qw(cb_coderef);
  192 +
  193 + my $usr1 = Reflex::Signal->new(
  194 + name => "USR1",
  195 + on_signal => cb_coderef { print "Got SIGUSR1.\n" },
  196 + );
  197 +
  198 +As a promise:
  199 +
  200 + my $usr2 = Reflex::Signal->new( name => "USR2" );
  201 + while ($usr2->next()) {
  202 + print "Got SIGUSR2.\n";
  203 + }
  204 +
  205 +May also be used with watchers, and Reflex::Trait::Observed, but
  206 +those use cases aren't shown here.
  207 +
  208 +=head1 DESCRIPTION
  209 +
  210 +Reflex::Signal is a general signal watcher. It may be used to notify
  211 +programs when they are sent a signal via kill.
  212 +
  213 +=head2 Public Attributes
  214 +
  215 +=head3 name
  216 +
  217 +"name" defines the name (or number) of an interesting signal.
  218 +The Reflex::Signal object will emit events when it detects that the
  219 +process has been given that signal.
  220 +
  221 +=head2 Public Methods
  222 +
  223 +None at this time. Destroy the object to stop it.
  224 +
  225 +=head2 Public Events
  226 +
  227 +Reflex::Signal and its subclasses emit just one event: "signal".
  228 +Generic signals have no additional information, but specific ones may.
  229 +For example, Reflex::PID (SIGCHLD) includes a process ID and
  230 +information about its exit.
  231 +
  232 +=head1 SEE ALSO
  233 +
  234 +L<Moose::Manual::Concepts>
  235 +
  236 +L<Reflex>
  237 +L<Reflex::PID>
  238 +L<Reflex::POE::Wheel::Run>
  239 +
  240 +L<Reflex/ACKNOWLEDGEMENTS>
  241 +L<Reflex/ASSISTANCE>
  242 +L<Reflex/AUTHORS>
  243 +L<Reflex/BUGS>
  244 +L<Reflex/BUGS>
  245 +L<Reflex/CONTRIBUTORS>
  246 +L<Reflex/COPYRIGHT>
  247 +L<Reflex/LICENSE>
  248 +L<Reflex/TODO>
  249 +
  250 +=cut
6 lib/Reflex/Role/Writable.pm
@@ -67,11 +67,17 @@ role {
67 67 $POE::Kernel::poe_kernel->select_write($self->$h(), undef);
68 68 };
69 69
  70 + # Work around a Moose edge case.
  71 + sub BUILD {}
  72 +
70 73 after BUILD => sub {
71 74 my ($self, $arg) = @_;
72 75 $self->$method_start() if $active;
73 76 };
74 77
  78 + # Work around a Moose edge case.
  79 + sub DEMOLISH {}
  80 +
75 81 # Turn off watcher during destruction.
76 82 after DEMOLISH => sub {
77 83 my $self = shift;

0 comments on commit 51d8dc1

Please sign in to comment.
Something went wrong with that request. Please try again.