File Coverage

blib/lib/AnyEvent/Blackboard.pm
Criterion Covered Total %
statement 45 55 81.8
branch 9 16 56.2
condition 2 5 40.0
subroutine 11 12 91.6
pod 5 5 100.0
total 72 93 77.4


line stmt bran cond sub pod time code
1             package AnyEvent::Blackboard;
2              
3             =head1 NAME
4              
5             AnyEvent::Blackboard - An extension of Async::Blackboard which uses AnyEvent
6             for timeouts.
7              
8             =head1 SYNOPSIS
9              
10             my $blackboard = AnyEvent::Blackboard->new();
11              
12             $blackboard->watch([qw( foo bar )], [ $object, "found_foobar" ]);
13             $blackboard->watch(foo => [ $object, "found_foo" ]);
14              
15             # After 250ms, provide ``undef'' for ``foo''
16             $blackboard->timeout(foo => 0.25);
17              
18             =head1 RATIONALE
19              
20             Async::Blackboard makes a fantastic synchronization component -- however, it
21             does have the possible condition of allowing control to be abandoned due to a
22             lack value. This subclass adds the functionality of timeouts on keys to ensure
23             this doesn't happen.
24              
25             =cut
26              
27 1     1   27986 use strict;
  1         2  
  1         46  
28 1     1   6 use warnings FATAL => "all";
  1         2  
  1         47  
29              
30 1     1   1607 use AnyEvent;
  1         5854  
  1         40  
31 1     1   980 use parent qw( Async::Blackboard );
  1         365  
  1         5  
32 1     1   2559 use Carp qw( croak confess );
  1         2  
  1         632  
33              
34             our $VERSION = "0.4.10";
35              
36             =head1 ATTRIBUTES
37              
38             =over 4
39              
40             =cut
41              
42             =item default_timeout -> Num
43              
44             Default timeout in (optionally fractional) seconds.
45              
46             =cut
47              
48             =item condvar -> AnyEvent::CondVar
49              
50             A conditional variable to track dispatches. (optional)
51              
52             When supplied, each dispatch group will be wrapped in calls to ``begin'' and
53             ``end'' on condvar instance.
54              
55             =cut
56              
57             sub new {
58 7     7 1 4541 my ($class, @arguments) = @_;
59              
60 7 50       32 if (@arguments % 2) {
61 0         0 croak "AnyEvent::Blackboard->new() requires a balanced list";
62             }
63              
64 7         19 my %options = @arguments;
65              
66 7         39 my $self = $class->SUPER::new();
67              
68 7         100 @$self{qw( -default_timeout -condvar )} =
69             @options{qw( default_timeout condvar )};
70              
71 7   33     316 $self->{-condvar} //= AnyEvent->condvar;
72              
73 7         1729 return $self;
74             }
75              
76             =back
77              
78             =cut
79              
80             =back
81              
82             =head1 METHODS
83              
84             =over 4
85              
86             =item timeout SECONDS, [ KEY, [, DEFAULT ] ]
87              
88             Set a timer for N seconds to provide "default" value as a value, defaults to
89             `undef`. This can be used to ensure that blackboard workflows do not reach a
90             dead-end if a required value is difficult to obtain.
91              
92             =cut
93              
94             sub timeout {
95 4     4 1 91 my ($self, $seconds, $key, $default) = @_;
96              
97 4 100       20 $key = [ $key ] unless (ref $key eq "ARRAY");
98              
99 4 50       24 unless ($self->has($key)) {
100             my $guard = AnyEvent->timer(
101             after => $seconds,
102             cb => sub {
103 2 50   2   7305 unless ($self->has($key)) {
104 2         46 $self->put($_ => $default) for @$key;
105             }
106             }
107 4         67 );
108              
109             # Cancel the timer if we find the object first (otherwise this is a NOOP).
110 4     3   897 $self->_watch($key, sub { undef $guard });
  3         206  
111             }
112             }
113              
114             =item watch KEYS, WATCHER [, KEYS, WATCHER ]
115              
116             =item watch KEY, WATCHER [, KEYS, WATCHER ]
117              
118             Overrides L only for the purpose of adding a timeout.
119              
120             =cut
121              
122             sub watch {
123 4     4 1 565 my ($self, @args) = @_;
124              
125 4 50       13 confess "Expected balanced as arguments" if @args % 2;
126              
127 4         14 my $timeout = $self->{-default_timeout};
128              
129 4 100       10 if ($timeout) {
130 2         5 my $i = 0;
131              
132 2         7 for my $key (grep $i++ % 2 == 0, @args) {
133 2         10 $self->timeout($timeout, $key);
134             }
135             }
136              
137 4         146 $self->SUPER::watch(@args);
138             }
139              
140             =item found KEY
141              
142             Wrap calls to ``found'' in condvar transaction counting, if a condvar is
143             supplied. The side-effect is that dispatching is wrapped in conditional
144             variable counting.
145              
146             =cut
147              
148             sub found {
149 0     0 1 0 my ($self, @args) = @_;
150              
151 0 0       0 if ($self->has_condvar) {
152 0         0 my $condvar = $self->condvar;
153              
154 0         0 $condvar->begin;
155              
156 0         0 $self->SUPER::found(@args);
157              
158 0         0 $condvar->end;
159             }
160             else {
161 0         0 $self->SUPER::found(@args);
162             }
163             }
164              
165             =item clone
166              
167             Create a clone of this blackboard. This will not dispatch any events, even if
168             the blackboard is prepopulated.
169              
170             =cut
171              
172             sub clone {
173 1     1 1 35 my ($self) = @_;
174              
175 1   50     8 my $class = ref $self || __PACKAGE__;
176              
177 1         4 my $default_timeout = $self->{-default_timeout};
178              
179 1         13 my $clone = $self->SUPER::clone;
180              
181             # This is a little on the side of evil...we're not supposed to know where
182             # this value is stored.
183 1         9 $clone->{-default_timeout} = $default_timeout;
184              
185             # Add timeouts for all current watcher interests. The timeout method
186             # ignores keys that are already defined.
187 1 50       4 if ($default_timeout) {
188 0         0 for my $key ($clone->watched) {
189 0         0 $clone->timeout($default_timeout, $key);
190             }
191             }
192              
193 1         4 return $clone;
194             }
195              
196             return __PACKAGE__;
197              
198             =back
199              
200             =head1 BUGS
201              
202             None known.
203              
204             =head1 LICENSE
205              
206             Copyright © 2011, Say Media.
207             Distributed under the Artistic License, 2.0.
208              
209             =cut