=head1 NAME

Coro::AIO - truly asynchronous file and directrory I/O

=head1 SYNOPSIS

   use Coro::AIO;

   # can now use any of the aio requests your IO::AIO module supports.

   # read 1MB of /etc/passwd, without blocking other coroutines
   my $fh = aio_open "/etc/passwd", O_RDONLY, 0
      or die "/etc/passwd: $!";
   aio_read $fh, 0, 1_000_000, my $buf, 0
      or die "aio_read: $!";
   aio_close $fh;

=head1 DESCRIPTION

This module implements a thin wrapper around L<IO::AIO|IO::AIO>. All of
the functions that expect a callback are being wrapped by this module.

The API is exactly the same as that of the corresponding IO::AIO routines,
except that you have to specify I<all> arguments I<except> the callback
argument. Instead the routines return the values normally passed to the
callback. Everything else, including C<$!> and perls stat cache, are set
as expected after these functions return.

You can mix calls to C<IO::AIO> functions with calls to this module. You
I<must not>, however, call these routines from within IO::AIO callbacks,
as this causes a deadlock. Start a coro inside the callback instead.

You also can, but do not need to, call C<IO::AIO::poll_cb>, as this
module automatically installs an event watcher for the C<IO::AIO> file
descriptor. It uses the L<AnyEvent|AnyEvent> module for this, so please
refer to its documentation on how it selects an appropriate Event module.

All other functions exported by default by IO::AIO (e.g. C<aioreq_pri>)
will be exported by default by Coro::AIO, too.

Functions that can be optionally imported from IO::AIO can be imported
from Coro::AIO or can be called directly, e.g. C<Coro::AIO::nreqs>.

You cannot specify priorities with C<aioreq_pri>, as this module
overwrites the request priority with the current coroutine priority at all
times.

For your convienience, here are the changed function signatures for most
of the requests, for documentation of these functions please have a look
at L<IO::AIO|the IO::AIO manual>.

The AnyEvent watcher can be disabled by executing C<undef
$Coro::AIO::WATCHER>. Please notify the author of when and why you think
this was necessary.

=over 4

=cut

package Coro::AIO;

use strict qw(subs vars);

use Coro ();
use AnyEvent;
use IO::AIO ();

use base Exporter::;

our $WATCHER;

if (AnyEvent::detect =~ /^AnyEvent::Impl::(?:Coro)?EV$/) {
   $WATCHER = EV::io (IO::AIO::poll_fileno, &EV::READ, \&IO::AIO::poll_cb);
} else {
   our $FH; open $FH, "<&=" . IO::AIO::poll_fileno;
   $WATCHER = AnyEvent->io (fh => $FH, poll => 'r', cb => \&IO::AIO::poll_cb);
}

our @EXPORT    = @IO::AIO::EXPORT;
our @EXPORT_OK = @IO::AIO::EXPORT_OK;
our $AUTOLOAD;

{
   my @reqs = @IO::AIO::AIO_REQ ? @IO::AIO::AIO_REQ : @EXPORT;
   my %reqs = map +($_ => 1), @reqs;

   eval
      join "",
         map "sub $_(" . (prototype "IO::AIO::$_") . ");",
            grep !$reqs{$_},
                @EXPORT, @EXPORT_OK;

   for my $sub (@reqs) {
      push @EXPORT, $sub;
      
      my $iosub = "IO::AIO::$sub";
      my $proto = prototype $iosub;

      $proto =~ s/;?\$$// or die "$iosub: unable to remove callback slot from prototype";

      eval qq{
#line 1 "Coro::AIO::$sub($proto)"
         sub $sub($proto) {
            my \$current = \$Coro::current;
            my \$state;
            my \@res;

            push \@_, sub {
               \$state = _get_state;
               \@res = \@_;
               \$current->ready;
            };

            aioreq_pri \$Coro::current->prio;
            &$iosub;

            &Coro::schedule;
            &Coro::schedule while !\$state;

            _set_state \$state;
            wantarray ? \@res : \$res[0]
         }
      };
      die if $@;
   }
}

sub AUTOLOAD {
   (my $func = $AUTOLOAD) =~ s/^.*:://;
   *$AUTOLOAD = \&{"IO::AIO::$func"};
   goto &$AUTOLOAD;
}

=item $fh = aio_open $pathname, $flags, $mode

=item $status = aio_close $fh

=item $retval = aio_read  $fh,$offset,$length, $data,$dataoffset

=item $retval = aio_write $fh,$offset,$length, $data,$dataoffset

=item $retval = aio_sendfile $out_fh, $in_fh, $in_offset, $length

=item $retval = aio_readahead $fh,$offset,$length

=item $status = aio_stat $fh_or_path
      
=item $status = aio_lstat $fh

=item $status = aio_unlink $pathname

=item $status = aio_rmdir $pathname

=item $entries = aio_readdir $pathname

=item ($dirs, $nondirs) = aio_scandir $path, $maxreq

=item $status = aio_fsync $fh

=item $status = aio_fdatasync $fh

=item ... = aio_xxx ...

Any additional aio requests follow the same scheme: same parameters except
you must not specify a callback but instead get the callback arguments as
return values.

=back

=head1 SEE ALSO

L<Coro::Socket> and L<Coro::Handle> for non-blocking socket operation.

=head1 AUTHOR

 Marc Lehmann <schmorp@schmorp.de>
 http://home.schmorp.de/

=cut

1
