--- IO-AIO/AIO.pm 2005/08/16 23:33:34 1.28 +++ IO-AIO/AIO.pm 2006/10/22 00:49:29 1.55 @@ -17,6 +17,15 @@ $_[0] > 0 or die "read error: $!"; }; + use IO::AIO 2; # version has aio objects + + my $req = aio_unlink "/tmp/file", sub { }; + $req->cancel; # cancel request if still in queue + + # AnyEvent + open my $fh, "<&=" . IO::AIO::poll_fileno or die "$!"; + my $w = AnyEvent->io (fh => $fh, poll => 'r', cb => sub { IO::AIO::poll_cb }); + # Event Event->io (fd => IO::AIO::poll_fileno, poll => 'r', @@ -59,20 +68,23 @@ package IO::AIO; no warnings; +use strict 'vars'; use base 'Exporter'; -use Fcntl (); - BEGIN { - $VERSION = 1.2; + our $VERSION = '2.0'; + + our @EXPORT = 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_fsync aio_fdatasync aio_readahead aio_rename aio_link aio_move + aio_group); + our @EXPORT_OK = qw(poll_fileno poll_cb min_parallel max_parallel max_outstanding nreqs); - @EXPORT = qw(aio_read aio_write aio_open aio_close aio_stat aio_lstat aio_unlink - aio_rmdir aio_symlink aio_fsync aio_fdatasync aio_readahead); - @EXPORT_OK = qw(poll_fileno poll_cb min_parallel max_parallel max_outstanding nreqs); + @IO::AIO::GRP::ISA = 'IO::AIO::REQ'; require XSLoader; - XSLoader::load IO::AIO, $VERSION; + XSLoader::load ("IO::AIO", $VERSION); } =head1 FUNCTIONS @@ -90,6 +102,9 @@ All functions expecting a filehandle keep a copy of the filehandle internally until the request has finished. +All requests return objects of type L that allow further +manipulation of those requests while they are in-flight. + The pathnames you pass to these routines I be absolute and encoded in byte form. The reason for the former is that at the time the request is being executed, the current working directory could have @@ -105,7 +120,7 @@ =over 4 -=item aio_open $pathname, $flags, $mode, $callback +=item aio_open $pathname, $flags, $mode, $callback->($fh) Asynchronously open or create a file and call the callback with a newly created filehandle for the file. @@ -132,7 +147,7 @@ } }; -=item aio_close $fh, $callback +=item aio_close $fh, $callback->($status) Asynchronously close a file and call the callback with the result code. I although accepted, you should not pass in a perl @@ -143,15 +158,19 @@ This is supposed to be a bug in the API, so that might change. It's therefore best to avoid this function. -=item aio_read $fh,$offset,$length, $data,$dataoffset,$callback +=item aio_read $fh,$offset,$length, $data,$dataoffset, $callback->($retval) -=item aio_write $fh,$offset,$length, $data,$dataoffset,$callback +=item aio_write $fh,$offset,$length, $data,$dataoffset, $callback->($retval) Reads or writes C bytes from the specified C and C into the scalar given by C and offset C and calls the callback without the actual number of bytes read (or -1 on error, just like the syscall). +The C<$data> scalar I be modified in any way while the request +is outstanding. Modifying it can result in segfaults or WW3 (if the +necessary/optional hardware is installed). + Example: Read 15 bytes at offset 7 into scalar C<$buffer>, starting at offset C<0> within the scalar: @@ -160,7 +179,98 @@ print "read $_[0] bytes: <$buffer>\n"; }; -=item aio_readahead $fh,$offset,$length, $callback +=item aio_move $srcpath, $dstpath, $callback->($status) + +Try to move the I (directories not supported as either source or +destination) from C<$srcpath> to C<$dstpath> and call the callback with +the C<0> (error) or C<-1> ok. + +This is a composite request that tries to rename(2) the file first. If +rename files with C, it creates the destination file with mode 0200 +and copies the contents of the source file into it using C, +followed by restoring atime, mtime, access mode and uid/gid, in that +order, and unlinking the C<$srcpath>. + +If an error occurs, the partial destination file will be unlinked, if +possible, except when setting atime, mtime, access mode and uid/gid, where +errors are being ignored. + +=cut + +sub aio_move($$$) { + my ($src, $dst, $cb) = @_; + + my $grp = aio_group; + + add $grp aio_rename $src, $dst, sub { + if ($_[0] && $! == EXDEV) { + add $grp aio_open $src, O_RDONLY, 0, sub { + if (my $src_fh = $_[0]) { + my @stat = stat $src_fh; + + add $grp aio_open $dst, O_WRONLY, 0200, sub { + if (my $dst_fh = $_[0]) { + add $grp aio_sendfile $dst_fh, $src_fh, 0, $stat[7], sub { + close $src_fh; + + if ($_[0] == $stat[7]) { + utime $stat[8], $stat[9], $dst; + chmod $stat[2] & 07777, $dst_fh; + chown $stat[4], $stat[5], $dst_fh; + close $dst_fh; + + add $grp aio_unlink $src, sub { + $cb->($_[0]); + }; + } else { + my $errno = $!; + add $grp aio_unlink $dst, sub { + $! = $errno; + $cb->(-1); + }; + } + }; + } else { + $cb->(-1); + } + }, + + } else { + $cb->(-1); + } + }; + } else { + $cb->($_[0]); + } + }; + + $grp +} + +=item aio_sendfile $out_fh, $in_fh, $in_offset, $length, $callback->($retval) + +Tries to copy C<$length> bytes from C<$in_fh> to C<$out_fh>. It starts +reading at byte offset C<$in_offset>, and starts writing at the current +file offset of C<$out_fh>. Because of that, it is not safe to issue more +than one C per C<$out_fh>, as they will interfere with each +other. + +This call tries to make use of a native C syscall to provide +zero-copy operation. For this to work, C<$out_fh> should refer to a +socket, and C<$in_fh> should refer to mmap'able file. + +If the native sendfile call fails or is not implemented, it will be +emulated, so you can call C on any type of filehandle +regardless of the limitations of the operating system. + +Please note, however, that C can read more bytes from +C<$in_fh> than are written, and there is no way to find out how many +bytes have been read from C alone, as C only +provides the number of bytes written to C<$out_fh>. Only if the result +value equals C<$length> one can assume that C<$length> bytes have been +read. + +=item aio_readahead $fh,$offset,$length, $callback->($retval) C populates the page cache with data from a file so that subsequent reads from that file will not block on disk I/O. The C<$offset> @@ -174,9 +284,9 @@ If that syscall doesn't exist (likely if your OS isn't Linux) it will be emulated by simply reading the data, which would have a similar effect. -=item aio_stat $fh_or_path, $callback +=item aio_stat $fh_or_path, $callback->($status) -=item aio_lstat $fh, $callback +=item aio_lstat $fh, $callback->($status) Works like perl's C or C in void context. The callback will be called after the stat and the results will be available using C @@ -196,22 +306,197 @@ print "size is ", -s _, "\n"; }; -=item aio_unlink $pathname, $callback +=item aio_unlink $pathname, $callback->($status) Asynchronously unlink (delete) a file and call the callback with the result code. -=item aio_rmdir $pathname, $callback +=item aio_link $srcpath, $dstpath, $callback->($status) + +Asynchronously create a new link to the existing object at C<$srcpath> at +the path C<$dstpath> and call the callback with the result code. + +=item aio_symlink $srcpath, $dstpath, $callback->($status) + +Asynchronously create a new symbolic link to the existing object at C<$srcpath> at +the path C<$dstpath> and call the callback with the result code. + +=item aio_rename $srcpath, $dstpath, $callback->($status) + +Asynchronously rename the object at C<$srcpath> to C<$dstpath>, just as +rename(2) and call the callback with the result code. + +=item aio_rmdir $pathname, $callback->($status) Asynchronously rmdir (delete) a directory and call the callback with the result code. -=item aio_fsync $fh, $callback +=item aio_readdir $pathname, $callback->($entries) + +Unlike the POSIX call of the same name, C reads an entire +directory (i.e. opendir + readdir + closedir). The entries will not be +sorted, and will B include the C<.> and C<..> entries. + +The callback a single argument which is either C or an array-ref +with the filenames. + +=item aio_scandir $path, $maxreq, $callback->($dirs, $nondirs) + +Scans a directory (similar to C) but additionally tries to +separate the entries of directory C<$path> into two sets of names, ones +you can recurse into (directories or links to them), and ones you cannot +recurse into (everything else). + +C is a composite request that consists of many sub +requests. C<$maxreq> specifies the maximum number of outstanding aio +requests that this function generates. If it is C<< <= 0 >>, then a +suitable default will be chosen (currently 8). + +On error, the callback is called without arguments, otherwise it receives +two array-refs with path-relative entry names. + +Example: + + aio_scandir $dir, 0, sub { + my ($dirs, $nondirs) = @_; + print "real directories: @$dirs\n"; + print "everything else: @$nondirs\n"; + }; + +Implementation notes. + +The C cannot be avoided, but C'ing every entry can. + +After reading the directory, the modification time, size etc. of the +directory before and after the readdir is checked, and if they match (and +isn't the current time), the link count will be used to decide how many +entries are directories (if >= 2). Otherwise, no knowledge of the number +of subdirectories will be assumed. + +Then entries will be sorted into likely directories (everything without +a non-initial dot currently) and likely non-directories (everything +else). Then every entry plus an appended C will be C'ed, +likely directories first. If that succeeds, it assumes that the entry +is a directory or a symlink to directory (which will be checked +seperately). This is often faster than stat'ing the entry itself because +filesystems might detect the type of the entry without reading the inode +data (e.g. ext2fs filetype feature). + +If the known number of directories (link count - 2) has been reached, the +rest of the entries is assumed to be non-directories. + +This only works with certainty on POSIX (= UNIX) filesystems, which +fortunately are the vast majority of filesystems around. + +It will also likely work on non-POSIX filesystems with reduced efficiency +as those tend to return 0 or 1 as link counts, which disables the +directory counting heuristic. + +=cut + +sub aio_scandir($$$) { + my ($path, $maxreq, $cb) = @_; + + my $grp = aio_group; + + $maxreq = 8 if $maxreq <= 0; + + # stat once + add $grp aio_stat $path, sub { + return $cb->() if $_[0]; + my $now = time; + my $hash1 = join ":", (stat _)[0,1,3,7,9]; + + # read the directory entries + add $grp aio_readdir $path, sub { + my $entries = shift + or return $cb->(); + + # stat the dir another time + add $grp aio_stat $path, sub { + my $hash2 = join ":", (stat _)[0,1,3,7,9]; + + my $ndirs; + + # take the slow route if anything looks fishy + if ($hash1 ne $hash2 or (stat _)[9] == $now) { + $ndirs = -1; + } else { + # if nlink == 2, we are finished + # on non-posix-fs's, we rely on nlink < 2 + $ndirs = (stat _)[3] - 2 + or return $cb->([], $entries); + } + + # sort into likely dirs and likely nondirs + # dirs == files without ".", short entries first + $entries = [map $_->[0], + sort { $b->[1] cmp $a->[1] } + map [$_, sprintf "%s%04d", (/.\./ ? "1" : "0"), length], + @$entries]; + + my (@dirs, @nondirs); + + my ($statcb, $schedcb); + my $nreq = 0; + + $schedcb = sub { + if (@$entries) { + if ($nreq < $maxreq) { + my $ent = pop @$entries; + $nreq++; + add $grp aio_stat "$path/$ent/.", sub { $statcb->($_[0], $ent) }; + } + } elsif (!$nreq) { + # finished + undef $statcb; + undef $schedcb; + $cb->(\@dirs, \@nondirs) if $cb; + undef $cb; + } + }; + $statcb = sub { + my ($status, $entry) = @_; + + if ($status < 0) { + $nreq--; + push @nondirs, $entry; + &$schedcb; + } else { + # need to check for real directory + add $grp aio_lstat "$path/$entry", sub { + $nreq--; + + if (-d _) { + push @dirs, $entry; + + if (!--$ndirs) { + push @nondirs, @$entries; + $entries = []; + } + } else { + push @nondirs, $entry; + } + + &$schedcb; + } + } + }; + + &$schedcb while @$entries && $nreq < $maxreq; + }; + }; + }; + + $grp +} + +=item aio_fsync $fh, $callback->($status) Asynchronously call fsync on the given filehandle and call the callback with the fsync result code. -=item aio_fdatasync $fh, $callback +=item aio_fdatasync $fh, $callback->($status) Asynchronously call fdatasync on the given filehandle and call the callback with the fdatasync result code. @@ -219,6 +504,112 @@ 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_group $callback->() + +[EXPERIMENTAL] + +This is a very special aio request: Instead of doing something, it is a +container for other aio requests, which is useful if you want to bundle +many requests into a single, composite, request. + +Returns an object of class L. See its documentation below +for more info. + +Example: + + my $grp = aio_group sub { + print "all stats done\n"; + }; + + add $grp + (aio_stat ...), + (aio_stat ...), + ...; + +=item aio_sleep $fractional_seconds, $callback->() *NOT EXPORTED* + +Mainly used for debugging and benchmarking, this aio request puts one of +the request workers to sleep for the given time. + +=back + +=head2 IO::AIO::REQ CLASS + +All non-aggregate C functions return an object of this class when +called in non-void context. + +A request always moves through the following five states in its lifetime, +in order: B (request has been created, but has not been executed +yet), B (request is currently being executed), B +(request has been executed but callback has not been called yet), +B (results are being processed synchronously, includes calling the +callback) and B (request has reached the end of its lifetime and +holds no resources anymore). + +=over 4 + +=item $req->cancel + +Cancels the request, if possible. Has the effect of skipping execution +when entering the B state and skipping calling the callback when +entering the the B state, but will leave the request otherwise +untouched. That means that requests that currently execute will not be +stopped and resources held by the request will not be freed prematurely. + +=back + +=head2 IO::AIO::GRP CLASS + +This class is a subclass of L, so all its methods apply to +objects of this class, too. + +A IO::AIO::GRP object is a special request that can contain multiple other +aio requests. + +You create one by calling the C constructing function with a +callback that will be called when all contained requests have entered the +C state: + + my $grp = aio_group sub { + print "all requests are done\n"; + }; + +You add requests by calling the C method with one or more +C objects: + + $grp->add (aio_unlink "..."); + + add $grp aio_stat "...", sub { ... }; + +This makes it very easy to create composite requests (see the source of +C for an application) that work and feel like simple requests. + +The IO::AIO::GRP objects will be cleaned up during calls to +C, just like any other request. + +They can be canceled like any other request. Canceling will cancel not +just the request itself, but also all requests it contains. + +They can also can also be added to other IO::AIO::GRP objects. + +Their lifetime, simplified, looks like this: when they are empty, they +will finish very quickly. If they contain only requests that are in the +C state, they will also finish. Otherwise they will continue to +exist. + +=over 4 + +=item $grp->add (...) + +=item add $grp ... + +Add one or more +Cancels the request, if possible. Has the effect of skipping execution +when entering the B state and skipping calling the callback when +entering the the B state, but will leave the request otherwise +untouched. That means that requests that currently execute will not be +stopped and resources held by the request will not be freed prematurely. + =back =head2 SUPPORT FUNCTIONS @@ -285,24 +676,29 @@ =item IO::AIO::min_parallel $nthreads -Set the minimum number of AIO threads to C<$nthreads>. The default is -C<1>, which means a single asynchronous operation can be done at one time +Set the minimum number of AIO threads to C<$nthreads>. The current default +is C<4>, which means four asynchronous operations can be done at one time (the number of outstanding operations, however, is unlimited). +IO::AIO starts threads only on demand, when an AIO request is queued and +no free thread exists. + It is recommended to keep the number of threads low, as some Linux kernel versions will scale negatively with the number of threads (higher parallelity => MUCH higher latency). With current Linux 2.6 versions, 4-32 threads should be fine. -Under normal circumstances you don't need to call this function, as this -module automatically starts some threads (the exact number might change, -and is currently 4). +Under most circumstances you don't need to call this function, as the +module selects a default that is suitable for low to moderate load. =item IO::AIO::max_parallel $nthreads -Sets the maximum number of AIO threads to C<$nthreads>. If more than -the specified number of threads are currently running, kill them. This -function blocks until the limit is reached. +Sets the maximum number of AIO threads to C<$nthreads>. If more than the +specified number of threads are currently running, this function kills +them. This function blocks until the limit is reached. + +While C<$nthreads> are zero, aio requests get queued but not executed +until the number of threads has been increased again. This module automatically runs C at program end, to ensure that all threads are killed and that there are no outstanding requests. @@ -316,7 +712,7 @@ some requests have been handled. The default is very large, so normally there is no practical limit. If you -queue up many requests in a loop it it often improves speed if you set +queue up many requests in a loop it often improves speed if you set this to a relatively low number, such as C<100>. Under normal circumstances you don't need to call this function. @@ -351,14 +747,23 @@ =head2 FORK BEHAVIOUR -IO::AIO handles all outstanding AIO requests before the fork, destroys all -AIO threads, and recreates them in both the parent and the child after the -fork. +This module should do "the right thing" when the process using it forks: +Before the fork, IO::AIO enters a quiescent state where no requests +can be added in other threads and no results will be processed. After +the fork the parent simply leaves the quiescent state and continues +request/result processing, while the child clears the request/result +queue (so the requests started before the fork will only be handled in +the parent). Threads will be started on demand until the limit ste in the +parent process has been reached again. + +In short: the parent will, after a short pause, continue as if fork had +not been called, while the child will act as if IO::AIO has not been used +yet. =head1 SEE ALSO -L, L. +L, L (obsolete). =head1 AUTHOR