1df6c8
From ddb0038de77a4269fa7eed1bb217bfb6bed1b7ba Mon Sep 17 00:00:00 2001
1df6c8
From: N Balachandran <nbalacha@redhat.com>
1df6c8
Date: Fri, 9 Aug 2019 14:34:22 +0530
1df6c8
Subject: [PATCH 337/344] fuse: Set limit on invalidate queue size
1df6c8
1df6c8
If the glusterfs fuse client process is unable to
1df6c8
process the invalidate requests quickly enough, the
1df6c8
number of such requests quickly grows large enough
1df6c8
to use a significant amount of memory.
1df6c8
We are now introducing another option to set an upper
1df6c8
limit on these to prevent runaway memory usage.
1df6c8
1df6c8
> Upstream https://review.gluster.org/23187
1df6c8
> Change-Id: Iddfff1ee2de1466223e6717f7abd4b28ed947788
1df6c8
> Fixes: bz#1732717
1df6c8
> Signed-off-by: N Balachandran <nbalacha@redhat.com>
1df6c8
1df6c8
BUG: 1763208
1df6c8
Change-Id: I666cdf6c70999a0f0bc79969e8df0a9dde93b6e4
1df6c8
Signed-off-by: Csaba Henk <csaba@redhat.com>
1df6c8
Reviewed-on: https://code.engineering.redhat.com/gerrit/187529
1df6c8
Tested-by: RHGS Build Bot <nigelb@redhat.com>
1df6c8
Reviewed-by: Sunil Kumar Heggodu Gopala Acharya <sheggodu@redhat.com>
1df6c8
---
1df6c8
 doc/mount.glusterfs.8                       |  5 +++
1df6c8
 glusterfsd/src/glusterfsd.c                 | 21 ++++++++++
1df6c8
 glusterfsd/src/glusterfsd.h                 |  3 +-
1df6c8
 libglusterfs/src/glusterfs/glusterfs.h      |  1 +
1df6c8
 libglusterfs/src/glusterfs/inode.h          |  1 +
1df6c8
 libglusterfs/src/inode.c                    | 31 +++++++++++----
1df6c8
 xlators/mount/fuse/src/fuse-bridge.c        | 60 ++++++++++++++++++++++-------
1df6c8
 xlators/mount/fuse/src/fuse-bridge.h        |  3 +-
1df6c8
 xlators/mount/fuse/utils/mount.glusterfs.in |  7 ++++
1df6c8
 9 files changed, 108 insertions(+), 24 deletions(-)
1df6c8
1df6c8
diff --git a/doc/mount.glusterfs.8 b/doc/mount.glusterfs.8
1df6c8
index 286631b..b35b362 100644
1df6c8
--- a/doc/mount.glusterfs.8
1df6c8
+++ b/doc/mount.glusterfs.8
1df6c8
@@ -126,6 +126,11 @@ Provide list of backup volfile servers in the following format [default: None]
1df6c8
 Set fuse module's limit for number of inodes kept in LRU list to N [default: 131072]
1df6c8
 .TP
1df6c8
 .TP
1df6c8
+\fBinvalidate-limit=\fRN
1df6c8
+Suspend fuse invalidations implied by 'lru-limit' if  number of outstanding
1df6c8
+invalidations reaches N
1df6c8
+.TP
1df6c8
+.TP
1df6c8
 \fBbackground-qlen=\fRN
1df6c8
 Set fuse module's background queue length to N [default: 64]
1df6c8
 .TP
1df6c8
diff --git a/glusterfsd/src/glusterfsd.c b/glusterfsd/src/glusterfsd.c
1df6c8
index 5b5e996..0856471 100644
1df6c8
--- a/glusterfsd/src/glusterfsd.c
1df6c8
+++ b/glusterfsd/src/glusterfsd.c
1df6c8
@@ -212,6 +212,9 @@ static struct argp_option gf_options[] = {
1df6c8
     {"lru-limit", ARGP_FUSE_LRU_LIMIT_KEY, "N", 0,
1df6c8
      "Set fuse module's limit for number of inodes kept in LRU list to N "
1df6c8
      "[default: 131072]"},
1df6c8
+    {"invalidate-limit", ARGP_FUSE_INVALIDATE_LIMIT_KEY, "N", 0,
1df6c8
+     "Suspend inode invalidations implied by 'lru-limit' if the number of "
1df6c8
+     "outstanding invalidations reaches N"},
1df6c8
     {"background-qlen", ARGP_FUSE_BACKGROUND_QLEN_KEY, "N", 0,
1df6c8
      "Set fuse module's background queue length to N "
1df6c8
      "[default: 64]"},
1df6c8
@@ -504,6 +507,16 @@ set_fuse_mount_options(glusterfs_ctx_t *ctx, dict_t *options)
1df6c8
         }
1df6c8
     }
1df6c8
 
1df6c8
+    if (cmd_args->invalidate_limit >= 0) {
1df6c8
+        ret = dict_set_int32(options, "invalidate-limit",
1df6c8
+                             cmd_args->invalidate_limit);
1df6c8
+        if (ret < 0) {
1df6c8
+            gf_msg("glusterfsd", GF_LOG_ERROR, 0, glusterfsd_msg_4,
1df6c8
+                   "invalidate-limit");
1df6c8
+            goto err;
1df6c8
+        }
1df6c8
+    }
1df6c8
+
1df6c8
     if (cmd_args->background_qlen) {
1df6c8
         ret = dict_set_int32(options, "background-qlen",
1df6c8
                              cmd_args->background_qlen);
1df6c8
@@ -1283,6 +1296,14 @@ parse_opts(int key, char *arg, struct argp_state *state)
1df6c8
             argp_failure(state, -1, 0, "unknown LRU limit option %s", arg);
1df6c8
             break;
1df6c8
 
1df6c8
+        case ARGP_FUSE_INVALIDATE_LIMIT_KEY:
1df6c8
+            if (!gf_string2int32(arg, &cmd_args->invalidate_limit))
1df6c8
+                break;
1df6c8
+
1df6c8
+            argp_failure(state, -1, 0, "unknown invalidate limit option %s",
1df6c8
+                         arg);
1df6c8
+            break;
1df6c8
+
1df6c8
         case ARGP_FUSE_BACKGROUND_QLEN_KEY:
1df6c8
             if (!gf_string2int(arg, &cmd_args->background_qlen))
1df6c8
                 break;
1df6c8
diff --git a/glusterfsd/src/glusterfsd.h b/glusterfsd/src/glusterfsd.h
1df6c8
index fa55789..ee655f0 100644
1df6c8
--- a/glusterfsd/src/glusterfsd.h
1df6c8
+++ b/glusterfsd/src/glusterfsd.h
1df6c8
@@ -111,7 +111,8 @@ enum argp_option_keys {
1df6c8
     ARGP_FUSE_FLUSH_HANDLE_INTERRUPT_KEY = 189,
1df6c8
     ARGP_FUSE_LRU_LIMIT_KEY = 190,
1df6c8
     ARGP_FUSE_AUTO_INVAL_KEY = 191,
1df6c8
-    ARGP_BRICK_MUX_KEY = 192
1df6c8
+    ARGP_BRICK_MUX_KEY = 192,
1df6c8
+    ARGP_FUSE_INVALIDATE_LIMIT_KEY = 195,
1df6c8
 };
1df6c8
 
1df6c8
 struct _gfd_vol_top_priv {
1df6c8
diff --git a/libglusterfs/src/glusterfs/glusterfs.h b/libglusterfs/src/glusterfs/glusterfs.h
1df6c8
index 79c93ae..3b594c0 100644
1df6c8
--- a/libglusterfs/src/glusterfs/glusterfs.h
1df6c8
+++ b/libglusterfs/src/glusterfs/glusterfs.h
1df6c8
@@ -541,6 +541,7 @@ struct _cmd_args {
1df6c8
     int client_pid_set;
1df6c8
     unsigned uid_map_root;
1df6c8
     int32_t lru_limit;
1df6c8
+    int32_t invalidate_limit;
1df6c8
     int background_qlen;
1df6c8
     int congestion_threshold;
1df6c8
     char *fuse_mountopts;
1df6c8
diff --git a/libglusterfs/src/glusterfs/inode.h b/libglusterfs/src/glusterfs/inode.h
1df6c8
index 52efdd8..4421c47 100644
1df6c8
--- a/libglusterfs/src/glusterfs/inode.h
1df6c8
+++ b/libglusterfs/src/glusterfs/inode.h
1df6c8
@@ -107,6 +107,7 @@ struct _inode {
1df6c8
     struct list_head list;        /* active/lru/purge */
1df6c8
 
1df6c8
     struct _inode_ctx *_ctx; /* replacement for dict_t *(inode->ctx) */
1df6c8
+    bool in_invalidate_list; /* Set if inode is in table invalidate list */
1df6c8
     bool invalidate_sent;    /* Set it if invalidator_fn is called for inode */
1df6c8
 };
1df6c8
 
1df6c8
diff --git a/libglusterfs/src/inode.c b/libglusterfs/src/inode.c
1df6c8
index 96ddea5..5331e93 100644
1df6c8
--- a/libglusterfs/src/inode.c
1df6c8
+++ b/libglusterfs/src/inode.c
1df6c8
@@ -558,8 +558,8 @@ __inode_unref(inode_t *inode, bool clear)
1df6c8
 
1df6c8
     this = THIS;
1df6c8
 
1df6c8
-    if (clear && inode->invalidate_sent) {
1df6c8
-        inode->invalidate_sent = false;
1df6c8
+    if (clear && inode->in_invalidate_list) {
1df6c8
+        inode->in_invalidate_list = false;
1df6c8
         inode->table->invalidate_size--;
1df6c8
         __inode_activate(inode);
1df6c8
     }
1df6c8
@@ -573,7 +573,7 @@ __inode_unref(inode_t *inode, bool clear)
1df6c8
         inode->_ctx[index].ref--;
1df6c8
     }
1df6c8
 
1df6c8
-    if (!inode->ref && !inode->invalidate_sent) {
1df6c8
+    if (!inode->ref && !inode->in_invalidate_list) {
1df6c8
         inode->table->active_size--;
1df6c8
 
1df6c8
         nlookup = GF_ATOMIC_GET(inode->nlookup);
1df6c8
@@ -609,14 +609,14 @@ __inode_ref(inode_t *inode, bool is_invalidate)
1df6c8
         return inode;
1df6c8
 
1df6c8
     if (!inode->ref) {
1df6c8
-        if (inode->invalidate_sent) {
1df6c8
-            inode->invalidate_sent = false;
1df6c8
+        if (inode->in_invalidate_list) {
1df6c8
+            inode->in_invalidate_list = false;
1df6c8
             inode->table->invalidate_size--;
1df6c8
         } else {
1df6c8
             inode->table->lru_size--;
1df6c8
         }
1df6c8
         if (is_invalidate) {
1df6c8
-            inode->invalidate_sent = true;
1df6c8
+            inode->in_invalidate_list = true;
1df6c8
             inode->table->invalidate_size++;
1df6c8
             list_move_tail(&inode->list, &inode->table->invalidate);
1df6c8
         } else {
1df6c8
@@ -1609,6 +1609,7 @@ static int
1df6c8
 inode_table_prune(inode_table_t *table)
1df6c8
 {
1df6c8
     int ret = 0;
1df6c8
+    int ret1 = 0;
1df6c8
     struct list_head purge = {
1df6c8
         0,
1df6c8
     };
1df6c8
@@ -1647,6 +1648,10 @@ inode_table_prune(inode_table_t *table)
1df6c8
                 /* check for valid inode with 'nlookup' */
1df6c8
                 nlookup = GF_ATOMIC_GET(entry->nlookup);
1df6c8
                 if (nlookup) {
1df6c8
+                    if (entry->invalidate_sent) {
1df6c8
+                        list_move_tail(&entry->list, &table->lru);
1df6c8
+                        continue;
1df6c8
+                    }
1df6c8
                     __inode_ref(entry, true);
1df6c8
                     tmp = entry;
1df6c8
                     break;
1df6c8
@@ -1668,9 +1673,19 @@ inode_table_prune(inode_table_t *table)
1df6c8
     if (tmp) {
1df6c8
         xlator_t *old_THIS = THIS;
1df6c8
         THIS = table->invalidator_xl;
1df6c8
-        table->invalidator_fn(table->invalidator_xl, tmp);
1df6c8
+        ret1 = table->invalidator_fn(table->invalidator_xl, tmp);
1df6c8
         THIS = old_THIS;
1df6c8
-        inode_unref(tmp);
1df6c8
+        pthread_mutex_lock(&table->lock);
1df6c8
+        {
1df6c8
+            if (!ret1) {
1df6c8
+                tmp->invalidate_sent = true;
1df6c8
+                __inode_unref(tmp, false);
1df6c8
+            } else {
1df6c8
+                /* Move this back to the lru list*/
1df6c8
+                __inode_unref(tmp, true);
1df6c8
+            }
1df6c8
+        }
1df6c8
+        pthread_mutex_unlock(&table->lock);
1df6c8
     }
1df6c8
 
1df6c8
     /* Just so that if purge list is handled too, then clear it off */
1df6c8
diff --git a/xlators/mount/fuse/src/fuse-bridge.c b/xlators/mount/fuse/src/fuse-bridge.c
1df6c8
index 1c946a2..8b2e7f0 100644
1df6c8
--- a/xlators/mount/fuse/src/fuse-bridge.c
1df6c8
+++ b/xlators/mount/fuse/src/fuse-bridge.c
1df6c8
@@ -26,7 +26,7 @@ static int gf_fuse_xattr_enotsup_log;
1df6c8
 void
1df6c8
 fini(xlator_t *this_xl);
1df6c8
 
1df6c8
-static void
1df6c8
+static int32_t
1df6c8
 fuse_invalidate_inode(xlator_t *this, uint64_t fuse_ino);
1df6c8
 
1df6c8
 /*
1df6c8
@@ -312,7 +312,7 @@ send_fuse_data(xlator_t *this, fuse_in_header_t *finh, void *data, size_t size)
1df6c8
 #define send_fuse_obj(this, finh, obj)                                         \
1df6c8
     send_fuse_data(this, finh, obj, sizeof(*(obj)))
1df6c8
 
1df6c8
-static void
1df6c8
+static int32_t
1df6c8
 fuse_invalidate_entry(xlator_t *this, uint64_t fuse_ino)
1df6c8
 {
1df6c8
 #if FUSE_KERNEL_MINOR_VERSION >= 11
1df6c8
@@ -328,17 +328,22 @@ fuse_invalidate_entry(xlator_t *this, uint64_t fuse_ino)
1df6c8
 
1df6c8
     priv = this->private;
1df6c8
     if (!priv->reverse_fuse_thread_started)
1df6c8
-        return;
1df6c8
+        return -1;
1df6c8
+
1df6c8
+    if (priv->invalidate_limit &&
1df6c8
+        (priv->invalidate_count >= priv->invalidate_limit)) {
1df6c8
+        return -1;
1df6c8
+    }
1df6c8
 
1df6c8
     inode = (inode_t *)(unsigned long)fuse_ino;
1df6c8
     if (inode == NULL)
1df6c8
-        return;
1df6c8
+        return -1;
1df6c8
 
1df6c8
     list_for_each_entry_safe(dentry, tmp, &inode->dentry_list, inode_list)
1df6c8
     {
1df6c8
         node = GF_CALLOC(1, sizeof(*node), gf_fuse_mt_invalidate_node_t);
1df6c8
         if (node == NULL)
1df6c8
-            break;
1df6c8
+            return -1;
1df6c8
 
1df6c8
         INIT_LIST_HEAD(&node->next);
1df6c8
 
1df6c8
@@ -375,20 +380,21 @@ fuse_invalidate_entry(xlator_t *this, uint64_t fuse_ino)
1df6c8
         pthread_mutex_lock(&priv->invalidate_mutex);
1df6c8
         {
1df6c8
             list_add_tail(&node->next, &priv->invalidate_list);
1df6c8
+            priv->invalidate_count++;
1df6c8
             pthread_cond_signal(&priv->invalidate_cond);
1df6c8
         }
1df6c8
         pthread_mutex_unlock(&priv->invalidate_mutex);
1df6c8
     }
1df6c8
 
1df6c8
 #endif
1df6c8
-    return;
1df6c8
+    return 0;
1df6c8
 }
1df6c8
 
1df6c8
 /*
1df6c8
  * Send an inval inode notification to fuse. This causes an invalidation of the
1df6c8
  * entire page cache mapping on the inode.
1df6c8
  */
1df6c8
-static void
1df6c8
+static int32_t
1df6c8
 fuse_invalidate_inode(xlator_t *this, uint64_t fuse_ino)
1df6c8
 {
1df6c8
 #if FUSE_KERNEL_MINOR_VERSION >= 11
1df6c8
@@ -401,15 +407,20 @@ fuse_invalidate_inode(xlator_t *this, uint64_t fuse_ino)
1df6c8
     priv = this->private;
1df6c8
 
1df6c8
     if (!priv->reverse_fuse_thread_started)
1df6c8
-        return;
1df6c8
+        return -1;
1df6c8
+
1df6c8
+    if (priv->invalidate_limit &&
1df6c8
+        (priv->invalidate_count >= priv->invalidate_limit)) {
1df6c8
+        return -1;
1df6c8
+    }
1df6c8
 
1df6c8
     inode = (inode_t *)(unsigned long)fuse_ino;
1df6c8
     if (inode == NULL)
1df6c8
-        return;
1df6c8
+        return -1;
1df6c8
 
1df6c8
     node = GF_CALLOC(1, sizeof(*node), gf_fuse_mt_invalidate_node_t);
1df6c8
     if (node == NULL)
1df6c8
-        return;
1df6c8
+        return -1;
1df6c8
 
1df6c8
     INIT_LIST_HEAD(&node->next);
1df6c8
 
1df6c8
@@ -435,6 +446,7 @@ fuse_invalidate_inode(xlator_t *this, uint64_t fuse_ino)
1df6c8
     pthread_mutex_lock(&priv->invalidate_mutex);
1df6c8
     {
1df6c8
         list_add_tail(&node->next, &priv->invalidate_list);
1df6c8
+        priv->invalidate_count++;
1df6c8
         pthread_cond_signal(&priv->invalidate_cond);
1df6c8
     }
1df6c8
     pthread_mutex_unlock(&priv->invalidate_mutex);
1df6c8
@@ -443,7 +455,7 @@ fuse_invalidate_inode(xlator_t *this, uint64_t fuse_ino)
1df6c8
     gf_log("glusterfs-fuse", GF_LOG_WARNING,
1df6c8
            "fuse_invalidate_inode not implemented on this system");
1df6c8
 #endif
1df6c8
-    return;
1df6c8
+    return 0;
1df6c8
 }
1df6c8
 
1df6c8
 #if FUSE_KERNEL_MINOR_VERSION >= 11
1df6c8
@@ -451,8 +463,9 @@ fuse_invalidate_inode(xlator_t *this, uint64_t fuse_ino)
1df6c8
 static int32_t
1df6c8
 fuse_inode_invalidate_fn(xlator_t *this, inode_t *inode)
1df6c8
 {
1df6c8
-    fuse_invalidate_entry(this, (uint64_t)(uintptr_t)inode);
1df6c8
-    return 0;
1df6c8
+    int32_t ret = 0;
1df6c8
+    ret = fuse_invalidate_entry(this, (uint64_t)(uintptr_t)inode);
1df6c8
+    return ret;
1df6c8
 }
1df6c8
 #endif
1df6c8
 
1df6c8
@@ -4003,7 +4016,9 @@ fuse_setxattr(xlator_t *this, fuse_in_header_t *finh, void *msg,
1df6c8
         gf_log("fuse", GF_LOG_TRACE, "got request to invalidate %" PRIu64,
1df6c8
                finh->nodeid);
1df6c8
 #if FUSE_KERNEL_MINOR_VERSION >= 11
1df6c8
-        fuse_invalidate_entry(this, finh->nodeid);
1df6c8
+        ret = fuse_invalidate_entry(this, finh->nodeid);
1df6c8
+        if (ret)
1df6c8
+            op_errno = EBUSY;
1df6c8
 #endif
1df6c8
         goto done;
1df6c8
     }
1df6c8
@@ -4812,6 +4827,7 @@ notify_kernel_loop(void *data)
1df6c8
                               fuse_invalidate_node_t, next);
1df6c8
 
1df6c8
             list_del_init(&node->next);
1df6c8
+            priv->invalidate_count--;
1df6c8
         }
1df6c8
         pthread_mutex_unlock(&priv->invalidate_mutex);
1df6c8
 
1df6c8
@@ -4855,6 +4871,7 @@ notify_kernel_loop(void *data)
1df6c8
             list_del_init(&node->next);
1df6c8
             GF_FREE(node);
1df6c8
         }
1df6c8
+        priv->invalidate_count = 0;
1df6c8
     }
1df6c8
     pthread_mutex_unlock(&priv->invalidate_mutex);
1df6c8
 
1df6c8
@@ -6080,6 +6097,9 @@ fuse_priv_dump(xlator_t *this)
1df6c8
                        (int)private->timed_response_fuse_thread_started);
1df6c8
     gf_proc_dump_write("reverse_thread_started", "%d",
1df6c8
                        (int)private->reverse_fuse_thread_started);
1df6c8
+    gf_proc_dump_write("invalidate_limit", "%u", private->invalidate_limit);
1df6c8
+    gf_proc_dump_write("invalidate_queue_length", "%" PRIu64,
1df6c8
+                       private->invalidate_count);
1df6c8
     gf_proc_dump_write("use_readdirp", "%d", private->use_readdirp);
1df6c8
 
1df6c8
     return 0;
1df6c8
@@ -6619,6 +6639,9 @@ init(xlator_t *this_xl)
1df6c8
 
1df6c8
     GF_OPTION_INIT("lru-limit", priv->lru_limit, uint32, cleanup_exit);
1df6c8
 
1df6c8
+    GF_OPTION_INIT("invalidate-limit", priv->invalidate_limit, uint32,
1df6c8
+                   cleanup_exit);
1df6c8
+
1df6c8
     GF_OPTION_INIT("event-history", priv->event_history, bool, cleanup_exit);
1df6c8
 
1df6c8
     GF_OPTION_INIT("thin-client", priv->thin_client, bool, cleanup_exit);
1df6c8
@@ -6955,6 +6978,15 @@ struct volume_options options[] = {
1df6c8
                        "reaching this limit (0 means 'unlimited')",
1df6c8
     },
1df6c8
     {
1df6c8
+        .key = {"invalidate-limit"},
1df6c8
+        .type = GF_OPTION_TYPE_INT,
1df6c8
+        .default_value = "0",
1df6c8
+        .min = 0,
1df6c8
+        .description = "suspend invalidations as of 'lru-limit' if the number "
1df6c8
+                       "of outstanding invalidations reaches this limit "
1df6c8
+                       "(0 means 'unlimited')",
1df6c8
+    },
1df6c8
+    {
1df6c8
         .key = {"auto-invalidation"},
1df6c8
         .type = GF_OPTION_TYPE_BOOL,
1df6c8
         .default_value = "true",
1df6c8
diff --git a/xlators/mount/fuse/src/fuse-bridge.h b/xlators/mount/fuse/src/fuse-bridge.h
1df6c8
index 697bd88..2311582 100644
1df6c8
--- a/xlators/mount/fuse/src/fuse-bridge.h
1df6c8
+++ b/xlators/mount/fuse/src/fuse-bridge.h
1df6c8
@@ -139,7 +139,7 @@ struct fuse_private {
1df6c8
     pthread_cond_t invalidate_cond;
1df6c8
     pthread_mutex_t invalidate_mutex;
1df6c8
     gf_boolean_t reverse_fuse_thread_started;
1df6c8
-
1df6c8
+    uint64_t invalidate_count;
1df6c8
     /* For communicating with separate mount thread. */
1df6c8
     int status_pipe[2];
1df6c8
 
1df6c8
@@ -191,6 +191,7 @@ struct fuse_private {
1df6c8
 
1df6c8
     /* LRU Limit, if not set, default is 128k for now */
1df6c8
     uint32_t lru_limit;
1df6c8
+    uint32_t invalidate_limit;
1df6c8
 };
1df6c8
 typedef struct fuse_private fuse_private_t;
1df6c8
 
1df6c8
diff --git a/xlators/mount/fuse/utils/mount.glusterfs.in b/xlators/mount/fuse/utils/mount.glusterfs.in
1df6c8
index cbde42d..61d7422 100755
1df6c8
--- a/xlators/mount/fuse/utils/mount.glusterfs.in
1df6c8
+++ b/xlators/mount/fuse/utils/mount.glusterfs.in
1df6c8
@@ -257,6 +257,10 @@ start_glusterfs ()
1df6c8
         cmd_line=$(echo "$cmd_line --lru-limit=$lru_limit");
1df6c8
     fi
1df6c8
 
1df6c8
+    if [ -n "$invalidate_limit" ]; then
1df6c8
+        cmd_line=$(echo "$cmd_line --invalidate-limit=$invalidate_limit");
1df6c8
+    fi
1df6c8
+
1df6c8
     if [ -n "$bg_qlen" ]; then
1df6c8
         cmd_line=$(echo "$cmd_line --background-qlen=$bg_qlen");
1df6c8
     fi
1df6c8
@@ -505,6 +509,9 @@ with_options()
1df6c8
         "lru-limit")
1df6c8
             lru_limit=$value
1df6c8
             ;;
1df6c8
+        "invalidate-limit")
1df6c8
+            invalidate_limit=$value
1df6c8
+            ;;
1df6c8
         "background-qlen")
1df6c8
             bg_qlen=$value
1df6c8
             ;;
1df6c8
-- 
1df6c8
1.8.3.1
1df6c8