diff src/core/ngx_open_file_cache.c @ 358:9121a0a91f47 NGINX_0_6_23

nginx 0.6.23 *) Change: the "off" parameter in the "ssl_session_cache" directive; now this is default parameter. *) Change: the "open_file_cache_retest" directive was renamed to the "open_file_cache_valid". *) Feature: the "open_file_cache_min_uses" directive. *) Feature: the ngx_http_gzip_static_module. *) Feature: the "gzip_disable" directive. *) Feature: the "memcached_pass" directive may be used inside the "if" block. *) Bugfix: a segmentation fault occurred in worker process, if the "memcached_pass" and "if" directives were used in the same location. *) Bugfix: if a "satisfy_any on" directive was used and not all access and auth modules directives were set, then other given access and auth directives were not tested; *) Bugfix: regex parameters in a "valid_referers" directive were not inherited from previous level. *) Bugfix: a "post_action" directive did run if a request was completed with 499 status code. *) Bugfix: optimization of 16K buffer usage in a SSL connection. Thanks to Ben Maurer. *) Bugfix: the STARTTLS in SMTP mode did not work. Thanks to Oleg Motienko. *) Bugfix: in HTTPS mode requests might fail with the "bad write retry" error; bug appeared in 0.5.13.
author Igor Sysoev <http://sysoev.ru>
date Thu, 27 Dec 2007 00:00:00 +0300
parents b743d290eb3b
children 9a242235a80a
line wrap: on
line diff
--- a/src/core/ngx_open_file_cache.c
+++ b/src/core/ngx_open_file_cache.c
@@ -18,22 +18,27 @@
 
 
 static void ngx_open_file_cache_cleanup(void *data);
+static ngx_int_t ngx_open_and_stat_file(u_char *name, ngx_open_file_info_t *of,
+    ngx_log_t *log);
+static void ngx_open_file_add_event(ngx_open_file_cache_t *cache,
+    ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log);
 static void ngx_open_file_cleanup(void *data);
 static void ngx_close_cached_file(ngx_open_file_cache_t *cache,
-    ngx_cached_open_file_t *file, ngx_log_t *log);
-static ngx_int_t ngx_open_and_stat_file(u_char *name, ngx_open_file_info_t *of,
-    ngx_log_t *log);
+    ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_log_t *log);
+static void ngx_open_file_del_event(ngx_cached_open_file_t *file);
 static void ngx_expire_old_cached_files(ngx_open_file_cache_t *cache,
     ngx_uint_t n, ngx_log_t *log);
 static void ngx_open_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
     ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+static ngx_cached_open_file_t *
+    ngx_open_file_lookup(ngx_open_file_cache_t *cache, ngx_str_t *name,
+    uint32_t hash);
 static void ngx_open_file_cache_remove(ngx_event_t *ev);
 
 
 ngx_open_file_cache_t *
 ngx_open_file_cache_init(ngx_pool_t *pool, ngx_uint_t max, time_t inactive)
 {
-    ngx_rbtree_node_t      *sentinel;
     ngx_pool_cleanup_t     *cln;
     ngx_open_file_cache_t  *cache;
 
@@ -42,19 +47,10 @@ ngx_open_file_cache_init(ngx_pool_t *poo
         return NULL;
     }
 
-    cache->list_head.prev = NULL;
-    cache->list_head.next = &cache->list_tail;
-
-    cache->list_tail.prev = &cache->list_head;
-    cache->list_tail.next = NULL;
+    ngx_rbtree_init(&cache->rbtree, &cache->sentinel,
+                    ngx_open_file_cache_rbtree_insert_value);
 
-    sentinel = ngx_palloc(pool, sizeof(ngx_rbtree_node_t));
-    if (sentinel == NULL) {
-        return NULL;
-    }
-
-    ngx_rbtree_init(&cache->rbtree, sentinel,
-                    ngx_open_file_cache_rbtree_insert_value);
+    ngx_queue_init(&cache->expire_queue);
 
     cache->current = 0;
     cache->max = max;
@@ -77,6 +73,7 @@ ngx_open_file_cache_cleanup(void *data)
 {
     ngx_open_file_cache_t  *cache = data;
 
+    ngx_queue_t             *q;
     ngx_cached_open_file_t  *file;
 
     ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
@@ -84,14 +81,15 @@ ngx_open_file_cache_cleanup(void *data)
 
     for ( ;; ) {
 
-        file = cache->list_tail.prev;
-
-        if (file == &cache->list_head) {
+        if (ngx_queue_empty(&cache->expire_queue)) {
             break;
         }
 
-        file->next->prev = file->prev;
-        file->prev->next = file->next;
+        q = ngx_queue_last(&cache->expire_queue);
+
+        file = ngx_queue_data(q, ngx_cached_open_file_t, queue);
+
+        ngx_queue_remove(q);
 
         ngx_rbtree_delete(&cache->rbtree, &file->node);
 
@@ -103,7 +101,7 @@ ngx_open_file_cache_cleanup(void *data)
         if (!file->err && !file->is_dir) {
             file->close = 1;
             file->count = 0;
-            ngx_close_cached_file(cache, file, ngx_cycle->log);
+            ngx_close_cached_file(cache, file, 0, ngx_cycle->log);
 
         } else {
             ngx_free(file->name);
@@ -132,11 +130,9 @@ ngx_open_cached_file(ngx_open_file_cache
     time_t                          now;
     uint32_t                        hash;
     ngx_int_t                       rc;
-    ngx_rbtree_node_t              *node, *sentinel;
     ngx_pool_cleanup_t             *cln;
     ngx_cached_open_file_t         *file;
     ngx_pool_cleanup_file_t        *clnf;
-    ngx_open_file_cache_event_t    *fev;
     ngx_open_file_cache_cleanup_t  *ofcln;
 
     of->err = 0;
@@ -167,145 +163,147 @@ ngx_open_cached_file(ngx_open_file_cache
         return NGX_ERROR;
     }
 
-    hash = ngx_crc32_long(name->data, name->len);
-
-    node = cache->rbtree.root;
-    sentinel = cache->rbtree.sentinel;
-
     now = ngx_time();
 
-    while (node != sentinel) {
+    hash = ngx_crc32_long(name->data, name->len);
+
+    file = ngx_open_file_lookup(cache, name, hash);
+
+    if (file) {
+
+        file->uses++;
+
+        ngx_queue_remove(&file->queue);
 
-        if (hash < node->key) {
-            node = node->left;
-            continue;
-        }
+        if (file->fd == NGX_INVALID_FILE && file->err == 0 && !file->is_dir) {
+
+            /* file was not used often enough to keep open */
+
+            rc = ngx_open_and_stat_file(name->data, of, pool->log);
 
-        if (hash > node->key) {
-            node = node->right;
-            continue;
+            if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
+                goto failed;
+            }
+
+            goto add_event;
         }
 
-        /* hash == node->key */
-
-        do {
-            file = (ngx_cached_open_file_t *) node;
-
-            rc = ngx_strcmp(name->data, file->name);
-
-            if (rc == 0) {
-
-                file->next->prev = file->prev;
-                file->prev->next = file->next;
-
-                if (file->event || now - file->created < of->retest) {
-                    if (file->err == 0) {
-                        of->fd = file->fd;
-                        of->uniq = file->uniq;
-                        of->mtime = file->mtime;
-                        of->size = file->size;
-
-                        of->is_dir = file->is_dir;
-                        of->is_file = file->is_file;
-                        of->is_link = file->is_link;
-                        of->is_exec = file->is_exec;
+        if ((file->event && file->use_event)
+            || (file->event == NULL && now - file->created < of->valid))
+        {
+            if (file->err == 0) {
 
-                        if (!file->is_dir) {
-                            file->count++;
-                        }
-
-                    } else {
-                        of->err = file->err;
-                    }
-
-                    goto found;
-                }
+                of->fd = file->fd;
+                of->uniq = file->uniq;
+                of->mtime = file->mtime;
+                of->size = file->size;
 
-                ngx_log_debug4(NGX_LOG_DEBUG_CORE, pool->log, 0,
-                               "retest open file: %s, fd:%d, c:%d, e:%d",
-                               file->name, file->fd, file->count, file->err);
-
-                if (file->is_dir) {
+                of->is_dir = file->is_dir;
+                of->is_file = file->is_file;
+                of->is_link = file->is_link;
+                of->is_exec = file->is_exec;
 
-                    /*
-                     * chances that directory became file are very small
-                     * so test_dir flag allows to use a single ngx_file_info()
-                     * syscall instead of three syscalls
-                     */
-
-                    of->test_dir = 1;
+                if (!file->is_dir) {
+                    file->count++;
+                    ngx_open_file_add_event(cache, file, of, pool->log);
                 }
 
-                rc = ngx_open_and_stat_file(name->data, of, pool->log);
+            } else {
+                of->err = file->err;
+            }
+
+            goto found;
+        }
+
+        ngx_log_debug4(NGX_LOG_DEBUG_CORE, pool->log, 0,
+                       "retest open file: %s, fd:%d, c:%d, e:%d",
+                       file->name, file->fd, file->count, file->err);
+
+        if (file->is_dir) {
+
+            /*
+             * chances that directory became file are very small
+             * so test_dir flag allows to use a single syscall
+             * in ngx_file_info() instead of three syscalls
+             */
+
+            of->test_dir = 1;
+        }
+
+        rc = ngx_open_and_stat_file(name->data, of, pool->log);
 
-                if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
-                    goto failed;
+        if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
+            goto failed;
+        }
+
+        if (of->is_dir) {
+
+            if (file->is_dir || file->err) {
+                goto update;
+            }
+
+            /* file became directory */
+
+        } else if (of->err == 0) {  /* file */
+
+            if (file->is_dir || file->err) {
+                goto add_event;
+            }
+
+            if (of->uniq == file->uniq
+                && of->mtime == file->mtime
+                && of->size == file->size)
+            {
+                if (ngx_close_file(of->fd) == NGX_FILE_ERROR) {
+                    ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
+                                  ngx_close_file_n " \"%s\" failed",
+                                  name->data);
                 }
 
-                if (of->is_dir) {
-                    if (file->is_dir || file->err) {
-                        goto update;
-                    }
-
-                    /* file became directory */
-
-                } else if (of->err == 0) {  /* file */
-
-                    if (file->is_dir || file->err) {
-                        goto update;
-                    }
+                of->fd = file->fd;
+                file->count++;
 
-                    if (of->uniq == file->uniq
-                        && of->mtime == file->mtime
-                        && of->size == file->size)
-                    {
-                        if (ngx_close_file(of->fd) == NGX_FILE_ERROR) {
-                            ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
-                                          ngx_close_file_n " \"%s\" failed",
-                                          name->data);
-                        }
-
-                        of->fd = file->fd;
-                        file->count++;
-
-                        goto renew;
-                    }
-
-                    /* file was changed */
-
-                } else { /* error to cache */
-
-                    if (file->err || file->is_dir) {
-                        goto update;
-                    }
-
-                    /* file was removed, etc. */
+                if (file->event) {
+                    file->use_event = 1;
+                    goto renew;
                 }
 
-                if (file->count == 0) {
-                    if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
-                        ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
-                                      ngx_close_file_n " \"%s\" failed",
-                                      name->data);
-                    }
+                ngx_open_file_add_event(cache, file, of, pool->log);
+
+                goto renew;
+            }
 
-                    goto update;
-                }
-
-                ngx_rbtree_delete(&cache->rbtree, &file->node);
+            /* file was changed */
 
-                cache->current--;
+        } else { /* error to cache */
 
-                file->close = 1;
-
-                goto create;
+            if (file->err || file->is_dir) {
+                goto update;
             }
 
-            node = (rc < 0) ? node->left : node->right;
+            /* file was removed, etc. */
+        }
+
+        if (file->count == 0) {
+
+            ngx_open_file_del_event(file);
 
-        } while (node != sentinel && hash == node->key);
+            if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
+                              ngx_close_file_n " \"%s\" failed",
+                              name->data);
+            }
 
-        break;
+            goto add_event;
+        }
+
+        ngx_rbtree_delete(&cache->rbtree, &file->node);
+
+        cache->current--;
+
+        file->close = 1;
+
+        goto create;
     }
 
     /* not found */
@@ -346,51 +344,17 @@ create:
 
     cache->current++;
 
+    file->uses = 1;
     file->count = 0;
+    file->use_event = 0;
+    file->event = NULL;
+
+add_event:
+
+    ngx_open_file_add_event(cache, file, of, pool->log);
 
 update:
 
-    if (of->events
-        && (ngx_event_flags & NGX_USE_VNODE_EVENT)
-        && of->fd != NGX_INVALID_FILE)
-    {
-        file->event = ngx_calloc(sizeof(ngx_event_t), pool->log);
-        if (file->event== NULL) {
-            goto failed;
-        }
-
-        fev = ngx_alloc(sizeof(ngx_open_file_cache_event_t), pool->log);
-        if (fev == NULL) {
-            goto failed;
-        }
-
-        fev->fd = of->fd;
-        fev->file = file;
-        fev->cache = cache;
-
-        file->event->handler = ngx_open_file_cache_remove;
-        file->event->data = fev;
-
-        /*
-         * although vnode event may be called while ngx_cycle->poll
-         * destruction; however, cleanup procedures are run before any
-         * memory freeing and events will be canceled.
-         */
-
-        file->event->log = ngx_cycle->log;
-
-        if (ngx_add_event(file->event, NGX_VNODE_EVENT, NGX_ONESHOT_EVENT)
-            != NGX_OK)
-        {
-            ngx_free(file->event->data);
-            ngx_free(file->event);
-            goto failed;
-        }
-
-    } else {
-        file->event = NULL;
-    }
-
     file->fd = of->fd;
     file->err = of->err;
 
@@ -419,16 +383,11 @@ found:
 
     file->accessed = now;
 
-    /* add to the inactive list head */
+    ngx_queue_insert_head(&cache->expire_queue, &file->queue);
 
-    file->next = cache->list_head.next;
-    file->next->prev = file;
-    file->prev = &cache->list_head;
-    cache->list_head.next = file;
-
-    ngx_log_debug4(NGX_LOG_DEBUG_CORE, pool->log, 0,
-                   "cached open file: %s, fd:%d, c:%d, e:%d",
-                   file->name, file->fd, file->count, file->err);
+    ngx_log_debug5(NGX_LOG_DEBUG_CORE, pool->log, 0,
+                   "cached open file: %s, fd:%d, c:%d, e:%d, u:%d",
+                   file->name, file->fd, file->count, file->err, file->uses);
 
     if (of->err == 0) {
 
@@ -438,6 +397,7 @@ found:
 
             ofcln->cache = cache;
             ofcln->file = file;
+            ofcln->min_uses = of->min_uses;
             ofcln->log = pool->log;
         }
 
@@ -543,6 +503,72 @@ ngx_open_and_stat_file(u_char *name, ngx
 }
 
 
+/*
+ * we ignore any possible event setting error and
+ * fallback to usual periodic file retests
+ */
+
+static void
+ngx_open_file_add_event(ngx_open_file_cache_t *cache,
+    ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log)
+{
+    ngx_open_file_cache_event_t  *fev;
+
+    if (!(ngx_event_flags & NGX_USE_VNODE_EVENT)
+        || !of->events
+        || file->event
+        || of->fd == NGX_INVALID_FILE
+        || file->uses < of->min_uses)
+    {
+        return;
+    }
+
+    file->event = ngx_calloc(sizeof(ngx_event_t), log);
+    if (file->event== NULL) {
+        return;
+    }
+
+    fev = ngx_alloc(sizeof(ngx_open_file_cache_event_t), log);
+    if (fev == NULL) {
+        ngx_free(file->event);
+        file->event = NULL;
+        return;
+    }
+
+    fev->fd = of->fd;
+    fev->file = file;
+    fev->cache = cache;
+
+    file->event->handler = ngx_open_file_cache_remove;
+    file->event->data = fev;
+
+    /*
+     * although vnode event may be called while ngx_cycle->poll
+     * destruction, however, cleanup procedures are run before any
+     * memory freeing and events will be canceled.
+     */
+
+    file->event->log = ngx_cycle->log;
+
+    if (ngx_add_event(file->event, NGX_VNODE_EVENT, NGX_ONESHOT_EVENT)
+        != NGX_OK)
+    {
+        ngx_free(file->event->data);
+        ngx_free(file->event);
+        file->event = NULL;
+        return;
+    }
+
+    /*
+     * we do not file->use_event here because there may be a race
+     * condition between opening file and adding event, so we rely
+     * upon event notification only after first file revalidation
+     */
+
+    return;
+}
+
+
 static void
 ngx_open_file_cleanup(void *data)
 {
@@ -550,7 +576,7 @@ ngx_open_file_cleanup(void *data)
 
     c->file->count--;
 
-    ngx_close_cached_file(c->cache, c->file, c->log);
+    ngx_close_cached_file(c->cache, c->file, c->min_uses, c->log);
 
     /* drop one or two expired open files */
     ngx_expire_old_cached_files(c->cache, 1, c->log);
@@ -559,50 +585,43 @@ ngx_open_file_cleanup(void *data)
 
 static void
 ngx_close_cached_file(ngx_open_file_cache_t *cache,
-    ngx_cached_open_file_t *file, ngx_log_t *log)
+    ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_log_t *log)
 {
-    ngx_log_debug4(NGX_LOG_DEBUG_CORE, log, 0,
-                   "close cached open file: %s, fd:%d, c:%d, %d",
-                   file->name, file->fd, file->count, file->close);
+    ngx_log_debug5(NGX_LOG_DEBUG_CORE, log, 0,
+                   "close cached open file: %s, fd:%d, c:%d, u:%d, %d",
+                   file->name, file->fd, file->count, file->uses, file->close);
 
     if (!file->close) {
 
         file->accessed = ngx_time();
 
-        if (cache->list_head.next != file) {
+        ngx_queue_remove(&file->queue);
 
-            /* delete from inactive list */
-
-            file->next->prev = file->prev;
-            file->prev->next = file->next;
+        ngx_queue_insert_head(&cache->expire_queue, &file->queue);
 
-            /* add to the inactive list head */
-
-            file->next = cache->list_head.next;
-            file->next->prev = file;
-            file->prev = &cache->list_head;
-            cache->list_head.next = file;
+        if (file->uses >= min_uses || file->count) {
+            return;
         }
-
-        return;
     }
 
-    if (file->event) {
-        (void) ngx_del_event(file->event, NGX_VNODE_EVENT,
-                             file->count ? NGX_FLUSH_EVENT : NGX_CLOSE_EVENT);
-
-        ngx_free(file->event->data);
-        ngx_free(file->event);
-        file->event = NULL;
-    }
+    ngx_open_file_del_event(file);
 
     if (file->count) {
         return;
     }
 
-    if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
-        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
-                      ngx_close_file_n " \"%s\" failed", file->name);
+    if (file->fd != NGX_INVALID_FILE) {
+
+        if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed", file->name);
+        }
+
+        file->fd = NGX_INVALID_FILE;
+    }
+
+    if (!file->close) {
+        return;
     }
 
     ngx_free(file->name);
@@ -611,10 +630,28 @@ ngx_close_cached_file(ngx_open_file_cach
 
 
 static void
+ngx_open_file_del_event(ngx_cached_open_file_t *file)
+{
+    if (file->event == NULL) {
+        return;
+    }
+
+    (void) ngx_del_event(file->event, NGX_VNODE_EVENT,
+                         file->count ? NGX_FLUSH_EVENT : NGX_CLOSE_EVENT);
+
+    ngx_free(file->event->data);
+    ngx_free(file->event);
+    file->event = NULL;
+    file->use_event = 0;
+}
+
+
+static void
 ngx_expire_old_cached_files(ngx_open_file_cache_t *cache, ngx_uint_t n,
     ngx_log_t *log)
 {
     time_t                   now;
+    ngx_queue_t             *q;
     ngx_cached_open_file_t  *file;
 
     now = ngx_time();
@@ -627,18 +664,19 @@ ngx_expire_old_cached_files(ngx_open_fil
 
     while (n < 3) {
 
-        file = cache->list_tail.prev;
-
-        if (file == &cache->list_head) {
+        if (ngx_queue_empty(&cache->expire_queue)) {
             return;
         }
 
+        q = ngx_queue_last(&cache->expire_queue);
+
+        file = ngx_queue_data(q, ngx_cached_open_file_t, queue);
+
         if (n++ != 0 && now - file->accessed <= cache->inactive) {
             return;
         }
 
-        file->next->prev = file->prev;
-        file->prev->next = file->next;
+        ngx_queue_remove(q);
 
         ngx_rbtree_delete(&cache->rbtree, &file->node);
 
@@ -649,7 +687,7 @@ ngx_expire_old_cached_files(ngx_open_fil
 
         if (!file->err && !file->is_dir) {
             file->close = 1;
-            ngx_close_cached_file(cache, file, log);
+            ngx_close_cached_file(cache, file, 0, log);
 
         } else {
             ngx_free(file->name);
@@ -700,6 +738,51 @@ ngx_open_file_cache_rbtree_insert_value(
 }
 
 
+static ngx_cached_open_file_t *
+ngx_open_file_lookup(ngx_open_file_cache_t *cache, ngx_str_t *name,
+    uint32_t hash)
+{
+    ngx_int_t                rc;
+    ngx_rbtree_node_t       *node, *sentinel;
+    ngx_cached_open_file_t  *file;
+
+    node = cache->rbtree.root;
+    sentinel = cache->rbtree.sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        do {
+            file = (ngx_cached_open_file_t *) node;
+
+            rc = ngx_strcmp(name->data, file->name);
+
+            if (rc == 0) {
+                return file;
+            }
+
+            node = (rc < 0) ? node->left : node->right;
+
+        } while (node != sentinel && hash == node->key);
+
+        break;
+    }
+
+    return NULL;
+}
+
+
 static void
 ngx_open_file_cache_remove(ngx_event_t *ev)
 {
@@ -709,8 +792,7 @@ ngx_open_file_cache_remove(ngx_event_t *
     fev = ev->data;
     file = fev->file;
 
-    file->next->prev = file->prev;
-    file->prev->next = file->next;
+    ngx_queue_remove(&file->queue);
 
     ngx_rbtree_delete(&fev->cache->rbtree, &file->node);
 
@@ -721,7 +803,7 @@ ngx_open_file_cache_remove(ngx_event_t *
 
     file->close = 1;
 
-    ngx_close_cached_file(fev->cache, file, ev->log);
+    ngx_close_cached_file(fev->cache, file, 0, ev->log);
 
     /* free memory only when fev->cache and fev->file are already not needed */