Possible cache_bypass_bug

Maxim Dounin mdounin at mdounin.ru
Mon Sep 26 03:08:47 UTC 2011


Hello!

On Mon, Sep 26, 2011 at 11:10:07AM +1000, John Ferlito wrote:

> 
> Howdy,
> 
> I'd like to confirm what I think is a bug before I lodge it in the bug
> tracker.
> 
> I'm seeing pages that shouldn't be cached be cached.
> 
> I have a config that looks something like
> 
> 
>   upstream app_pool {
>     server app.inodes.org;
>     fair;
>   }
> 
>   proxy_cache_path  /srv/www/reverse_proxy/cache levels=1:2 keys_zone=application_cache:8m max_size=1000m inactive=600m;
>   proxy_temp_path   /srv/www/reverse_proxy/cache/tmp;
> 
>   server {
>     listen                  80;
>     server_name             inodes.org;
> 
>     location / {
>       proxy_pass         http://app_pool;
>       proxy_pass_header  Set-Cookie;
> 
>       proxy_cache        application_cache;
>       proxy_cache_key    "$scheme$host$request_uri$query_string";
> 
>       proxy_set_header   Host $host;
>       proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
>       proxy_set_header   X-Real-IP $remote_addr;
> 
>       proxy_cache_valid  200 302 301 60m;
>       proxy_cache_valid  404         1m;
> 
>       proxy_cache_bypass $http_pragma;
>     }
>   }
> 
> 
> Now the behaviour I'm seeing is specifically for responses with a header like
> 
>   Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
> 
> That is they shouldn't be cached. They only get cached after a request with
> 
>   Pragma: no-cache
> 
> set.
> 
> If I create a cache log I see something like this.
> 
> 
> MISS   "GET /mospace/team/ HTTP/1.1" (200) # First load
> BYPASS "GET /mospace/team/ HTTP/1.1" (200) # Shift Reload
> HIT    "GET /mospace/team/ HTTP/1.1" (200) # Standard load
> 
> I've done some following of the code path and I think I understand
> where the bug lies.
> 
> In ngx_http_upstream_cache.c we essentially have the following code (snipped
> for clarity)
> 
>   static void
>   ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)
>   {
>     switch (ngx_http_test_predicates(r, u->conf->no_cache)) {
> 
>     case NGX_DECLINED:
>         u->cacheable = 0;
>         break;
> 
>     default: /* NGX_OK */
> 
>         if (u->cache_status == NGX_HTTP_CACHE_BYPASS) {
> 
>             r->cache->min_uses = u->conf->cache_min_uses;
>             r->cache->body_start = u->conf->buffer_size;
>             r->cache->file_cache = u->conf->cache->data;
> 
>             if (ngx_http_file_cache_create(r) != NGX_OK) {
>                 ngx_http_upstream_finalize_request(r, u, 0);
>                 return;
>             }
> 
>             u->cacheable = 1;
>         }
> 
>         break;
>     }
>   }
> 
> 
> So from my understanding if cache_bypass has been set we mark the page as being
> able to be cached.
> 
> Now this function is called in ngx_http_upstream_process_header, which does the
> following
> 
>     if (ngx_http_upstream_process_headers(r, u) != NGX_OK) {
>         return;
>     }
> 
>     if (!r->subrequest_in_memory) {
>         ngx_http_upstream_send_response(r, u);
>         return;
>     }
> 
> 
> From what I can tell ngx_http_upstream_process_headers will go through
> the headers and when it sees the appropriate Cache-Control header it
> will set cacheable to 0.
> 
> But later on in ngx_http_upstream_send_response we are setting it to
> 1. Hence we start caching private pages but only when a bypass header
> has been set.

Ack, this is clearly a bug.

> Perhaps we should set cacheable to 1 much earlier in the process when
> BYPASS is set? That is in ngx_http_upstream_cache
> 
>   switch (ngx_http_test_predicates(r, u->conf->cache_bypass)) {
>     // snip
>     case NGX_DECLINED:
>       u->cache_status = NGX_HTTP_CACHE_BYPASS;
>       u-> cachable = 1; // Add this here maybe?
>       return NGX_DECLINED;
>   }
> 
> This is the first time I've looked at the nginx source so perhaps the
> above would have some other unintended effect. If not let me know and
> I'll submit a patch.

As far as I see moving 

        u->cacheable = 1;

above the ngx_http_test_predicates(cache_bypass) check should be 
ok.

Obviously "u->cacheable = 1" should be removed from 
ngx_http_test_predicates(no_cache) switch.

Maxim Dounin



More information about the nginx-devel mailing list