--- IO-AIO/AIO.pm 2007/10/03 21:27:51 1.116 +++ IO-AIO/AIO.pm 2007/12/02 21:51:36 1.120 @@ -28,10 +28,13 @@ my $grp = aio_group sub { print "all stats done\n" }; add $grp aio_stat "..." for ...; - # AnyEvent integration + # AnyEvent integration (EV, Event, Glib, Tk, urxvt, pureperl...) open my $fh, "<&=" . IO::AIO::poll_fileno or die "$!"; my $w = AnyEvent->io (fh => $fh, poll => 'r', cb => sub { IO::AIO::poll_cb }); + # EV integration + my $w = EV::io IO::AIO::poll_fileno, EV::READ, \&IO::AIO::poll_cb; + # Event integration Event->io (fd => IO::AIO::poll_fileno, poll => 'r', @@ -185,19 +188,24 @@ package IO::AIO; +use Carp (); + no warnings; use strict 'vars'; use base 'Exporter'; BEGIN { - our $VERSION = '2.5'; + our $VERSION = '2.6'; + + our @AIO_REQ = qw(aio_sendfile aio_read aio_write aio_open aio_close + aio_stat aio_lstat aio_unlink aio_rmdir aio_readdir + aio_scandir aio_symlink aio_readlink aio_sync aio_fsync + aio_fdatasync aio_pathsync aio_readahead + aio_rename aio_link aio_move aio_copy aio_group + aio_nop aio_mknod aio_load aio_rmtree aio_mkdir aio_chown + aio_chmod aio_utime aio_truncate); - our @AIO_REQ = qw(aio_sendfile aio_read aio_write aio_open aio_close aio_stat - aio_lstat aio_unlink aio_rmdir aio_readdir aio_scandir aio_symlink - aio_readlink aio_fsync aio_fdatasync aio_readahead aio_rename aio_link - aio_move aio_copy aio_group aio_nop aio_mknod aio_load aio_rmtree aio_mkdir - aio_chown aio_chmod aio_utime aio_truncate); our @EXPORT = (@AIO_REQ, qw(aioreq_pri aioreq_nice aio_block)); our @EXPORT_OK = qw(poll_fileno poll_cb poll_wait flush min_parallel max_parallel max_idle @@ -315,17 +323,65 @@ Asynchronously close a file and call the callback with the result code. -Unlike the other functions operating on files, this function uses the -PerlIO layer to close the filehandle. The reason is that the PerlIO API -insists on closing the underlying fd itself, no matter what, and doesn't -allow modifications to the fd. Unfortunately, it is not clear that you can -call PerlIO from different threads (actually, its quite clear that this -won't work in some cases), so while it likely works perfectly with simple -file handles (such as the ones created by C) it might fail in -interesting ways for others. +Unfortunately, you can't do this to perl. Perl I very strongly on +closing the file descriptor associated with the filehandle itself. Here is +what aio_close will try: + + 1. dup()licate the fd + 2. asynchronously close() the duplicated fd + 3. dup()licate the fd once more + 4. let perl close() the filehandle + 5. asynchronously close the duplicated fd + +The idea is that the first close() flushes stuff to disk that closing an +fd will flush, so when perl closes the fd, nothing much will need to be +flushed. The second async. close() will then flush stuff to disk that +closing the last fd to the file will flush. + +Just FYI, SuSv3 has this to say on close: + + All outstanding record locks owned by the process on the file + associated with the file descriptor shall be removed. + + If fildes refers to a socket, close() shall cause the socket to be + destroyed. ... close() shall block for up to the current linger + interval until all data is transmitted. + [this actually sounds like a specification bug, but who knows] + +And at least Linux additionally actually flushes stuff on every close, +even when the file itself is still open. + +Sounds enourmously inefficient and complicated? Yes... please show me how +to nuke perl's fd out of existence... + +=cut + +sub aio_close($;$) { + aio_block { + my ($fh, $cb) = @_; + + my $pri = aioreq_pri; + my $grp = aio_group $cb; + + my $fd = fileno $fh; + + defined $fd or Carp::croak "aio_close called with fd-less filehandle"; -Having said that, aio_close tries to clean up the filehandle as much as -possible before handing it to an io thread, and generally does work. + # if the dups fail we will simply get EBADF + my $fd2 = _dup $fd; + aioreq_pri $pri; + add $grp _aio_close $fd2, sub { + my $fd2 = _dup $fd; + close $fh; + aioreq_pri $pri; + add $grp _aio_close $fd2, sub { + $grp->result ($_[0]); + }; + }; + + $grp + } +} =item aio_read $fh,$offset,$length, $data,$dataoffset, $callback->($retval) @@ -599,7 +655,9 @@ utime $stat[8], $stat[9], $dst; chmod $stat[2] & 07777, $dst_fh; chown $stat[4], $stat[5], $dst_fh; - close $dst_fh; + + aioreq_pri $pri; + add $grp aio_close $dst_fh; } else { $grp->result (-1); close $src_fh; @@ -842,6 +900,10 @@ } } +=item aio_sync $callback->($status) + +Asynchronously call sync and call the callback when finished. + =item aio_fsync $fh, $callback->($status) Asynchronously call fsync on the given filehandle and call the callback @@ -855,6 +917,46 @@ If this call isn't available because your OS lacks it or it couldn't be detected, it will be emulated by calling C instead. +=item aio_pathsync $path, $callback->($status) + +This request tries to open, fsync and close the given path. This is a +composite request intended tosync directories after directory operations +(E.g. rename). This might not work on all operating systems or have any +specific effect, but usually it makes sure that directory changes get +written to disc. It works for anything that can be opened for read-only, +not just directories. + +Passes C<0> when everything went ok, and C<-1> on error. + +=cut + +sub aio_pathsync($;$) { + aio_block { + my ($path, $cb) = @_; + + my $pri = aioreq_pri; + my $grp = aio_group $cb; + + aioreq_pri $pri; + add $grp aio_open $path, O_RDONLY, 0, sub { + my ($fh) = @_; + if ($fh) { + aioreq_pri $pri; + add $grp aio_fsync $fh, sub { + $grp->result ($_[0]); + + aioreq_pri $pri; + add $grp aio_close $fh; + }; + } else { + $grp->result (-1); + } + }; + + $grp + } +} + =item aio_group $callback->(...) This is a very special aio request: Instead of doing something, it is a @@ -1000,7 +1102,7 @@ =item $grp->result (...) Set the result value(s) that will be passed to the group callback when all -subrequests have finished and set thre groups errno to the current value +subrequests have finished and set the groups errno to the current value of errno (just like calling C without an error number). By default, no argument will be passed and errno is zero.