Remove premature documentation of pod-throttling
[irspy-moved-to-github.git] / lib / ZOOM / Pod.pm
1 # $Id: Pod.pm,v 1.12 2006-06-07 10:43:22 mike Exp $
2
3 package ZOOM::Pod;
4
5 use strict;
6 use warnings;
7
8 use ZOOM;
9
10 BEGIN {
11     # Just register the name
12     ZOOM::Log::mask_str("pod");
13     ZOOM::Log::mask_str("pod_unhandled");
14 }
15
16 =head1 NAME
17
18 ZOOM::Pod - Perl extension for handling pods of concurrent ZOOM connections
19
20 =head1 SYNOPSIS
21
22  use ZOOM::Pod;
23
24  $pod = new ZOOM::Pod("bagel.indexdata.com/gils",
25                       "bagel.indexdata.com/marc");
26  $pod->callback(ZOOM::Event::RECV_SEARCH, \&completed_search);
27  $pod->callback(ZOOM::Event::RECV_RECORD, \&got_record);
28  $pod->search_pqf("the");
29  $err = $pod->wait();
30  die "$pod->wait() failed with error $err" if $err;
31
32  sub completed_search {
33      ($conn, undef, $rs) = @_;
34      print $conn->option("host"), ": found ", $rs->size(), " records\n";
35      $rs->records(0, 1, 0); # Queues a request for the record
36      return 0;
37  }
38
39  sub got_record {
40      ($conn, undef, $rs) = @_;
41      $rec = $rs->record(0);
42      print $conn->option("host"), ": got $rec = '", $rec->render(), "'\n";
43      return 0;
44  }
45
46 =head1 DESCRIPTION
47
48 C<ZOOM:Pod> provides an API that simplifies asynchronous programming
49 using ZOOM.  A pod is a collection of asynchronous connections that
50 are run simultaneously to achieve broadcast searching and retrieval.
51 When a pod is created, a set of connections (or target-strings to
52 connect to) are specified.  Thereafter, they are treated as a unit,
53 and methods for searching, option-setting, etc. that are invoked on
54 the pod are delegated to each of its members.
55
56 The key method on a pod is C<wait()>, which enters a loop accepting
57 and dispatching events occurring on any of the connections in the pod.
58 Unless interrupted,the loop runs until there are no more events left,
59 i.e. no searches are outstanding and no requested records have still
60 to be received.
61
62 Event dispatching is done by means of callback functions, which can be
63 registered for each event.  A registered callback is invoked whenever
64 a corresponding event occurs.  A special callback can be nominated to
65 handle errors.
66
67 =head1 METHODS
68
69 =head2 new()
70
71  $pod = new ZOOM::Pod($conn1, $conn2, $conn3);
72  $pod = new ZOOM::Pod("bagel.indexdata.com/gils",
73                       "bagel.indexdata.com/marc");
74
75 Creates a new pod containing one or more connections.  Each connection
76 may be specified either by an existing C<ZOOM::Connection> object,
77 which I<must> be asynchronous; or by a ZOOM target string, in which
78 case the pod module will make the connection object itself.
79
80 Returns the new pod.
81
82 =cut
83
84 # Functionality to be added:
85 #
86 #       If the constructor's first argument is a number, then it is
87 #       taken as a limit on the number of connections to handle at any
88 #       one time.  In this case, the pod initially multiplexes between
89 #       the first I<n> connections, and brings further connections
90 #       into the active subset whenever already-active connections are
91 #       closed.
92
93 sub new {
94     my $class = shift();
95     my(@conn) = @_;
96
97     die "$class with no connections" if @conn == 0;
98     my @state; # Hashrefs with application state associated with connections
99     foreach my $conn (@conn) {
100         if (!ref $conn) {
101             $conn = new ZOOM::Connection($conn, 0, async => 1);
102             # The $conn object is always made, even if no there's no
103             # server.  Such errors are caught later, by the _check()
104             # call in wait(). 
105         }
106         push @state, {};
107     }
108
109     return bless {
110         conn => \@conn,
111         state => \@state,
112         rs => [],
113         callback => {},
114     }, $class;
115 }
116
117 =head2 option()
118
119  $oldElemSet = $pod->option("elementSetName");
120  $pod->option(elementSetName => "b");
121
122 Sets a specified option in all the connections in a pod.  Returns the
123 old value that the option had in first of the connections in the pod:
124 be aware that this value was not necessarily shared by all the members
125 of the pod ... but that is true often enough to be useful.
126
127 =cut
128
129 sub option {
130     my $this = shift();
131     my($key, $value) = @_;
132
133     my $old = $this->{conn}->[0]->option($key);
134     foreach my $conn (@{ $this->{conn} }) {
135         $conn->option($key, $value);
136     }
137
138     return $old;
139 }
140
141 =head2 callback()
142
143  $pod->callback(ZOOM::Event::RECV_SEARCH, \&completed_search);
144  $pod->callback("exception", sub { print "never mind: $@\n"; return 0 } );
145
146 Registers a callback to be invoked by the pod when an event happens.
147 Callback functions are invoked by C<wait()> (q.v.).
148
149 When registering a callback, the first argument is an event-code - one
150 of those defined in the C<ZOOM::Event> enumeration - and the second is
151 a function reference, or equivalently an inline code-fragment.  It is
152 acceptable to nominate the same function as the callback for multiple
153 events, by multiple invocations of C<callback()>.
154
155 When an event occurs during the execution of C<wait()>, the relevant
156 callback function is called with four arguments: the connection that the
157 event happened on; a state hash-reference associated with the
158 connection; the result-set associated with the connection; and the
159 event-type (so that a single function that handles events of multiple
160 types can switch on the code where necessary).  The callback function
161 can handle the event as it wishes, finishing up by returning an
162 integer.  If this is zero, then C<wait()> continues as normal; if it
163 is anything else, then that value is immediately returned from
164 C<wait()>.
165
166 So a simple event-handler might look like this:
167
168  sub got_event {
169       ($conn, $state, $rs, $event) = @_;
170       print "event $event on connection ", $conn->option("host"), "\n";
171       print "Found ", $rs->size(), " records\n"
172           if $event == ZOOM::Event::RECV_SEARCH;
173       return 0;
174  }
175
176 In addition to the event-type callbacks discussed above, there is a
177 special callback, C<"exception">, which is invoked if an exception
178 occurs.  This will nearly always be a ZOOM error, but this can be
179 tested using C<$exception-E<gt>isa("ZOOM::Exception")>.  This callback is
180 invoked with the same arguments as described above, except that
181 instead of the event-type, the fourth argument is a copy of the
182 exception, C<$@>.  Exception-handling callbacks may of course re-throw
183 the exception using C<die $exception>.
184
185 So a simple error-handler might look like this:
186
187  sub got_error {
188       ($conn, $state, $rs, $exception) = @_;
189       if ($exception->isa("ZOOM::Exception")) {
190           print "Caught error $exception - continuing";
191           return 0;
192       }
193       die $exception;
194  }
195
196 The C<$state> argument is a reference to an initially empty hash,
197 which the application can use as it sees fit, to store its own
198 connection-relation information.  For example, an application might
199 use C<$state-E<gt>{last}> to keep a record of which was the last record
200 retrieved from the associated connection.  The pod module itself does
201 not use the state hash at all, and applications are also welcome to
202 ignore it if they do not need it.
203
204 =cut
205
206 sub callback {
207     my $this = shift();
208     my($event, $sub) = @_;
209
210     my $old = $this->{callback}->{$event};
211     $this->{callback}->{$event} = $sub
212         if defined $sub;
213
214     return $old;
215 }
216
217 =head2 search_pqf()
218
219  $pod->search_pqf("@attr 1=1003 wedel");
220
221 Submits the specified query to each of the connections in a pod,
222 delegating to the same-named method of the C<ZOOM::Connection> class
223 and storing each result in a result-set object associated with the
224 connection that generated it.  Returns no value: success or failure
225 must subsequently be detected by inspecting the events and exceptions
226 generated by C<wait()>ing on the pod.
227
228 B<WARNING!>
229 An important simplifying assumption is that each connection can only
230 have one search active on it at a time: this allows the pod to
231 maintain the one-to-one mapping between connections and result-sets.
232 Submitting a new search on a connection before the old one has
233 completed will result in a total failure in the nature of causality,
234 and the spontaneous existence-failure of the universe.  Do not do
235 this.
236
237 =cut
238
239 sub search_pqf {
240     my $this = shift();
241     my($pqf) = @_;
242
243     foreach my $i (0..@{ $this->{conn} }-1) {
244         $this->{rs}->[$i] = $this->{conn}->[$i]->search_pqf($pqf);
245     }
246 }
247
248 =head2 wait()
249
250  $err = $pod->wait();
251  die "$pod->wait() failed with error $err" if $err;
252
253 Waits for events on the connections that make up the pod, usually
254 continuing until there are no more events left and then returning
255 zero.  Whenever an event occurs, a callback function is dispatched as
256 described above; if
257 that function returns a non-zero value, then C<wait()> terminates
258 immediately, whether or not any events remain, and returns that value.
259
260 If an error occurs on one of the connection in the pod, then it is
261 normally thrown as a C<ZOOM::Exception>.  If, however, there is a
262 special C<"exception"> callback registered, then the exception object
263 is passed to this instead.  As usual, the return value of the callback
264 indicates whether C<wait()> should continue (return-value 0) or return
265 immediately (any other value).  Exception-handling callbacks may of
266 course re-throw the exception.
267
268 =cut
269
270 sub wait {
271     my $this = shift();
272     my $res = 0;
273
274     while ((my $i = ZOOM::event($this->{conn})) != 0) {
275         my $conn = $this->{conn}->[$i-1];
276         my $ev = $conn->last_event();
277         my $evstr = ZOOM::event_str($ev);
278         ZOOM::Log::log("pod", "connection ", $i-1, ": event $ev ($evstr)");
279
280         eval {
281             $conn->_check();
282         }; if ($@) {
283             my $sub = $this->{callback}->{exception};
284             die $@ if !defined $sub;
285             $res = &$sub($conn, $this->{state}->[$i-1],
286                          $this->{rs}->[$i-1], $@);
287             last if $res != 0;
288             next;
289         }
290
291         my $sub = $this->{callback}->{$ev};
292         if (defined $sub) {
293             $res = &$sub($conn, $this->{state}->[$i-1],
294                          $this->{rs}->[$i-1], $ev);
295             last if $res != 0;
296         } else {
297             ZOOM::Log::log("pod_unhandled", "connection ", $i-1, ": unhandled event $ev ($evstr)");
298         }
299     }
300
301     return $res;
302 }
303
304
305 =head1 LOGGING
306
307 This module generates logging messages using C<ZOOM::Log::log()>,
308 which in turn relies on the YAZ logging facilities.  It uses two
309 logging levels:
310
311 =over 4
312
313 =item pod
314
315 Logs all events.
316
317 =item pod_unhandled
318
319 Logs unhandled events, i.e. events of types for which no callback has
320 been registered.
321
322 =back
323
324 These logging levels can be turned on by setting the C<YAZ_LOG>
325 environment variable to C<pod,pod_unhandled>.
326
327 =head1 SEE ALSO
328
329 The underlying
330 C<ZOOM>
331 module (part of the
332 C<Net::Z3950::ZOOM>
333 distribution).
334
335 =head1 AUTHOR
336
337 Mike Taylor, E<lt>mike@indexdata.comE<gt>
338
339 =head1 COPYRIGHT AND LICENCE
340
341 Copyright (C) 2006 by Index Data.
342
343 This library is free software; you can redistribute it and/or modify
344 it under the same terms as Perl itself, either Perl version 5.8.4 or,
345 at your option, any later version of Perl 5 you may have available.
346
347 =cut
348
349
350 1;