The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
Changes 03
LICENSE 44
MANIFEST 12
META.json 46
META.yml 1213
Makefile.PL 2720
README 970
lib/Twiggy/Server.pm 09
lib/Twiggy.pm 11
t/anyevent_closed_streaming.t 092
t/release-pod-syntax.t 32
11 files changed (This is a version diff) 149152
@@ -1,5 +1,8 @@
 Revision history for Perl extension Twiggy
 
+0.1025  2015-01-04 07:00:16 JST
+        - Fix a bug where Twiggy's run loop exits after a streaming request is cut off (hoelzro) #41
+
 0.1024  2013-10-12 11:35:35 PDT
         - Fix a bug where exit_guard is not correctly decremented when writing header failed (maedama) #37
 
@@ -1,4 +1,4 @@
-This software is copyright (c) 2013 by Tatsuhiko Miyagawa.
+This software is copyright (c) 2015 by Tatsuhiko Miyagawa.
 
 This is free software; you can redistribute it and/or modify it under
 the same terms as the Perl 5 programming language system itself.
@@ -12,7 +12,7 @@ b) the "Artistic License"
 
 --- The GNU General Public License, Version 1, February 1989 ---
 
-This software is Copyright (c) 2013 by Tatsuhiko Miyagawa.
+This software is Copyright (c) 2015 by Tatsuhiko Miyagawa.
 
 This is free software, licensed under:
 
@@ -22,7 +22,7 @@ This is free software, licensed under:
                      Version 1, February 1989
 
  Copyright (C) 1989 Free Software Foundation, Inc.
- 51 Franklin St, Suite 500, Boston, MA  02110-1335  USA
+ 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
@@ -272,7 +272,7 @@ That's all there is to it!
 
 --- The Artistic License 1.0 ---
 
-This software is Copyright (c) 2013 by Tatsuhiko Miyagawa.
+This software is Copyright (c) 2015 by Tatsuhiko Miyagawa.
 
 This is free software, licensed under:
 
@@ -1,10 +1,10 @@
+# This file was automatically generated by Dist::Zilla::Plugin::Manifest v5.029.
 Changes
 LICENSE
 MANIFEST
 META.json
 META.yml
 Makefile.PL
-README
 cpanfile
 dist.ini
 eg/chat-websocket/chat.psgi
@@ -29,6 +29,7 @@ script/twiggy
 t/00_compile.t
 t/anyevent.t
 t/anyevent_closed_connection.t
+t/anyevent_closed_streaming.t
 t/anyevent_extensions.t
 t/anyevent_manyconnections.t
 t/anyevent_server_starter.t
@@ -4,13 +4,13 @@
       "Tatsuhiko Miyagawa"
    ],
    "dynamic_config" : 0,
-   "generated_by" : "Dist::Milla version v1.0.4, Dist::Zilla version 4.300039, CPAN::Meta::Converter version 2.132830",
+   "generated_by" : "Dist::Zilla version 5.029, Dist::Milla version v1.0.9, CPAN::Meta::Converter version 2.143240",
    "license" : [
       "perl_5"
    ],
    "meta-spec" : {
       "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
-      "version" : "2"
+      "version" : 2
    },
    "name" : "Twiggy",
    "no_index" : {
@@ -26,11 +26,12 @@
    "prereqs" : {
       "configure" : {
          "requires" : {
-            "ExtUtils::MakeMaker" : "6.30"
+            "ExtUtils::MakeMaker" : "0"
          }
       },
       "develop" : {
          "requires" : {
+            "Dist::Milla" : "v1.0.9",
             "Test::Pod" : "1.41"
          }
       },
@@ -63,13 +64,14 @@
          "web" : "https://github.com/miyagawa/Twiggy"
       }
    },
-   "version" : "0.1024",
+   "version" : "0.1025",
    "x_contributors" : [
       "Adam Thomason <ad@mthomason.net>",
       "Chia-liang Kao <clkao@clkao.org>",
       "Kazuho Oku <kazuho@kazdev.in.labs.cybozu.co.jp>",
       "Moritz Onken <onken@netcubed.de>",
       "Pedro Melo <melo@simplicidade.org>",
+      "Rob Hoelz <rob@hoelz.ro>",
       "Sergey Zasenko <d3fin3@gmail.com>",
       "Tatsuhiko Miyagawa <miyagawa@gmail.com>",
       "Tomas Doran (t0m) <t0m@state51.co.uk>",
@@ -3,17 +3,17 @@ abstract: 'AnyEvent HTTP server for PSGI (like Thin)'
 author:
   - 'Tatsuhiko Miyagawa'
 build_requires:
-  Test::More: 0
-  Test::Requires: 0
-  Test::TCP: 0
+  Test::More: '0'
+  Test::Requires: '0'
+  Test::TCP: '0'
 configure_requires:
-  ExtUtils::MakeMaker: 6.30
+  ExtUtils::MakeMaker: '0'
 dynamic_config: 0
-generated_by: 'Dist::Milla version v1.0.4, Dist::Zilla version 4.300039, CPAN::Meta::Converter version 2.132830'
+generated_by: 'Dist::Zilla version 5.029, Dist::Milla version v1.0.9, CPAN::Meta::Converter version 2.143240'
 license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
-  version: 1.4
+  version: '1.4'
 name: Twiggy
 no_index:
   directory:
@@ -24,22 +24,23 @@ no_index:
     - eg
     - examples
 requires:
-  AnyEvent: 0
-  HTTP::Status: 0
-  Plack: 0.99
-  Try::Tiny: 0
-  perl: 5.008001
+  AnyEvent: '0'
+  HTTP::Status: '0'
+  Plack: '0.99'
+  Try::Tiny: '0'
+  perl: '5.008001'
 resources:
   bugtracker: https://github.com/miyagawa/Twiggy/issues
   homepage: https://github.com/miyagawa/Twiggy
   repository: https://github.com/miyagawa/Twiggy.git
-version: 0.1024
+version: '0.1025'
 x_contributors:
   - 'Adam Thomason <ad@mthomason.net>'
   - 'Chia-liang Kao <clkao@clkao.org>'
   - 'Kazuho Oku <kazuho@kazdev.in.labs.cybozu.co.jp>'
   - 'Moritz Onken <onken@netcubed.de>'
   - 'Pedro Melo <melo@simplicidade.org>'
+  - 'Rob Hoelz <rob@hoelz.ro>'
   - 'Sergey Zasenko <d3fin3@gmail.com>'
   - 'Tatsuhiko Miyagawa <miyagawa@gmail.com>'
   - 'Tomas Doran (t0m) <t0m@state51.co.uk>'
@@ -1,25 +1,26 @@
 
+# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.029.
 use strict;
 use warnings;
 
 use 5.008001;
 
-use ExtUtils::MakeMaker 6.30;
+use ExtUtils::MakeMaker;
 
 
 
 my %WriteMakefileArgs = (
   "ABSTRACT" => "AnyEvent HTTP server for PSGI (like Thin)",
   "AUTHOR" => "Tatsuhiko Miyagawa",
-  "BUILD_REQUIRES" => {},
   "CONFIGURE_REQUIRES" => {
-    "ExtUtils::MakeMaker" => "6.30"
+    "ExtUtils::MakeMaker" => 0
   },
   "DISTNAME" => "Twiggy",
   "EXE_FILES" => [
     "script/twiggy"
   ],
   "LICENSE" => "perl",
+  "MIN_PERL_VERSION" => "5.008001",
   "NAME" => "Twiggy",
   "PREREQ_PM" => {
     "AnyEvent" => 0,
@@ -32,37 +33,29 @@ my %WriteMakefileArgs = (
     "Test::Requires" => 0,
     "Test::TCP" => 0
   },
-  "VERSION" => "0.1024",
+  "VERSION" => "0.1025",
   "test" => {
     "TESTS" => "t/*.t"
   }
 );
 
 
-unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) {
-  my $tr = delete $WriteMakefileArgs{TEST_REQUIRES};
-  my $br = $WriteMakefileArgs{BUILD_REQUIRES};
-  for my $mod ( keys %$tr ) {
-    if ( exists $br->{$mod} ) {
-      $br->{$mod} = $tr->{$mod} if $tr->{$mod} > $br->{$mod};
-    }
-    else {
-      $br->{$mod} = $tr->{$mod};
-    }
-  }
-}
+my %FallbackPrereqs = (
+  "AnyEvent" => 0,
+  "ExtUtils::MakeMaker" => 0,
+  "HTTP::Status" => 0,
+  "Plack" => "0.99",
+  "Test::More" => 0,
+  "Test::Requires" => 0,
+  "Test::TCP" => 0,
+  "Try::Tiny" => 0
+);
 
-unless ( eval { ExtUtils::MakeMaker->VERSION(6.56) } ) {
-  my $br = delete $WriteMakefileArgs{BUILD_REQUIRES};
-  my $pp = $WriteMakefileArgs{PREREQ_PM};
-  for my $mod ( keys %$br ) {
-    if ( exists $pp->{$mod} ) {
-      $pp->{$mod} = $br->{$mod} if $br->{$mod} > $pp->{$mod};
-    }
-    else {
-      $pp->{$mod} = $br->{$mod};
-    }
-  }
+
+unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) {
+  delete $WriteMakefileArgs{TEST_REQUIRES};
+  delete $WriteMakefileArgs{BUILD_REQUIRES};
+  $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs;
 }
 
 delete $WriteMakefileArgs{CONFIGURE_REQUIRES}
@@ -1,97 +0,0 @@
-NAME
-    Twiggy - AnyEvent HTTP server for PSGI (like Thin)
-
-SYNOPSIS
-      twiggy --listen :8080
-
-    See "twiggy -h" for more details.
-
-      use Twiggy::Server;
-
-      my $server = Twiggy::Server->new(
-          host => $host,
-          port => $port,
-      );
-      $server->register_service($app);
-
-      AE::cv->recv;
-
-DESCRIPTION
-    Twiggy is a lightweight and fast HTTP server with unique features such
-    as:
-
-    PSGI
-        Can run any PSGI applications. Fully supports *psgi.nonblocking* and
-        *psgi.streaming* interfaces.
-
-    AnyEvent
-        This server uses AnyEvent and runs in a non-blocking event loop, so
-        it's best to run event-driven web applications that runs I/O bound
-        jobs or delayed responses such as long-poll, WebSocket or streaming
-        content (server push).
-
-        This software used to be called Plack::Server::AnyEvent but was
-        renamed to Twiggy. See "NAMING" for details.
-
-    Fast header parser
-        Uses XS/C based HTTP header parser for the best performance.
-        (optional, install the HTTP::Parser::XS module to enable it; see
-        also Plack::HTTPParser for more information).
-
-    Lightweight and Fast
-        The memory required to run twiggy is 6MB and it can serve more than
-        4500 req/s with a single process on Perl 5.10 with MacBook Pro 13"
-        late 2009.
-
-    Superdaemon aware
-        Supports Server::Starter for hot deploy and graceful restarts.
-
-        To use it, instead of the usual:
-
-            plackup --server Twiggy --port 8111 app.psgi
-
-        install Server::Starter and use:
-
-            start_server --port 8111 plackup --server Twiggy app.psgi
-
-ENVIRONMENT
-    The following environment variables are supported.
-
-    TWIGGY_DEBUG
-        Set to true to enable debug messages from Twiggy.
-
-NAMING
-  Twiggy?
-    Because it is like Thin <http://code.macournoyer.com/thin/>, Ruby's Rack
-    web server using EventMachine. You know, Twiggy is thin :)
-
-  Why the cute name instead of more descriptive namespace? Are you on drugs?
-    I'm sick of naming Perl software like
-    HTTP::Server::PSGI::How::Its::Written::With::What::Module and people
-    call it HSPHIWWWM on IRC. It's hard to say on speeches and newbies would
-    ask questions what they stand for every day. That's crazy.
-
-    This module actually includes the longer alias and an empty subclass
-    AnyEvent::Server::PSGI for those who like to type more ::'s. It would
-    actually help you find this software by searching for *PSGI Server
-    AnyEvent* on CPAN, which i believe is a good thing.
-
-    Yes, maybe I'm on drugs. We'll see.
-
-LICENSE
-    This module is licensed under the same terms as Perl itself.
-
-AUTHOR
-    Tatsuhiko Miyagawa
-
-    Tokuhiro Matsuno
-
-    Yuval Kogman
-
-    Hideki Yamamura
-
-    Daisuke Maki
-
-SEE ALSO
-    Plack AnyEvent Tatsumaki
-
@@ -315,6 +315,8 @@ sub _run_app {
         Carp::carp("Returning AnyEvent condvar is deprecated and will be removed in the next release of Twiggy. Use the streaming callback interface intstead.");
         $res->cb(sub { $self->_write_psgi_response($sock, shift->recv) });
     } elsif ( ref $res eq 'CODE' ) {
+        my $created_writer;
+
         $res->(
             sub {
                 my $res = shift;
@@ -327,6 +329,7 @@ sub _run_app {
                     $self->_flush($sock);
 
                     my $writer = Twiggy::Writer->new($sock, $self->{exit_guard});
+                    $created_writer = 1;
 
                     my $buf = $self->_format_headers($status, $headers);
                     $writer->write($$buf);
@@ -340,6 +343,10 @@ sub _run_app {
             },
             $sock,
         );
+
+        if($created_writer) {
+            $self->{exit_guard}->end; # normally _write_psgi_response calls this, but it doesn't get called when we use a writer!
+        }
     } else {
         croak("Unknown response type: $res");
     }
@@ -598,6 +605,8 @@ use AnyEvent::Handle;
 sub new {
     my ( $class, $socket, $exit ) = @_;
 
+    $exit->begin if $exit;
+
     bless { handle => AnyEvent::Handle->new( fh => $socket ), exit_guard => $exit }, $class;
 }
 
@@ -2,7 +2,7 @@ package Twiggy;
 use strict;
 use warnings;
 use 5.008_001;
-our $VERSION = '0.1024';
+our $VERSION = '0.1025';
 
 1;
 __END__
@@ -0,0 +1,92 @@
+use strict;
+use warnings;
+
+use Test::Requires qw(AnyEvent::HTTP);
+use AnyEvent::HTTP;
+use Test::More;
+use Test::TCP;
+use Plack::Loader;
+use POSIX ();
+use Time::HiRes qw(usleep);
+
+sub do_streaming_request {
+    my ( $url, $callback ) = @_;
+
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my $cond = AnyEvent->condvar;
+
+    http_get $url, timeout => 3, want_body_handle => 1, sub {
+        my ( $h, $headers ) = @_;
+
+        is $headers->{'Status'}, 200, 'streaming response should succeed';
+
+        $h->on_read(sub {
+            $h->push_read(line => sub {
+                my ( undef, $line ) = @_;
+
+                my $stop = $callback->($line, $cond);
+                if($stop) {
+                    $h->destroy;
+                    $cond->send;
+                }
+            });
+        });
+
+        $h->on_error(sub {
+            my ( undef, undef, $error ) = @_;
+
+            fail "Unexpected error: $error";
+            $h->destroy;
+            $cond->send;
+        });
+
+        $h->on_eof(sub {
+            $h->destroy;
+            $cond->send;
+        });
+    };
+    $cond->recv;
+}
+
+my $app = sub {
+    my ( $env ) = @_;
+
+    return sub {
+        my ( $respond ) = @_;
+
+        my $writer = $respond->( [200, ['Content-Type', 'text/plain'] ] );
+
+        foreach my $number ( 1 .. 10 ) {
+            $writer->write($number . "\n");
+            usleep 100_000;
+        }
+    };
+};
+
+my $server = Test::TCP->new(
+    code => sub {
+        my ( $port ) = @_;
+
+        my $server = Plack::Loader->load('Twiggy', port => $port, host => '127.0.0.1');
+        $server->run($app);
+        exit;
+    },
+);
+
+do_streaming_request('http://127.0.0.1:' . $server->port, sub {
+    my ( $line, $cond ) = @_;
+
+    if($line == 5) {
+        return 1;
+    }
+    return;
+});
+
+sleep 1; # give the process a bit to clean up, if it died
+
+my $kid = waitpid $server->pid, POSIX::WNOHANG;
+
+ok $kid != $server->pid, 'Server should stay alive after a single client breaks it connection';
+
+done_testing();
@@ -7,9 +7,8 @@ BEGIN {
   }
 }
 
+# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests.
 use Test::More;
-
-eval "use Test::Pod 1.41";
-plan skip_all => "Test::Pod 1.41 required for testing POD" if $@;
+use Test::Pod 1.41;
 
 all_pod_files_ok();