changeset 47:5cab730994f3 draft

Keepalive: rename tests.
author Maxim Dounin <mdounin@mdounin.ru>
date Tue, 22 Jan 2013 19:05:49 +0400
parents 92125e266aa4
children 450ad1052368
files t/fastcgi-keepalive.t t/fastcgi_keepalive.t t/memcached-keepalive.t t/memcached_keepalive.t t/memcached_keepalive_stale.t t/proxy.t t/proxy_keepalive.t t/stale.t
diffstat 8 files changed, 837 insertions(+), 837 deletions(-) [+]
line wrap: on
line diff
--- a/t/fastcgi-keepalive.t	Tue Jun 26 02:52:10 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +0,0 @@
-#!/usr/bin/perl
-
-# (C) Maxim Dounin
-
-# Test for fastcgi backend with keepalive.
-
-###############################################################################
-
-use warnings;
-use strict;
-
-use Test::More;
-use Test::Nginx;
-
-###############################################################################
-
-select STDERR; $| = 1;
-select STDOUT; $| = 1;
-
-my $t = Test::Nginx->new()->write_file_expand('nginx.conf', <<'EOF');
-
-%%TEST_GLOBALS%%
-
-master_process off;
-daemon         off;
-
-events {
-}
-
-http {
-    %%TEST_GLOBALS_HTTP%%
-
-    upstream backend {
-        server 127.0.0.1:8081;
-        keepalive 1;
-    }
-
-    server {
-        listen       127.0.0.1:8080;
-        server_name  localhost;
-
-        location / {
-            fastcgi_pass backend;
-            fastcgi_keep_conn on;
-        }
-    }
-}
-
-EOF
-
-$t->run_daemon(\&fastcgi_test_daemon);
-
-eval {
-	open OLDERR, ">&", \*STDERR; close STDERR;
-	$t->run();
-	open STDERR, ">&", \*OLDERR;
-};
-plan(skip_all => 'no keepalive patches') if $@;
-
-$t->plan(6);
-
-###############################################################################
-
-like(http_get('/'), qr/SEE-THIS/, 'fastcgi request');
-like(http_get('/redir'), qr/302/, 'fastcgi redirect');
-like(http_get('/'), qr/^request: 3$/m, 'fastcgi third request');
-
-like(http_get('/single'), qr/^connection: 1$/m, 'single connection used');
-
-# New connection to fastcgi application should be established after HEAD
-# requests since nginx doesn't read whole response (as it doesn't need
-# body).
-
-unlike(http_head('/head'), qr/SEE-THIS/, 'no data in HEAD');
-
-like(http_get('/after'), qr/^connection: 2$/m, 'new connection after HEAD');
-
-###############################################################################
-
-# Simple FastCGI responder implementation.  Unlike FCGI and FCGI::Async it's
-# able to count connections.
-
-# http://www.fastcgi.com/devkit/doc/fcgi-spec.html
-
-sub fastcgi_read_record($) {
-	my ($socket) = @_;
-
-	my ($n, $h, $header);
-
-	$n = $socket->read($header, 8);
-	return undef if !defined $n or $n != 8;
-
-	@{$h}{qw/ version type id clen plen /} = unpack("CCnnC", $header);
-
-	$n = $socket->read($h->{content}, $h->{clen});
-	return undef if $n != $h->{clen};
-
-	$n = $socket->read($h->{padding}, $h->{plen});
-	return undef if $n != $h->{plen};
-
-	$h->{socket} = $socket;
-	return $h;
-}
-
-sub fastcgi_respond($$) {
-	my ($h, $body) = @_;
-
-	# stdout
-	$h->{socket}->write(pack("CCnnCx", $h->{version}, 6, $h->{id},
-		length($body), 0));
-	$h->{socket}->write($body);
-
-	# write some text to stdout and stderr splitted over multiple network
-	# packets to test if we correctly set pipe length in various places
-
-	my $tt = "test text, just for test";
-
-	$h->{socket}->write(pack("CCnnCx", $h->{version}, 6, $h->{id},
-		length($tt . $tt), 0) . $tt);
-	select(undef, undef, undef, 0.1);
-	$h->{socket}->write($tt . pack("CC", $h->{version}, 7));
-	select(undef, undef, undef, 0.1);
-	$h->{socket}->write(pack("nnCx", $h->{id}, length($tt), 0));
-	$h->{socket}->write($tt);
-
-	# close stdout
-	$h->{socket}->write(pack("CCnnCx", $h->{version}, 6, $h->{id}, 0, 0));
-
-	select(undef, undef, undef, 0.1);
-
-	# end request
-	$h->{socket}->write(pack("CCnnCx", $h->{version}, 3, $h->{id}, 8, 0));
-	$h->{socket}->write(pack("NCxxx", 0, 0));
-}
-
-sub fastcgi_test_daemon {
-	my $server = IO::Socket::INET->new(
-		Proto => 'tcp',
-		LocalAddr => '127.0.0.1:8081',
-		Listen => 5,
-		Reuse => 1
-	)
-		or die "Can't create listening socket: $!\n";
-
-	local $SIG{PIPE} = 'IGNORE';
-
-	my $ccount = 0;
-	my $rcount = 0;
-
-	while (my $client = $server->accept()) {
-		$client->autoflush(1);
-		Test::Nginx::log_core('||', "fastcgi connection");
-
-		$ccount++;
-
-		while (my $h = fastcgi_read_record($client)) {
-			Test::Nginx::log_core('||', "fastcgi record: "
-				. " $h->{version}, $h->{type}, $h->{id}, "
-				. "'$h->{content}'");
-
-			# skip everything unless stdin, then respond
-			next if $h->{type} != 5;
-
-			$rcount++;
-
-			# respond
-			fastcgi_respond($h, <<EOF);
-Location: http://localhost:8080/redirect
-Content-Type: text/html
-
-SEE-THIS
-request: $rcount
-connection: $ccount
-EOF
-		}
-
-		close $client;
-	}
-}
-
-###############################################################################
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/t/fastcgi_keepalive.t	Tue Jan 22 19:05:49 2013 +0400
@@ -0,0 +1,181 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+
+# Test for fastcgi backend with keepalive.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+master_process off;
+daemon         off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    upstream backend {
+        server 127.0.0.1:8081;
+        keepalive 1;
+    }
+
+    server {
+        listen       127.0.0.1:8080;
+        server_name  localhost;
+
+        location / {
+            fastcgi_pass backend;
+            fastcgi_keep_conn on;
+        }
+    }
+}
+
+EOF
+
+$t->run_daemon(\&fastcgi_test_daemon);
+
+eval {
+	open OLDERR, ">&", \*STDERR; close STDERR;
+	$t->run();
+	open STDERR, ">&", \*OLDERR;
+};
+plan(skip_all => 'no keepalive patches') if $@;
+
+$t->plan(6);
+
+###############################################################################
+
+like(http_get('/'), qr/SEE-THIS/, 'fastcgi request');
+like(http_get('/redir'), qr/302/, 'fastcgi redirect');
+like(http_get('/'), qr/^request: 3$/m, 'fastcgi third request');
+
+like(http_get('/single'), qr/^connection: 1$/m, 'single connection used');
+
+# New connection to fastcgi application should be established after HEAD
+# requests since nginx doesn't read whole response (as it doesn't need
+# body).
+
+unlike(http_head('/head'), qr/SEE-THIS/, 'no data in HEAD');
+
+like(http_get('/after'), qr/^connection: 2$/m, 'new connection after HEAD');
+
+###############################################################################
+
+# Simple FastCGI responder implementation.  Unlike FCGI and FCGI::Async it's
+# able to count connections.
+
+# http://www.fastcgi.com/devkit/doc/fcgi-spec.html
+
+sub fastcgi_read_record($) {
+	my ($socket) = @_;
+
+	my ($n, $h, $header);
+
+	$n = $socket->read($header, 8);
+	return undef if !defined $n or $n != 8;
+
+	@{$h}{qw/ version type id clen plen /} = unpack("CCnnC", $header);
+
+	$n = $socket->read($h->{content}, $h->{clen});
+	return undef if $n != $h->{clen};
+
+	$n = $socket->read($h->{padding}, $h->{plen});
+	return undef if $n != $h->{plen};
+
+	$h->{socket} = $socket;
+	return $h;
+}
+
+sub fastcgi_respond($$) {
+	my ($h, $body) = @_;
+
+	# stdout
+	$h->{socket}->write(pack("CCnnCx", $h->{version}, 6, $h->{id},
+		length($body), 0));
+	$h->{socket}->write($body);
+
+	# write some text to stdout and stderr splitted over multiple network
+	# packets to test if we correctly set pipe length in various places
+
+	my $tt = "test text, just for test";
+
+	$h->{socket}->write(pack("CCnnCx", $h->{version}, 6, $h->{id},
+		length($tt . $tt), 0) . $tt);
+	select(undef, undef, undef, 0.1);
+	$h->{socket}->write($tt . pack("CC", $h->{version}, 7));
+	select(undef, undef, undef, 0.1);
+	$h->{socket}->write(pack("nnCx", $h->{id}, length($tt), 0));
+	$h->{socket}->write($tt);
+
+	# close stdout
+	$h->{socket}->write(pack("CCnnCx", $h->{version}, 6, $h->{id}, 0, 0));
+
+	select(undef, undef, undef, 0.1);
+
+	# end request
+	$h->{socket}->write(pack("CCnnCx", $h->{version}, 3, $h->{id}, 8, 0));
+	$h->{socket}->write(pack("NCxxx", 0, 0));
+}
+
+sub fastcgi_test_daemon {
+	my $server = IO::Socket::INET->new(
+		Proto => 'tcp',
+		LocalAddr => '127.0.0.1:8081',
+		Listen => 5,
+		Reuse => 1
+	)
+		or die "Can't create listening socket: $!\n";
+
+	local $SIG{PIPE} = 'IGNORE';
+
+	my $ccount = 0;
+	my $rcount = 0;
+
+	while (my $client = $server->accept()) {
+		$client->autoflush(1);
+		Test::Nginx::log_core('||', "fastcgi connection");
+
+		$ccount++;
+
+		while (my $h = fastcgi_read_record($client)) {
+			Test::Nginx::log_core('||', "fastcgi record: "
+				. " $h->{version}, $h->{type}, $h->{id}, "
+				. "'$h->{content}'");
+
+			# skip everything unless stdin, then respond
+			next if $h->{type} != 5;
+
+			$rcount++;
+
+			# respond
+			fastcgi_respond($h, <<EOF);
+Location: http://localhost:8080/redirect
+Content-Type: text/html
+
+SEE-THIS
+request: $rcount
+connection: $ccount
+EOF
+		}
+
+		close $client;
+	}
+}
+
+###############################################################################
--- a/t/memcached-keepalive.t	Tue Jun 26 02:52:10 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,184 +0,0 @@
-#!/usr/bin/perl
-
-# (C) Maxim Dounin
-
-# Test for memcached with keepalive.
-
-###############################################################################
-
-use warnings;
-use strict;
-
-use Test::More;
-use Test::Nginx;
-
-###############################################################################
-
-select STDERR; $| = 1;
-select STDOUT; $| = 1;
-
-eval { require Cache::Memcached; };
-plan(skip_all => 'Cache::Memcached not installed') if $@;
-
-my $t = Test::Nginx->new()->has('rewrite')->has_daemon('memcached')->plan(16)
-	->write_file_expand('nginx.conf', <<'EOF');
-
-%%TEST_GLOBALS%%
-
-daemon         off;
-
-events {
-}
-
-http {
-    %%TEST_GLOBALS_HTTP%%
-
-    upstream memd {
-        server 127.0.0.1:8081;
-        keepalive 1;
-    }
-
-    upstream memd3 {
-        server 127.0.0.1:8081;
-        server 127.0.0.1:8082;
-        keepalive 1;
-    }
-
-    upstream memd4 {
-        server 127.0.0.1:8081;
-        server 127.0.0.1:8082;
-        keepalive 10;
-    }
-
-    server {
-        listen       127.0.0.1:8080;
-        server_name  localhost;
-
-        location / {
-            set $memcached_key $uri;
-            memcached_pass memd;
-        }
-
-        location /next {
-            set $memcached_key $uri;
-            memcached_next_upstream  not_found;
-            memcached_pass memd;
-        }
-
-        location /memd3 {
-            set $memcached_key "/";
-            memcached_pass memd3;
-        }
-
-        location /memd4 {
-            set $memcached_key "/";
-            memcached_pass memd4;
-        }
-    }
-}
-
-EOF
-
-my $memhelp = `memcached -h`;
-my @memopts1 = ();
-my @memopts2 = ();
-
-if ($memhelp =~ /repcached/) {
-	# repcached patches adds additional listen socket memcached
-	# that should be different too
-
-	push @memopts1, '-X', '8091';
-	push @memopts2, '-X', '8092';
-}
-if ($memhelp =~ /-U/) {
-	# UDP ports no longer off by default in memcached 1.2.7+
-
-	push @memopts1, '-U', '0';
-	push @memopts2, '-U', '0';
-}
-
-$t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8081', @memopts1);
-$t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8082', @memopts2);
-
-$t->run();
-
-$t->waitforsocket('127.0.0.1:8081')
-	or die "Unable to start memcached";
-$t->waitforsocket('127.0.0.1:8082')
-	or die "Unable to start second memcached";
-
-###############################################################################
-
-my $memd1 = Cache::Memcached->new(servers => [ '127.0.0.1:8081' ]);
-my $memd2 = Cache::Memcached->new(servers => [ '127.0.0.1:8082' ]);
-
-$memd1->set('/', 'SEE-THIS');
-$memd2->set('/', 'SEE-THIS');
-$memd1->set('/big', 'X' x 1000000);
-
-my $total = $memd1->stats()->{total}->{total_connections};
-
-like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request');
-like(http_get('/notfound'), qr/404/, 'keepalive memcached not found');
-like(http_get('/next'), qr/404/,
-	'keepalive not found with memcached_next_upstream');
-like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request again');
-like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request again');
-like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request again');
-
-is($memd1->stats()->{total}->{total_connections}, $total + 1,
-	'only one connection used');
-
-# Since nginx doesn't read all data from connection in some situations (head
-# requests, post_action, errors writing to client) we have to close such
-# connections.  Check if we really do close them.
-
-$total = $memd1->stats()->{total}->{total_connections};
-
-unlike(http_head('/'), qr/SEE-THIS/, 'head request');
-like(http_get('/'), qr/SEE-THIS/, 'get after head');
-
-is($memd1->stats()->{total}->{total_connections}, $total + 1,
-	'head request closes connection');
-
-$total = $memd1->stats()->{total}->{total_connections};
-
-unlike(http_head('/big'), qr/XXX/, 'big head');
-like(http_get('/'), qr/SEE-THIS/, 'get after big head');
-
-is($memd1->stats()->{total}->{total_connections}, $total + 1,
-	'big head request closes connection');
-
-# two backends with maximum number of cached connections set to 1,
-# should establish new connection on each request
-
-$total = $memd1->stats()->{total}->{total_connections} +
-	$memd2->stats()->{total}->{total_connections};
-
-http_get('/memd3');
-http_get('/memd3');
-http_get('/memd3');
-
-is($memd1->stats()->{total}->{total_connections} +
-	$memd2->stats()->{total}->{total_connections}, $total + 3,
-	'3 connections should be established');
-
-# two backends with maximum number of cached connections set to 10,
-# should establish only two connections (1 per backend)
-
-$total = $memd1->stats()->{total}->{total_connections} +
-        $memd2->stats()->{total}->{total_connections};
-
-http_get('/memd4');
-http_get('/memd4');
-http_get('/memd4');
-
-is($memd1->stats()->{total}->{total_connections} +
-	$memd2->stats()->{total}->{total_connections}, $total + 2,
-	'connection per backend');
-
-$t->stop();
-
-like(`grep -F '[alert]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no alerts');
-
-###############################################################################
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/t/memcached_keepalive.t	Tue Jan 22 19:05:49 2013 +0400
@@ -0,0 +1,184 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+
+# Test for memcached with keepalive.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+eval { require Cache::Memcached; };
+plan(skip_all => 'Cache::Memcached not installed') if $@;
+
+my $t = Test::Nginx->new()->has('rewrite')->has_daemon('memcached')->plan(16)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon         off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    upstream memd {
+        server 127.0.0.1:8081;
+        keepalive 1;
+    }
+
+    upstream memd3 {
+        server 127.0.0.1:8081;
+        server 127.0.0.1:8082;
+        keepalive 1;
+    }
+
+    upstream memd4 {
+        server 127.0.0.1:8081;
+        server 127.0.0.1:8082;
+        keepalive 10;
+    }
+
+    server {
+        listen       127.0.0.1:8080;
+        server_name  localhost;
+
+        location / {
+            set $memcached_key $uri;
+            memcached_pass memd;
+        }
+
+        location /next {
+            set $memcached_key $uri;
+            memcached_next_upstream  not_found;
+            memcached_pass memd;
+        }
+
+        location /memd3 {
+            set $memcached_key "/";
+            memcached_pass memd3;
+        }
+
+        location /memd4 {
+            set $memcached_key "/";
+            memcached_pass memd4;
+        }
+    }
+}
+
+EOF
+
+my $memhelp = `memcached -h`;
+my @memopts1 = ();
+my @memopts2 = ();
+
+if ($memhelp =~ /repcached/) {
+	# repcached patches adds additional listen socket memcached
+	# that should be different too
+
+	push @memopts1, '-X', '8091';
+	push @memopts2, '-X', '8092';
+}
+if ($memhelp =~ /-U/) {
+	# UDP ports no longer off by default in memcached 1.2.7+
+
+	push @memopts1, '-U', '0';
+	push @memopts2, '-U', '0';
+}
+
+$t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8081', @memopts1);
+$t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8082', @memopts2);
+
+$t->run();
+
+$t->waitforsocket('127.0.0.1:8081')
+	or die "Unable to start memcached";
+$t->waitforsocket('127.0.0.1:8082')
+	or die "Unable to start second memcached";
+
+###############################################################################
+
+my $memd1 = Cache::Memcached->new(servers => [ '127.0.0.1:8081' ]);
+my $memd2 = Cache::Memcached->new(servers => [ '127.0.0.1:8082' ]);
+
+$memd1->set('/', 'SEE-THIS');
+$memd2->set('/', 'SEE-THIS');
+$memd1->set('/big', 'X' x 1000000);
+
+my $total = $memd1->stats()->{total}->{total_connections};
+
+like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request');
+like(http_get('/notfound'), qr/404/, 'keepalive memcached not found');
+like(http_get('/next'), qr/404/,
+	'keepalive not found with memcached_next_upstream');
+like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request again');
+like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request again');
+like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request again');
+
+is($memd1->stats()->{total}->{total_connections}, $total + 1,
+	'only one connection used');
+
+# Since nginx doesn't read all data from connection in some situations (head
+# requests, post_action, errors writing to client) we have to close such
+# connections.  Check if we really do close them.
+
+$total = $memd1->stats()->{total}->{total_connections};
+
+unlike(http_head('/'), qr/SEE-THIS/, 'head request');
+like(http_get('/'), qr/SEE-THIS/, 'get after head');
+
+is($memd1->stats()->{total}->{total_connections}, $total + 1,
+	'head request closes connection');
+
+$total = $memd1->stats()->{total}->{total_connections};
+
+unlike(http_head('/big'), qr/XXX/, 'big head');
+like(http_get('/'), qr/SEE-THIS/, 'get after big head');
+
+is($memd1->stats()->{total}->{total_connections}, $total + 1,
+	'big head request closes connection');
+
+# two backends with maximum number of cached connections set to 1,
+# should establish new connection on each request
+
+$total = $memd1->stats()->{total}->{total_connections} +
+	$memd2->stats()->{total}->{total_connections};
+
+http_get('/memd3');
+http_get('/memd3');
+http_get('/memd3');
+
+is($memd1->stats()->{total}->{total_connections} +
+	$memd2->stats()->{total}->{total_connections}, $total + 3,
+	'3 connections should be established');
+
+# two backends with maximum number of cached connections set to 10,
+# should establish only two connections (1 per backend)
+
+$total = $memd1->stats()->{total}->{total_connections} +
+        $memd2->stats()->{total}->{total_connections};
+
+http_get('/memd4');
+http_get('/memd4');
+http_get('/memd4');
+
+is($memd1->stats()->{total}->{total_connections} +
+	$memd2->stats()->{total}->{total_connections}, $total + 2,
+	'connection per backend');
+
+$t->stop();
+
+like(`grep -F '[alert]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no alerts');
+
+###############################################################################
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/t/memcached_keepalive_stale.t	Tue Jan 22 19:05:49 2013 +0400
@@ -0,0 +1,114 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+
+# Test for stale events handling in upstream keepalive.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+eval { require Cache::Memcached; };
+plan(skip_all => 'Cache::Memcached not installed') if $@;
+
+my $t = Test::Nginx->new()->has('rewrite')->has_daemon('memcached')->plan(2)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon         off;
+
+worker_processes 2;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    upstream memd {
+        server 127.0.0.1:8081;
+        keepalive 1;
+    }
+
+    server {
+        listen       127.0.0.1:8080 sndbuf=32k;
+        server_name  localhost;
+
+        location / {
+            set $memcached_key $uri;
+            memcached_pass memd;
+        }
+    }
+}
+
+EOF
+
+my $memhelp = `memcached -h`;
+my @memopts1 = ();
+
+if ($memhelp =~ /repcached/) {
+	# repcached patches adds additional listen socket memcached
+	# that should be different too
+
+	push @memopts1, '-X', '8091';
+}
+if ($memhelp =~ /-U/) {
+	# UDP ports no longer off by default in memcached 1.2.7+
+
+	push @memopts1, '-U', '0';
+}
+
+$t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8081', @memopts1);
+
+$t->run();
+
+$t->waitforsocket('127.0.0.1:8081')
+	or die "Unable to start memcached";
+
+###############################################################################
+
+my $memd1 = Cache::Memcached->new(servers => [ '127.0.0.1:8081' ]);
+
+# It's possible that stale events occur, i.e. read event handler called
+# for just saved upstream connection without any data available for
+# read.  We shouldn't close upstream connection in such situation.
+#
+# This happens due to reading from upstream connection on downstream write
+# events.  More likely to happen with multiple workers due to use of posted
+# events.
+#
+# Stale event may only happen if reading response from upstream requires
+# entering event loop, i.e. response should be big enough.  On the other
+# hand, it is less likely to occur with full client's connection output
+# buffer.
+#
+# We use here 2 workers, 20k response and set output buffer on clients
+# connection to 32k.  This allows more or less reliably reproduce stale
+# events at least on FreeBSD testbed here.
+
+$memd1->set('/big', 'X' x 20480);
+
+my $total = $memd1->stats()->{total}->{total_connections};
+
+for (1 .. 100) {
+	http_get('/big');
+}
+
+cmp_ok($memd1->stats()->{total}->{total_connections}, '<=', $total + 2,
+	'only one connection per worker used');
+
+$t->stop();
+
+like(`grep -F '[alert]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no alerts');
+
+###############################################################################
--- a/t/proxy.t	Tue Jun 26 02:52:10 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,358 +0,0 @@
-#!/usr/bin/perl
-
-# (C) Maxim Dounin
-
-# Tests for proxy with keepalive.
-
-###############################################################################
-
-use warnings;
-use strict;
-
-use Test::More;
-use IO::Socket::INET;
-use Socket qw/ CRLF /;
-
-use Test::Nginx;
-
-###############################################################################
-
-select STDERR; $| = 1;
-select STDOUT; $| = 1;
-
-my $t = Test::Nginx->new()->has(qw/http proxy ssi rewrite/)
-	->write_file_expand('nginx.conf', <<'EOF');
-
-%%TEST_GLOBALS%%
-
-daemon off;
-
-events {
-}
-
-http {
-    %%TEST_GLOBALS_HTTP%%
-
-    upstream backend {
-        server 127.0.0.1:8081;
-        keepalive 1;
-    }
-
-    server {
-        listen       127.0.0.1:8080;
-        server_name  localhost;
-
-        proxy_read_timeout 2s;
-        proxy_http_version 1.1;
-        proxy_set_header Connection "";
-
-        location / {
-            proxy_pass http://backend;
-        }
-
-        location /unbuffered/ {
-            proxy_pass http://backend;
-            proxy_buffering off;
-        }
-
-        location /inmemory/ {
-            ssi on;
-            rewrite ^ /ssi.html break;
-        }
-    }
-}
-
-EOF
-
-$t->write_file('ssi.html',
-	'<!--#include virtual="/include$request_uri" set="x" -->' .
-	'set: <!--#echo var="x" -->');
-
-$t->run_daemon(\&http_daemon);
-
-eval {
-	open OLDERR, ">&", \*STDERR; close STDERR;
-	$t->run();
-	open STDERR, ">&", \*OLDERR;
-};
-plan(skip_all => 'no keepalive patches') if $@;
-
-$t->plan(50);
-
-###############################################################################
-
-# There are 3 mostly independend modes of upstream operation:
-#
-# 1. Buffered, i.e. normal mode with "proxy_buffering on;"
-# 2. Unbuffered, i.e. "proxy_buffering off;".
-# 3. In memory, i.e. ssi <!--#include ... set -->
-#
-# These all should be tested.
-
-my ($r, $n);
-
-# buffered
-
-like($r = http_get('/buffered/length1'), qr/SEE-THIS/, 'buffered');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/buffered/length2'), qr/X-Connection: $n.*SEE/ms, 'buffered 2');
-
-like($r = http_get('/buffered/chunked1'), qr/SEE-THIS/, 'buffered chunked');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/buffered/chunked2'), qr/X-Connection: $n/,
-	'buffered chunked 2');
-
-like($r = http_get('/buffered/complex1'), qr/(0123456789){100}/,
-	'buffered complex chunked');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/buffered/complex2'), qr/X-Connection: $n/,
-	'buffered complex chunked 2');
-
-like($r = http_get('/buffered/chunk01'), qr/200 OK/, 'buffered 0 chunk');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/buffered/chunk02'), qr/X-Connection: $n/, 'buffered 0 chunk 2');
-
-like($r = http_head('/buffered/length/head1'), qr/(?!SEE-THIS)/,
-	'buffered head');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_head('/buffered/length/head2'), qr/X-Connection: $n/,
-	'buffered head 2');
-
-like($r = http_get('/buffered/empty1'), qr/200 OK/, 'buffered empty');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/buffered/empty2'), qr/X-Connection: $n/, 'buffered empty 2');
-
-like($r = http_get('/buffered/304nolen1'), qr/304 Not/, 'buffered 304');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/buffered/304nolen2'), qr/X-Connection: $n/, 'buffered 304 2');
-
-like($r = http_get('/buffered/304len1'), qr/304 Not/,
-	'buffered 304 with length');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/buffered/304len2'), qr/X-Connection: $n/,
-	'buffered 304 with length 2');
-
-# unbuffered
-
-like($r = http_get('/unbuffered/length1'), qr/SEE-THIS/, 'unbuffered');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/unbuffered/length2'), qr/X-Connection: $n/, 'unbuffered 2');
-
-like($r = http_get('/unbuffered/chunked1'), qr/SEE-THIS/, 'unbuffered chunked');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/unbuffered/chunked2'), qr/X-Connection: $n/,
-	'unbuffered chunked 2');
-
-like($r = http_get('/unbuffered/complex1'), qr/(0123456789){100}/,
-	'unbuffered complex chunked');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/unbuffered/complex2'), qr/X-Connection: $n/,
-	'unbuffered complex chunked 2');
-
-like($r = http_get('/unbuffered/chunk01'), qr/200 OK/, 'unbuffered 0 chunk');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/unbuffered/chunk02'), qr/X-Connection: $n/,
-	'unbuffered 0 chunk 2');
-
-like($r = http_get('/unbuffered/empty1'), qr/200 OK/, 'unbuffered empty');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/unbuffered/empty2'), qr/X-Connection: $n/,
-	'unbuffered empty 2');
-
-like($r = http_head('/unbuffered/length/head1'), qr/(?!SEE-THIS)/,
-	'unbuffered head');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_head('/unbuffered/length/head2'), qr/X-Connection: $n/,
-	'unbuffered head 2');
-
-like($r = http_get('/unbuffered/304nolen1'), qr/304 Not/, 'unbuffered 304');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/unbuffered/304nolen2'), qr/X-Connection: $n/,
-	'unbuffered 304 2');
-
-like($r = http_get('/unbuffered/304len1'), qr/304 Not/,
-	'unbuffered 304 with length');
-$r =~ m/X-Connection: (\d+)/; $n = $1;
-like(http_get('/unbuffered/304len2'), qr/X-Connection: $n/,
-	'unbuffered 304 with length 2');
-
-# in memory
-
-like($r = http_get('/inmemory/length1'), qr/SEE-THIS/, 'inmemory');
-$r =~ m/SEE-THIS(\d+)/; $n = $1;
-like(http_get('/inmemory/length2'), qr/SEE-THIS$n/, 'inmemory 2');
-
-like($r = http_get('/inmemory/empty1'), qr/200 OK/, 'inmemory empty');
-$r =~ m/SEE-THIS(\d+)/; $n = $1;
-like(http_get('/inmemory/empty2'), qr/200 OK/, 'inmemory empty 2');
-
-like($r = http_get('/inmemory/chunked1'), qr/SEE-THIS/, 'inmemory chunked');
-$r =~ m/SEE-THIS(\d+)/; $n = $1;
-like(http_get('/inmemory/chunked2'), qr/SEE-THIS$n/, 'inmemory chunked 2');
-
-like($r = http_get('/inmemory/complex1'), qr/(0123456789){100}/,
-	'inmemory complex chunked');
-$r =~ m/SEE-THIS(\d+)/; $n = $1;
-like(http_get('/inmemory/complex2'), qr/SEE-THIS$n/,
-	'inmemory complex chunked 2');
-
-like(http_get('/inmemory/chunk01'), qr/set: $/, 'inmemory 0 chunk');
-like(http_get('/inmemory/chunk02'), qr/set: $/, 'inmemory 0 chunk 2');
-
-# closed connection tests
-
-like(http_get('/buffered/closed1'), qr/200 OK/, 'buffered closed 1');
-like(http_get('/buffered/closed2'), qr/200 OK/, 'buffered closed 2');
-like(http_get('/unbuffered/closed1'), qr/200 OK/, 'unbuffered closed 1');
-like(http_get('/unbuffered/closed2'), qr/200 OK/, 'unbuffered closed 2');
-like(http_get('/inmemory/closed1'), qr/200 OK/, 'inmemory closed 1');
-like(http_get('/inmemory/closed2'), qr/200 OK/, 'inmemory closed 2');
-
-# check for errors, shouldn't be any
-
-like(`grep -F '[alert]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no alerts');
-like(`grep -F '[error]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no errors');
-
-###############################################################################
-
-sub http_daemon {
-	my $server = IO::Socket::INET->new(
-        	Proto => 'tcp',
-		LocalHost => '127.0.0.1:8081',
-        	Listen => 5,
-        	Reuse => 1
-	)
-        	or die "Can't create listening socket: $!\n";
-
-	my $ccount = 0;
-        my $rcount = 0;
-
-	# dumb server which is able to keep connections alive
-
-	while (my $client = $server->accept()) {
-		Test::Nginx::log_core('||', "connection from " . $client->peerhost());
-        	$client->autoflush(1);
-		$ccount++;
-
-		while (1) {
-			my $headers = '';
-			my $uri = '';
-
-        		while (<$client>) {
-				Test::Nginx::log_core('||', $_);
-				$headers .= $_;
-                		last if (/^\x0d?\x0a?$/);
-        		}
-
-			last if $headers eq '';
-			$rcount++;
-
-			$uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
-
-			if ($uri =~ m/length/) {
-				print $client
-					"HTTP/1.1 200 OK" . CRLF .
-					"X-Request: $rcount" . CRLF .
-					"X-Connection: $ccount" . CRLF .
-					"Content-Length: 26" . CRLF . CRLF;
-				print $client "TEST-OK-IF-YOU-SEE-THIS" .
-					sprintf("%03d", $ccount)
-					unless $headers =~ /^HEAD/i;
-
-                        } elsif ($uri =~ m/empty/) {
-				print $client
-					"HTTP/1.1 200 OK" . CRLF .
-					"X-Request: $rcount" . CRLF .
-					"X-Connection: $ccount" . CRLF .
-					"Content-Length: 0" . CRLF . CRLF;
-
-                        } elsif ($uri =~ m/304nolen/) {
-				print $client
-					"HTTP/1.1 304 Not Modified" . CRLF .
-					"X-Request: $rcount" . CRLF .
-					"X-Connection: $ccount" . CRLF . CRLF;
-
-                        } elsif ($uri =~ m/304len/) {
-				print $client
-					"HTTP/1.1 304 Not Modified" . CRLF .
-					"X-Request: $rcount" . CRLF .
-					"X-Connection: $ccount" . CRLF .
-					"Content-Length: 100" . CRLF . CRLF;
-
-			} elsif ($uri =~ m/chunked/) {
-				print $client
-					"HTTP/1.1 200 OK" . CRLF .
-					"X-Request: $rcount" . CRLF .
-					"X-Connection: $ccount" . CRLF .
-					"Transfer-Encoding: chunked" . CRLF .
-					CRLF;
-				print $client
-					"1a" . CRLF .
-					"TEST-OK-IF-YOU-SEE-THIS" .
-					sprintf("%03d", $ccount) . CRLF .
-					"0" . CRLF . CRLF
-					unless $headers =~ /^HEAD/i;
-
-			} elsif ($uri =~ m/complex/) {
-				print $client
-					"HTTP/1.1 200 OK" . CRLF .
-					"X-Request: $rcount" . CRLF .
-					"X-Connection: $ccount" . CRLF .
-					"Transfer-Encoding: chunked" . CRLF .
-					CRLF;
-
-				if ($headers !~ /^HEAD/i) {
-					for my $n (1..100) {
-						print $client
-							"a" . CRLF .
-							"0123456789" . CRLF;
-						select undef, undef, undef, 0.01
-							if $n % 50 == 0;
-					}
-					print $client
-						"1a" . CRLF .
-						"TEST-OK-IF-YOU-SEE-THIS" .
-						sprintf("%03d", $ccount) .
-						CRLF .
-						"0" . CRLF;
-					select undef, undef, undef, 0.05;
-					print $client CRLF;
-				}
-
-                        } elsif ($uri =~ m/chunk0/) {
-				print $client
-					"HTTP/1.1 200 OK" . CRLF .
-					"X-Request: $rcount" . CRLF .
-					"X-Connection: $ccount" . CRLF .
-					"Transfer-Encoding: chunked" . CRLF .
-					CRLF;
-				print $client
-					"0" . CRLF . CRLF
-					unless $headers =~ /^HEAD/i;
-
-			} elsif ($uri =~ m/closed/) {
-				print $client
-					"HTTP/1.1 200 OK" . CRLF .
-					"X-Request: $rcount" . CRLF .
-					"X-Connection: $ccount" . CRLF .
-					"Connection: close" . CRLF .
-					"Content-Length: 12" . CRLF . CRLF .
-					"0123456789" . CRLF;
-				last;
-
-			} else {
-        			print $client
-					"HTTP/1.1 404 Not Found" . CRLF .
-					"X-Request: $rcount" . CRLF .
-					"X-Connection: $ccount" . CRLF .
-					"Connection: close" . CRLF . CRLF .
-					"Oops, '$uri' not found" . CRLF;
-				last;
-			}
-		}
-
-        	close $client;
-	}
-}
-
-###############################################################################
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/t/proxy_keepalive.t	Tue Jan 22 19:05:49 2013 +0400
@@ -0,0 +1,358 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+
+# Tests for proxy with keepalive.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+use IO::Socket::INET;
+use Socket qw/ CRLF /;
+
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http proxy ssi rewrite/)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    upstream backend {
+        server 127.0.0.1:8081;
+        keepalive 1;
+    }
+
+    server {
+        listen       127.0.0.1:8080;
+        server_name  localhost;
+
+        proxy_read_timeout 2s;
+        proxy_http_version 1.1;
+        proxy_set_header Connection "";
+
+        location / {
+            proxy_pass http://backend;
+        }
+
+        location /unbuffered/ {
+            proxy_pass http://backend;
+            proxy_buffering off;
+        }
+
+        location /inmemory/ {
+            ssi on;
+            rewrite ^ /ssi.html break;
+        }
+    }
+}
+
+EOF
+
+$t->write_file('ssi.html',
+	'<!--#include virtual="/include$request_uri" set="x" -->' .
+	'set: <!--#echo var="x" -->');
+
+$t->run_daemon(\&http_daemon);
+
+eval {
+	open OLDERR, ">&", \*STDERR; close STDERR;
+	$t->run();
+	open STDERR, ">&", \*OLDERR;
+};
+plan(skip_all => 'no keepalive patches') if $@;
+
+$t->plan(50);
+
+###############################################################################
+
+# There are 3 mostly independend modes of upstream operation:
+#
+# 1. Buffered, i.e. normal mode with "proxy_buffering on;"
+# 2. Unbuffered, i.e. "proxy_buffering off;".
+# 3. In memory, i.e. ssi <!--#include ... set -->
+#
+# These all should be tested.
+
+my ($r, $n);
+
+# buffered
+
+like($r = http_get('/buffered/length1'), qr/SEE-THIS/, 'buffered');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/buffered/length2'), qr/X-Connection: $n.*SEE/ms, 'buffered 2');
+
+like($r = http_get('/buffered/chunked1'), qr/SEE-THIS/, 'buffered chunked');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/buffered/chunked2'), qr/X-Connection: $n/,
+	'buffered chunked 2');
+
+like($r = http_get('/buffered/complex1'), qr/(0123456789){100}/,
+	'buffered complex chunked');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/buffered/complex2'), qr/X-Connection: $n/,
+	'buffered complex chunked 2');
+
+like($r = http_get('/buffered/chunk01'), qr/200 OK/, 'buffered 0 chunk');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/buffered/chunk02'), qr/X-Connection: $n/, 'buffered 0 chunk 2');
+
+like($r = http_head('/buffered/length/head1'), qr/(?!SEE-THIS)/,
+	'buffered head');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_head('/buffered/length/head2'), qr/X-Connection: $n/,
+	'buffered head 2');
+
+like($r = http_get('/buffered/empty1'), qr/200 OK/, 'buffered empty');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/buffered/empty2'), qr/X-Connection: $n/, 'buffered empty 2');
+
+like($r = http_get('/buffered/304nolen1'), qr/304 Not/, 'buffered 304');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/buffered/304nolen2'), qr/X-Connection: $n/, 'buffered 304 2');
+
+like($r = http_get('/buffered/304len1'), qr/304 Not/,
+	'buffered 304 with length');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/buffered/304len2'), qr/X-Connection: $n/,
+	'buffered 304 with length 2');
+
+# unbuffered
+
+like($r = http_get('/unbuffered/length1'), qr/SEE-THIS/, 'unbuffered');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/unbuffered/length2'), qr/X-Connection: $n/, 'unbuffered 2');
+
+like($r = http_get('/unbuffered/chunked1'), qr/SEE-THIS/, 'unbuffered chunked');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/unbuffered/chunked2'), qr/X-Connection: $n/,
+	'unbuffered chunked 2');
+
+like($r = http_get('/unbuffered/complex1'), qr/(0123456789){100}/,
+	'unbuffered complex chunked');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/unbuffered/complex2'), qr/X-Connection: $n/,
+	'unbuffered complex chunked 2');
+
+like($r = http_get('/unbuffered/chunk01'), qr/200 OK/, 'unbuffered 0 chunk');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/unbuffered/chunk02'), qr/X-Connection: $n/,
+	'unbuffered 0 chunk 2');
+
+like($r = http_get('/unbuffered/empty1'), qr/200 OK/, 'unbuffered empty');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/unbuffered/empty2'), qr/X-Connection: $n/,
+	'unbuffered empty 2');
+
+like($r = http_head('/unbuffered/length/head1'), qr/(?!SEE-THIS)/,
+	'unbuffered head');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_head('/unbuffered/length/head2'), qr/X-Connection: $n/,
+	'unbuffered head 2');
+
+like($r = http_get('/unbuffered/304nolen1'), qr/304 Not/, 'unbuffered 304');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/unbuffered/304nolen2'), qr/X-Connection: $n/,
+	'unbuffered 304 2');
+
+like($r = http_get('/unbuffered/304len1'), qr/304 Not/,
+	'unbuffered 304 with length');
+$r =~ m/X-Connection: (\d+)/; $n = $1;
+like(http_get('/unbuffered/304len2'), qr/X-Connection: $n/,
+	'unbuffered 304 with length 2');
+
+# in memory
+
+like($r = http_get('/inmemory/length1'), qr/SEE-THIS/, 'inmemory');
+$r =~ m/SEE-THIS(\d+)/; $n = $1;
+like(http_get('/inmemory/length2'), qr/SEE-THIS$n/, 'inmemory 2');
+
+like($r = http_get('/inmemory/empty1'), qr/200 OK/, 'inmemory empty');
+$r =~ m/SEE-THIS(\d+)/; $n = $1;
+like(http_get('/inmemory/empty2'), qr/200 OK/, 'inmemory empty 2');
+
+like($r = http_get('/inmemory/chunked1'), qr/SEE-THIS/, 'inmemory chunked');
+$r =~ m/SEE-THIS(\d+)/; $n = $1;
+like(http_get('/inmemory/chunked2'), qr/SEE-THIS$n/, 'inmemory chunked 2');
+
+like($r = http_get('/inmemory/complex1'), qr/(0123456789){100}/,
+	'inmemory complex chunked');
+$r =~ m/SEE-THIS(\d+)/; $n = $1;
+like(http_get('/inmemory/complex2'), qr/SEE-THIS$n/,
+	'inmemory complex chunked 2');
+
+like(http_get('/inmemory/chunk01'), qr/set: $/, 'inmemory 0 chunk');
+like(http_get('/inmemory/chunk02'), qr/set: $/, 'inmemory 0 chunk 2');
+
+# closed connection tests
+
+like(http_get('/buffered/closed1'), qr/200 OK/, 'buffered closed 1');
+like(http_get('/buffered/closed2'), qr/200 OK/, 'buffered closed 2');
+like(http_get('/unbuffered/closed1'), qr/200 OK/, 'unbuffered closed 1');
+like(http_get('/unbuffered/closed2'), qr/200 OK/, 'unbuffered closed 2');
+like(http_get('/inmemory/closed1'), qr/200 OK/, 'inmemory closed 1');
+like(http_get('/inmemory/closed2'), qr/200 OK/, 'inmemory closed 2');
+
+# check for errors, shouldn't be any
+
+like(`grep -F '[alert]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no alerts');
+like(`grep -F '[error]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no errors');
+
+###############################################################################
+
+sub http_daemon {
+	my $server = IO::Socket::INET->new(
+        	Proto => 'tcp',
+		LocalHost => '127.0.0.1:8081',
+        	Listen => 5,
+        	Reuse => 1
+	)
+        	or die "Can't create listening socket: $!\n";
+
+	my $ccount = 0;
+        my $rcount = 0;
+
+	# dumb server which is able to keep connections alive
+
+	while (my $client = $server->accept()) {
+		Test::Nginx::log_core('||', "connection from " . $client->peerhost());
+        	$client->autoflush(1);
+		$ccount++;
+
+		while (1) {
+			my $headers = '';
+			my $uri = '';
+
+        		while (<$client>) {
+				Test::Nginx::log_core('||', $_);
+				$headers .= $_;
+                		last if (/^\x0d?\x0a?$/);
+        		}
+
+			last if $headers eq '';
+			$rcount++;
+
+			$uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+
+			if ($uri =~ m/length/) {
+				print $client
+					"HTTP/1.1 200 OK" . CRLF .
+					"X-Request: $rcount" . CRLF .
+					"X-Connection: $ccount" . CRLF .
+					"Content-Length: 26" . CRLF . CRLF;
+				print $client "TEST-OK-IF-YOU-SEE-THIS" .
+					sprintf("%03d", $ccount)
+					unless $headers =~ /^HEAD/i;
+
+                        } elsif ($uri =~ m/empty/) {
+				print $client
+					"HTTP/1.1 200 OK" . CRLF .
+					"X-Request: $rcount" . CRLF .
+					"X-Connection: $ccount" . CRLF .
+					"Content-Length: 0" . CRLF . CRLF;
+
+                        } elsif ($uri =~ m/304nolen/) {
+				print $client
+					"HTTP/1.1 304 Not Modified" . CRLF .
+					"X-Request: $rcount" . CRLF .
+					"X-Connection: $ccount" . CRLF . CRLF;
+
+                        } elsif ($uri =~ m/304len/) {
+				print $client
+					"HTTP/1.1 304 Not Modified" . CRLF .
+					"X-Request: $rcount" . CRLF .
+					"X-Connection: $ccount" . CRLF .
+					"Content-Length: 100" . CRLF . CRLF;
+
+			} elsif ($uri =~ m/chunked/) {
+				print $client
+					"HTTP/1.1 200 OK" . CRLF .
+					"X-Request: $rcount" . CRLF .
+					"X-Connection: $ccount" . CRLF .
+					"Transfer-Encoding: chunked" . CRLF .
+					CRLF;
+				print $client
+					"1a" . CRLF .
+					"TEST-OK-IF-YOU-SEE-THIS" .
+					sprintf("%03d", $ccount) . CRLF .
+					"0" . CRLF . CRLF
+					unless $headers =~ /^HEAD/i;
+
+			} elsif ($uri =~ m/complex/) {
+				print $client
+					"HTTP/1.1 200 OK" . CRLF .
+					"X-Request: $rcount" . CRLF .
+					"X-Connection: $ccount" . CRLF .
+					"Transfer-Encoding: chunked" . CRLF .
+					CRLF;
+
+				if ($headers !~ /^HEAD/i) {
+					for my $n (1..100) {
+						print $client
+							"a" . CRLF .
+							"0123456789" . CRLF;
+						select undef, undef, undef, 0.01
+							if $n % 50 == 0;
+					}
+					print $client
+						"1a" . CRLF .
+						"TEST-OK-IF-YOU-SEE-THIS" .
+						sprintf("%03d", $ccount) .
+						CRLF .
+						"0" . CRLF;
+					select undef, undef, undef, 0.05;
+					print $client CRLF;
+				}
+
+                        } elsif ($uri =~ m/chunk0/) {
+				print $client
+					"HTTP/1.1 200 OK" . CRLF .
+					"X-Request: $rcount" . CRLF .
+					"X-Connection: $ccount" . CRLF .
+					"Transfer-Encoding: chunked" . CRLF .
+					CRLF;
+				print $client
+					"0" . CRLF . CRLF
+					unless $headers =~ /^HEAD/i;
+
+			} elsif ($uri =~ m/closed/) {
+				print $client
+					"HTTP/1.1 200 OK" . CRLF .
+					"X-Request: $rcount" . CRLF .
+					"X-Connection: $ccount" . CRLF .
+					"Connection: close" . CRLF .
+					"Content-Length: 12" . CRLF . CRLF .
+					"0123456789" . CRLF;
+				last;
+
+			} else {
+        			print $client
+					"HTTP/1.1 404 Not Found" . CRLF .
+					"X-Request: $rcount" . CRLF .
+					"X-Connection: $ccount" . CRLF .
+					"Connection: close" . CRLF . CRLF .
+					"Oops, '$uri' not found" . CRLF;
+				last;
+			}
+		}
+
+        	close $client;
+	}
+}
+
+###############################################################################
--- a/t/stale.t	Tue Jun 26 02:52:10 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
-#!/usr/bin/perl
-
-# (C) Maxim Dounin
-
-# Test for stale events handling in upstream keepalive.
-
-###############################################################################
-
-use warnings;
-use strict;
-
-use Test::More;
-use Test::Nginx;
-
-###############################################################################
-
-select STDERR; $| = 1;
-select STDOUT; $| = 1;
-
-eval { require Cache::Memcached; };
-plan(skip_all => 'Cache::Memcached not installed') if $@;
-
-my $t = Test::Nginx->new()->has('rewrite')->has_daemon('memcached')->plan(2)
-	->write_file_expand('nginx.conf', <<'EOF');
-
-%%TEST_GLOBALS%%
-
-daemon         off;
-
-worker_processes 2;
-
-events {
-}
-
-http {
-    %%TEST_GLOBALS_HTTP%%
-
-    upstream memd {
-        server 127.0.0.1:8081;
-        keepalive 1;
-    }
-
-    server {
-        listen       127.0.0.1:8080 sndbuf=32k;
-        server_name  localhost;
-
-        location / {
-            set $memcached_key $uri;
-            memcached_pass memd;
-        }
-    }
-}
-
-EOF
-
-my $memhelp = `memcached -h`;
-my @memopts1 = ();
-
-if ($memhelp =~ /repcached/) {
-	# repcached patches adds additional listen socket memcached
-	# that should be different too
-
-	push @memopts1, '-X', '8091';
-}
-if ($memhelp =~ /-U/) {
-	# UDP ports no longer off by default in memcached 1.2.7+
-
-	push @memopts1, '-U', '0';
-}
-
-$t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8081', @memopts1);
-
-$t->run();
-
-$t->waitforsocket('127.0.0.1:8081')
-	or die "Unable to start memcached";
-
-###############################################################################
-
-my $memd1 = Cache::Memcached->new(servers => [ '127.0.0.1:8081' ]);
-
-# It's possible that stale events occur, i.e. read event handler called
-# for just saved upstream connection without any data available for
-# read.  We shouldn't close upstream connection in such situation.
-#
-# This happens due to reading from upstream connection on downstream write
-# events.  More likely to happen with multiple workers due to use of posted
-# events.
-#
-# Stale event may only happen if reading response from upstream requires
-# entering event loop, i.e. response should be big enough.  On the other
-# hand, it is less likely to occur with full client's connection output
-# buffer.
-#
-# We use here 2 workers, 20k response and set output buffer on clients
-# connection to 32k.  This allows more or less reliably reproduce stale
-# events at least on FreeBSD testbed here.
-
-$memd1->set('/big', 'X' x 20480);
-
-my $total = $memd1->stats()->{total}->{total_connections};
-
-for (1 .. 100) {
-	http_get('/big');
-}
-
-cmp_ok($memd1->stats()->{total}->{total_connections}, '<=', $total + 2,
-	'only one connection per worker used');
-
-$t->stop();
-
-like(`grep -F '[alert]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no alerts');
-
-###############################################################################