ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/MP.pm
Revision: 1.33
Committed: Wed Aug 5 22:40:51 2009 UTC (14 years, 9 months ago) by root
Branch: MAIN
Changes since 1.32: +144 -85 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::MP - multi-processing/message-passing framework
4    
5     =head1 SYNOPSIS
6    
7     use AnyEvent::MP;
8    
9 root 1.22 $NODE # contains this node's noderef
10     NODE # returns this node's noderef
11     NODE $port # returns the noderef of the port
12 root 1.2
13     snd $port, type => data...;
14    
15 root 1.22 $SELF # receiving/own port id in rcv callbacks
16    
17 root 1.2 rcv $port, smartmatch => $cb->($port, @msg);
18    
19     # examples:
20     rcv $port2, ping => sub { snd $_[0], "pong"; 0 };
21     rcv $port1, pong => sub { warn "pong received\n" };
22     snd $port2, ping => $port1;
23    
24     # more, smarter, matches (_any_ is exported by this module)
25     rcv $port, [child_died => $pid] => sub { ...
26     rcv $port, [_any_, _any_, 3] => sub { .. $_[2] is 3
27    
28 root 1.1 =head1 DESCRIPTION
29    
30 root 1.2 This module (-family) implements a simple message passing framework.
31    
32     Despite its simplicity, you can securely message other processes running
33     on the same or other hosts.
34    
35 root 1.23 For an introduction to this module family, see the L<AnyEvent::MP::Intro>
36     manual page.
37    
38     At the moment, this module family is severly broken and underdocumented,
39 root 1.21 so do not use. This was uploaded mainly to reserve the CPAN namespace -
40 root 1.23 stay tuned! The basic API should be finished, however.
41 root 1.6
42 root 1.2 =head1 CONCEPTS
43    
44     =over 4
45    
46     =item port
47    
48 root 1.29 A port is something you can send messages to (with the C<snd> function).
49    
50     Some ports allow you to register C<rcv> handlers that can match specific
51     messages. All C<rcv> handlers will receive messages they match, messages
52     will not be queued.
53 root 1.2
54 root 1.3 =item port id - C<noderef#portname>
55 root 1.2
56 root 1.29 A port id is normaly the concatenation of a noderef, a hash-mark (C<#>) as
57     separator, and a port name (a printable string of unspecified format). An
58 root 1.30 exception is the the node port, whose ID is identical to its node
59 root 1.29 reference.
60 root 1.2
61     =item node
62    
63     A node is a single process containing at least one port - the node
64 root 1.29 port. You can send messages to node ports to find existing ports or to
65     create new ports, among other things.
66 root 1.2
67 root 1.29 Nodes are either private (single-process only), slaves (connected to a
68     master node only) or public nodes (connectable from unrelated nodes).
69 root 1.2
70 root 1.5 =item noderef - C<host:port,host:port...>, C<id@noderef>, C<id>
71 root 1.2
72 root 1.29 A node reference is a string that either simply identifies the node (for
73     private and slave nodes), or contains a recipe on how to reach a given
74 root 1.2 node (for public nodes).
75    
76 root 1.29 This recipe is simply a comma-separated list of C<address:port> pairs (for
77     TCP/IP, other protocols might look different).
78    
79     Node references come in two flavours: resolved (containing only numerical
80     addresses) or unresolved (where hostnames are used instead of addresses).
81    
82     Before using an unresolved node reference in a message you first have to
83     resolve it.
84    
85 root 1.2 =back
86    
87 root 1.3 =head1 VARIABLES/FUNCTIONS
88 root 1.2
89     =over 4
90    
91 root 1.1 =cut
92    
93     package AnyEvent::MP;
94    
95 root 1.8 use AnyEvent::MP::Base;
96 root 1.2
97 root 1.1 use common::sense;
98    
99 root 1.2 use Carp ();
100    
101 root 1.1 use AE ();
102    
103 root 1.2 use base "Exporter";
104    
105 root 1.25 our $VERSION = '0.1';
106 root 1.8 our @EXPORT = qw(
107 root 1.22 NODE $NODE *SELF node_of _any_
108 root 1.31 resolve_node initialise_node
109 root 1.22 snd rcv mon kil reg psub
110     port
111 root 1.8 );
112 root 1.2
113 root 1.22 our $SELF;
114    
115     sub _self_die() {
116     my $msg = $@;
117     $msg =~ s/\n+$// unless ref $msg;
118     kil $SELF, die => $msg;
119     }
120    
121     =item $thisnode = NODE / $NODE
122    
123     The C<NODE> function returns, and the C<$NODE> variable contains
124     the noderef of the local node. The value is initialised by a call
125     to C<become_public> or C<become_slave>, after which all local port
126     identifiers become invalid.
127    
128 root 1.33 =item $noderef = node_of $port
129 root 1.22
130     Extracts and returns the noderef from a portid or a noderef.
131    
132 root 1.29 =item $cv = resolve_node $noderef
133    
134     Takes an unresolved node reference that may contain hostnames and
135     abbreviated IDs, resolves all of them and returns a resolved node
136     reference.
137    
138     In addition to C<address:port> pairs allowed in resolved noderefs, the
139     following forms are supported:
140    
141     =over 4
142    
143     =item the empty string
144    
145     An empty-string component gets resolved as if the default port (4040) was
146     specified.
147    
148     =item naked port numbers (e.g. C<1234>)
149    
150     These are resolved by prepending the local nodename and a colon, to be
151     further resolved.
152    
153     =item hostnames (e.g. C<localhost:1234>, C<localhost>)
154    
155     These are resolved by using AnyEvent::DNS to resolve them, optionally
156     looking up SRV records for the C<aemp=4040> port, if no port was
157     specified.
158    
159     =back
160    
161 root 1.22 =item $SELF
162    
163     Contains the current port id while executing C<rcv> callbacks or C<psub>
164     blocks.
165 root 1.3
166 root 1.22 =item SELF, %SELF, @SELF...
167    
168     Due to some quirks in how perl exports variables, it is impossible to
169     just export C<$SELF>, all the symbols called C<SELF> are exported by this
170     module, but only C<$SELF> is currently used.
171 root 1.3
172 root 1.33 =item snd $port, type => @data
173 root 1.3
174 root 1.33 =item snd $port, @msg
175 root 1.3
176 root 1.8 Send the given message to the given port ID, which can identify either
177     a local or a remote port, and can be either a string or soemthignt hat
178     stringifies a sa port ID (such as a port object :).
179    
180     While the message can be about anything, it is highly recommended to use a
181     string as first element (a portid, or some word that indicates a request
182     type etc.).
183 root 1.3
184     The message data effectively becomes read-only after a call to this
185     function: modifying any argument is not allowed and can cause many
186     problems.
187    
188     The type of data you can transfer depends on the transport protocol: when
189     JSON is used, then only strings, numbers and arrays and hashes consisting
190     of those are allowed (no objects). When Storable is used, then anything
191     that Storable can serialise and deserialise is allowed, and for the local
192     node, anything can be passed.
193    
194 root 1.22 =item $local_port = port
195 root 1.2
196 root 1.31 Create a new local port object that can be used either as a pattern
197     matching port ("full port") or a single-callback port ("miniport"),
198     depending on how C<rcv> callbacks are bound to the object.
199 root 1.3
200 root 1.33 =item $port = port { my @msg = @_; $finished }
201 root 1.10
202 root 1.33 Creates a "miniport", that is, a very lightweight port without any pattern
203     matching behind it, and returns its ID. Semantically the same as creating
204     a port and calling C<rcv $port, $callback> on it.
205 root 1.15
206     The block will be called for every message received on the port. When the
207     callback returns a true value its job is considered "done" and the port
208     will be destroyed. Otherwise it will stay alive.
209    
210 root 1.17 The message will be passed as-is, no extra argument (i.e. no port id) will
211 root 1.15 be passed to the callback.
212    
213     If you need the local port id in the callback, this works nicely:
214    
215 root 1.31 my $port; $port = port {
216 root 1.15 snd $otherport, reply => $port;
217     };
218 root 1.10
219     =cut
220    
221 root 1.33 sub rcv($@);
222    
223 root 1.22 sub port(;&) {
224     my $id = "$UNIQ." . $ID++;
225     my $port = "$NODE#$id";
226    
227     if (@_) {
228 root 1.33 rcv $port, shift;
229 root 1.22 } else {
230 root 1.33 $PORT{$id} = sub { }; # nop
231 root 1.22 }
232 root 1.10
233 root 1.22 $port
234 root 1.10 }
235    
236 root 1.33 =item reg $port, $name
237 root 1.8
238 root 1.22 Registers the given port under the name C<$name>. If the name already
239     exists it is replaced.
240 root 1.8
241 root 1.22 A port can only be registered under one well known name.
242 root 1.8
243 root 1.22 A port automatically becomes unregistered when it is killed.
244 root 1.8
245     =cut
246    
247 root 1.22 sub reg(@) {
248 root 1.33 my ($port, $name) = @_;
249 root 1.8
250 root 1.33 $REG{$name} = $port;
251 root 1.22 }
252 root 1.18
253 root 1.33 =item rcv $port, $callback->(@msg)
254 root 1.31
255 root 1.33 Replaces the callback on the specified miniport (after converting it to
256     one if required).
257 root 1.31
258 root 1.33 =item rcv $port, tagstring => $callback->(@msg), ...
259 root 1.3
260 root 1.33 =item rcv $port, $smartmatch => $callback->(@msg), ...
261 root 1.3
262 root 1.33 =item rcv $port, [$smartmatch...] => $callback->(@msg), ...
263 root 1.3
264 root 1.32 Register callbacks to be called on matching messages on the given full
265 root 1.33 port (after converting it to one if required).
266 root 1.3
267     The callback has to return a true value when its work is done, after
268     which is will be removed, or a false value in which case it will stay
269     registered.
270    
271 root 1.33 The global C<$SELF> (exported by this module) contains C<$port> while
272 root 1.22 executing the callback.
273    
274     Runtime errors wdurign callback execution will result in the port being
275     C<kil>ed.
276    
277 root 1.3 If the match is an array reference, then it will be matched against the
278     first elements of the message, otherwise only the first element is being
279     matched.
280    
281     Any element in the match that is specified as C<_any_> (a function
282     exported by this module) matches any single element of the message.
283    
284     While not required, it is highly recommended that the first matching
285     element is a string identifying the message. The one-string-only match is
286     also the most efficient match (by far).
287    
288     =cut
289    
290     sub rcv($@) {
291 root 1.33 my $port = shift;
292     my ($noderef, $portid) = split /#/, $port, 2;
293 root 1.3
294 root 1.22 ($NODE{$noderef} || add_node $noderef) == $NODE{""}
295 root 1.33 or Carp::croak "$port: rcv can only be called on local ports, caught";
296 root 1.22
297 root 1.33 if (@_ == 1) {
298     my $cb = shift;
299     delete $PORT_DATA{$portid};
300     $PORT{$portid} = sub {
301     local $SELF = $port;
302     eval {
303     &$cb
304     and kil $port;
305     };
306     _self_die if $@;
307     };
308     } else {
309     my $self = $PORT_DATA{$portid} ||= do {
310     my $self = bless {
311     id => $port,
312     }, "AnyEvent::MP::Port";
313    
314     $PORT{$portid} = sub {
315     local $SELF = $port;
316    
317     eval {
318     for (@{ $self->{rc0}{$_[0]} }) {
319     $_ && &{$_->[0]}
320     && undef $_;
321     }
322    
323     for (@{ $self->{rcv}{$_[0]} }) {
324     $_ && [@_[1 .. @{$_->[1]}]] ~~ $_->[1]
325     && &{$_->[0]}
326     && undef $_;
327     }
328    
329     for (@{ $self->{any} }) {
330     $_ && [@_[0 .. $#{$_->[1]}]] ~~ $_->[1]
331     && &{$_->[0]}
332     && undef $_;
333     }
334     };
335     _self_die if $@;
336     };
337    
338     $self
339     };
340 root 1.22
341 root 1.33 "AnyEvent::MP::Port" eq ref $self
342     or Carp::croak "$port: rcv can only be called on message matching ports, caught";
343 root 1.22
344 root 1.33 while (@_) {
345     my ($match, $cb) = splice @_, 0, 2;
346    
347     if (!ref $match) {
348     push @{ $self->{rc0}{$match} }, [$cb];
349     } elsif (("ARRAY" eq ref $match && !ref $match->[0])) {
350     my ($type, @match) = @$match;
351     @match
352     ? push @{ $self->{rcv}{$match->[0]} }, [$cb, \@match]
353     : push @{ $self->{rc0}{$match->[0]} }, [$cb];
354     } else {
355     push @{ $self->{any} }, [$cb, $match];
356     }
357 root 1.22 }
358 root 1.3 }
359 root 1.31
360 root 1.33 $port
361 root 1.2 }
362    
363 root 1.22 =item $closure = psub { BLOCK }
364 root 1.2
365 root 1.22 Remembers C<$SELF> and creates a closure out of the BLOCK. When the
366     closure is executed, sets up the environment in the same way as in C<rcv>
367     callbacks, i.e. runtime errors will cause the port to get C<kil>ed.
368    
369     This is useful when you register callbacks from C<rcv> callbacks:
370    
371     rcv delayed_reply => sub {
372     my ($delay, @reply) = @_;
373     my $timer = AE::timer $delay, 0, psub {
374     snd @reply, $SELF;
375     };
376     };
377 root 1.3
378 root 1.8 =cut
379 root 1.3
380 root 1.22 sub psub(&) {
381     my $cb = shift;
382 root 1.3
383 root 1.22 my $port = $SELF
384     or Carp::croak "psub can only be called from within rcv or psub callbacks, not";
385 root 1.1
386 root 1.22 sub {
387     local $SELF = $port;
388 root 1.2
389 root 1.22 if (wantarray) {
390     my @res = eval { &$cb };
391     _self_die if $@;
392     @res
393     } else {
394     my $res = eval { &$cb };
395     _self_die if $@;
396     $res
397     }
398     }
399 root 1.2 }
400    
401 root 1.33 =item $guard = mon $port, $cb->(@reason)
402 root 1.32
403 root 1.33 =item $guard = mon $port, $otherport
404 root 1.32
405 root 1.33 =item $guard = mon $port, $otherport, @msg
406 root 1.32
407     Monitor the given port and do something when the port is killed.
408    
409     In the first form, the callback is simply called with any number
410     of C<@reason> elements (no @reason means that the port was deleted
411     "normally"). Note also that I<< the callback B<must> never die >>, so use
412     C<eval> if unsure.
413    
414     In the second form, the other port will be C<kil>'ed with C<@reason>, iff
415     a @reason was specified, i.e. on "normal" kils nothing happens, while
416     under all other conditions, the other port is killed with the same reason.
417    
418     In the last form, a message of the form C<@msg, @reason> will be C<snd>.
419    
420     Example: call a given callback when C<$port> is killed.
421    
422     mon $port, sub { warn "port died because of <@_>\n" };
423    
424     Example: kill ourselves when C<$port> is killed abnormally.
425    
426     mon $port, $self;
427    
428     Example: send us a restart message another C<$port> is killed.
429    
430     mon $port, $self => "restart";
431    
432     =cut
433    
434     sub mon {
435     my ($noderef, $port) = split /#/, shift, 2;
436    
437     my $node = $NODE{$noderef} || add_node $noderef;
438    
439     my $cb = shift;
440    
441     unless (ref $cb) {
442     if (@_) {
443     # send a kill info message
444     my (@msg) = ($cb, @_);
445     $cb = sub { snd @msg, @_ };
446     } else {
447     # simply kill other port
448     my $port = $cb;
449     $cb = sub { kil $port, @_ if @_ };
450     }
451     }
452    
453     $node->monitor ($port, $cb);
454    
455     defined wantarray
456     and AnyEvent::Util::guard { $node->unmonitor ($port, $cb) }
457     }
458    
459     =item $guard = mon_guard $port, $ref, $ref...
460    
461     Monitors the given C<$port> and keeps the passed references. When the port
462     is killed, the references will be freed.
463    
464     Optionally returns a guard that will stop the monitoring.
465    
466     This function is useful when you create e.g. timers or other watchers and
467     want to free them when the port gets killed:
468    
469     $port->rcv (start => sub {
470     my $timer; $timer = mon_guard $port, AE::timer 1, 1, sub {
471     undef $timer if 0.9 < rand;
472     });
473     });
474    
475     =cut
476    
477     sub mon_guard {
478     my ($port, @refs) = @_;
479    
480     mon $port, sub { 0 && @refs }
481     }
482    
483     =item lnk $port1, $port2
484    
485     Link two ports. This is simply a shorthand for:
486    
487     mon $port1, $port2;
488     mon $port2, $port1;
489    
490     It means that if either one is killed abnormally, the other one gets
491     killed as well.
492    
493 root 1.33 =item kil $port[, @reason]
494 root 1.32
495     Kill the specified port with the given C<@reason>.
496    
497     If no C<@reason> is specified, then the port is killed "normally" (linked
498     ports will not be kileld, or even notified).
499    
500     Otherwise, linked ports get killed with the same reason (second form of
501     C<mon>, see below).
502    
503     Runtime errors while evaluating C<rcv> callbacks or inside C<psub> blocks
504     will be reported as reason C<< die => $@ >>.
505    
506     Transport/communication errors are reported as C<< transport_error =>
507     $message >>.
508    
509 root 1.8 =back
510    
511     =head1 FUNCTIONS FOR NODES
512    
513     =over 4
514 root 1.2
515 root 1.33 =item initialise_node $noderef, $seednode, $seednode...
516    
517     =item initialise_node "slave/", $master, $master...
518    
519     Initialises a node - must be called exactly once before calling other
520     AnyEvent::MP functions when talking to other nodes is required.
521    
522     All arguments are noderefs, which can be either resolved or unresolved.
523    
524     There are two types of networked nodes, public nodes and slave nodes:
525    
526     =over 4
527    
528     =item public nodes
529    
530     For public nodes, C<$noderef> must either be a (possibly unresolved)
531     noderef, in which case it will be resolved, or C<undef> (or missing), in
532     which case the noderef will be guessed.
533    
534     Afterwards, the node will bind itself on all endpoints and try to connect
535     to all additional C<$seednodes> that are specified. Seednodes are optional
536     and can be used to quickly bootstrap the node into an existing network.
537    
538     =item slave nodes
539    
540     When the C<$noderef> is the special string C<slave/>, then the node will
541     become a slave node. Slave nodes cannot be contacted from outside and will
542     route most of their traffic to the master node that they attach to.
543    
544     At least one additional noderef is required: The node will try to connect
545     to all of them and will become a slave attached to the first node it can
546     successfully connect to.
547    
548     =back
549    
550     This function will block until all nodes have been resolved and, for slave
551     nodes, until it has successfully established a connection to a master
552     server.
553    
554     Example: become a public node listening on the default node.
555    
556     initialise_node;
557    
558     Example: become a public node, and try to contact some well-known master
559     servers to become part of the network.
560    
561     initialise_node undef, "master1", "master2";
562    
563     Example: become a public node listening on port C<4041>.
564    
565     initialise_node 4041;
566    
567     Example: become a public node, only visible on localhost port 4044.
568 root 1.8
569 root 1.33 initialise_node "locahost:4044";
570 root 1.8
571 root 1.33 Example: become a slave node to any of the specified master servers.
572 root 1.29
573 root 1.33 initialise_node "slave/", "master1", "192.168.13.17", "mp.example.net";
574 root 1.2
575 root 1.8 =cut
576 root 1.1
577 root 1.4 =back
578    
579     =head1 NODE MESSAGES
580    
581 root 1.5 Nodes understand the following messages sent to them. Many of them take
582     arguments called C<@reply>, which will simply be used to compose a reply
583     message - C<$reply[0]> is the port to reply to, C<$reply[1]> the type and
584     the remaining arguments are simply the message data.
585 root 1.4
586 root 1.29 While other messages exist, they are not public and subject to change.
587    
588 root 1.4 =over 4
589    
590     =cut
591    
592 root 1.22 =item lookup => $name, @reply
593 root 1.3
594 root 1.8 Replies with the port ID of the specified well-known port, or C<undef>.
595 root 1.3
596 root 1.7 =item devnull => ...
597    
598     Generic data sink/CPU heat conversion.
599    
600 root 1.4 =item relay => $port, @msg
601    
602     Simply forwards the message to the given port.
603    
604     =item eval => $string[ @reply]
605    
606     Evaluates the given string. If C<@reply> is given, then a message of the
607 root 1.5 form C<@reply, $@, @evalres> is sent.
608    
609     Example: crash another node.
610    
611     snd $othernode, eval => "exit";
612 root 1.4
613     =item time => @reply
614    
615     Replies the the current node time to C<@reply>.
616    
617 root 1.5 Example: tell the current node to send the current time to C<$myport> in a
618     C<timereply> message.
619    
620     snd $NODE, time => $myport, timereply => 1, 2;
621     # => snd $myport, timereply => 1, 2, <time>
622    
623 root 1.2 =back
624    
625 root 1.26 =head1 AnyEvent::MP vs. Distributed Erlang
626    
627 root 1.27 AnyEvent::MP got lots of its ideas from distributed erlang (erlang node
628     == aemp node, erlang process == aemp port), so many of the documents and
629     programming techniques employed by erlang apply to AnyEvent::MP. Here is a
630     sample:
631    
632     http://www.erlang.se/doc/programming_rules.shtml
633     http://erlang.org/doc/getting_started/part_frame.html # chapters 3 and 4
634     http://erlang.org/download/erlang-book-part1.pdf # chapters 5 and 6
635     http://erlang.org/download/armstrong_thesis_2003.pdf # chapters 4 and 5
636    
637     Despite the similarities, there are also some important differences:
638 root 1.26
639     =over 4
640    
641     =item * Node references contain the recipe on how to contact them.
642    
643     Erlang relies on special naming and DNS to work everywhere in the
644     same way. AEMP relies on each node knowing it's own address(es), with
645     convenience functionality.
646    
647 root 1.27 This means that AEMP requires a less tightly controlled environment at the
648     cost of longer node references and a slightly higher management overhead.
649    
650 root 1.26 =item * Erlang uses processes and a mailbox, AEMP does not queue.
651    
652     Erlang uses processes that selctively receive messages, and therefore
653     needs a queue. AEMP is event based, queuing messages would serve no useful
654     purpose.
655    
656     (But see L<Coro::MP> for a more erlang-like process model on top of AEMP).
657    
658     =item * Erlang sends are synchronous, AEMP sends are asynchronous.
659    
660     Sending messages in erlang is synchronous and blocks the process. AEMP
661     sends are immediate, connection establishment is handled in the
662     background.
663    
664     =item * Erlang can silently lose messages, AEMP cannot.
665    
666     Erlang makes few guarantees on messages delivery - messages can get lost
667     without any of the processes realising it (i.e. you send messages a, b,
668     and c, and the other side only receives messages a and c).
669    
670     AEMP guarantees correct ordering, and the guarantee that there are no
671     holes in the message sequence.
672    
673     =item * In erlang, processes can be declared dead and later be found to be
674     alive.
675    
676     In erlang it can happen that a monitored process is declared dead and
677     linked processes get killed, but later it turns out that the process is
678     still alive - and can receive messages.
679    
680     In AEMP, when port monitoring detects a port as dead, then that port will
681     eventually be killed - it cannot happen that a node detects a port as dead
682     and then later sends messages to it, finding it is still alive.
683    
684     =item * Erlang can send messages to the wrong port, AEMP does not.
685    
686     In erlang it is quite possible that a node that restarts reuses a process
687     ID known to other nodes for a completely different process, causing
688     messages destined for that process to end up in an unrelated process.
689    
690     AEMP never reuses port IDs, so old messages or old port IDs floating
691     around in the network will not be sent to an unrelated port.
692    
693     =item * Erlang uses unprotected connections, AEMP uses secure
694     authentication and can use TLS.
695    
696     AEMP can use a proven protocol - SSL/TLS - to protect connections and
697     securely authenticate nodes.
698    
699 root 1.28 =item * The AEMP protocol is optimised for both text-based and binary
700     communications.
701    
702     The AEMP protocol, unlike the erlang protocol, supports both
703     language-independent text-only protocols (good for debugging) and binary,
704     language-specific serialisers (e.g. Storable).
705    
706     It has also been carefully designed to be implementable in other languages
707     with a minimum of work while gracefully degrading fucntionality to make the
708     protocol simple.
709    
710 root 1.26 =back
711    
712 root 1.1 =head1 SEE ALSO
713    
714     L<AnyEvent>.
715    
716     =head1 AUTHOR
717    
718     Marc Lehmann <schmorp@schmorp.de>
719     http://home.schmorp.de/
720    
721     =cut
722    
723     1
724