[PATCH] Upstream: fixed $upstream_response_time for filter_finalize + error_page.

Maxim Dounin mdounin at mdounin.ru
Fri Feb 13 15:05:12 UTC 2015


Hello!

On Thu, Feb 12, 2015 at 05:30:27PM -0800, Yichun Zhang (agentzh) wrote:

> Hello!
> 
> Please review the following patch.
> 
> Thanks!
> -agentzh
> 
> # HG changeset patch
> # User Yichun Zhang <agentzh at gmail.com>
> # Date 1423789183 28800
> #      Thu Feb 12 16:59:43 2015 -0800
> # Node ID 8b3d7171f35e74c8bea3234e88d8977b4f11f815
> # Parent  f3f25ad09deee27485050a75732e5f46ab1b18b3
> Upstream: fixed $upstream_response_time for filter_finalize + error_page.
> 
> ngx_http_upstream_finalize_request() is always called twice when an
> output filter module calls ngx_http_filter_finalize_request() *and*
> a custom error page is configured by the error_page directive. This
> is because
> 
> 1. ngx_http_filter_finalize_request() triggers
>         calling ngx_http_terminate_request
>         => calling ngx_http_upstream_cleanup
>         => calling ngx_http_upstream_finalize_request
> 
> 2. ngx_http_internal_redirect() returns NGX_DONE
>         ==> ngx_http_special_response_handler() returns NGX_DONE
>         ==> ngx_http_filter_finalize_request() returns NGX_ERROR
>         ==> ngx_http_send_header() returns NGX_ERROR
>         ==> ngx_http_upstream_send_response() calls
>             ngx_http_upstream_finalize_request() again in the same
>             ngx_http_upstream_send_response() call as 1).
> 
> This might result in corrupted $upstream_response_time values (close
> to the absolute timestamp value) when u->state->response_sec happens
> to be non-zero.
> 
> This patch ensures that the $upstream_response_time value is only
> calculated upon the first ngx_http_upstream_finalize_request()
> invocation.

Yes, filter finalization functionality is known to be very fragile 
and can easily cause problems if one will try to redirect it's 
processing with error_page.  Especially if one'll try to redirect 
the processing from one upstream to another upstream server.

Current solution to the problem is "don't do this, it hurts".  
This mostly works as filter finalization is only used in a few 
very specific cases.

> diff -r f3f25ad09dee -r 8b3d7171f35e src/http/ngx_http_upstream.c
> --- a/src/http/ngx_http_upstream.c Wed Feb 11 20:18:55 2015 +0300
> +++ b/src/http/ngx_http_upstream.c Thu Feb 12 16:59:43 2015 -0800
> @@ -3738,7 +3738,7 @@ static void
>  ngx_http_upstream_finalize_request(ngx_http_request_t *r,
>      ngx_http_upstream_t *u, ngx_int_t rc)
>  {
> -    ngx_uint_t   flush;
> +    ngx_uint_t   flush, cleaned;
>      ngx_time_t  *tp;
> 
>      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
> @@ -3747,6 +3747,10 @@ ngx_http_upstream_finalize_request(ngx_h
>      if (u->cleanup) {
>          *u->cleanup = NULL;
>          u->cleanup = NULL;
> +        cleaned = 0;
> +
> +    } else {
> +        cleaned = 1;
>      }

This approach looks wrong for me.  It tries to ensure that the 
u->state will not be corrupted, but the problem here is that we've 
already finalized the request, and doing _anything_ would be wrong.

Rather, I would suggest something like this:

--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -3744,10 +3744,13 @@ ngx_http_upstream_finalize_request(ngx_h
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "finalize http upstream request: %i", rc);
 
-    if (u->cleanup) {
-        *u->cleanup = NULL;
-        u->cleanup = NULL;
-    }
+    if (u->cleanup == NULL) {
+        /* the request was already finalized */
+        ngx_http_finalize_request(r, NGX_DONE);
+    }
+
+    *u->cleanup = NULL;
+    u->cleanup = NULL;
 
     if (u->resolved && u->resolved->ctx) {
         ngx_resolve_name_done(u->resolved->ctx);

(Untested though.)

-- 
Maxim Dounin
http://nginx.org/



More information about the nginx-devel mailing list