[libio-async-perl] 01/05: Imported Upstream version 0.64

gregor herrmann gregoa at debian.org
Mon Oct 20 18:41:59 UTC 2014


This is an automated email from the git hooks/post-receive script.

gregoa pushed a commit to branch master
in repository libio-async-perl.

commit d3ed014d0e3d30e5a3cb32091af9f354f13c8cd4
Author: gregor herrmann <gregoa at debian.org>
Date:   Mon Oct 20 20:23:54 2014 +0200

    Imported Upstream version 0.64
---
 Build.PL                            |   8 +-
 Changes                             |  20 ++++
 MANIFEST                            |   1 +
 META.json                           |  80 ++++++++--------
 META.yml                            |  77 ++++++++--------
 Makefile.PL                         |   5 +-
 lib/IO/Async.pm                     |   2 +-
 lib/IO/Async/Channel.pm             |   7 +-
 lib/IO/Async/ChildManager.pm        |  12 +--
 lib/IO/Async/File.pm                |  12 +--
 lib/IO/Async/FileStream.pm          |  10 +-
 lib/IO/Async/Function.pm            |  40 ++++----
 lib/IO/Async/Future.pm              |   2 +-
 lib/IO/Async/Handle.pm              |  52 ++++++++---
 lib/IO/Async/Internals/Connector.pm |   2 +-
 lib/IO/Async/Listener.pm            |  66 +++++++------
 lib/IO/Async/Loop.pm                |  15 +--
 lib/IO/Async/Loop/Poll.pm           |   2 +-
 lib/IO/Async/Loop/Select.pm         |   2 +-
 lib/IO/Async/LoopTests.pm           |   2 +-
 lib/IO/Async/MergePoint.pm          |   2 +-
 lib/IO/Async/Notifier.pm            |   4 +-
 lib/IO/Async/OS.pm                  |  23 ++++-
 lib/IO/Async/OS/MSWin32.pm          |   2 +-
 lib/IO/Async/OS/cygwin.pm           |   2 +-
 lib/IO/Async/OS/linux.pm            |  56 ++++++++++++
 lib/IO/Async/PID.pm                 |  10 +-
 lib/IO/Async/Process.pm             |  50 ++++++----
 lib/IO/Async/Protocol.pm            |  10 +-
 lib/IO/Async/Protocol/LineStream.pm |   8 +-
 lib/IO/Async/Protocol/Stream.pm     |  14 +--
 lib/IO/Async/Resolver.pm            |  11 ++-
 lib/IO/Async/Routine.pm             |  16 ++--
 lib/IO/Async/Signal.pm              |  10 +-
 lib/IO/Async/Socket.pm              |  48 ++++++----
 lib/IO/Async/Stream.pm              | 178 ++++++++++++++++++++----------------
 lib/IO/Async/Test.pm                |   2 +-
 lib/IO/Async/Timer.pm               |   2 +-
 lib/IO/Async/Timer/Absolute.pm      |  10 +-
 lib/IO/Async/Timer/Countdown.pm     |  12 +--
 lib/IO/Async/Timer/Periodic.pm      |  14 +--
 t/20handle.t                        |  30 ++++++
 t/21stream-2write.t                 |  12 ++-
 t/24listener.t                      |  44 +++++++--
 t/42function.t                      |  22 +++++
 45 files changed, 631 insertions(+), 378 deletions(-)

diff --git a/Build.PL b/Build.PL
index 53163f9..e9c15ac 100644
--- a/Build.PL
+++ b/Build.PL
@@ -6,13 +6,14 @@ use Module::Build;
 my $build = Module::Build->new(
    module_name => 'IO::Async',
    requires => {
-      'Future' => '0.12',
+      'Future' => '0.22', # ->else_done
       'Future::Utils' => '0.18', # try_repeat
       'Exporter' => '5.57',
       'File::stat' => 0,
       'IO::Poll' => 0,
       'Socket' => '2.007',
       'Storable' => 0,
+      'Struct::Dumb' => 0,
       'Time::HiRes' => 0,
 
       # Fails on perl 5.8.3 for unknown reasons
@@ -37,6 +38,11 @@ my $build = Module::Build->new(
    create_makefile_pl => 'traditional',
    create_license => 1,
    create_readme  => 1,
+   meta_merge => {
+      resources => {
+         x_IRC => "irc://irc.perl.org/#io-async",
+      },
+   },
 );
 
 $build->create_build_script;
diff --git a/Changes b/Changes
index a4ba41b..678d3e1 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,25 @@
 Revision history for IO-Async
 
+0.64    2014/10/17 17:51:07
+        [CHANGES]
+         * Make specific mention of 'TCP' and 'UDP' around socket examples
+           where appropriate
+         * Allow construction of an IO::Async::Handle using fileno integers
+           directly
+         * Provide a better search for 'all open filehandles' via IO::Async::OS
+           on Linux (RT97942)
+         * Allow IO::Async::Listener to have handle_constructor or handle_class
+           as a subclass method (RT97208)
+         * Clarify documentation on how to use IO::Async::Process's
+           on_exception event (RT98929)
+
+        [BUGFIXES]
+         * Ensure that Stream's write Futures are also informed of write errors
+           (RT97433)
+         * Remember to ->remove_child the individual workers of an
+           IO::Async::Function (RT99552)
+         * Fix IO::Async::Function synopsis example (RT97713)
+
 0.63    2014/07/11 15:09:08
         [CHANGES]
          * Allow Notifier subclasses to last-ditch handle unrecognised
diff --git a/MANIFEST b/MANIFEST
index 73a352b..9a42393 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -26,6 +26,7 @@ lib/IO/Async/MergePoint.pm
 lib/IO/Async/Notifier.pm
 lib/IO/Async/OS.pm
 lib/IO/Async/OS/cygwin.pm
+lib/IO/Async/OS/linux.pm
 lib/IO/Async/OS/MSWin32.pm
 lib/IO/Async/PID.pm
 lib/IO/Async/Process.pm
diff --git a/META.json b/META.json
index f2d86d9..8ba254d 100644
--- a/META.json
+++ b/META.json
@@ -4,7 +4,7 @@
       "Paul Evans <leonerd at leonerd.org.uk>"
    ],
    "dynamic_config" : 1,
-   "generated_by" : "Module::Build version 0.4205",
+   "generated_by" : "Module::Build version 0.421",
    "license" : [
       "perl_5"
    ],
@@ -30,11 +30,12 @@
          "requires" : {
             "Exporter" : "5.57",
             "File::stat" : "0",
-            "Future" : "0.12",
+            "Future" : "0.22",
             "Future::Utils" : "0.18",
             "IO::Poll" : "0",
             "Socket" : "2.007",
             "Storable" : "0",
+            "Struct::Dumb" : "0",
             "Time::HiRes" : "0",
             "perl" : "5.010"
          }
@@ -43,142 +44,147 @@
    "provides" : {
       "IO::Async" : {
          "file" : "lib/IO/Async.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Channel" : {
          "file" : "lib/IO/Async/Channel.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::ChildManager" : {
          "file" : "lib/IO/Async/ChildManager.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::File" : {
          "file" : "lib/IO/Async/File.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::FileStream" : {
          "file" : "lib/IO/Async/FileStream.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Function" : {
          "file" : "lib/IO/Async/Function.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Future" : {
          "file" : "lib/IO/Async/Future.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Handle" : {
          "file" : "lib/IO/Async/Handle.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Listener" : {
          "file" : "lib/IO/Async/Listener.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Loop" : {
          "file" : "lib/IO/Async/Loop.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Loop::Poll" : {
          "file" : "lib/IO/Async/Loop/Poll.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Loop::Select" : {
          "file" : "lib/IO/Async/Loop/Select.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::LoopTests" : {
          "file" : "lib/IO/Async/LoopTests.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::MergePoint" : {
          "file" : "lib/IO/Async/MergePoint.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Notifier" : {
          "file" : "lib/IO/Async/Notifier.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::OS" : {
          "file" : "lib/IO/Async/OS.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::OS::MSWin32" : {
          "file" : "lib/IO/Async/OS/MSWin32.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::OS::cygwin" : {
          "file" : "lib/IO/Async/OS/cygwin.pm",
-         "version" : "0.63"
+         "version" : "0.64"
+      },
+      "IO::Async::OS::linux" : {
+         "file" : "lib/IO/Async/OS/linux.pm",
+         "version" : "0.64"
       },
       "IO::Async::PID" : {
          "file" : "lib/IO/Async/PID.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Process" : {
          "file" : "lib/IO/Async/Process.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Protocol" : {
          "file" : "lib/IO/Async/Protocol.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Protocol::LineStream" : {
          "file" : "lib/IO/Async/Protocol/LineStream.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Protocol::Stream" : {
          "file" : "lib/IO/Async/Protocol/Stream.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Resolver" : {
          "file" : "lib/IO/Async/Resolver.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Routine" : {
          "file" : "lib/IO/Async/Routine.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Signal" : {
          "file" : "lib/IO/Async/Signal.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Socket" : {
          "file" : "lib/IO/Async/Socket.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Stream" : {
          "file" : "lib/IO/Async/Stream.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Test" : {
          "file" : "lib/IO/Async/Test.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Timer" : {
          "file" : "lib/IO/Async/Timer.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Timer::Absolute" : {
          "file" : "lib/IO/Async/Timer/Absolute.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Timer::Countdown" : {
          "file" : "lib/IO/Async/Timer/Countdown.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       },
       "IO::Async::Timer::Periodic" : {
          "file" : "lib/IO/Async/Timer/Periodic.pm",
-         "version" : "0.63"
+         "version" : "0.64"
       }
    },
    "release_status" : "stable",
    "resources" : {
       "license" : [
          "http://dev.perl.org/licenses/"
-      ]
+      ],
+      "x_IRC" : "irc://irc.perl.org/#io-async"
    },
-   "version" : "0.63"
+   "version" : "0.64"
 }
diff --git a/META.yml b/META.yml
index 3bf9568..9dad0e2 100644
--- a/META.yml
+++ b/META.yml
@@ -9,7 +9,7 @@ build_requires:
   Test::More: '0.88'
   Test::Refcount: '0'
 dynamic_config: 1
-generated_by: 'Module::Build version 0.4205, CPAN::Meta::Converter version 2.133380'
+generated_by: 'Module::Build version 0.421, CPAN::Meta::Converter version 2.142060'
 license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
@@ -18,115 +18,120 @@ name: IO-Async
 provides:
   IO::Async:
     file: lib/IO/Async.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Channel:
     file: lib/IO/Async/Channel.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::ChildManager:
     file: lib/IO/Async/ChildManager.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::File:
     file: lib/IO/Async/File.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::FileStream:
     file: lib/IO/Async/FileStream.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Function:
     file: lib/IO/Async/Function.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Future:
     file: lib/IO/Async/Future.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Handle:
     file: lib/IO/Async/Handle.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Listener:
     file: lib/IO/Async/Listener.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Loop:
     file: lib/IO/Async/Loop.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Loop::Poll:
     file: lib/IO/Async/Loop/Poll.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Loop::Select:
     file: lib/IO/Async/Loop/Select.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::LoopTests:
     file: lib/IO/Async/LoopTests.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::MergePoint:
     file: lib/IO/Async/MergePoint.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Notifier:
     file: lib/IO/Async/Notifier.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::OS:
     file: lib/IO/Async/OS.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::OS::MSWin32:
     file: lib/IO/Async/OS/MSWin32.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::OS::cygwin:
     file: lib/IO/Async/OS/cygwin.pm
-    version: '0.63'
+    version: '0.64'
+  IO::Async::OS::linux:
+    file: lib/IO/Async/OS/linux.pm
+    version: '0.64'
   IO::Async::PID:
     file: lib/IO/Async/PID.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Process:
     file: lib/IO/Async/Process.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Protocol:
     file: lib/IO/Async/Protocol.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Protocol::LineStream:
     file: lib/IO/Async/Protocol/LineStream.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Protocol::Stream:
     file: lib/IO/Async/Protocol/Stream.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Resolver:
     file: lib/IO/Async/Resolver.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Routine:
     file: lib/IO/Async/Routine.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Signal:
     file: lib/IO/Async/Signal.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Socket:
     file: lib/IO/Async/Socket.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Stream:
     file: lib/IO/Async/Stream.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Test:
     file: lib/IO/Async/Test.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Timer:
     file: lib/IO/Async/Timer.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Timer::Absolute:
     file: lib/IO/Async/Timer/Absolute.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Timer::Countdown:
     file: lib/IO/Async/Timer/Countdown.pm
-    version: '0.63'
+    version: '0.64'
   IO::Async::Timer::Periodic:
     file: lib/IO/Async/Timer/Periodic.pm
-    version: '0.63'
+    version: '0.64'
 recommends:
   IO::Socket::IP: '0'
 requires:
   Exporter: '5.57'
   File::stat: '0'
-  Future: '0.12'
+  Future: '0.22'
   Future::Utils: '0.18'
   IO::Poll: '0'
   Socket: '2.007'
   Storable: '0'
+  Struct::Dumb: '0'
   Time::HiRes: '0'
   perl: '5.010'
 resources:
+  IRC: irc://irc.perl.org/#io-async
   license: http://dev.perl.org/licenses/
-version: '0.63'
+version: '0.64'
diff --git a/Makefile.PL b/Makefile.PL
index 93a3823..0c963a1 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -1,4 +1,4 @@
-# Note: this file was auto-generated by Module::Build::Compat version 0.4205
+# Note: this file was auto-generated by Module::Build::Compat version 0.4210
 require 5.010;
 use ExtUtils::MakeMaker;
 WriteMakefile
@@ -9,11 +9,12 @@ WriteMakefile
                    'Exporter' => '5.57',
                    'File::Temp' => 0,
                    'File::stat' => 0,
-                   'Future' => '0.12',
+                   'Future' => '0.22',
                    'Future::Utils' => '0.18',
                    'IO::Poll' => 0,
                    'Socket' => '2.007',
                    'Storable' => 0,
+                   'Struct::Dumb' => 0,
                    'Test::Fatal' => 0,
                    'Test::Identity' => 0,
                    'Test::More' => '0.88',
diff --git a/lib/IO/Async.pm b/lib/IO/Async.pm
index dab4786..fd1cfe1 100644
--- a/lib/IO/Async.pm
+++ b/lib/IO/Async.pm
@@ -12,7 +12,7 @@ use warnings;
 # It is provided simply to keep CPAN happy:
 #   cpan -i IO::Async
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 =head1 NAME
 
diff --git a/lib/IO/Async/Channel.pm b/lib/IO/Async/Channel.pm
index 649426c..f459ee7 100644
--- a/lib/IO/Async/Channel.pm
+++ b/lib/IO/Async/Channel.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Notifier ); # just to get _capture_weakself
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 use Storable qw( freeze thaw );
@@ -78,6 +78,9 @@ sub new
 
 =head1 METHODS
 
+The following methods documented with a trailing call to C<< ->get >> return
+L<Future> instances.
+
 =cut
 
 =head2 $channel->configure( %params )
@@ -170,7 +173,7 @@ be passed and all Perl references are true the truth of the result of this
 method can be used to detect that the channel is still open and has not yet
 been closed.
 
-=head2 $channel->recv ==> $data
+=head2 $data = $channel->recv->get
 
 When called on an asynchronous mode Channel this method returns a future which
 will eventually yield the next Perl reference value that becomes available
diff --git a/lib/IO/Async/ChildManager.pm b/lib/IO/Async/ChildManager.pm
index f8dd15b..c8d486d 100644
--- a/lib/IO/Async/ChildManager.pm
+++ b/lib/IO/Async/ChildManager.pm
@@ -1,14 +1,14 @@
 #  You may distribute under the terms of either the GNU General Public License
 #  or the Artistic License (the same terms as Perl itself)
 #
-#  (C) Paul Evans, 2007-2013 -- leonerd at leonerd.org.uk
+#  (C) Paul Evans, 2007-2014 -- leonerd at leonerd.org.uk
 
 package IO::Async::ChildManager;
 
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 # Not a notifier
 
@@ -19,14 +19,10 @@ use IO::Async::OS;
 use Carp;
 use Scalar::Util qw( weaken );
 
-use POSIX qw( _exit sysconf _SC_OPEN_MAX dup dup2 nice );
+use POSIX qw( _exit dup dup2 nice );
 
 use constant LENGTH_OF_I => length( pack( "I", 0 ) );
 
-# Win32 [and maybe other places] don't have an _SC_OPEN_MAX. About the best we
-# can do really is just make up some largeish number and hope for the best.
-use constant OPEN_MAX_FD => eval { sysconf(_SC_OPEN_MAX) } || 1024;
-
 =head1 NAME
 
 C<IO::Async::ChildManager> - facilitates the execution of child processes
@@ -604,7 +600,7 @@ sub _spawn_in_child
          }
       }
 
-      foreach ( 0 .. OPEN_MAX_FD ) {
+      foreach ( IO::Async::OS->potentially_open_fds ) {
          next if $fds_refcount{$_};
          next if $_ == fileno $writepipe;
          POSIX::close( $_ );
diff --git a/lib/IO/Async/File.pm b/lib/IO/Async/File.pm
index ce3abdc..ccfcfe5 100644
--- a/lib/IO/Async/File.pm
+++ b/lib/IO/Async/File.pm
@@ -9,7 +9,7 @@ use 5.010; # //
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use base qw( IO::Async::Timer::Periodic );
 
@@ -91,28 +91,24 @@ L<File::stat> instances containing the old and new C<stat()> fields.
 
 The following named parameters may be passed to C<new> or C<configure>.
 
-=over 8
-
-=item handle => IO
+=head2 handle => IO
 
 The opened filehandle to watch for C<stat()> changes if C<filename> is not
 supplied.
 
-=item filename => STRING
+=head2 filename => STRING
 
 Optional. If supplied, watches the named file rather than the filehandle given
 in C<handle>. The file will be opened for reading and then watched for
 renames. If the file is renamed, the new filename is opened and tracked
 similarly after closing the previous file.
 
-=item interval => NUM
+=head2 interval => NUM
 
 Optional. The interval in seconds to poll the filehandle using C<stat(2)>
 looking for size changes. A default of 2 seconds will be applied if not
 defined.
 
-=back
-
 =cut
 
 sub _init
diff --git a/lib/IO/Async/FileStream.pm b/lib/IO/Async/FileStream.pm
index 21a8a98..0a82f7b 100644
--- a/lib/IO/Async/FileStream.pm
+++ b/lib/IO/Async/FileStream.pm
@@ -8,7 +8,7 @@ package IO::Async::FileStream;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use base qw( IO::Async::Stream );
 
@@ -132,23 +132,19 @@ The following named parameters may be passed to C<new> or C<configure>, in
 addition to the parameters relating to reading supported by
 C<IO::Async::Stream>.
 
-=over 8
-
-=item filename => STRING
+=head2 filename => STRING
 
 Optional. If supplied, watches the named file rather than the filehandle given
 in C<read_handle>. The file will be opened by the constructor, and then
 watched for renames. If the file is renamed, the new filename is opened and
 tracked similarly after closing the previous file.
 
-=item interval => NUM
+=head2 interval => NUM
 
 Optional. The interval in seconds to poll the filehandle using C<stat(2)>
 looking for size changes. A default of 2 seconds will be applied if not
 defined.
 
-=back
-
 =cut
 
 sub configure
diff --git a/lib/IO/Async/Function.pm b/lib/IO/Async/Function.pm
index 0f59581..04b8be9 100644
--- a/lib/IO/Async/Function.pm
+++ b/lib/IO/Async/Function.pm
@@ -8,7 +8,7 @@ package IO::Async::Function;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use base qw( IO::Async::Notifier );
 use IO::Async::Timer::Countdown;
@@ -39,10 +39,10 @@ C<IO::Async::Function> - call a function asynchronously
 
  $function->call(
     args => [ 123454321 ],
- )->then( sub {
+ )->on_done( sub {
     my $isprime = shift;
     print "123454321 " . ( $isprime ? "is" : "is not" ) . " a prime number\n";
- })->else(
+ })->on_fail( sub {
     print STDERR "Cannot determine if it's prime - $_[0]\n";
  })->get;
 
@@ -103,50 +103,46 @@ also L<IO::Async::Routine>.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item code => CODE
+=head2 code => CODE
 
 The body of the function to execute.
 
-=item model => "spawn" | "thread"
+=head2 model => "fork" | "thread"
 
 Optional. Requests a specific C<IO::Async::Routine> model. If not supplied,
 leaves the default choice up to Routine.
 
-=item min_workers => INT
+=head2 min_workers => INT
 
-=item max_workers => INT
+=head2 max_workers => INT
 
 The lower and upper bounds of worker processes to try to keep running. The
 actual number running at any time will be kept somewhere between these bounds
 according to load.
 
-=item max_worker_calls => INT
+=head2 max_worker_calls => INT
 
 Optional. If provided, stop a worker process after it has processed this
 number of calls. (New workers may be started to replace stopped ones, within
 the bounds given above).
 
-=item idle_timeout => NUM
+=head2 idle_timeout => NUM
 
 Optional. If provided, idle worker processes will be shut down after this
 amount of time, if there are more than C<min_workers> of them.
 
-=item exit_on_die => BOOL
+=head2 exit_on_die => BOOL
 
 Optional boolean, controls what happens after the C<code> throws an
 exception. If missing or false, the worker will continue running to process
 more requests. If true, the worker will be shut down. A new worker might be
 constructed by the C<call> method to replace it, if necessary.
 
-=item setup => ARRAY
+=head2 setup => ARRAY
 
 Optional array reference. Specifies the C<setup> key to pass to the underlying
 L<IO::Async::Process> when setting up new worker processes.
 
-=back
-
 =cut
 
 sub _init
@@ -250,6 +246,9 @@ sub _remove_from_loop
 
 =head1 METHODS
 
+The following methods documented with a trailing call to C<< ->get >> return
+L<Future> instances.
+
 =cut
 
 =head2 $function->start
@@ -295,7 +294,7 @@ sub restart
    $self->start;
 }
 
-=head2 $function->call( %params ) ==> @result
+=head2 @result = $function->call( %params )->get
 
 Schedules an invocation of the contained function to be executed on one of the
 worker processes. If a non-busy worker is available now, it will be called
@@ -596,6 +595,13 @@ sub stop
 
    if( my $function = $worker->parent ) {
       delete $function->{workers}{$worker->id};
+
+      if( $worker->{busy} ) {
+         $worker->{remove_on_idle}++;
+      }
+      else {
+         $function->remove_child( $worker );
+      }
    }
 }
 
@@ -642,6 +648,8 @@ sub call
 
       my $function = $worker->parent;
       $function->_dispatch_pending if $function;
+
+      $function->remove_child( $worker ) if $function and $worker->{remove_on_idle};
    }));
 }
 
diff --git a/lib/IO/Async/Future.pm b/lib/IO/Async/Future.pm
index 0ac031b..4b5baee 100644
--- a/lib/IO/Async/Future.pm
+++ b/lib/IO/Async/Future.pm
@@ -8,7 +8,7 @@ package IO::Async::Future;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use base qw( Future );
 Future->VERSION( '0.05' ); # to respect subclassing
diff --git a/lib/IO/Async/Handle.pm b/lib/IO/Async/Handle.pm
index b7d1da7..902fe0d 100644
--- a/lib/IO/Async/Handle.pm
+++ b/lib/IO/Async/Handle.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Notifier );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 
@@ -87,38 +87,42 @@ Loop object.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
+=head2 read_handle => IO
 
-=item read_handle => IO
-
-=item write_handle => IO
+=head2 write_handle => IO
 
 The reading and writing IO handles. Each must implement the C<fileno> method.
 Primarily used for passing C<STDIN> / C<STDOUT>; see the SYNOPSIS section of
 C<IO::Async::Stream> for an example.
 
-=item handle => IO
+=head2 handle => IO
 
 The IO handle for both reading and writing; instead of passing each separately
 as above. Must implement C<fileno> method in way that C<IO::Handle> does.
 
-=item on_read_ready => CODE
+=head2 read_fileno => INT
+
+=head2 write_fileno => INT
+
+File descriptor numbers for reading and writing. If these are given as an
+alternative to C<read_handle> or C<write_handle> then a new C<IO::Handle>
+instance will be constructed around each.
+
+=head2 on_read_ready => CODE
 
-=item on_write_ready => CODE
+=head2 on_write_ready => CODE
 
-=item on_closed => CODE
+=head2 on_closed => CODE
 
 CODE references for event handlers.
 
-=item want_readready => BOOL
+=head2 want_readready => BOOL
 
-=item want_writeready => BOOL
+=head2 want_writeready => BOOL
 
 If present, enable or disable read- or write-ready notification as per the
 C<want_readready> and C<want_writeready> methods.
 
-=back
-
 It is required that a matching C<on_read_ready> or C<on_write_ready> are
 available for any handle that is provided; either passed as a callback CODE
 reference or as an overridden the method. I.e. if only a C<read_handle> is
@@ -156,6 +160,21 @@ sub configure
       $self->{on_closed} = delete $params{on_closed};
    }
 
+   if( defined $params{read_fileno} and defined $params{write_fileno} and
+       $params{read_fileno} == $params{write_fileno} ) {
+      $params{handle} = IO::Handle->new_from_fd( $params{read_fileno}, "r+" );
+
+      delete $params{read_fileno};
+      delete $params{write_fileno};
+   }
+   else {
+      $params{read_handle} = IO::Handle->new_from_fd( delete $params{read_fileno}, "r" )
+         if defined $params{read_fileno};
+
+      $params{write_handle} = IO::Handle->new_from_fd( delete $params{write_fileno}, "w" )
+         if defined $params{write_fileno};
+   }
+
    # 'handle' is a shortcut for setting read_ and write_
    if( exists $params{handle} ) {
       $params{read_handle}  = $params{handle};
@@ -311,6 +330,9 @@ sub notifier_name
 
 =head1 METHODS
 
+The following methods documented with a trailing call to C<< ->get >> return
+L<Future> instances.
+
 =cut
 
 =head2 $handle->set_handles( %params )
@@ -421,7 +443,7 @@ sub close_write
    $self->_closed if !$self->{read_handle};
 }
 
-=head2 $handle->new_close_future ==> ()
+=head2 $handle->new_close_future->get
 
 Returns a new L<IO::Async::Future> object which will become done when the
 handle is closed. Cancelling the C<$future> will remove this notification
@@ -606,7 +628,7 @@ sub bind
    $self->read_handle->bind( $addr ) or croak "Cannot bind - $!";
 }
 
-=head2 $handle->connect( %args ) ==> ( $handle )
+=head2 $handle = $handle->connect( %args )->get
 
 A convenient wrapper for calling the C<connect> method on the underlying
 L<IO::Async::Loop> object.
diff --git a/lib/IO/Async/Internals/Connector.pm b/lib/IO/Async/Internals/Connector.pm
index ca02386..43845cf 100644
--- a/lib/IO/Async/Internals/Connector.pm
+++ b/lib/IO/Async/Internals/Connector.pm
@@ -9,7 +9,7 @@ package # hide from CPAN
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Scalar::Util qw( weaken );
 
diff --git a/lib/IO/Async/Listener.pm b/lib/IO/Async/Listener.pm
index bb3d137..e73f4ce 100644
--- a/lib/IO/Async/Listener.pm
+++ b/lib/IO/Async/Listener.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Handle );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use IO::Async::Handle;
 use IO::Async::OS;
@@ -136,23 +136,21 @@ simply be ignored.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item on_accept => CODE
+=head2 on_accept => CODE
 
-=item on_stream => CODE
+=head2 on_stream => CODE
 
-=item on_socket => CODE
+=head2 on_socket => CODE
 
 CODE reference for the event handlers. Because of the mutually-exclusive
 nature of their behaviour, only one of these may be set at a time. Setting one
 will remove the other two.
 
-=item handle => IO
+=head2 handle => IO
 
 The IO handle containing an existing listen-mode socket.
 
-=item handle_constructor => CODE
+=head2 handle_constructor => CODE
 
 Optional. If defined, gives a CODE reference to be invoked every time a new
 client socket is accepted from the listening socket. It is passed the listener
@@ -161,7 +159,11 @@ C<IO::Async::Handle> or a subclass, used to wrap the new client socket.
 
  $handle = $handle_constructor->( $listener )
 
-=item handle_class => STRING
+This can also be given as a subclass method
+
+ $handle = $listener->handle_constructor()
+
+=head2 handle_class => STRING
 
 Optional. If defined and C<handle_constructor> isn't, then new wrapper handles
 are constructed by invoking the C<new> method on the given class name, passing
@@ -169,22 +171,24 @@ in no additional parameters.
 
  $handle = $handle_class->new()
 
-=item acceptor => STRING|CODE
+This can also be given as a subclass method
+
+ $handle = $listener->handle_class->new
+
+=head2 acceptor => STRING|CODE
 
 Optional. If defined, gives the name of a method or a CODE reference to use to
 implement the actual accept behaviour. This will be invoked as:
 
- $listener->acceptor( $socket ) ==> $accepted
+ ( $accepted ) = $listener->acceptor( $socket )->get
 
- $listener->acceptor( $socket, handle => $handle ) ==> $handle
+ ( $handle ) = $listener->acceptor( $socket, handle => $handle )->get
 
 It is invoked with the listening socket as its its argument, and optionally
 an C<IO::Async::Handle> instance as a named parameter, and is expected to
 return a C<Future> that will eventually yield the newly-accepted socket or
 handle instance, if such was provided.
 
-=back
-
 =cut
 
 sub _init
@@ -237,15 +241,6 @@ sub configure
       $self->{$_} = delete $params{$_} if exists $params{$_};
    }
 
-   my $new_handle;
-   if( my $constructor = $self->{handle_constructor} ) {
-      $new_handle = $self->{handle_constructor};
-   }
-   elsif( my $class = $self->{handle_class} ) {
-      $new_handle = sub { $class->new };
-   }
-   $self->{new_handle} = $new_handle;
-
    if( keys %params ) {
       croak "Cannot pass though configuration keys to underlying Handle - " . join( ", ", keys %params );
    }
@@ -272,8 +267,23 @@ sub on_read_ready
    }
    # on_accept needs to be last in case of multiple layers of subclassing
    elsif( $on_done = $self->can_event( "on_accept" ) ) {
-      my $new_handle = $self->{new_handle};
-      $acceptor_params{handle} = $new_handle->( $self ) if $new_handle;
+      my $handle;
+
+      # Test both params before moving on to either method
+      if( my $constructor = $self->{handle_constructor} ) {
+         $handle = $self->{handle_constructor}->( $self );
+      }
+      elsif( my $class = $self->{handle_class} ) {
+         $handle = $class->new;
+      }
+      elsif( $self->can( "handle_constructor" ) ) {
+         $handle = $self->handle_constructor;
+      }
+      elsif( $self->can( "handle_class" ) ) {
+         $handle = $self->handle_class->new;
+      }
+
+      $acceptor_params{handle} = $handle if $handle;
    }
    else {
       die "ARG! Missing on_accept,on_stream,on_socket!";
@@ -320,6 +330,9 @@ sub _accept
 
 =head1 METHODS
 
+The following methods documented with a trailing call to C<< ->get >> return
+L<Future> instances.
+
 =cut
 
 =head2 $acceptor = $listener->acceptor
@@ -473,8 +486,7 @@ The C<addr> or C<addrs> parameters should contain a definition of a plain
 socket address in a form that the L<IO::Async::OS> C<extract_addrinfo>
 method can use.
 
-This example shows how to use the C<Socket> functions to construct one for
-TCP port 8001 on address 10.0.0.1:
+This example shows how to listen on TCP port 8001 on address 10.0.0.1:
 
  $listener->listen(
     addr => {
diff --git a/lib/IO/Async/Loop.pm b/lib/IO/Async/Loop.pm
index c79a5d0..a59a2e6 100644
--- a/lib/IO/Async/Loop.pm
+++ b/lib/IO/Async/Loop.pm
@@ -8,7 +8,7 @@ package IO::Async::Loop;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 # When editing this value don't forget to update the docs below
 use constant NEED_API_VERSION => '0.33';
@@ -590,7 +590,7 @@ sub await_all
    $self->loop_once until _all_ready @futures;
 }
 
-=head2 $loop->delay_future( %args ) ==> ()
+=head2 $loop->delay_future( %args )->get
 
 Returns a new C<IO::Async::Future> instance which will become done at a given
 point in time. The C<%args> should contain an C<at> or C<after> key as per the
@@ -614,7 +614,7 @@ sub delay_future
    return $future;
 }
 
-=head2 $loop->timeout_future( %args ) ==> ()
+=head2 $loop->timeout_future( %args )->get
 
 Returns a new C<IO::Async::Future> instance which will fail at a given point
 in time. The C<%args> should contain an C<at> or C<after> key as per the
@@ -648,6 +648,9 @@ Most of the following methods are higher-level wrappers around base
 functionality provided by the low-level API documented below. They may be
 used by C<IO::Async::Notifier> subclasses or called directly by the program.
 
+The following methods documented with a trailing call to C<< ->get >> return
+L<Future> instances.
+
 =cut
 
 sub __new_feature
@@ -1061,7 +1064,7 @@ sub set_resolver
    $self->{resolver} = $resolver;
 }
 
-=head2 $loop->resolve( %params ) ==> @result
+=head2 @result = $loop->resolve( %params )->get
 
 This method performs a single name resolution operation. It uses an
 internally-stored C<IO::Async::Resolver> object. For more detail, see the
@@ -1077,7 +1080,7 @@ sub resolve
    $self->resolver->resolve( %params );
 }
 
-=head2 $loop->connect( %params ) ==> ( $handle|$socket )
+=head2 $handle|$socket = $loop->connect( %params )->get
 
 This method performs a non-blocking connection to a given address or set of
 addresses, returning a L<IO::Async::Future> which represents the operation. On
@@ -1370,7 +1373,7 @@ sub connect
    $future->on_ready( sub { undef $future } ); # intentional cycle
 }
 
-=head2 $loop->listen( %params ) ==> $listener
+=head2 $listener = $loop->listen( %params )->get
 
 This method sets up a listening socket and arranges for an acceptor callback
 to be invoked each time a new connection is accepted on the socket. Internally
diff --git a/lib/IO/Async/Loop/Poll.pm b/lib/IO/Async/Loop/Poll.pm
index 2527301..a1154d3 100644
--- a/lib/IO/Async/Loop/Poll.pm
+++ b/lib/IO/Async/Loop/Poll.pm
@@ -8,7 +8,7 @@ package IO::Async::Loop::Poll;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 use constant API_VERSION => '0.49';
 
 use base qw( IO::Async::Loop );
diff --git a/lib/IO/Async/Loop/Select.pm b/lib/IO/Async/Loop/Select.pm
index a7f426f..37ff60d 100644
--- a/lib/IO/Async/Loop/Select.pm
+++ b/lib/IO/Async/Loop/Select.pm
@@ -8,7 +8,7 @@ package IO::Async::Loop::Select;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 use constant API_VERSION => '0.49';
 
 use base qw( IO::Async::Loop );
diff --git a/lib/IO/Async/LoopTests.pm b/lib/IO/Async/LoopTests.pm
index d38527e..315e6e4 100644
--- a/lib/IO/Async/LoopTests.pm
+++ b/lib/IO/Async/LoopTests.pm
@@ -27,7 +27,7 @@ use POSIX qw( SIGTERM );
 use Socket qw( sockaddr_family AF_UNIX );
 use Time::HiRes qw( time );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 # Abstract Units of Time
 use constant AUT => $ENV{TEST_QUICK_TIMERS} ? 0.1 : 1;
diff --git a/lib/IO/Async/MergePoint.pm b/lib/IO/Async/MergePoint.pm
index 14103a2..f00e88b 100644
--- a/lib/IO/Async/MergePoint.pm
+++ b/lib/IO/Async/MergePoint.pm
@@ -8,7 +8,7 @@ package IO::Async::MergePoint;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 
diff --git a/lib/IO/Async/Notifier.pm b/lib/IO/Async/Notifier.pm
index 4f51152..44d18a6 100644
--- a/lib/IO/Async/Notifier.pm
+++ b/lib/IO/Async/Notifier.pm
@@ -8,11 +8,13 @@ package IO::Async::Notifier;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 use Scalar::Util qw( weaken );
 
+use Future 0.22; # ->else_done
+
 # Perl 5.8.4 cannot do trampolines by modiying @_ then goto &$code
 use constant HAS_BROKEN_TRAMPOLINES => ( $] == "5.008004" );
 
diff --git a/lib/IO/Async/OS.pm b/lib/IO/Async/OS.pm
index 0ebf833..e344291 100644
--- a/lib/IO/Async/OS.pm
+++ b/lib/IO/Async/OS.pm
@@ -1,14 +1,14 @@
 #  You may distribute under the terms of either the GNU General Public License
 #  or the Artistic License (the same terms as Perl itself)
 #
-#  (C) Paul Evans, 2012-2013 -- leonerd at leonerd.org.uk
+#  (C) Paul Evans, 2012-2014 -- leonerd at leonerd.org.uk
 
 package IO::Async::OS;
 
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 our @ISA = qw( IO::Async::OS::_Base );
 
@@ -30,6 +30,12 @@ use Socket 1.95 qw(
 
 use IO::Socket (); # empty import
 
+use POSIX qw( sysconf _SC_OPEN_MAX );
+
+# Win32 [and maybe other places] don't have an _SC_OPEN_MAX. About the best we
+# can do really is just make up some largeish number and hope for the best.
+use constant OPEN_MAX_FD => eval { sysconf(_SC_OPEN_MAX) } || 1024;
+
 # Some constants that define features of the OS
 
 use constant HAVE_SOCKADDR_IN6 => defined eval { pack_sockaddr_in6 0, inet_pton( AF_INET6, "2001::1" ) };
@@ -565,6 +571,19 @@ sub loop_unwatch_signal
    undef $SIG{$signal};
 }
 
+=head2 @fds = IO::Async::OS->potentially_open_fds
+
+Returns a list of filedescriptors which might need closing. By default this
+will return C<0 .. _SC_OPEN_MAX>. OS-specific subclasses may have a better
+guess.
+
+=cut
+
+sub potentially_open_fds
+{
+   return 0 .. OPEN_MAX_FD;
+}
+
 =head1 AUTHOR
 
 Paul Evans <leonerd at leonerd.org.uk>
diff --git a/lib/IO/Async/OS/MSWin32.pm b/lib/IO/Async/OS/MSWin32.pm
index d406f94..3c49ddd 100644
--- a/lib/IO/Async/OS/MSWin32.pm
+++ b/lib/IO/Async/OS/MSWin32.pm
@@ -8,7 +8,7 @@ package IO::Async::OS::MSWin32;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 our @ISA = qw( IO::Async::OS::_Base );
 
diff --git a/lib/IO/Async/OS/cygwin.pm b/lib/IO/Async/OS/cygwin.pm
index 42f222e..a477805 100644
--- a/lib/IO/Async/OS/cygwin.pm
+++ b/lib/IO/Async/OS/cygwin.pm
@@ -8,7 +8,7 @@ package IO::Async::OS::cygwin;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 our @ISA = qw( IO::Async::OS::_Base );
 
diff --git a/lib/IO/Async/OS/linux.pm b/lib/IO/Async/OS/linux.pm
new file mode 100644
index 0000000..26f365d
--- /dev/null
+++ b/lib/IO/Async/OS/linux.pm
@@ -0,0 +1,56 @@
+#  You may distribute under the terms of either the GNU General Public License
+#  or the Artistic License (the same terms as Perl itself)
+#
+#  (C) Paul Evans, 2014 -- leonerd at leonerd.org.uk
+
+package IO::Async::OS::linux;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.64';
+
+our @ISA = qw( IO::Async::OS::_Base );
+
+=head1 NAME
+
+C<IO::Async::OS::linux> - operating system abstractions on C<Linux> for C<IO::Async>
+
+=head1 DESCRIPTION
+
+This module contains OS support code for C<Linux>.
+
+See instead L<IO::Async::OS>.
+
+=cut
+
+# Try to use /proc/pid/fd to get the list of actually-open file descriptors
+# for our process. Saves a bit of time when running with high ulimit -n /
+# fileno counts.
+sub potentially_open_fds
+{
+   my $class = shift;
+
+   opendir my $fd_path, "/proc/$$/fd" or do {
+      warn "Cannot open /proc/$$/fd, falling back to generic method - $!";
+      return $class->SUPER::potentially_open_fds
+   };
+
+   # Skip ., .., our directory handle itself and any other cruft
+   # except fileno() isn't available for the handle so we'll
+   # end up with that in the output anyway. As long as we're
+   # called just before the relevant close() loop, this
+   # should be harmless enough.
+   my @fd = map { m/^([0-9]+)$/ ? $1 : () } readdir $fd_path;
+   closedir $fd_path;
+
+   return @fd;
+}
+
+=head1 AUTHOR
+
+Paul Evans <leonerd at leonerd.org.uk>
+
+=cut
+
+0x55AA;
diff --git a/lib/IO/Async/PID.pm b/lib/IO/Async/PID.pm
index 35902fc..167d9eb 100644
--- a/lib/IO/Async/PID.pm
+++ b/lib/IO/Async/PID.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Notifier );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 
@@ -76,19 +76,15 @@ Invoked when the watched process exits.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item pid => INT
+=head2 pid => INT
 
 The process ID to watch. Must be given before the object has been added to the
 containing C<IO::Async::Loop> object.
 
-=item on_exit => CODE
+=head2 on_exit => CODE
 
 CODE reference for the C<on_exit> event.
 
-=back
-
 Once the C<on_exit> continuation has been invoked, the C<IO::Async::PID>
 object is removed from the containing C<IO::Async::Loop> object.
 
diff --git a/lib/IO/Async/Process.pm b/lib/IO/Async/Process.pm
index b7ff390..a86c8e7 100644
--- a/lib/IO/Async/Process.pm
+++ b/lib/IO/Async/Process.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Notifier );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 
@@ -86,6 +86,26 @@ C<< Loop->open_child >>'s C<on_error>.
 If this is not provided and the process exits with an exception, then
 C<on_finish> is invoked instead, being passed just the exit code.
 
+Since this is just the results of the underlying C<< $loop->spawn_child >>
+C<on_exit> handler in a different order it is possible that the C<$exception>
+field will be an empty string. It will however always be defined. This can be
+used to distinguish the two cases:
+
+ on_exception => sub {
+    my ( $self, $exception, $errno, $exitcode ) = @_;
+
+    if( length $exception ) {
+       print STDERR "The process died with the exception $exception " .
+          "(errno was $errno)\n";
+    }
+    elsif( ( my $status = W_EXITSTATUS($exitcode) ) == 255 ) {
+       print STDERR "The process failed to exec() - $errno\n";
+    }
+    else {
+       print STDERR "The process exited with exit status $status\n";
+    }
+ }
+
 =cut
 
 =head1 CONSTRUCTOR
@@ -114,16 +134,12 @@ sub _init
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
+=head2 on_finish => CODE
 
-=item on_finish => CODE
-
-=item on_exception => CODE
+=head2 on_exception => CODE
 
 CODE reference for the event handlers.
 
-=back
-
 Once the C<on_finish> continuation has been invoked, the C<IO::Async::Process>
 object is removed from the containing C<IO::Async::Loop> object.
 
@@ -131,25 +147,23 @@ The following parameters may be passed to C<new>, or to C<configure> before
 the process has been started (i.e. before it has been added to the C<Loop>).
 Once the process is running these cannot be changed.
 
-=over 8
-
-=item command => ARRAY or STRING
+=head2 command => ARRAY or STRING
 
 Either a reference to an array containing the command and its arguments, or a
 plain string containing the command. This value is passed into perl's
 C<exec(2)> function.
 
-=item code => CODE
+=head2 code => CODE
 
 A block of code to execute in the child process. It will be called in scalar
 context inside an C<eval> block.
 
-=item setup => ARRAY
+=head2 setup => ARRAY
 
 Optional reference to an array to pass to the underlying C<Loop>
 C<spawn_child> method.
 
-=item fdI<n> => HASH
+=head2 fdI<n> => HASH
 
 A hash describing how to set up file descriptor I<n>. The hash may contain the
 following keys:
@@ -217,22 +231,20 @@ written the pipe will be closed.
 
 =back
 
-=item stdin => ...
+=head2 stdin => ...
 
-=item stdout => ...
+=head2 stdout => ...
 
-=item stderr => ...
+=head2 stderr => ...
 
 Shortcuts for C<fd0>, C<fd1> and C<fd2> respectively.
 
-=item stdio => ...
+=head2 stdio => ...
 
 Special filehandle to affect STDIN and STDOUT at the same time. This
 filehandle supports being configured for both reading and writing at the same
 time.
 
-=back
-
 =cut
 
 sub configure
diff --git a/lib/IO/Async/Protocol.pm b/lib/IO/Async/Protocol.pm
index 5fcc99d..ba360d0 100644
--- a/lib/IO/Async/Protocol.pm
+++ b/lib/IO/Async/Protocol.pm
@@ -8,7 +8,7 @@ package IO::Async::Protocol;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use base qw( IO::Async::Notifier );
 
@@ -53,18 +53,14 @@ Optional. Invoked when the transport handle becomes closed.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item transport => IO::Async::Handle
+=head2 transport => IO::Async::Handle
 
 The C<IO::Async::Handle> to delegate communications to.
 
-=item on_closed => CODE
+=head2 on_closed => CODE
 
 CODE reference for the C<on_closed> event.
 
-=back
-
 When a new C<transport> object is given, it will be configured by calling the
 C<setup_transport> method, then added as a child notifier. If a different
 transport object was already configured, this will first be removed and
diff --git a/lib/IO/Async/Protocol/LineStream.pm b/lib/IO/Async/Protocol/LineStream.pm
index e333f63..08db62a 100644
--- a/lib/IO/Async/Protocol/LineStream.pm
+++ b/lib/IO/Async/Protocol/LineStream.pm
@@ -8,7 +8,7 @@ package IO::Async::Protocol::LineStream;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use base qw( IO::Async::Protocol::Stream );
 
@@ -72,14 +72,10 @@ Invoked when a new complete line of input is received.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item on_read_line => CODE
+=head2 on_read_line => CODE
 
 CODE reference for the C<on_read_line> event.
 
-=back
-
 =cut
 
 sub _init
diff --git a/lib/IO/Async/Protocol/Stream.pm b/lib/IO/Async/Protocol/Stream.pm
index 6f2d0a6..ac3fec9 100644
--- a/lib/IO/Async/Protocol/Stream.pm
+++ b/lib/IO/Async/Protocol/Stream.pm
@@ -8,7 +8,7 @@ package IO::Async::Protocol::Stream;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use base qw( IO::Async::Protocol );
 
@@ -101,25 +101,21 @@ been invoked on it, or on an incoming EOF).
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
+=head2 on_read => CODE
 
-=item on_read => CODE
+=head2 on_read_eof => CODE
 
-=item on_read_eof => CODE
-
-=item on_write_eof => CODE
+=head2 on_write_eof => CODE
 
 CODE references for the events.
 
-=item handle => IO
+=head2 handle => IO
 
 A shortcut for the common case where the transport only needs to be a plain
 C<IO::Async::Stream> object. If this argument is provided without a
 C<transport> object, a new C<IO::Async::Stream> object will be built around
 the given IO handle, and used as the transport.
 
-=back
-
 =cut
 
 sub configure
diff --git a/lib/IO/Async/Resolver.pm b/lib/IO/Async/Resolver.pm
index 81a334d..f8e9a20 100644
--- a/lib/IO/Async/Resolver.pm
+++ b/lib/IO/Async/Resolver.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Function );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 # Socket 2.006 fails to getaddrinfo() AI_NUMERICHOST properly on MSWin32
 use Socket 2.007 qw(
@@ -115,9 +115,12 @@ sub _init
 
 =head1 METHODS
 
+The following methods documented with a trailing call to C<< ->get >> return
+L<Future> instances.
+
 =cut
 
-=head2 $loop->resolve( %params ) ==> @result
+=head2 @result = $loop->resolve( %params )->get
 
 Performs a single name resolution operation, as given by the keys in the hash.
 
@@ -211,7 +214,7 @@ sub resolve
    $future->on_ready( sub { undef $future } ); # intentional cycle
 }
 
-=head2 $resolver->getaddrinfo( %args ) ==> @addrs
+=head2 @addrs = $resolver->getaddrinfo( %args )->get
 
 A shortcut wrapper around the C<getaddrinfo> resolver, taking its arguments in
 a more convenient form.
@@ -362,7 +365,7 @@ sub getaddrinfo
    $future->on_ready( sub { undef $future } ); # intentional cycle
 }
 
-=head2 $resolver->getnameinfo( %args ) ==> ( $host, $service )
+=head2 ( $host, $service ) = $resolver->getnameinfo( %args )->get
 
 A shortcut wrapper around the C<getnameinfo> resolver, taking its arguments in
 a more convenient form.
diff --git a/lib/IO/Async/Routine.pm b/lib/IO/Async/Routine.pm
index 5a3997a..ae8bef6 100644
--- a/lib/IO/Async/Routine.pm
+++ b/lib/IO/Async/Routine.pm
@@ -8,7 +8,7 @@ package IO::Async::Routine;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use base qw( IO::Async::Notifier );
 
@@ -120,9 +120,7 @@ Invoked if the code block fails with an exception.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item model => "fork" | "thread"
+=head2 model => "fork" | "thread"
 
 Optional. Defines how the routine will detach itself from the main process.
 C<fork> uses a child process detached using an L<IO::Async::Process>.
@@ -132,29 +130,27 @@ If the model is not specified, the environment variable
 C<IO_ASYNC_ROUTINE_MODEL> is used to pick a default. If that isn't defined,
 C<fork> is preferred if it is available, otherwise C<thread>.
 
-=item channels_in => ARRAY of IO::Async::Channel
+=head2 channels_in => ARRAY of IO::Async::Channel
 
 ARRAY reference of C<IO::Async::Channel> objects to set up for passing values
 in to the Routine.
 
-=item channels_out => ARRAY of IO::Async::Channel
+=head2 channels_out => ARRAY of IO::Async::Channel
 
 ARRAY reference of C<IO::Async::Channel> objects to set up for passing values
 out of the Routine.
 
-=item code => CODE
+=head2 code => CODE
 
 CODE reference to the body of the Routine, to execute once the channels are
 set up.
 
-=item setup => ARRAY
+=head2 setup => ARRAY
 
 Optional. For C<fork()>-based Routines, gives a reference to an array to pass
 to the underlying C<Loop> C<fork_child> method. Ignored for thread-based
 Routines.
 
-=back
-
 =cut
 
 use constant PREFERRED_MODEL =>
diff --git a/lib/IO/Async/Signal.pm b/lib/IO/Async/Signal.pm
index ab6e460..bfdc758 100644
--- a/lib/IO/Async/Signal.pm
+++ b/lib/IO/Async/Signal.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Notifier );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 
@@ -61,19 +61,15 @@ Invoked when the signal is received.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item name => STRING
+=head2 name => STRING
 
 The name of the signal to watch. This should be a bare name like C<TERM>. Can
 only be given at construction time.
 
-=item on_receipt => CODE
+=head2 on_receipt => CODE
 
 CODE reference for the C<on_receipt> event.
 
-=back
-
 Once constructed, the C<Signal> will need to be added to the C<Loop> before it
 will work.
 
diff --git a/lib/IO/Async/Socket.pm b/lib/IO/Async/Socket.pm
index 3094b07..5430ebb 100644
--- a/lib/IO/Async/Socket.pm
+++ b/lib/IO/Async/Socket.pm
@@ -8,7 +8,7 @@ package IO::Async::Socket;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use base qw( IO::Async::Handle );
 
@@ -68,9 +68,9 @@ provides a queue of outgoing data. It invokes the C<on_recv> handler when new
 data is received from the filehandle. Data may be sent to the filehandle by
 calling the C<send> method.
 
-It is primarily intended for C<SOCK_DGRAM> or C<SOCK_RAW> sockets; for
-C<SOCK_STREAM> sockets an instance of L<IO::Async::Stream> is probably more
-appropriate.
+It is primarily intended for C<SOCK_DGRAM> or C<SOCK_RAW> sockets (such as UDP
+or packet-capture); for C<SOCK_STREAM> sockets (such as TCP) an instance of
+L<IO::Async::Stream> is more appropriate.
 
 =head1 EVENTS
 
@@ -121,39 +121,37 @@ sub _init
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item read_handle => IO
+=head2 read_handle => IO
 
 The IO handle to receive from. Must implement C<fileno> and C<recv> methods.
 
-=item write_handle => IO
+=head2 write_handle => IO
 
 The IO handle to send to. Must implement C<fileno> and C<send> methods.
 
-=item handle => IO
+=head2 handle => IO
 
 Shortcut to specifying the same IO handle for both of the above.
 
-=item on_recv => CODE
+=head2 on_recv => CODE
 
-=item on_recv_error => CODE
+=head2 on_recv_error => CODE
 
-=item on_outgoing_empty => CODE
+=head2 on_outgoing_empty => CODE
 
-=item on_send_error => CODE
+=head2 on_send_error => CODE
 
-=item autoflush => BOOL
+=head2 autoflush => BOOL
 
 Optional. If true, the C<send> method will atempt to send data to the
 operating system immediately, without waiting for the loop to indicate the
 filehandle is write-ready.
 
-=item recv_len => INT
+=head2 recv_len => INT
 
 Optional. Sets the buffer size for C<recv> calls. Defaults to 64 KiB.
 
-=item recv_all => BOOL
+=head2 recv_all => BOOL
 
 Optional. If true, repeatedly call C<recv> when the receiving handle first
 becomes read-ready. By default this is turned off, meaning at most one
@@ -166,14 +164,12 @@ filehandles of processing time. Turning this option on may improve bulk data
 transfer rate, at the risk of delaying or stalling processing on other
 filehandles.
 
-=item send_all => INT
+=head2 send_all => INT
 
 Optional. Analogous to the C<recv_all> option, but for sending. When
 C<autoflush> is enabled, this option only affects deferred sending if the
 initial attempt failed.
 
-=back
-
 The condition requiring an C<on_recv> handler is checked at the time the
 object is added to a Loop; it is allowed to create a C<IO::Async::Socket>
 object with a read handle but without a C<on_recv> handler, provided that
@@ -328,6 +324,20 @@ sub on_write_ready
    }
 }
 
+=head1 EXAMPLES
+
+=head2 Using a UDP Socket
+
+C<UDP> is carried by the C<SOCK_DGRAM> socket type, for which the string
+C<'dgram'> is a convenient shortcut:
+
+ $loop->connect(
+    host     => $hostname,
+    service  => $service,
+    socktype => 'dgram',
+    ...
+ )
+
 =head1 SEE ALSO
 
 =over 4
diff --git a/lib/IO/Async/Stream.pm b/lib/IO/Async/Stream.pm
index 4762845..f0c0e55 100644
--- a/lib/IO/Async/Stream.pm
+++ b/lib/IO/Async/Stream.pm
@@ -1,7 +1,7 @@
 #  You may distribute under the terms of either the GNU General Public License
 #  or the Artistic License (the same terms as Perl itself)
 #
-#  (C) Paul Evans, 2006-2013 -- leonerd at leonerd.org.uk
+#  (C) Paul Evans, 2006-2014 -- leonerd at leonerd.org.uk
 
 package IO::Async::Stream;
 
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use 5.010; # //
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use base qw( IO::Async::Handle );
 
@@ -25,15 +25,14 @@ use Scalar::Util qw( blessed );
 our $READLEN  = 8192;
 our $WRITELEN = 8192;
 
-# Indicies in writequeue elements
-use constant WQ_DATA     => 0;
-use constant WQ_WRITELEN => 1;
-use constant WQ_ON_WRITE => 2;
-use constant WQ_ON_FLUSH => 3;
-use constant WQ_WATCHING => 4;
-# Indicies into readqueue elements
-use constant RQ_ONREAD => 0;
-use constant RQ_FUTURE => 1;
+use Struct::Dumb;
+
+# Element of the writequeue
+struct Writer => [qw( data writelen on_write on_flush on_error watching )];
+
+# Element of the readqueue
+struct Reader => [qw( on_read future )];
+
 # Bitfields in the want flags
 use constant WANT_READ_FOR_READ   => 0x01;
 use constant WANT_READ_FOR_WRITE  => 0x02;
@@ -84,9 +83,11 @@ a byte-stream. It provides buffering for both incoming and outgoing data. It
 invokes the C<on_read> handler when new data is read from the filehandle. Data
 may be written to the filehandle by calling the C<write> method.
 
-For implementing real network protocols that are based on messages sent over a
-byte-stream (such as a TCP socket), it may be more appropriate to use a
-subclass of L<IO::Async::Protocol::Stream>.
+This class is suitable for any kind of filehandle that provides a
+possibly-bidirectional reliable byte stream, such as a pipe, TTY, or
+C<SOCK_STREAM> socket (such as TCP or a byte-oriented UNIX local socket). For
+datagram or raw message-based sockets (such as UDP) see instead
+L<IO::Async::Socket>.
 
 =cut
 
@@ -199,8 +200,8 @@ sub _init
 {
    my $self = shift;
 
-   $self->{writequeue} = []; # Queue of ARRAYs of [ $data, $on_write, $on_flush ]
-   $self->{readqueue} = []; # Queue of ARRAYs of [ CODE, $readfuture ]
+   $self->{writequeue} = []; # Queue of Writers
+   $self->{readqueue} = []; # Queue of Readers
    $self->{writeable} = 1; # "innocent until proven guilty" (by means of EAGAIN)
    $self->{readbuff} = "";
 
@@ -219,35 +220,33 @@ sub _init
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item read_handle => IO
+=head2 read_handle => IO
 
 The IO handle to read from. Must implement C<fileno> and C<sysread> methods.
 
-=item write_handle => IO
+=head2 write_handle => IO
 
 The IO handle to write to. Must implement C<fileno> and C<syswrite> methods.
 
-=item handle => IO
+=head2 handle => IO
 
 Shortcut to specifying the same IO handle for both of the above.
 
-=item on_read => CODE
+=head2 on_read => CODE
 
-=item on_read_error => CODE
+=head2 on_read_error => CODE
 
-=item on_outgoing_empty => CODE
+=head2 on_outgoing_empty => CODE
 
-=item on_write_error => CODE
+=head2 on_write_error => CODE
 
-=item on_writeable_start => CODE
+=head2 on_writeable_start => CODE
 
-=item on_writeable_stop => CODE
+=head2 on_writeable_stop => CODE
 
 CODE references for event handlers.
 
-=item autoflush => BOOL
+=head2 autoflush => BOOL
 
 Optional. If true, the C<write> method will attempt to write data to the
 operating system immediately, without waiting for the loop to indicate the
@@ -257,11 +256,11 @@ contain up-to-date logging or console information.
 It currently defaults to false for any file handle, but future versions of
 C<IO::Async> may enable this by default on STDOUT and STDERR.
 
-=item read_len => INT
+=head2 read_len => INT
 
 Optional. Sets the buffer size for C<read> calls. Defaults to 8 KiBytes.
 
-=item read_all => BOOL
+=head2 read_all => BOOL
 
 Optional. If true, attempt to read as much data from the kernel as possible
 when the handle becomes readable. By default this is turned off, meaning at
@@ -275,19 +274,19 @@ filehandles of processing time. Turning this option on may improve bulk data
 transfer rate, at the risk of delaying or stalling processing on other
 filehandles.
 
-=item write_len => INT
+=head2 write_len => INT
 
 Optional. Sets the buffer size for C<write> calls. Defaults to 8 KiBytes.
 
-=item write_all => BOOL
+=head2 write_all => BOOL
 
 Optional. Analogous to the C<read_all> option, but for writing. When
 C<autoflush> is enabled, this option only affects deferred writing if the
 initial attempt failed due to buffer space.
 
-=item read_high_watermark => INT
+=head2 read_high_watermark => INT
 
-=item read_low_watermark => INT
+=head2 read_low_watermark => INT
 
 Optional. If defined, gives a way to implement flow control or other
 behaviours that depend on the size of Stream's read buffer.
@@ -313,9 +312,9 @@ If these options are used with the default event handlers, be careful not to
 cause deadlocks by having a high watermark sufficiently low that a single
 C<on_read> invocation might not consider it finished yet.
 
-=item reader => STRING|CODE
+=head2 reader => STRING|CODE
 
-=item writer => STRING|CODE
+=head2 writer => STRING|CODE
 
 Optional. If defined, gives the name of a method or a CODE reference to use
 to implement the actual reading from or writing to the filehandle. These will
@@ -330,14 +329,14 @@ value on success, zero on EOF, or C<undef> with C<$!> set for errors. If not
 provided, they will be substituted by implenentations using C<sysread> and
 C<syswrite> on the underlying handle, respectively.
 
-=item close_on_read_eof => BOOL
+=head2 close_on_read_eof => BOOL
 
 Optional. Usually true, but if set to a false value then the stream will not
 be C<close>d when an EOF condition occurs on read. This is normally not useful
 as at that point the underlying stream filehandle is no longer useable, but it
 may be useful for reading regular files, or interacting with TTY devices.
 
-=item encoding => STRING
+=head2 encoding => STRING
 
 If supplied, sets the name of encoding of the underlying stream. If an
 encoding is set, then the C<write> method will expect to receive Unicode
@@ -358,8 +357,6 @@ believe it to be reasonably stable.
 This note applies only to the C<on_read> event; data written using the
 C<write> method does not rely on any undocumented features of C<Encode>.
 
-=back
-
 If a read handle is given, it is required that either an C<on_read> callback
 reference is configured, or that the object provides an C<on_read> method. It
 is optional whether either is true for C<on_outgoing_empty>; if neither is
@@ -437,6 +434,9 @@ sub _add_to_loop
 
 =head1 METHODS
 
+The following methods documented with a trailing call to C<< ->get >> return
+L<Future> instances.
+
 =cut
 
 =head2 $stream->want_readready_for_read( $set )
@@ -575,6 +575,10 @@ sub close_now
 {
    my $self = shift;
 
+   foreach ( @{ $self->{writequeue} } ) {
+       $_->on_error->( "stream closing" ) if $_->on_error;
+   }
+
    undef @{ $self->{writequeue} };
    undef $self->{stream_closing};
 
@@ -666,6 +670,13 @@ yet empty; if more data has been queued since the call.
 
  $on_flush->( $stream )
 
+=item on_error => CODE
+
+A CODE reference which will be invoked if a C<syswrite> error happens while
+performing this write. Invoked as for the C<Stream>'s C<on_write_error> event.
+
+ $on_error->( $stream, $errno )
+
 =back
 
 If the object is not yet a member of a loop and doesn't yet have a
@@ -677,7 +688,7 @@ C<on_flush> continuation will be invoked, if supplied. This can be used to
 obtain a marker, to invoke some code once the output queue has been flushed up
 to this point.
 
-=head2 $stream->write( ... ) ==> ()
+=head2 $stream->write( ... )->get
 
 If called in non-void context, this method returns a L<Future> which will
 complete (with no value) when the write operation has been flushed. This may
@@ -704,57 +715,58 @@ sub _flush_one_write
    my $writequeue = $self->{writequeue};
 
    my $head;
-   while( $head = $writequeue->[0] and ref $head->[WQ_DATA] ) {
-      if( ref $head->[WQ_DATA] eq "CODE" ) {
-         my $data = $head->[WQ_DATA]->( $self );
+   while( $head = $writequeue->[0] and ref $head->data ) {
+      if( ref $head->data eq "CODE" ) {
+         my $data = $head->data->( $self );
          if( !defined $data ) {
-            $head->[WQ_ON_FLUSH]->( $self ) if $head->[WQ_ON_FLUSH];
+            $head->on_flush->( $self ) if $head->on_flush;
             shift @$writequeue;
             return 1;
          }
          if( !ref $data and my $encoding = $self->{encoding} ) {
             $data = $encoding->encode( $data );
          }
-         unshift @$writequeue, my $new = [ $data ];
-         $new->[$_] = $head->[$_] for WQ_WRITELEN, WQ_ON_WRITE; # not ON_FLUSH
+         unshift @$writequeue, my $new = Writer(
+            $data, $head->writelen, $head->on_write, undef, undef, 0
+         );
          next;
       }
-      elsif( blessed $head->[WQ_DATA] and $head->[WQ_DATA]->isa( "Future" ) ) {
-         my $f = $head->[WQ_DATA];
+      elsif( blessed $head->data and $head->data->isa( "Future" ) ) {
+         my $f = $head->data;
          if( !$f->is_ready ) {
-            return 0 if $head->[WQ_WATCHING];
+            return 0 if $head->watching;
             $f->on_ready( sub { $self->_flush_one_write } );
-            $head->[WQ_WATCHING]++;
+            $head->watching++;
             return 0;
          }
          my $data = $f->get;
          if( !ref $data and my $encoding = $self->{encoding} ) {
             $data = $encoding->encode( $data );
          }
-         $head->[WQ_DATA] = $data;
+         $head->data = $data;
          next;
       }
       else {
-         die "Unsure what to do with reference ".ref($head->[WQ_DATA])." in write queue";
+         die "Unsure what to do with reference ".ref($head->data)." in write queue";
       }
    }
 
    my $second;
    while( $second = $writequeue->[1] and
-          !ref $second->[WQ_DATA] and
-          $head->[WQ_WRITELEN] == $second->[WQ_WRITELEN] and
-          !$head->[WQ_ON_WRITE] and !$second->[WQ_ON_WRITE] and
-          !$head->[WQ_ON_FLUSH] ) {
-      $head->[WQ_DATA] .= $second->[WQ_DATA];
-      $head->[WQ_ON_WRITE] = $second->[WQ_ON_WRITE];
-      $head->[WQ_ON_FLUSH] = $second->[WQ_ON_FLUSH];
+          !ref $second->data and
+          $head->writelen == $second->writelen and
+          !$head->on_write and !$second->on_write and
+          !$head->on_flush ) {
+      $head->data .= $second->data;
+      $head->on_write = $second->on_write;
+      $head->on_flush = $second->on_flush;
       splice @$writequeue, 1, 1, ();
    }
 
-   die "TODO: head data does not contain a plain string" if ref $head->[WQ_DATA];
+   die "TODO: head data does not contain a plain string" if ref $head->data;
 
    my $writer = $self->{writer};
-   my $len = $self->$writer( $self->write_handle, $head->[WQ_DATA], $head->[WQ_WRITELEN] );
+   my $len = $self->$writer( $self->write_handle, $head->data, $head->writelen );
 
    if( !defined $len ) {
       my $errno = $!;
@@ -771,18 +783,19 @@ sub _flush_one_write
          $self->maybe_invoke_event( on_write_eof => );
       }
 
+      $head->on_error->( $self, $errno ) if $head->on_error;
       $self->maybe_invoke_event( on_write_error => $errno )
          or $self->close_now;
 
       return 0;
    }
 
-   if( my $on_write = $head->[WQ_ON_WRITE] ) {
+   if( my $on_write = $head->on_write ) {
       $on_write->( $self, $len );
    }
 
-   if( !length $head->[WQ_DATA] ) {
-      $head->[WQ_ON_FLUSH]->( $self ) if $head->[WQ_ON_FLUSH];
+   if( !length $head->data ) {
+      $head->on_flush->( $self ) if $head->on_flush;
       shift @{ $self->{writequeue} };
    }
 
@@ -808,18 +821,30 @@ sub write
 
    my $on_write = delete $params{on_write};
    my $on_flush = delete $params{on_flush};
+   my $on_error = delete $params{on_error};
 
    my $f;
    if( defined wantarray ) {
       my $orig_on_flush = $on_flush;
+      my $orig_on_error = $on_error;
       $f = $self->loop->new_future;
       $on_flush = sub {
          $f->done;
          $orig_on_flush->( @_ ) if $orig_on_flush;
       };
+      $on_error = sub {
+         my $self = shift;
+         my ( $errno ) = @_;
+
+         $f->fail( "write failed: $errno", syswrite => $errno ) unless $f->is_ready;
+
+         $orig_on_error->( $self, @_ ) if $orig_on_error;
+      };
    }
 
-   push @{ $self->{writequeue} }, [ $data, $params{write_len} // $self->{write_len}, $on_write, $on_flush ];
+   push @{ $self->{writequeue} }, Writer(
+      $data, $params{write_len} // $self->{write_len}, $on_write, $on_flush, $on_error, 0
+   );
 
    keys %params and croak "Unrecognised keys for ->write - " . join( ", ", keys %params );
 
@@ -875,7 +900,7 @@ sub _flush_one_read
    my $readqueue = $self->{readqueue};
 
    my $ret;
-   if( $readqueue->[0] and my $on_read = $readqueue->[0][RQ_ONREAD] ) {
+   if( $readqueue->[0] and my $on_read = $readqueue->[0]->on_read ) {
       $ret = $on_read->( $self, \$self->{readbuff}, $eof );
    }
    else {
@@ -890,7 +915,7 @@ sub _flush_one_read
 
    if( ref $ret eq "CODE" ) {
       # Replace the top CODE, or add it if there was none
-      $readqueue->[0] = [ $ret ];
+      $readqueue->[0] = Reader( $ret, undef );
       return 1;
    }
    elsif( @$readqueue and !defined $ret ) {
@@ -937,7 +962,7 @@ sub _do_read
             or $self->close_now;
 
          foreach ( @{ $self->{readqueue} } ) {
-            $_->[RQ_FUTURE]->fail( "read failed: $errno", sysread => $errno ) if $_->[RQ_FUTURE];
+            $_->future->fail( "read failed: $errno", sysread => $errno ) if $_->future;
          }
          undef @{ $self->{readqueue} };
 
@@ -960,7 +985,7 @@ sub _do_read
          $self->maybe_invoke_event( on_read_eof => );
          $self->close_now if $self->{close_on_read_eof};
          foreach ( @{ $self->{readqueue} } ) {
-            $_->[RQ_FUTURE]->done( undef ) if $_->[RQ_FUTURE];
+            $_->future->done( undef ) if $_->future;
          }
          undef @{ $self->{readqueue} };
          return;
@@ -1012,7 +1037,7 @@ sub push_on_read
    my ( $on_read, %args ) = @_;
    # %args undocumented for internal use
 
-   push @{ $self->{readqueue} }, [ $on_read, $args{future} ];
+   push @{ $self->{readqueue} }, Reader( $on_read, $args{future} );
 
    # TODO: Should this always defer?
    1 while length $self->{readbuff} and $self->_flush_one_read( 0 );
@@ -1075,9 +1100,9 @@ sub _read_future
    return $f;
 }
 
-=head2 $stream->read_atmost( $len ) ==> ( $string, $eof )
+=head2 ( $string, $eof ) = $stream->read_atmost( $len )->get
 
-=head2 $stream->read_exactly( $len ) ==> ( $string, $eof )
+=head2 ( $string, $eof ) = $stream->read_exactly( $len )->get
 
 Completes the C<Future> when the read buffer contains C<$len> or more
 characters of input. C<read_atmost> will also complete after the first
@@ -1117,7 +1142,7 @@ sub read_exactly
    return $f;
 }
 
-=head2 $stream->read_until( $end ) ==> ( $string, $eof )
+=head2 ( $string, $eof ) = $stream->read_until( $end )->get
 
 Completes the C<Future> when the read buffer contains a match for C<$end>,
 which may either be a plain string or a compiled C<Regexp> reference. Yields
@@ -1151,7 +1176,7 @@ sub read_until
    return $f;
 }
 
-=head2 $stream->read_until_eof ==> ( $string, $eof )
+=head2 ( $string, $eof ) = $stream->read_until_eof->get
 
 Completes the C<Future> when the stream is eventually closed at EOF, and
 yields all of the data that was available.
@@ -1233,9 +1258,6 @@ available then C<0> is returned, to indicate it should not be tried again. If
 a line was successfully extracted, then C<1> is returned, to indicate it
 should try again in case more lines exist in the buffer.
 
-For implementing real network protocols that are based on lines of text it may
-be more appropriate to use a subclass of L<IO::Async::Protocol::LineStream>.
-
 =head2 Reading binary data
 
 This C<on_read> method accepts incoming records in 16-byte chunks, printing
diff --git a/lib/IO/Async/Test.pm b/lib/IO/Async/Test.pm
index 29715e3..1e8ff24 100644
--- a/lib/IO/Async/Test.pm
+++ b/lib/IO/Async/Test.pm
@@ -8,7 +8,7 @@ package IO::Async::Test;
 use strict;
 use warnings;
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Exporter 'import';
 our @EXPORT = qw(
diff --git a/lib/IO/Async/Timer.pm b/lib/IO/Async/Timer.pm
index 42230f6..ccfe597 100644
--- a/lib/IO/Async/Timer.pm
+++ b/lib/IO/Async/Timer.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Notifier );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 
diff --git a/lib/IO/Async/Timer/Absolute.pm b/lib/IO/Async/Timer/Absolute.pm
index 3c61d2f..0672da0 100644
--- a/lib/IO/Async/Timer/Absolute.pm
+++ b/lib/IO/Async/Timer/Absolute.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Timer );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 
@@ -67,18 +67,14 @@ Invoked when the timer expires.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item on_expire => CODE
+=head2 on_expire => CODE
 
 CODE reference for the C<on_expire> event.
 
-=item time => NUM
+=head2 time => NUM
 
 The epoch time at which the timer will expire.
 
-=back
-
 Once constructed, the timer object will need to be added to the C<Loop> before
 it will work.
 
diff --git a/lib/IO/Async/Timer/Countdown.pm b/lib/IO/Async/Timer/Countdown.pm
index 9c478ad..6ee88f5 100644
--- a/lib/IO/Async/Timer/Countdown.pm
+++ b/lib/IO/Async/Timer/Countdown.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Timer );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 
@@ -68,25 +68,21 @@ Invoked when the timer expires.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item on_expire => CODE
+=head2 on_expire => CODE
 
 CODE reference for the C<on_expire> event.
 
-=item delay => NUM
+=head2 delay => NUM
 
 The delay in seconds after starting the timer until it expires. Cannot be
 changed if the timer is running. A timer with a zero delay expires
 "immediately".
 
-=item remove_on_expire => BOOL
+=head2 remove_on_expire => BOOL
 
 Optional. If true, remove this timer object from its parent notifier or
 containing loop when it expires. Defaults to false.
 
-=back
-
 Once constructed, the timer object will need to be added to the C<Loop> before
 it will work. It will also need to be started by the C<start> method.
 
diff --git a/lib/IO/Async/Timer/Periodic.pm b/lib/IO/Async/Timer/Periodic.pm
index ba8bdc4..84da8d9 100644
--- a/lib/IO/Async/Timer/Periodic.pm
+++ b/lib/IO/Async/Timer/Periodic.pm
@@ -9,7 +9,7 @@ use strict;
 use warnings;
 use base qw( IO::Async::Timer );
 
-our $VERSION = '0.63';
+our $VERSION = '0.64';
 
 use Carp;
 
@@ -69,18 +69,16 @@ Invoked on each interval of the timer.
 
 The following named parameters may be passed to C<new> or C<configure>:
 
-=over 8
-
-=item on_tick => CODE
+=head2 on_tick => CODE
 
 CODE reference for the C<on_tick> event.
 
-=item interval => NUM
+=head2 interval => NUM
 
 The interval in seconds between invocations of the callback or method. Cannot
 be changed if the timer is running.
 
-=item first_interval => NUM
+=head2 first_interval => NUM
 
 Optional. If defined, the interval in seconds after calling the C<start>
 method before the first invocation of the callback or method. Thereafter, the
@@ -91,7 +89,7 @@ Even if this value is zero, the first invocation will be made asynchronously,
 by the containing C<Loop> object, and not synchronously by the C<start> method
 itself.
 
-=item reschedule => STRING
+=head2 reschedule => STRING
 
 Optional. Must be one of C<hard>, C<skip> or C<drift>. Defines the algorithm
 used to reschedule the next invocation.
@@ -109,8 +107,6 @@ previous iteration's event handler returns. This allows it to slowly drift over
 time and become desynchronised with other events of the same interval or
 multiples/fractions of it.
 
-=back
-
 Once constructed, the timer object will need to be added to the C<Loop> before
 it will work. It will also need to be started by the C<start> method.
 
diff --git a/t/20handle.t b/t/20handle.t
index a4e273a..78023ef 100644
--- a/t/20handle.t
+++ b/t/20handle.t
@@ -7,6 +7,7 @@ use IO::Async::Test;
 
 use Test::More;
 use Test::Fatal;
+use Test::Identity;
 use Test::Refcount;
 
 use IO::Async::Loop;
@@ -383,6 +384,35 @@ my $sub_writeready = 0;
    ok( ( unpack_sockaddr_in( $handle->read_handle->sockname ) )[0], 'handle->sockname has nonzero port' );
 }
 
+# Construction of IO::Handle from fileno
+{
+   my $handle = IO::Async::Handle->new(
+      read_fileno => 0,
+      on_read_ready => sub { },
+   );
+
+   ok( defined $handle->read_handle, '->new with read_fileno creates read_handle' );
+   is( $handle->read_handle->fileno, 0, '->fileno of read_handle' );
+
+   $handle = IO::Async::Handle->new(
+      write_fileno => 1,
+      on_write_ready => sub { },
+   );
+
+   ok( defined $handle->write_handle, '->new with write_fileno creates write_handle' );
+   is( $handle->write_handle->fileno, 1, '->fileno of write_handle' );
+
+   $handle = IO::Async::Handle->new(
+      read_fileno  => 2,
+      write_fileno => 2,
+      on_read_ready  => sub { },
+      on_write_ready => sub { },
+   );
+
+   identical( $handle->read_handle, $handle->write_handle,
+      '->new with equal read and write fileno only creates one handle' );
+}
+
 done_testing;
 
 package TestHandle;
diff --git a/t/21stream-2write.t b/t/21stream-2write.t
index f110df4..b49cea8 100644
--- a/t/21stream-2write.t
+++ b/t/21stream-2write.t
@@ -257,10 +257,10 @@ SKIP: {
       on_write_eof => sub { $eof++ },
    );
 
-   $stream->write( "Junk" );
-
    $loop->add( $stream );
 
+   my $write_future = $stream->write( "Junk" );
+
    $rd->close;
 
    ok( !$stream->is_write_eof, '$stream->is_write_eof before wait' );
@@ -272,6 +272,9 @@ SKIP: {
    is( $eof, 1, 'EOF indication after wait' );
 
    ok( !defined $stream->loop, 'EOF stream no longer member of Loop' );
+
+   ok( $write_future->is_ready,'write future ready after stream closed' );
+   ok( $write_future->is_failed,'write future failed after stream closed' );
 }
 
 # Close
@@ -456,12 +459,15 @@ SKIP: {
 
    $loop->add( $stream );
 
-   $stream->write( "hello" );
+   my $write_future = $stream->write( "hello" );
 
    wait_for { defined $write_errno };
 
    cmp_ok( $write_errno, "==", ECONNRESET, 'errno after failed write' );
 
+   ok( $write_future->is_ready,'write future ready after failed write' );
+   ok( $write_future->is_failed,'write future failed after failed write' );
+
    $loop->remove( $stream );
 }
 
diff --git a/t/24listener.t b/t/24listener.t
index 5fa4a22..5a296aa 100644
--- a/t/24listener.t
+++ b/t/24listener.t
@@ -178,8 +178,15 @@ $listensock = IO::Socket::INET->new(
 }
 
 # Subclass
-my $sub_newclient;
 {
+   my $sub_newclient;
+   {
+      package TestListener;
+      use base qw( IO::Async::Listener );
+
+      sub on_accept { ( undef, $sub_newclient ) = @_ }
+   }
+
    my $listener = TestListener->new(
       handle => $listensock,
    );
@@ -212,6 +219,36 @@ my $sub_newclient;
    is_oneref( $listener, 'subclass $listener has refcount 1 after removing from Loop' );
 }
 
+# Subclass with handle_constructor
+{
+   {
+      package TestListener::WithConstructor;
+      use base qw( IO::Async::Listener );
+
+      sub handle_constructor { return IO::Async::Stream->new }
+   }
+
+   my $accepted;
+
+   my $listener = TestListener::WithConstructor->new(
+      handle => $listensock,
+      on_accept => sub { ( undef, $accepted ) = @_; },
+   );
+
+   $loop->add( $listener );
+
+   my $clientsock = IO::Socket::INET->new( Type => SOCK_STREAM )
+      or die "Cannot socket() - $!";
+
+   $clientsock->connect( $listensock->sockname ) or die "Cannot connect() - $!";
+
+   wait_for { defined $accepted };
+
+   isa_ok( $accepted, "IO::Async::Stream", '$accepted with handle_constructor method' );
+
+   $loop->remove( $listener );
+}
+
 {
    my $newclient;
    my $listener = IO::Async::Listener->new(
@@ -262,8 +299,3 @@ my $sub_newclient;
 }
 
 done_testing;
-
-package TestListener;
-use base qw( IO::Async::Listener );
-
-sub on_accept { ( undef, $sub_newclient ) = @_ }
diff --git a/t/42function.t b/t/42function.t
index 6324ecb..f4b1c4d 100644
--- a/t/42function.t
+++ b/t/42function.t
@@ -8,6 +8,7 @@ use IO::Async::Test;
 use Test::More;
 use Test::Fatal;
 use Test::Refcount;
+use constant HAVE_TEST_MEMORYGROWTH => eval { require Test::MemoryGrowth; };
 
 use File::Temp qw( tempdir );
 use Time::HiRes qw( sleep );
@@ -544,4 +545,25 @@ SKIP: {
    $loop->remove( $function );
 }
 
+# Leak test (RT99552)
+if( HAVE_TEST_MEMORYGROWTH ) {
+   diag( "Performing memory leak test" );
+
+   my $function = IO::Async::Function->new(
+      max_workers => 8,
+      code => sub {},
+   );
+
+   $loop->add( $function );
+
+   Test::MemoryGrowth::no_growth( sub {
+      $function->restart;
+      $function->call( args => [] )->get;
+   }, calls => 100,
+      'IO::Async::Function calls do not leak memory' );
+
+   $loop->remove( $function );
+   undef $function;
+}
+
 done_testing;

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-perl/packages/libio-async-perl.git



More information about the Pkg-perl-cvs-commits mailing list