ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-FCP/bin/fmd
Revision: 1.16
Committed: Mon Dec 4 13:47:28 2006 UTC (19 years, 9 months ago) by root
Branch: MAIN
Changes since 1.15: +6 -8 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3 root 1.14 system "echo $$ >fmd.pid";
4    
5 root 1.1 =head1 fmd - the freenet mass downloader
6    
7     Fmd is at a very early stage of development (and very hackish, too), as I
8     am learning the basics of freenet myself.
9    
10     However, I use it in production, and since it verifies everything it
11     decodes etc., it seems to be quite safe to use, that is, if you know how
12     to debug perl :)
13    
14     =head2 FEATURES
15    
16     - decoding is done "in place", i.e. all non-checkblock blocks are
17     stored in-place and will not be moved, only checkblocks will
18     be moved to their final position in the file before decoding.
19     - high resistance against failures of fred or fmd
20     - extremely persistent retry behaviour - there is no such thing
21     as a permanent failure.
22     - handles hundreds of simultaneous downloads with grace.
23    
24     =head2 ENVIRONMENT
25    
26     Set FMD_HOME to a directory (default ~/fmd) where fmd will store it's
27     files. It will not store your porn files or other freenet data outside
28     that directory.
29    
30     The subdirectory db will contain a database (soon to go away), while tmp
31     contains queue files and partial splitfiles. All finished files will be
32     moved to the done subdir.
33    
34     FREDHOST and FREDPORT do the obvious. Fix these docs if you disagree.
35    
36     Also edit the fmd executable for the number of threads and other
37 root 1.14 non-useful constants. The default number (250) works for me, probably not
38 root 1.1 for you.
39    
40     =head2 COMMANDS
41    
42     =over 4
43    
44     =item CHK@... (space or "/") filename
45    
46     Just pasting a CHK and a filename sperated by one or more spaces or
47     slashes will add files to the queue. The line can have leading garbage,
48     i.e. you can paste full uris.
49    
50     =item <attachment>...</attachment>
51    
52     =item <attach>...</attach>
53    
54     =item <attached>...</attached>
55    
56     Will add frosts mentally deranged pseudo-xml format, soon to be replaced
57     by something even more horrible.
58    
59     =item l
60    
61     List all jobs by number.
62    
63     =item <number> (optionally trailing command)
64    
65     Selects the job with the given number for further comamnds that require a current job.
66    
67     =item s
68    
69     Show the current job. Bot useful right now.
70    
71     =item k
72    
73     Kill the current job. This will keep the temp. file around. Sorry. (But it
74     could be used to reconstruct the download.. hmm..)
75    
76     =item pri<number>
77    
78 root 1.2 Set the current job priority to <pri>. The default is 1. A job with higher
79     priority will (on average) get more requests.
80 root 1.1
81 root 1.2 The useful range is probably 0..100, but be careful and limit yourself to
82     small values (<10), otherwise your other downloads might starve!
83 root 1.1
84     =item q
85    
86     Kill the command prompt. Yupp.
87    
88     =back
89    
90     =cut
91    
92 root 1.10 use strict;
93    
94 root 1.1 use Net::FCP;
95     use Storable;
96     use Time::HiRes;
97 root 1.15 use Coro 3.0;
98     use Coro::Channel;
99 root 1.1 use Coro::Event;
100     use Coro::Handle;
101     use Coro::Signal;
102     use Coro::Timer;
103 root 1.9 use Coro::AIO;
104 root 1.1 use List::Util;
105     use Digest::SHA1;
106     use Algorithm::FEC;
107     use Digest::SHA1;
108     use Net::FCP::Util;
109     use POSIX ();
110    
111 root 1.9 Coro::AIO::max_poll_time 0.01;
112     Coro::AIO::min_parallel 1;
113     Coro::AIO::max_parallel 2;
114    
115 root 1.1 $|=1;
116    
117 root 1.14 our $MAX_TXN = 250; # use max. this many transactions in parallel
118 root 1.9 our @HTL = (5,12,20);
119 root 1.1 our $VERIFY_CHK = 1; # verify all blocks, again and again (only useful to debugging).
120     our $FMD_HOME = $ENV{FMD_HOME} || "$ENV{HOME}/fmd";
121    
122     our $FCP = new Net::FCP;
123    
124     defined $FMD_HOME
125     or die "you currently must define FMD_HOME to a persistent directory";
126    
127     mkdir $FMD_HOME, 0700;
128    
129     our $QUEUE_HOME = "$FMD_HOME/tmp";
130     mkdir $QUEUE_HOME, 0700;
131     our $DONE_HOME = "$FMD_HOME/done";
132     mkdir $DONE_HOME, 0700;
133    
134     our %job;
135    
136     sub push_key {
137     my ($key, $title) = @_;
138    
139     for my $job (values %job) {
140     if ($job->{p}{key} eq $key) {
141     warn "job $job->{id} already works on this, not adding";
142     return;
143     }
144     }
145    
146     my $job = job->new_from_key ($key, $title);
147     warn "added as job $job->{id}\n";
148     $job;
149     }
150    
151     sub cmdline {
152     my ($i, $o) = @_;
153    
154     my $cmd = async {
155     my $job;
156     while (print $o "> " and defined ($_ = <$i>)) {
157     chomp;
158     if (/<attach(?:ment|ed|)>(.*) \* (CHK[^<]+)<\/attach/) {
159     $job = push_key $2, $1;
160 root 1.7 } elsif (/(CHK\@[a-zA-Z0-9,~\-]{54})[\/ ]+(.*?)\s*$/) {
161 root 1.1 $job = push_key $1, $2;
162     } elsif (s/^(\d+)//) {
163     if ($job = $job{$1}) {
164     print $o delete $job->{log};
165     if ($job->{input}) {
166     }
167     }
168     redo;
169     } elsif (/^q/) {
170     close $i;
171     close $o;
172     } elsif (/^l/) {
173     for my $job (sort { $a->{id} <=> $b->{id} } values %job) {
174     print $o "$_ $job->{id}: $job->{p}{key} $job->{p}{title} $job->{status}\n";
175     }
176 root 1.16 # } elsif (/^pri\s*(\d+)/) {
177     # if ($job) {
178     # $job->{p}{pri} = $1;
179     # $job->save;
180     # }
181 root 1.1 } elsif (/^s$/) {
182     print $o $job->show if $job;
183     } elsif (/^k$/) {
184     $job->kill if $job;
185 root 1.14 } elsif (/^p(.*)/) {
186 root 1.16 print $o +(join " ", eval $1), "\n";
187 root 1.1 } elsif (/\S/) {
188     #
189     } else {
190     print $o "?\n";
191     }
192     for my $job (sort { $a->{id} <=> $b->{id} } values %job) {
193     if ($job->{_input}) {
194     print $o "> $job->{id} $job->{title} $job->{status}\n";
195     }
196     }
197     }
198     };
199     }
200    
201     package job;
202    
203 root 1.10 use strict;
204    
205 root 1.1 use Coro;
206 root 1.9 use Coro::AIO;
207 root 1.1 use Fcntl;
208     use IO::Handle;
209 root 1.6 use Array::Heap2;
210 root 1.1
211 root 1.14 our $count = 0;
212 root 1.1
213     sub new {
214     my $class = shift;
215    
216     my $self = bless { @_ }, $class;
217    
218 root 1.9 $self->{p}{title} !~ /\//
219     or die "$self->{p}{title}: malformed key\n";
220    
221 root 1.13 $self->{p}{title} =~ s/\s+$//;
222     $self->{p}{title} =~ s/\n/_/g;
223    
224 root 1.1 $self->{id} = ++$count;
225     $self->{job} ||= "$QUEUE_HOME/" . Time::HiRes::time . ":$count.j";
226    
227     $job{$self->{id}} = $self;
228    
229     $self->save;
230     $self->start;
231     $self;
232     }
233    
234     sub new_from_key {
235     my ($class, $key, $title) = @_;
236     $class->new (p => { key => $key, title => $title, state => "examine" });
237     }
238    
239     sub new_from_file {
240     my ($class, $path) = @_;
241     $class->new (job => $path, p => Storable::retrieve $path);
242     }
243    
244     sub save {
245     my ($self) = @_;
246     Storable::nstore $self->{p}, "$self->{job}~";
247 root 1.12 rename "$self->{job}~", $self->{job};
248 root 1.1 }
249    
250     sub clean {
251     my ($self) = @_;
252    
253     delete $job{$self->{id}};
254     $self->save;
255 root 1.12 rename $self->{job}, "$DONE_HOME/$self->{p}{title}.job";
256     unlink $self->{job};
257 root 1.1 }
258    
259     sub kill {
260     my ($self) = @_;
261    
262     $self->clean;
263     $self->{coro}->cancel;
264     }
265    
266 root 1.14 our @queue;
267     our $queue_change = new Coro::Signal;
268     our $queue_alloc = 0;
269 root 1.1
270     async {
271     for (;;) {
272     while (@queue
273     and (($queue[0][0] > 10 and $queue_alloc < $MAX_TXN)
274     or ($queue[0][0] > 1 and $queue_alloc < $MAX_TXN - 3)
275     or $queue_alloc < $MAX_TXN - 5)) {
276     (pop_heap @queue)->[1]->send;
277     $queue_alloc++;
278 root 1.4 Coro::Timer::sleep 0.05;
279 root 1.1 }
280     $queue_change->wait;
281     }
282     };
283    
284     sub txn_begin {
285     my ($pri) = @_;
286     my $sig = new Coro::Signal;
287    
288     #warn "txn_begin $pri\n";#d#
289     push_heap @queue, [$pri, $sig];
290     $queue_change->send;
291     $sig->wait;
292     }
293    
294     sub txn_end {
295     $queue_alloc--;
296     $queue_change->send;
297 root 1.15 }
298    
299     sub txn_client_get {
300     my %arg = @_;
301    
302     txn_begin $arg{pri};
303     $FCP->txn_client_get ($arg{uri}, $arg{htl})->cb (unblock_sub {
304     txn_end;
305    
306     $arg{cb}->(@_);
307     });
308 root 1.1 }
309    
310     sub fetch_uri {
311     my ($pri, $uri) = @_;
312    
313     for(my $count = 1; ; $count += 0.3) {
314     for my $htl (@HTL) {
315 root 1.4 txn_begin time + $htl + $count;
316 root 1.1 my ($meta, $data) = eval { @{ $FCP->client_get ($uri, $htl) } };
317     txn_end;
318     if ($@) {
319     if (UNIVERSAL::isa ($@, Net::FCP::Exception::)) {
320     if ($@->type ("data_not_found")
321     || $@->type ("route_not_found")) {
322     next;
323     }
324     if ($@->type ("short_data")) {
325     warn "(short_data, redo)\n";
326     redo;
327     }
328     die;
329     }
330     }
331     if (defined $data) {
332     return ($meta, $data);
333     }
334     }
335     }
336    
337     die;
338     }
339    
340     sub log {
341     my ($self, $text) = @_;
342     my $time = POSIX::strftime "%H:%M:%S", localtime time;
343 root 1.3 warn "$time $self->{id},$self->{p}{pri}: $text\n";
344 root 1.1 $self->{log} .= "$time $text\n";
345     }
346    
347     sub feedback {
348     my ($self, $prompt) = @_;
349     $self->{input} = [$Coro::current, $prompt];
350     Coro::schedule;
351     }
352    
353     sub show {
354     my ($self) = @_;
355    
356     "ID: $self->{id}\n"
357     . "Title: $self->{p}{title}\n"
358     . "Blocks#: " . @{$self->{p}{blk}} . "\n"
359     . "Blocks: " . (join "", map {
360     $_->{done} ? "+" : "-"
361     } @{$self->{p}{blk}}) . "\n" .
362 root 1.11 ""
363 root 1.1 }
364    
365 root 1.14 our $id;
366 root 1.1
367     sub MAXSEG (){ 128*1024*1024 }
368     sub MINSEG (){ 6* 128*1024 }
369    
370     sub blocksize($) {
371     return
372     $_[0] >= 64*1024*1024 ? 1024*1024
373     : $_[0] >= 32*1024*1024 ? 512*1024
374     : $_[0] >= 1024*1024 ? 256*1024
375     : 128*1024;
376     }
377    
378     sub start {
379     my ($self) = @_;
380    
381     $self->{p}{pri} ||= 1;
382     $self->{p}{state} ||= "examine";
383    
384 root 1.9 $self->{job} =~ /^(.*)\.j$/ or die "$self->{job}: missing .j";
385     $self->{file} = "$1.d";
386     $self->{fh} = aio_open $self->{file}, O_RDWR|O_CREAT, 0600
387 root 1.1 or die "$self->{file}: $!";
388    
389     $self->{status} = "starting";
390     $self->{coro} = async {
391     for(;;) {
392     my ($state, @args) = ref $self->{p}{state} ? @{$self->{p}{state}} : $self->{p}{state};
393     my $next = eval { $self->can ("state_$state")->($self, @args) };
394     if ($@) {
395     $self->log ($@);
396     $next = $self->feedback ("continue with state: ");
397     }
398 root 1.10 $self->log ($self->{status} = "STATE CHANGE: ". join ", ", ref $next ? @$next : $next);
399 root 1.1 $self->{p}{state} = $next;
400     $self->save;
401     }
402     };
403     }
404    
405     sub state_finish {
406     my ($self, $save) = @_;
407    
408     if ($save) {
409 root 1.10 aio_fsync $self->{fh};
410 root 1.1 close $self->{fh};
411    
412 root 1.10 aio_unlink "$DONE_HOME/$self->{p}{title}";
413     aio_link $self->{file}, "$DONE_HOME/$self->{p}{title}"
414     and die "link: $self->{file} => $DONE_HOME/$self->{p}{title}: $!";
415 root 1.1 system "sync";
416     }
417     $self->clean;
418    
419 root 1.10 aio_unlink $self->{file};
420 root 1.1
421     $self->{status} = "finished";
422     $self->feedback ("finished");
423     terminate;
424     }
425    
426     sub state_examine {
427     my ($self) = @_;
428     my $p = $self->{p};
429    
430     $self->{status} = "initial fetch";
431    
432     for (;;) {
433     $self->log ("fetching $p->{key} (=$p->{title})");
434    
435     ($p->{meta}, $p->{data}) = fetch_uri 100, "freenet:$p->{key}";
436     $self->save;
437     #use PApp::Util; print STDERR PApp::Util::dumpval [keys %{$meta->{document}[0]{split_file}}];
438     $self->log ("type $p->{meta}{document}[0]{info}{format}");
439    
440     if (my $splitfile = $p->{meta}{document}[0]{split_file}) {
441     return "splitfile";
442     } elsif ((defined $p->{data}) and (length $p->{data})) {
443 root 1.8 syswrite $self->{fh}, delete $p->{data};
444 root 1.12 aio_fsync $self->{fh};
445 root 1.1 return ["finish", 1];
446     }
447    
448     $self->log ("EMPTY, retrying in an hour");
449     Coro::Timer::sleep 3600;
450     }
451    
452     }
453    
454     sub state_splitfile {
455     my ($self) = @_;
456     my $p = $self->{p};
457    
458     my $splitfile = $p->{meta}{document}[0]{split_file};
459     my $filesize = hex $splitfile->{size};
460    
461     if ($splitfile->{algo_name} eq "OnionFEC_a_1_2") {
462     my $data_packets = hex $splitfile->{block_count};
463     my $check_packets = hex $splitfile->{check_block_count};
464    
465     my $blk = ($p->{blk} ||= []);
466    
467     unless (@$blk) {
468     for (1..$data_packets) {
469     push @$blk, {
470     uri => $splitfile->{block}{sprintf "%x", $_},
471     };
472     }
473     for (1..$check_packets) {
474     push @$blk, {
475     uri => $splitfile->{check_block}{sprintf "%x", $_},
476     };
477     }
478     }
479    
480     my @segments;
481     my $segments = 0;
482    
483     {
484     # that is a horrible algorithm :(, these freenet freaks are... java-disabled
485     # hardcoding lots of magic parameters is soo dumb.
486     my $size = $filesize;
487     my $offset = 0;
488     my $offset2 = ($filesize & ~(1024*1024-1)) + 1024*1024; # leave enough space after last data block
489     my $idx = 0;
490     my $idx2 = $data_packets;
491     my @redundandy = (0,1,2); # maybe OnionFAC_a_1_2 means 1/2 redundancy(?)
492    
493     while ($size > 0) {
494     my $segsize = $size >= MAXSEG ? MAXSEG : $size <= MINSEG ? MINSEG : $size;
495     my $blksize = blocksize $segsize;
496     my $seg =
497     {
498     id => $segments++,
499     todo => int (($segsize + $blksize - 1) / $blksize),
500     done => 0,
501     blk => [],
502     blksize => $blksize,
503     };
504    
505     push @segments, $seg;
506     $size -= $segsize;
507    
508     while ($segsize > 0) {
509     push @{$seg->{blk}}, $idx;
510     for ($blk->[$idx++]) {
511     $_->{offset} = $offset;
512     #$_->{size} = $blksize > $segsize ? $segsize : $blksize; # WRONG
513     $_->{size} = $blksize;
514     $_->{seg} = $seg;
515     }
516    
517     $segsize -= $blksize;
518     $offset += $blksize;
519    
520     if (($redundandy[0] += $redundandy[1]) >= $redundandy[2]) {
521     $redundandy[0] -= $redundandy[2];
522    
523     push @{$seg->{blk}}, $idx2;
524     for ($blk->[$idx2++]) {
525     $_->{offset} = $offset2;
526     $_->{size} = $blksize;
527     $_->{seg} = $seg;
528     }
529    
530     $offset2 += $blksize;
531     }
532     }
533     }
534    
535     $idx == $data_packets
536     or die "$self->{id}/$p->{tile} $self->{job}\nidx $idx != data_packets $data_packets";
537     $idx2 == $data_packets + $check_packets
538     or die "$self->{id}/$p->{tile} $self->{job}\nidx2 $idx2 != data_packets $data_packets + check_packets $check_packets";
539     }
540    
541     for (@$blk) {
542     ++$_->{seg}{done} if $_->{done};
543     delete $_->{htl};
544     }
545    
546     my $fail = 0;
547     my $sig = new Coro::Signal;
548    
549     $self->{status} = "splitfile fetch (" . @$blk . " blocks)";
550    
551     my @txn;
552    
553     for (;;) {
554     for my $id (0 .. $#$blk) {
555     my $blk = $blk->[$id];
556    
557     next if $txn[$id] || $blk->{done} || $blk->{seg}{todo} <= $blk->{seg}{done};
558    
559     my $htl = $HTL[$blk->{htl}++ % @HTL];
560 root 1.5 my $pri = int time + $htl / 20 * 7200 * rand;
561 root 1.1
562 root 1.5 #warn $self->{id} . ", GET<$htl, $pri>\n";#d#
563 root 1.15 $txn[$id] ||= txn_client_get pri => $pri, uri => $blk->{uri}, htl => $htl, cb => sub {
564 root 1.1 undef $txn[$id];
565    
566     my $seg = $blk->{seg};
567    
568     my ($meta, $data) = eval { @{ $_[0]->result } };
569    
570     if (defined $data) {
571     $blk->{size} == length $data
572     or die sprintf "block $id expected size %d, got %d\n", $blk->{size}, length $data;
573    
574 root 1.9 (length $data) == (aio_write $self->{fh}, $blk->{offset}, (length $data), $data, 0)
575 root 1.1 or die "unable to write chunk to disk, not setting valid flag";
576 root 1.9 aio_fsync $self->{fh};
577 root 1.1
578     $blk->{done} = 1;
579     $blk->{meta} = $meta->{raw} if length $meta->{raw};#d#
580     $seg->{done}++;
581     $self->save;
582    
583     $::htl_sum += $htl;
584     $::htl_cnt++;
585    
586 root 1.4 $self->log (sprintf "got block $seg->{id}.$id %d ($seg->{done}/$seg->{todo}) at htl $htl (%f) and pri $pri",
587     length $data, $::htl_sum / $::htl_cnt);
588 root 1.1 } else {
589     if ($@) {
590     if ($@->type ("data_not_found")) {
591     # nop
592     } elsif ($@->type ("network_error")) {
593     $self->log ("$@, retrying in 1s");
594     CORE::sleep 1;
595     } else {
596     $self->log ("$@");
597     }
598     }
599     ++$fail;
600     }
601     $self->{status} = "splitfile fetch ($seg->{done}/$seg->{todo}, $fail failed)";
602    
603     $sig->send;
604 root 1.15 };
605 root 1.1 }
606    
607     for my $seg (@segments) {
608     if ($seg->{done} >= $seg->{todo} && !$seg->{finished}) {
609    
610     $self->log ("segment done, cancelling segment $seg->{id}");
611     for my $id (@{$seg->{blk}}) {
612     (delete $txn[$id])->cancel if $txn[$id];
613     }
614    
615 root 1.10 my $verify;
616 root 1.1 for my $id (@{$seg->{blk}}) {
617     my $blk = $blk->[$id];
618     if ($blk->{done}) {
619 root 1.10 aio_read $self->{fh}, $blk->{offset}, $blk->{size}, my $buf, 0;
620 root 1.1
621     my $k1 = Net::FCP::Util::extract_chk_hash $blk->{uri};
622     my $k2 = Net::FCP::Util::generate_chk_hash $blk->{meta}, $buf;
623    
624     if ($k1 ne $k2) {
625 root 1.10 $verify .= "v";
626 root 1.1 #warn sprintf "$p->{title} block $id BROKEN (%s != %s)", (unpack "H*", $k1), (unpack "H*", $k2);
627     $blk->{done} = 0;
628     $seg->{done}--;
629     $self->save;
630     } else {
631 root 1.10 $verify .= "V";
632 root 1.1 }
633     }
634     }
635 root 1.10 $self->log ("verified segment $seg->{id}");
636     $self->log ($verify);
637 root 1.1
638     if ($seg->{done} >= $seg->{todo} && !$seg->{finished}) {
639     $self->log ("verified segment OK $seg->{id}");
640     $seg->{finished}++;
641     $segments--;
642     } else {
643     $self->log ("verified segment NOT OK $seg->{id}");
644     }
645     }
646     }
647    
648     last unless $segments;
649    
650     $sig->wait;
651     }
652    
653     $self->log ("decoding < $self->{job} $self->{file} $filesize >");
654    
655     for my $seg (@segments) {
656     my @part;
657     my @idx;
658     my @blk = map $blk->[$_], sort { $a <=> $b } @{$seg->{blk}};
659    
660     for my $id (0 .. $#blk) {
661     my $blk = $blk[$id];
662     next unless $blk->{done};
663    
664     push @part, [$self->{fh}, $blk->{offset}];
665     push @idx, $id;
666    
667     last if @idx == $seg->{todo};
668     }
669    
670     my $fec = new Algorithm::FEC
671     $seg->{todo},
672     scalar @blk,
673     $seg->{blksize};
674    
675     $fec->shuffle (\@part, \@idx);
676    
677     # now copy check blocks to their destination position
678     for my $i (0 .. $#idx) {
679     next if $idx[$i] == $i;
680    
681     my $src = $part[$i];
682     $part[$i] = [$self->{fh}, $blk[$i]{offset}];
683     $fec->copy ($src, $part[$i]);
684     }
685    
686     $fec->set_decode_blocks (\@part, \@idx);
687     $fec->decode;
688     }
689    
690     my $sha1 = new Digest::SHA1;
691     open my $dd, "-|", "head -c$filesize \Q$self->{file}\E"
692     or die "DD: $!";
693     #$dd = Coro::Handle::unblock $dd;
694     $sha1->addfile ($dd);
695     $sha1 = $sha1->hexdigest;
696    
697     if (exists $p->{meta}{document}[0]{info}{checksum}
698     and $p->{meta}{document}[0]{info}{checksum} ne $sha1) {
699     $self->log ("META: $p->{meta}{document}[0]{info}{checksum} and real checksum $sha1 for $filesize DIFFER");
700 root 1.9 # $self->feedback ("CHECKSUM ERROR");
701     # terminate;
702 root 1.1 }
703    
704     truncate $self->{fh}, $filesize;
705     sysseek $self->{fh}, 0, 0;
706    
707     return ["finish", 1];
708     } else {
709     $self->log ("splitfile algo '$splitfile->{algo_name}' unknown");
710     $self->feedback ("algo unknown");
711     terminate;
712     }
713     }
714    
715     package main;
716    
717     $|=1;
718    
719 root 1.13 async {
720     for (<\Q$QUEUE_HOME\E/*.j>) {
721     job->new_from_file ($_);
722     print "J";
723     }
724     print "\n";
725     };
726 root 1.1
727     open my $stdin , "<&0" or die;
728     open my $stdout, ">&1" or die;
729     cmdline unblock $stdin, unblock $stdout;
730    
731     &Coro::Event::loop;
732