<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -24,7 +24,7 @@
  */
 #include &quot;Bucket.h&quot;
 
-using namespace Passenger;
+namespace Passenger {
 
 static void bucket_destroy(void *data);
 static apr_status_t bucket_read(apr_bucket *a, const char **str, apr_size_t *len, apr_read_type_e block);
@@ -42,7 +42,8 @@ static const apr_bucket_type_t apr_bucket_type_passenger_pipe = {
 
 struct BucketData {
 	Application::SessionPtr session;
-	apr_file_t *pipe;
+	PassengerBucketStatePtr state;
+	int stream;
 	
 	~BucketData() {
 		/* The session here is an ApplicationPoolServer::RemoteSession.
@@ -71,15 +72,13 @@ bucket_destroy(void *data) {
 
 static apr_status_t
 bucket_read(apr_bucket *bucket, const char **str, apr_size_t *len, apr_read_type_e block) {
-	apr_file_t *pipe;
 	char *buf;
-	apr_status_t ret;
-
-	BucketData *data = (BucketData *) bucket-&gt;data;
-	pipe = data-&gt;pipe;
-
+	ssize_t ret;
+	BucketData *data;
+	
+	data = (BucketData *) bucket-&gt;data;
 	*str = NULL;
-	*len = APR_BUCKET_BUFF_SIZE;
+	*len = 0;
 	
 	if (block == APR_NONBLOCK_READ) {
 		/*
@@ -101,55 +100,72 @@ bucket_read(apr_bucket *bucket, const char **str, apr_size_t *len, apr_read_type
 		return APR_EAGAIN;
 	}
 	
-	buf = (char *) apr_bucket_alloc(*len, bucket-&gt;list); // TODO: check for failure?
-
-	do {
-		ret = apr_file_read(pipe, buf, len);
-	} while (APR_STATUS_IS_EAGAIN(ret));
-
-	if (ret != APR_SUCCESS &amp;&amp; ret != APR_EOF) {
-		// ... we might want to set an error flag here ...
-		delete data;
-		bucket-&gt;data = NULL;
-		apr_bucket_free(buf);
-		return ret;
+	buf = (char *) apr_bucket_alloc(APR_BUCKET_BUFF_SIZE, bucket-&gt;list);
+	if (buf == NULL) {
+		return APR_ENOMEM;
 	}
-	/*
-	 * If there's more to read we have to keep the rest of the pipe
-	 * for later.
-	 */
-	if (*len &gt; 0) {
+	
+	do {
+		ret = read(data-&gt;stream, buf, APR_BUCKET_BUFF_SIZE);
+	} while (ret == -1 &amp;&amp; errno == EINTR);
+	
+	if (ret &gt; 0) {
 		apr_bucket_heap *h;
 		
+		data-&gt;state-&gt;bytesRead += ret;
+		
 		*str = buf;
+		*len = ret;
 		bucket-&gt;data = NULL;
 		
-		/* Change the current bucket to refer to what we read */
+		/* Change the current bucket (which is a Passenger Bucket) into a heap bucket
+		 * that contains the data that we just read. This newly created heap bucket
+		 * will be the first in the bucket list.
+		 */
 		bucket = apr_bucket_heap_make(bucket, buf, *len, apr_bucket_free);
 		h = (apr_bucket_heap *) bucket-&gt;data;
 		h-&gt;alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
+		
+		/* And after this newly created bucket we insert a new Passenger Bucket
+		 * which can read the next chunk from the stream.
+		 */
 		APR_BUCKET_INSERT_AFTER(bucket, passenger_bucket_create(
-			data-&gt;session, pipe, bucket-&gt;list));
+			data-&gt;session, data-&gt;state, bucket-&gt;list));
+		
+		/* The newly created Passenger Bucket has a reference to the session
+		 * object, so we can delete data here.
+		 */
 		delete data;
-	} else {
+		
+		return APR_SUCCESS;
+		
+	} else if (ret == 0) {
+		data-&gt;state-&gt;completed = true;
 		delete data;
 		bucket-&gt;data = NULL;
 		
-		apr_bucket_free(buf);
 		bucket = apr_bucket_immortal_make(bucket, &quot;&quot;, 0);
 		*str = (const char *) bucket-&gt;data;
-		// if (rv != APR_EOF) {
-		//     ... we might want to set an error flag here ...
-		// }
+		*len = 0;
+		return APR_SUCCESS;
+		
+	} else /* ret == -1 */ {
+		int e = errno;
+		data-&gt;state-&gt;completed = true;
+		data-&gt;state-&gt;errorCode = e;
+		delete data;
+		bucket-&gt;data = NULL;
+		apr_bucket_free(buf);
+		return APR_FROM_OS_ERROR(e);
 	}
-	return APR_SUCCESS;
 }
 
 static apr_bucket *
-passenger_bucket_make(apr_bucket *bucket, Application::SessionPtr session, apr_file_t *pipe) {
+passenger_bucket_make(apr_bucket *bucket, Application::SessionPtr session, PassengerBucketStatePtr state) {
 	BucketData *data = new BucketData();
 	data-&gt;session  = session;
-	data-&gt;pipe     = pipe;
+	data-&gt;stream   = session-&gt;getStream();
+	data-&gt;state    = state;
 	
 	bucket-&gt;type   = &amp;apr_bucket_type_passenger_pipe;
 	bucket-&gt;length = (apr_size_t)(-1);
@@ -159,13 +175,14 @@ passenger_bucket_make(apr_bucket *bucket, Application::SessionPtr session, apr_f
 }
 
 apr_bucket *
-passenger_bucket_create(Application::SessionPtr session, apr_file_t *pipe, apr_bucket_alloc_t *list) {
+passenger_bucket_create(Application::SessionPtr session, PassengerBucketStatePtr state, apr_bucket_alloc_t *list) {
 	apr_bucket *bucket;
 	
 	bucket = (apr_bucket *) apr_bucket_alloc(sizeof(*bucket), list);
 	APR_BUCKET_INIT(bucket);
 	bucket-&gt;free = apr_bucket_free;
 	bucket-&gt;list = list;
-	return passenger_bucket_make(bucket, session, pipe);
+	return passenger_bucket_make(bucket, session, state);
 }
 
+} // namespace Passenger</diff>
      <filename>ext/apache2/Bucket.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -25,26 +25,63 @@
 #ifndef _PASSENGER_BUCKET_H_
 #define _PASSENGER_BUCKET_H_
 
-/**
- * apr_bucket_pipe closes a pipe's file descriptor when it has reached
- * end-of-stream, but not when an error has occurred. This behavior is
- * undesirable because it can easily cause file descriptor leaks.
- *
- * passenger_bucket is like apr_bucket_pipe, but it also holds a reference to
- * a Session. When a read error has occured or when end-of-stream has been
- * reached, the Session will be dereferenced, so that the underlying file
- * descriptor is closed.
- *
- * passenger_bucket also ignores the APR_NONBLOCK_READ flag because that's
- * known to cause strange I/O problems.
- */
-
+#include &lt;boost/shared_ptr.hpp&gt;
 #include &lt;apr_buckets.h&gt;
 #include &quot;Application.h&quot;
 
+namespace Passenger {
+
+using namespace boost;
+
+struct PassengerBucketState {
+	/** The number of bytes that this PassengerBucket has read so far. */
+	unsigned long bytesRead;
+	
+	/** Whether this PassengerBucket is completed, i.e. no more data
+	 * can be read from the underlying file descriptor. When true,
+	 * this can either mean that EOF has been reached, or that an I/O
+	 * error occured. Use errorCode to check whether an error occurred.
+	 */
+	bool completed;
+	
+	/** When completed is true, errorCode contains the errno value of
+	 * the last read() call.
+	 *
+	 * A value of 0 means that no error occured.
+	 */
+	int errorCode;
+	
+	PassengerBucketState() {
+		bytesRead = 0;
+		completed = false;
+		errorCode = 0;
+	}
+};
+
+typedef shared_ptr&lt;PassengerBucketState&gt; PassengerBucketStatePtr;
+
+/**
+ * We used to use an apr_bucket_pipe for forwarding the backend process's
+ * response to the HTTP client. However, apr_bucket_pipe has a number of
+ * issues:
+ * - It closes the pipe's file descriptor when it has reached
+ *   end-of-stream, but not when an error has occurred. This behavior is
+ *   undesirable because it can easily cause file descriptor leaks.
+ * - It does weird non-blocking-I/O related things which can cause it
+ *   to read less data than can actually be read.
+ *
+ * PassengerBucket is like apr_bucket_pipe, but:
+ * - It also holds a reference to a Session. When a read error has occured
+ *   or when end-of-stream has been reached, the Session will be
+ *   dereferenced, so that the underlying file descriptor is closed.
+ * - It ignores the APR_NONBLOCK_READ flag because that's known to cause
+ *   strange I/O problems.
+ * - It can store its current state in a PassengerBucketState data structure.
+ */
 apr_bucket *passenger_bucket_create(Passenger::Application::SessionPtr session,
-                                    apr_file_t *pipe,
+                                    PassengerBucketStatePtr state,
                                     apr_bucket_alloc_t *list);
 
-#endif /* _PASSENGER_BUCKET_H_ */
+} // namespace Passenger
 
+#endif /* _PASSENGER_BUCKET_H_ */</diff>
      <filename>ext/apache2/Bucket.h</filename>
    </modified>
    <modified>
      <diff>@@ -447,8 +447,6 @@ private:
 		try {
 			this_thread::disable_interruption di;
 			this_thread::disable_syscall_interruption dsi;
-			apr_bucket_brigade *bb;
-			apr_bucket *b;
 			Application::SessionPtr session;
 			bool expectingUploadData;
 			shared_ptr&lt;BufferedUpload&gt; uploadData;
@@ -554,21 +552,21 @@ private:
 			/********** Step 4: forwarding the response from the backend
 			                    process back to the HTTP client **********/
 			
-			/* Setup all the streams. */
 			UPDATE_TRACE_POINT();
-			apr_file_t *readerPipe = NULL;
-			int reader = session-&gt;getStream();
-			pid_t backendPid = session-&gt;getPid();
-			apr_os_pipe_put(&amp;readerPipe, &amp;reader, r-&gt;pool);
-			apr_file_pipe_timeout_set(readerPipe, r-&gt;server-&gt;timeout);
-
+			apr_bucket_brigade *bb;
+			apr_bucket *b;
+			PassengerBucketStatePtr bucketState;
+			pid_t backendPid;
+			
 			/* Setup the bucket brigade. */
+			bucketState = ptr(new PassengerBucketState());
 			bb = apr_brigade_create(r-&gt;connection-&gt;pool, r-&gt;connection-&gt;bucket_alloc);
-			b = passenger_bucket_create(session, readerPipe, r-&gt;connection-&gt;bucket_alloc);
+			b = passenger_bucket_create(session, bucketState, r-&gt;connection-&gt;bucket_alloc);
 			
 			/* The bucket (b) still has a reference to the session, so the reset()
 			 * call here is guaranteed not to throw any exceptions.
 			 */
+			backendPid = session-&gt;getPid();
 			session.reset();
 			
 			APR_BRIGADE_INSERT_TAIL(bb, b);
@@ -610,6 +608,19 @@ private:
 				
 				UPDATE_TRACE_POINT();
 				ap_pass_brigade(r-&gt;output_filters, bb);
+				
+				if (r-&gt;connection-&gt;aborted) {
+					P_WARN(&quot;The HTTP client closed the connection before &quot;
+						&quot;the response could be completely sent. As a &quot;
+						&quot;result, you will probably see a 'Broken Pipe' &quot;
+						&quot;error in this log file. Please ignore it, &quot;
+						&quot;this is normal.&quot;);
+				} else if (!bucketState-&gt;completed) {
+					P_WARN(&quot;Apache stopped forwarding the backend's response, &quot;
+						&quot;even though the HTTP client did not close the &quot;
+						&quot;connection. Is this an Apache bug?&quot;);
+				}
+				
 				return OK;
 			} else if (backendData[0] == '\0') {
 				if ((long long) timer.elapsed() &gt;= r-&gt;server-&gt;timeout / 1000) {</diff>
      <filename>ext/apache2/Hooks.cpp</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>a702b5c4e30a3790a63a041e0bfa068b28f61fcf</id>
    </parent>
  </parents>
  <author>
    <name>Hongli Lai (Phusion)</name>
    <email>hongli@phusion.nl</email>
  </author>
  <url>http://github.com/FooBarWidget/passenger/commit/3fbcdffd5db60ecdd137c384480853c9debbe1aa</url>
  <id>3fbcdffd5db60ecdd137c384480853c9debbe1aa</id>
  <committed-date>2009-05-31T07:37:17-07:00</committed-date>
  <authored-date>2009-05-22T11:23:29-07:00</authored-date>
  <message>Do not use apr_file_t for reading the data from the backend process. Instead, use our own I/O functions because APR's are buggy.</message>
  <tree>9964fd30d338ad780e12c173433375f0d309850b</tree>
  <committer>
    <name>Hongli Lai (Phusion)</name>
    <email>hongli@phusion.nl</email>
  </committer>
</commit>
