user/dev discussion of public-inbox itself
 help / color / mirror / code / Atom feed
* [PATCH 0/3] more networking/daemon fixes
@ 2016-05-24  4:22 Eric Wong
  2016-05-24  4:22 ` [PATCH 1/3] standardize timer-related event-loop code Eric Wong
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Eric Wong @ 2016-05-24  4:22 UTC (permalink / raw)
  To: meta

This series finally reinstates process limiting by queueing
processes instead of rejecting and falling back to dumb.
Falling back to dumb fails badly if we have to switch in
the middle of a pack negotiation.

I've tested it a bunch with aborted git clones of large
repositories with our own -httpd (using async) and fixed a few
longstanding bugs.  The generic PSGI code still needs work
to resume properly and to avoid leaking references.

Eric Wong (3):
      standardize timer-related event-loop code
      http: fix various race conditions
      git-http-backend: use qspawn to limit running processes

 lib/PublicInbox/EvCleanup.pm      | 41 ++++++++++++++++++++
 lib/PublicInbox/GitHTTPBackend.pm | 38 ++++++++-----------
 lib/PublicInbox/HTTP.pm           | 80 +++++++++++++++++++--------------------
 lib/PublicInbox/HTTPD/Async.pm    |  3 +-
 lib/PublicInbox/NNTP.pm           | 30 +++++++--------
 lib/PublicInbox/Qspawn.pm         | 52 +++++++++++++++++++++++++
 t/qspawn.t                        | 60 +++++++++++++++++++++++++++++
 7 files changed, 223 insertions(+), 81 deletions(-)


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/3] standardize timer-related event-loop code
  2016-05-24  4:22 [PATCH 0/3] more networking/daemon fixes Eric Wong
@ 2016-05-24  4:22 ` Eric Wong
  2016-05-24  4:22 ` [PATCH 2/3] http: fix various race conditions Eric Wong
  2016-05-24  4:22 ` [PATCH 3/3] git-http-backend: use qspawn to limit running processes Eric Wong
  2 siblings, 0 replies; 4+ messages in thread
From: Eric Wong @ 2016-05-24  4:22 UTC (permalink / raw)
  To: meta

Standardize the code we have in place to avoid creating too many
timer objects.  We do not need exact timers for things that don't
need to be run ASAP, so we can play things fast and loose to avoid
wasting power with unnecessary wakeups.

We only need two classes of timers:

* asap - run this on the next loop tick, after operating on
  @Danga::Socket::ToClose to close remaining sockets

* later - run at some point in the future.  It could be as
  soon as immediately (like "asap"), and as late as 60s into
  the future.

In the future, we support an "emergency" switch to fire "later"
timers immediately.
---
 lib/PublicInbox/EvCleanup.pm   | 41 +++++++++++++++++++++++++++++++++++++++++
 lib/PublicInbox/HTTP.pm        | 26 +++++++++++++-------------
 lib/PublicInbox/HTTPD/Async.pm |  3 ++-
 lib/PublicInbox/NNTP.pm        | 30 +++++++++++++-----------------
 4 files changed, 69 insertions(+), 31 deletions(-)
 create mode 100644 lib/PublicInbox/EvCleanup.pm

diff --git a/lib/PublicInbox/EvCleanup.pm b/lib/PublicInbox/EvCleanup.pm
new file mode 100644
index 0000000..5efb093
--- /dev/null
+++ b/lib/PublicInbox/EvCleanup.pm
@@ -0,0 +1,41 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+
+# event cleanups (currently for Danga::Socket)
+package PublicInbox::EvCleanup;
+use strict;
+use warnings;
+
+my $asapq = { queue => [], timer => undef };
+my $laterq = { queue => [], timer => undef };
+
+sub _run_all ($) {
+	my ($q) = @_;
+
+	my $run = $q->{queue};
+	$q->{queue} = [];
+	$q->{timer} = undef;
+	$_->() foreach @$run;
+}
+
+sub _run_asap () { _run_all($asapq) }
+sub _run_later () { _run_all($laterq) }
+
+sub asap ($) {
+	my ($cb) = @_;
+	push @{$asapq->{queue}}, $cb;
+	$asapq->{timer} ||= Danga::Socket->AddTimer(0, *_run_asap);
+}
+
+sub later ($) {
+	my ($cb) = @_;
+	push @{$laterq->{queue}}, $cb;
+	$laterq->{timer} ||= Danga::Socket->AddTimer(60, *_run_later);
+}
+
+END {
+	_run_asap();
+	_run_later();
+}
+
+1;
diff --git a/lib/PublicInbox/HTTP.pm b/lib/PublicInbox/HTTP.pm
index 104a213..00c9a04 100644
--- a/lib/PublicInbox/HTTP.pm
+++ b/lib/PublicInbox/HTTP.pm
@@ -26,13 +26,22 @@ use constant {
 
 # FIXME: duplicated code with NNTP.pm
 my $WEAKEN = {}; # string(inbox) -> inbox
-my $WEAKTIMER;
+my $weakt;
 sub weaken_task () {
-	$WEAKTIMER = undef;
+	$weakt = undef;
 	$_->weaken_all for values %$WEAKEN;
 	$WEAKEN = {};
 }
 
+my $pipelineq = [];
+my $pipet;
+sub process_pipelineq () {
+	my $q = $pipelineq;
+	$pipet = undef;
+	$pipelineq = [];
+	rbuf_process($_) foreach @$q;
+}
+
 # Use the same configuration parameter as git since this is primarily
 # a slow-client sponge for git-http-backend
 # TODO: support per-respository http.maxRequestBuffer somehow...
@@ -234,7 +243,7 @@ sub response_write {
 		if (my $obj = $env->{'pi-httpd.inbox'}) {
 			# grace period for reaping resources
 			$WEAKEN->{"$obj"} = $obj;
-			$WEAKTIMER ||= Danga::Socket->AddTimer(60, *weaken_task);
+			$weakt ||= PublicInbox::EvCleanup::later(*weaken_task);
 		}
 		$self->{env} = undef;
 	};
@@ -281,15 +290,6 @@ sub more ($$) {
 	$self->write($_[1]);
 }
 
-my $pipelineq = [];
-my $next_tick;
-sub process_pipelineq () {
-	$next_tick = undef;
-	my $q = $pipelineq;
-	$pipelineq = [];
-	rbuf_process($_) foreach @$q;
-}
-
 # overrides existing Danga::Socket method
 sub event_write {
 	my ($self) = @_;
@@ -300,7 +300,7 @@ sub event_write {
 		$self->watch_read(1);
 	} else { # avoid recursion for pipelined requests
 		push @$pipelineq, $self;
-		$next_tick ||= Danga::Socket->AddTimer(0, *process_pipelineq);
+		$pipet ||= PublicInbox::EvCleanup::asap(*process_pipelineq);
 	}
 }
 
diff --git a/lib/PublicInbox/HTTPD/Async.pm b/lib/PublicInbox/HTTPD/Async.pm
index bd2eacb..47ba27d 100644
--- a/lib/PublicInbox/HTTPD/Async.pm
+++ b/lib/PublicInbox/HTTPD/Async.pm
@@ -10,6 +10,7 @@ use strict;
 use warnings;
 use base qw(Danga::Socket);
 use fields qw(cb cleanup);
+require PublicInbox::EvCleanup;
 
 sub new {
 	my ($class, $io, $cb, $cleanup) = @_;
@@ -61,7 +62,7 @@ sub close {
 	$self->SUPER::close(@_);
 
 	# we defer this to the next timer loop since close is deferred
-	Danga::Socket->AddTimer(0, $cleanup) if $cleanup;
+	PublicInbox::EvCleanup::asap($cleanup) if $cleanup;
 }
 
 # do not let ourselves be closed during graceful termination
diff --git a/lib/PublicInbox/NNTP.pm b/lib/PublicInbox/NNTP.pm
index ac536f7..f3de4b1 100644
--- a/lib/PublicInbox/NNTP.pm
+++ b/lib/PublicInbox/NNTP.pm
@@ -11,6 +11,7 @@ use PublicInbox::Search;
 use PublicInbox::Msgmap;
 use PublicInbox::Git;
 use PublicInbox::MID qw(mid2path);
+require PublicInbox::EvCleanup;
 use Email::Simple;
 use POSIX qw(strftime);
 use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
@@ -38,15 +39,15 @@ my $LIST_HEADERS = join("\r\n", @OVERVIEW,
 my %DISABLED; # = map { $_ => 1 } qw(xover list_overview_fmt newnews xhdr);
 
 my $EXPMAP; # fd -> [ idle_time, $self ]
-my $EXPTIMER;
+my $expt;
 our $EXPTIME = 180; # 3 minutes
 my $WEAKEN = {}; # string(nntpd) -> nntpd
-my $WEAKTIMER;
+my $weakt;
+my $nextt;
 
-my $next_tick;
 my $nextq = [];
 sub next_tick () {
-	$next_tick = undef;
+	$nextt = undef;
 	my $q = $nextq;
 	$nextq = [];
 	foreach my $nntp (@$q) {
@@ -70,7 +71,7 @@ sub update_idle_time ($) {
 # reduce FD pressure by closing some "git cat-file --batch" processes
 # and unused FDs for msgmap and Xapian indices
 sub weaken_groups () {
-	$WEAKTIMER = undef;
+	$weakt = undef;
 	foreach my $nntpd (values %$WEAKEN) {
 		$_->weaken_all foreach (@{$nntpd->{grouplist}});
 	}
@@ -81,7 +82,6 @@ sub expire_old () {
 	my $now = now();
 	my $exp = $EXPTIME;
 	my $old = $now - $exp;
-	my $next = $now + $exp;
 	my $nr = 0;
 	my %new;
 	while (my ($fd, $v) = each %$EXPMAP) {
@@ -89,26 +89,22 @@ sub expire_old () {
 		if ($idle_time < $old) {
 			$nntp->close; # idempotent
 		} else {
-			my $nexp = $idle_time + $exp;
-			$next = $nexp if ($nexp < $next);
 			++$nr;
 			$new{$fd} = $v;
 		}
 	}
 	$EXPMAP = \%new;
 	if ($nr) {
-		$next -= $now;
-		$next = 0 if $next < 0;
-		$EXPTIMER = Danga::Socket->AddTimer($next, *expire_old);
+		$expt = PublicInbox::EvCleanup::later(*expire_old);
 		weaken_groups();
 	} else {
-		$EXPTIMER = undef;
+		$expt = undef;
 		# noop to kick outselves out of the loop ASAP so descriptors
 		# really get closed
-		Danga::Socket->AddTimer(0, sub {});
+		PublicInbox::EvCleanup::asap(sub {});
 
 		# grace period for reaping resources
-		$WEAKTIMER ||= Danga::Socket->AddTimer(30, *weaken_groups);
+		$weakt ||= PublicInbox::EvCleanup::later(*weaken_groups);
 	}
 }
 
@@ -122,7 +118,7 @@ sub new ($$$) {
 	$self->watch_read(1);
 	update_idle_time($self);
 	$WEAKEN->{"$nntpd"} = $nntpd;
-	$EXPTIMER ||= Danga::Socket->AddTimer($EXPTIME, *expire_old);
+	$expt ||= PublicInbox::EvCleanup::later(*expire_old);
 	$self;
 }
 
@@ -633,7 +629,7 @@ sub long_response ($$$$) {
 			update_idle_time($self);
 
 			push @$nextq, $self;
-			$next_tick ||= Danga::Socket->AddTimer(0, *next_tick);
+			$nextt ||= PublicInbox::EvCleanup::asap(*next_tick);
 		} else { # all done!
 			$self->{long_res} = undef;
 			$self->watch_read(1);
@@ -996,7 +992,7 @@ sub watch_read {
 		# in case we really did dispatch a read event and started
 		# another long response.
 		push @$nextq, $self;
-		$next_tick ||= Danga::Socket->AddTimer(0, *next_tick);
+		$nextt ||= PublicInbox::EvCleanup::asap(*next_tick);
 	}
 	$rv;
 }

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/3] http: fix various race conditions
  2016-05-24  4:22 [PATCH 0/3] more networking/daemon fixes Eric Wong
  2016-05-24  4:22 ` [PATCH 1/3] standardize timer-related event-loop code Eric Wong
@ 2016-05-24  4:22 ` Eric Wong
  2016-05-24  4:22 ` [PATCH 3/3] git-http-backend: use qspawn to limit running processes Eric Wong
  2 siblings, 0 replies; 4+ messages in thread
From: Eric Wong @ 2016-05-24  4:22 UTC (permalink / raw)
  To: meta

We no longer override Danga::Socket::event_write and instead
re-enable reads by queuing up another callback in the $close
response callback.  This is necessary because event_write may not be
completely done writing a response, only the existing buffered data.

Furthermore, the {closed} field can almost be set at any time when
writing, so we must check it before acting on pipelined requests as
well as during write callbacks in more().
---
 lib/PublicInbox/HTTP.pm | 60 ++++++++++++++++++++++++-------------------------
 1 file changed, 30 insertions(+), 30 deletions(-)

diff --git a/lib/PublicInbox/HTTP.pm b/lib/PublicInbox/HTTP.pm
index 00c9a04..6aae5c8 100644
--- a/lib/PublicInbox/HTTP.pm
+++ b/lib/PublicInbox/HTTP.pm
@@ -39,7 +39,10 @@ sub process_pipelineq () {
 	my $q = $pipelineq;
 	$pipet = undef;
 	$pipelineq = [];
-	rbuf_process($_) foreach @$q;
+	foreach (@$q) {
+		next if $_->{closed};
+		rbuf_process($_);
+	}
 }
 
 # Use the same configuration parameter as git since this is primarily
@@ -228,26 +231,36 @@ sub identity_wcb ($) {
 	sub { $self->write(\($_[0])) if $_[0] ne '' }
 }
 
+sub next_request ($) {
+	my ($self) = @_;
+	$self->watch_write(0);
+	if ($self->{rbuf} eq '') { # wait for next request
+		$self->watch_read(1);
+	} else { # avoid recursion for pipelined requests
+		push @$pipelineq, $self;
+		$pipet ||= PublicInbox::EvCleanup::asap(*process_pipelineq);
+	}
+}
+
+sub response_done ($$) {
+	my ($self, $alive) = @_;
+	my $env = $self->{env};
+	$self->{env} = undef;
+	$self->write("0\r\n\r\n") if $alive == 2;
+	$self->write(sub { $alive ? next_request($self) : $self->close });
+	if (my $obj = $env->{'pi-httpd.inbox'}) {
+		# grace period for reaping resources
+		$WEAKEN->{"$obj"} = $obj;
+		PublicInbox::EvCleanup::later(*weaken_task);
+	}
+}
+
 sub response_write {
 	my ($self, $env, $res) = @_;
 	my $alive = response_header_write($self, $env, $res);
 
 	my $write = $alive == 2 ? chunked_wcb($self) : identity_wcb($self);
-	my $close = sub {
-		$self->write("0\r\n\r\n") if $alive == 2;
-		if ($alive) {
-			$self->event_write; # watch for readability if done
-		} else {
-			Danga::Socket::write($self, sub { $self->close });
-		}
-		if (my $obj = $env->{'pi-httpd.inbox'}) {
-			# grace period for reaping resources
-			$WEAKEN->{"$obj"} = $obj;
-			$weakt ||= PublicInbox::EvCleanup::later(*weaken_task);
-		}
-		$self->{env} = undef;
-	};
-
+	my $close = sub { response_done($self, $alive) };
 	if (defined(my $body = $res->[2])) {
 		if (ref $body eq 'ARRAY') {
 			$write->($_) foreach @$body;
@@ -278,6 +291,7 @@ sub response_write {
 use constant MSG_MORE => ($^O eq 'linux') ? 0x8000 : 0;
 sub more ($$) {
 	my $self = $_[0];
+	return if $self->{closed};
 	if (MSG_MORE && !$self->{write_buf_size}) {
 		my $n = send($self->{sock}, $_[1], MSG_MORE);
 		if (defined $n) {
@@ -290,20 +304,6 @@ sub more ($$) {
 	$self->write($_[1]);
 }
 
-# overrides existing Danga::Socket method
-sub event_write {
-	my ($self) = @_;
-	# only continue watching for readability when we are done writing:
-	return if $self->write(undef) != 1;
-
-	if ($self->{rbuf} eq '') { # wait for next request
-		$self->watch_read(1);
-	} else { # avoid recursion for pipelined requests
-		push @$pipelineq, $self;
-		$pipet ||= PublicInbox::EvCleanup::asap(*process_pipelineq);
-	}
-}
-
 sub input_prepare {
 	my ($self, $env) = @_;
 	my $input = $null_io;

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 3/3] git-http-backend: use qspawn to limit running processes
  2016-05-24  4:22 [PATCH 0/3] more networking/daemon fixes Eric Wong
  2016-05-24  4:22 ` [PATCH 1/3] standardize timer-related event-loop code Eric Wong
  2016-05-24  4:22 ` [PATCH 2/3] http: fix various race conditions Eric Wong
@ 2016-05-24  4:22 ` Eric Wong
  2 siblings, 0 replies; 4+ messages in thread
From: Eric Wong @ 2016-05-24  4:22 UTC (permalink / raw)
  To: meta

Having an excessive amount of git-pack-objects processes is
dangerous to the health of the server.  Queue up process spawning
for long-running responses and serve them sequentially, instead.
---
 lib/PublicInbox/GitHTTPBackend.pm | 38 ++++++++++---------------
 lib/PublicInbox/Qspawn.pm         | 52 +++++++++++++++++++++++++++++++++
 t/qspawn.t                        | 60 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 127 insertions(+), 23 deletions(-)
 create mode 100644 lib/PublicInbox/Qspawn.pm
 create mode 100644 t/qspawn.t

diff --git a/lib/PublicInbox/GitHTTPBackend.pm b/lib/PublicInbox/GitHTTPBackend.pm
index ded56b3..9464cb4 100644
--- a/lib/PublicInbox/GitHTTPBackend.pm
+++ b/lib/PublicInbox/GitHTTPBackend.pm
@@ -8,9 +8,9 @@ use strict;
 use warnings;
 use Fcntl qw(:seek);
 use IO::File;
-use PublicInbox::Spawn qw(spawn);
 use HTTP::Date qw(time2str);
 use HTTP::Status qw(status_message);
+use PublicInbox::Qspawn;
 
 # n.b. serving "description" and "cloneurl" should be innocuous enough to
 # not cause problems.  serving "config" might...
@@ -167,11 +167,6 @@ sub serve_smart {
 	unless (defined $fd && $fd >= 0) {
 		$in = input_to_file($env) or return r(500);
 	}
-	my ($rpipe, $wpipe);
-	unless (pipe($rpipe, $wpipe)) {
-		err($env, "error creating pipe: $! - going static");
-		return;
-	}
 	my %env = %ENV;
 	# GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL
 	# may be set in the server-process and are passed as-is
@@ -187,20 +182,13 @@ sub serve_smart {
 	my $git_dir = $git->{git_dir};
 	$env{GIT_HTTP_EXPORT_ALL} = '1';
 	$env{PATH_TRANSLATED} = "$git_dir/$path";
-	my %rdr = ( 0 => fileno($in), 1 => fileno($wpipe) );
-	my $pid = spawn([qw(git http-backend)], \%env, \%rdr);
-	unless (defined $pid) {
-		err($env, "error spawning: $! - going static");
-		return;
-	}
-	$wpipe = $in = undef;
-	my $fh;
+	my %rdr = ( 0 => fileno($in) );
+	my $x = PublicInbox::Qspawn->new([qw(git http-backend)], \%env, \%rdr);
+	my ($fh, $rpipe);
 	my $end = sub {
 		$rpipe = undef;
-		my $e = $pid == waitpid($pid, 0) ?
-			$? : "PID:$pid still running?";
-		if ($e) {
-			err($env, "git http-backend ($git_dir): $e");
+		if (my $err = $x->finish) {
+			err($env, "git http-backend ($git_dir): $err");
 			drop_client($env);
 		}
 		$fh->close if $fh; # async-only
@@ -248,11 +236,15 @@ sub serve_smart {
 		# holding the input here is a waste of FDs and memory
 		$env->{'psgi.input'} = undef;
 
-		if ($async) {
-			$async = $async->($rpipe, $cb, $end);
-		} else { # generic PSGI
-			$cb->() while $rd_hdr;
-		}
+		$x->start(sub { # may run later, much later...
+			($rpipe) = @_;
+			$in = undef;
+			if ($async) {
+				$async = $async->($rpipe, $cb, $end);
+			} else { # generic PSGI
+				$cb->() while $rd_hdr;
+			}
+		});
 	};
 }
 
diff --git a/lib/PublicInbox/Qspawn.pm b/lib/PublicInbox/Qspawn.pm
new file mode 100644
index 0000000..9e4c8e0
--- /dev/null
+++ b/lib/PublicInbox/Qspawn.pm
@@ -0,0 +1,52 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+package PublicInbox::Qspawn;
+use strict;
+use warnings;
+use PublicInbox::Spawn qw(popen_rd);
+our $LIMIT = 1;
+my $running = 0;
+my @run_queue;
+
+sub new ($$$;) {
+	my ($class, $cmd, $env, $opt) = @_;
+	bless { args => [ $cmd, $env, $opt ] }, $class;
+}
+
+sub _do_spawn {
+	my ($self, $cb) = @_;
+	my $err;
+	($self->{rpipe}, $self->{pid}) = popen_rd(@{$self->{args}});
+	if ($self->{pid}) {
+		$running++;
+	} else {
+		$self->{err} = $!;
+	}
+	$cb->($self->{rpipe});
+}
+
+sub finish ($) {
+	my ($self) = @_;
+	if (delete $self->{rpipe}) {
+		my $pid = delete $self->{pid};
+		$self->{err} = $pid == waitpid($pid, 0) ? $? :
+				"PID:$pid still running?";
+		$running--;
+	}
+	if (my $next = shift @run_queue) {
+		_do_spawn(@$next);
+	}
+	$self->{err};
+}
+
+sub start ($$) {
+	my ($self, $cb) = @_;
+
+	if ($running < $LIMIT) {
+		_do_spawn($self, $cb);
+	} else {
+		push @run_queue, [ $self, $cb ];
+	}
+}
+
+1;
diff --git a/t/qspawn.t b/t/qspawn.t
new file mode 100644
index 0000000..05072e2
--- /dev/null
+++ b/t/qspawn.t
@@ -0,0 +1,60 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use Test::More;
+use_ok 'PublicInbox::Qspawn';
+{
+	my $x = PublicInbox::Qspawn->new([qw(true)]);
+	my $run = 0;
+	$x->start(sub {
+		my ($rpipe) = @_;
+		is(0, sysread($rpipe, my $buf, 1), 'read zero bytes');
+		ok(!$x->finish, 'no error on finish');
+		$run = 1;
+	});
+	is($run, 1, 'callback ran alright');
+}
+
+{
+	my $x = PublicInbox::Qspawn->new([qw(false)]);
+	my $run = 0;
+	$x->start(sub {
+		my ($rpipe) = @_;
+		is(0, sysread($rpipe, my $buf, 1), 'read zero bytes from false');
+		my $err = $x->finish;
+		is($err, 256, 'error on finish');
+		$run = 1;
+	});
+	is($run, 1, 'callback ran alright');
+}
+
+foreach my $cmd ([qw(sleep 1)], [qw(sh -c), 'sleep 1; false']) {
+	my $s = PublicInbox::Qspawn->new($cmd);
+	my @run;
+	$s->start(sub {
+		my ($rpipe) = @_;
+		push @run, 'sleep';
+		is(0, sysread($rpipe, my $buf, 1), 'read zero bytes');
+	});
+	my $n = 0;
+	my @t = map {
+		my $i = $n++;
+		my $x = PublicInbox::Qspawn->new([qw(true)]);
+		$x->start(sub {
+			my ($rpipe) = @_;
+			push @run, $i;
+		});
+		[$x, $i]
+	} (0..2);
+
+	if ($cmd->[-1] =~ /false\z/) {
+		ok($s->finish, 'got error on false after sleep');
+	} else {
+		ok(!$s->finish, 'no error on sleep');
+	}
+	ok(!$_->[0]->finish, "true $_->[1] succeeded") foreach @t;
+	is_deeply([qw(sleep 0 1 2)], \@run, 'ran in order');
+}
+
+done_testing();
+
+1;

^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2016-05-24  4:22 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-05-24  4:22 [PATCH 0/3] more networking/daemon fixes Eric Wong
2016-05-24  4:22 ` [PATCH 1/3] standardize timer-related event-loop code Eric Wong
2016-05-24  4:22 ` [PATCH 2/3] http: fix various race conditions Eric Wong
2016-05-24  4:22 ` [PATCH 3/3] git-http-backend: use qspawn to limit running processes Eric Wong

Code repositories for project(s) associated with this public inbox

	https://80x24.org/public-inbox.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).