package AnyEvent::Fork::RPC::Async;

use common::sense; # actually required to avoid spurious warnings...

our $VERSION = 2; # protocol version

use Errno ();

use AnyEvent;

# declare only
sub AnyEvent::Fork::RPC::event;
sub AnyEvent::Fork::RPC::flush;

sub do_exit { exit } # workaround for perl 5.14 and below

sub run {
   my %kv = splice @_, pop;

   my $rfh = shift;
   my $wfh = fileno $rfh ? $rfh : *STDOUT;

   my $function   = delete $kv{function};
   my $serialiser = delete $kv{serialiser};
   my $rlen       = delete $kv{rlen};
   my $done       = delete $kv{done};

   $0 =~ s/^(\d+).*$/$1 $function/s;

   {
      package main;
      my $init = delete $kv{init};
      &$init if length $init;
      $function = \&$function; # resolve function early for extra speed
   }

   %kv = (); # save some very small amount of memory

   my $busy = 1; # exit when == 0

   my ($f, $t) = eval $serialiser; AE::log fatal => $@ if $@;
   my ($wbuf, $ww);

   my $wcb = sub {
      my $len = syswrite $wfh, $wbuf;

      unless (defined $len) {
         if ($! != Errno::EAGAIN && $! != Errno::EWOULDBLOCK) {
            undef $ww;
            AE::log fatal => "AnyEvent::Fork::RPC: write error ($!), parent gone?";
         }
      }

      substr $wbuf, 0, $len, "";

      unless (length $wbuf) {
         undef $ww;
         unless ($busy) {
            shutdown $wfh, 1;
            @_ = (); goto &$done;
         }
      }
   };

   my $write = sub {
      $wbuf .= $_[0];
      $ww ||= AE::io $wfh, 1, $wcb;
   };

   *AnyEvent::Fork::RPC::flush = sub {
      while (length $wbuf) {
         my $len = syswrite $wfh, $wbuf;

         if (defined $len) {
            substr $wbuf, 0, $len, "";
         } elsif ($! == Errno::EAGAIN || $! == Errno::EWOULDBLOCK) {
            my $fdset;
            (vec $fdset, fileno $wfh, 1) = 1;
            # buggy windows often sets exceptfds instead of wfds
            select undef, my $wset = $fdset, my $eset = $fdset, undef;
         } else {
            return 0;
         }
      }

      1
   };

   *AnyEvent::Fork::RPC::event = sub {
      $write->(pack "NN/a*", 0, &$f);
   };

   my ($rbuf, $rw);

   my $len;

   $rw = AE::io $rfh, 0, sub {
      $rlen = $rlen * 2 + 16 if $rlen - 128 < length $rbuf;
      $len = sysread $rfh, $rbuf, $rlen - length $rbuf, length $rbuf;

      if ($len) {
         while (8 <= length $rbuf) {
            (my $id, $len) = unpack "NN", $rbuf;
            8 + $len <= length $rbuf
               or last;

            my @r = $t->(substr $rbuf, 8, $len);
            substr $rbuf, 0, 8 + $len, "";
            
            ++$busy;
            $function->(sub {
               --$busy;
               $write->(pack "NN/a*", $id, &$f);
            }, @r);
         }
      } elsif (defined $len or $! == Errno::EINVAL) { # EINVAL is for microshit windoze
         undef $rw;
         --$busy;
         $ww ||= AE::io $wfh, 1, $wcb;
      } elsif ($! != Errno::EAGAIN && $! != Errno::EWOULDBLOCK) {
         undef $rw;
         AE::log fatal => "AnyEvent::Fork::RPC: read error in child: $!";
      }
   };

   $AnyEvent::MODEL eq "AnyEvent::Impl::EV"
      ? EV::run ()
      : AE::cv->recv;
}

1

