From: Greg Kroah-Hartman Date: Mon, 16 Jan 2012 20:25:40 +0000 (-0800) Subject: 3.2-stable patches X-Git-Tag: v3.1.10~8 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4da4fefdb1c718996dbf5ddeebf0eacd235bd49e;p=thirdparty%2Fkernel%2Fstable-queue.git 3.2-stable patches added patches: dcache-use-a-dispose-list-in-select_parent.patch fix-shrink_dcache_parent-livelock.patch --- diff --git a/queue-3.2/dcache-use-a-dispose-list-in-select_parent.patch b/queue-3.2/dcache-use-a-dispose-list-in-select_parent.patch new file mode 100644 index 00000000000..61694186db1 --- /dev/null +++ b/queue-3.2/dcache-use-a-dispose-list-in-select_parent.patch @@ -0,0 +1,170 @@ +From b48f03b319ba78f3abf9a7044d1f436d8d90f4f9 Mon Sep 17 00:00:00 2001 +From: Dave Chinner +Date: Tue, 23 Aug 2011 18:56:24 +1000 +Subject: dcache: use a dispose list in select_parent + +From: Dave Chinner + +commit b48f03b319ba78f3abf9a7044d1f436d8d90f4f9 upstream. + +select_parent currently abuses the dentry cache LRU to provide +cleanup features for child dentries that need to be freed. It moves +them to the tail of the LRU, then tells shrink_dcache_parent() to +calls __shrink_dcache_sb to unconditionally move them to a dispose +list (as DCACHE_REFERENCED is ignored). __shrink_dcache_sb() has to +relock the dentries to move them off the LRU onto the dispose list, +but otherwise does not touch the dentries that select_parent() moved +to the tail of the LRU. It then passses the dispose list to +shrink_dentry_list() which tries to free the dentries. + +IOWs, the use of __shrink_dcache_sb() is superfluous - we can build +exactly the same list of dentries for disposal directly in +select_parent() and call shrink_dentry_list() instead of calling +__shrink_dcache_sb() to do that. This means that we avoid long holds +on the lru lock walking the LRU moving dentries to the dispose list +We also avoid the need to relock each dentry just to move it off the +LRU, reducing the numebr of times we lock each dentry to dispose of +them in shrink_dcache_parent() from 3 to 2 times. + +Further, we remove one of the two callers of __shrink_dcache_sb(). +This also means that __shrink_dcache_sb can be moved into back into +prune_dcache_sb() and we no longer have to handle referenced +dentries conditionally, simplifying the code. + +Signed-off-by: Dave Chinner +Signed-off-by: Linus Torvalds +Signed-off-by: Al Viro +Signed-off-by: Greg Kroah-Hartman + +--- + fs/dcache.c | 63 ++++++++++++++++++++---------------------------------------- + 1 file changed, 21 insertions(+), 42 deletions(-) + +--- a/fs/dcache.c ++++ b/fs/dcache.c +@@ -275,15 +275,15 @@ static void dentry_lru_prune(struct dent + } + } + +-static void dentry_lru_move_tail(struct dentry *dentry) ++static void dentry_lru_move_list(struct dentry *dentry, struct list_head *list) + { + spin_lock(&dcache_lru_lock); + if (list_empty(&dentry->d_lru)) { +- list_add_tail(&dentry->d_lru, &dentry->d_sb->s_dentry_lru); ++ list_add_tail(&dentry->d_lru, list); + dentry->d_sb->s_nr_dentry_unused++; + dentry_stat.nr_unused++; + } else { +- list_move_tail(&dentry->d_lru, &dentry->d_sb->s_dentry_lru); ++ list_move_tail(&dentry->d_lru, list); + } + spin_unlock(&dcache_lru_lock); + } +@@ -769,14 +769,18 @@ static void shrink_dentry_list(struct li + } + + /** +- * __shrink_dcache_sb - shrink the dentry LRU on a given superblock +- * @sb: superblock to shrink dentry LRU. +- * @count: number of entries to prune +- * @flags: flags to control the dentry processing ++ * prune_dcache_sb - shrink the dcache ++ * @sb: superblock ++ * @count: number of entries to try to free ++ * ++ * Attempt to shrink the superblock dcache LRU by @count entries. This is ++ * done when we need more memory an called from the superblock shrinker ++ * function. + * +- * If flags contains DCACHE_REFERENCED reference dentries will not be pruned. ++ * This function may fail to free any resources if all the dentries are in ++ * use. + */ +-static void __shrink_dcache_sb(struct super_block *sb, int count, int flags) ++void prune_dcache_sb(struct super_block *sb, int count) + { + struct dentry *dentry; + LIST_HEAD(referenced); +@@ -795,13 +799,7 @@ relock: + goto relock; + } + +- /* +- * If we are honouring the DCACHE_REFERENCED flag and the +- * dentry has this flag set, don't free it. Clear the flag +- * and put it back on the LRU. +- */ +- if (flags & DCACHE_REFERENCED && +- dentry->d_flags & DCACHE_REFERENCED) { ++ if (dentry->d_flags & DCACHE_REFERENCED) { + dentry->d_flags &= ~DCACHE_REFERENCED; + list_move(&dentry->d_lru, &referenced); + spin_unlock(&dentry->d_lock); +@@ -821,23 +819,6 @@ relock: + } + + /** +- * prune_dcache_sb - shrink the dcache +- * @sb: superblock +- * @nr_to_scan: number of entries to try to free +- * +- * Attempt to shrink the superblock dcache LRU by @nr_to_scan entries. This is +- * done when we need more memory an called from the superblock shrinker +- * function. +- * +- * This function may fail to free any resources if all the dentries are in +- * use. +- */ +-void prune_dcache_sb(struct super_block *sb, int nr_to_scan) +-{ +- __shrink_dcache_sb(sb, nr_to_scan, DCACHE_REFERENCED); +-} +- +-/** + * shrink_dcache_sb - shrink dcache for a superblock + * @sb: superblock + * +@@ -1091,7 +1072,7 @@ EXPORT_SYMBOL(have_submounts); + * drop the lock and return early due to latency + * constraints. + */ +-static int select_parent(struct dentry * parent) ++static int select_parent(struct dentry *parent, struct list_head *dispose) + { + struct dentry *this_parent; + struct list_head *next; +@@ -1113,12 +1094,11 @@ resume: + + spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); + +- /* +- * move only zero ref count dentries to the end +- * of the unused list for prune_dcache ++ /* ++ * move only zero ref count dentries to the dispose list. + */ + if (!dentry->d_count) { +- dentry_lru_move_tail(dentry); ++ dentry_lru_move_list(dentry, dispose); + found++; + } else { + dentry_lru_del(dentry); +@@ -1180,14 +1160,13 @@ rename_retry: + * + * Prune the dcache to remove unused children of the parent dentry. + */ +- + void shrink_dcache_parent(struct dentry * parent) + { +- struct super_block *sb = parent->d_sb; ++ LIST_HEAD(dispose); + int found; + +- while ((found = select_parent(parent)) != 0) +- __shrink_dcache_sb(sb, found, 0); ++ while ((found = select_parent(parent, &dispose)) != 0) ++ shrink_dentry_list(&dispose); + } + EXPORT_SYMBOL(shrink_dcache_parent); + diff --git a/queue-3.2/fix-shrink_dcache_parent-livelock.patch b/queue-3.2/fix-shrink_dcache_parent-livelock.patch new file mode 100644 index 00000000000..36de8507a68 --- /dev/null +++ b/queue-3.2/fix-shrink_dcache_parent-livelock.patch @@ -0,0 +1,126 @@ +From eaf5f9073533cde21c7121c136f1c3f072d9cf59 Mon Sep 17 00:00:00 2001 +From: Miklos Szeredi +Date: Tue, 10 Jan 2012 18:22:25 +0100 +Subject: fix shrink_dcache_parent() livelock + +From: Miklos Szeredi + +commit eaf5f9073533cde21c7121c136f1c3f072d9cf59 upstream. + +Two (or more) concurrent calls of shrink_dcache_parent() on the same dentry may +cause shrink_dcache_parent() to loop forever. + +Here's what appears to happen: + +1 - CPU0: select_parent(P) finds C and puts it on dispose list, returns 1 + +2 - CPU1: select_parent(P) locks P->d_lock + +3 - CPU0: shrink_dentry_list() locks C->d_lock + dentry_kill(C) tries to lock P->d_lock but fails, unlocks C->d_lock + +4 - CPU1: select_parent(P) locks C->d_lock, + moves C from dispose list being processed on CPU0 to the new +dispose list, returns 1 + +5 - CPU0: shrink_dentry_list() finds dispose list empty, returns + +6 - Goto 2 with CPU0 and CPU1 switched + +Basically select_parent() steals the dentry from shrink_dentry_list() and thinks +it found a new one, causing shrink_dentry_list() to think it's making progress +and loop over and over. + +One way to trigger this is to make udev calls stat() on the sysfs file while it +is going away. + +Having a file in /lib/udev/rules.d/ with only this one rule seems to the trick: + +ATTR{vendor}=="0x8086", ATTR{device}=="0x10ca", ENV{PCI_SLOT_NAME}="%k", ENV{MATCHADDR}="$attr{address}", RUN+="/bin/true" + +Then execute the following loop: + +while true; do + echo -bond0 > /sys/class/net/bonding_masters + echo +bond0 > /sys/class/net/bonding_masters + echo -bond1 > /sys/class/net/bonding_masters + echo +bond1 > /sys/class/net/bonding_masters +done + +One fix would be to check all callers and prevent concurrent calls to +shrink_dcache_parent(). But I think a better solution is to stop the +stealing behavior. + +This patch adds a new dentry flag that is set when the dentry is added to the +dispose list. The flag is cleared in dentry_lru_del() in case the dentry gets a +new reference just before being pruned. + +If the dentry has this flag, select_parent() will skip it and let +shrink_dentry_list() retry pruning it. With select_parent() skipping those +dentries there will not be the appearance of progress (new dentries found) when +there is none, hence shrink_dcache_parent() will not loop forever. + +Set the flag is also set in prune_dcache_sb() for consistency as suggested by +Linus. + +Signed-off-by: Miklos Szeredi +Signed-off-by: Al Viro +Signed-off-by: Greg Kroah-Hartman + +--- + fs/dcache.c | 15 +++++++++++---- + include/linux/dcache.h | 1 + + 2 files changed, 12 insertions(+), 4 deletions(-) + +--- a/fs/dcache.c ++++ b/fs/dcache.c +@@ -242,6 +242,7 @@ static void dentry_lru_add(struct dentry + static void __dentry_lru_del(struct dentry *dentry) + { + list_del_init(&dentry->d_lru); ++ dentry->d_flags &= ~DCACHE_SHRINK_LIST; + dentry->d_sb->s_nr_dentry_unused--; + dentry_stat.nr_unused--; + } +@@ -805,6 +806,7 @@ relock: + spin_unlock(&dentry->d_lock); + } else { + list_move_tail(&dentry->d_lru, &tmp); ++ dentry->d_flags |= DCACHE_SHRINK_LIST; + spin_unlock(&dentry->d_lock); + if (!--count) + break; +@@ -1096,14 +1098,19 @@ resume: + + /* + * move only zero ref count dentries to the dispose list. ++ * ++ * Those which are presently on the shrink list, being processed ++ * by shrink_dentry_list(), shouldn't be moved. Otherwise the ++ * loop in shrink_dcache_parent() might not make any progress ++ * and loop forever. + */ +- if (!dentry->d_count) { ++ if (dentry->d_count) { ++ dentry_lru_del(dentry); ++ } else if (!(dentry->d_flags & DCACHE_SHRINK_LIST)) { + dentry_lru_move_list(dentry, dispose); ++ dentry->d_flags |= DCACHE_SHRINK_LIST; + found++; +- } else { +- dentry_lru_del(dentry); + } +- + /* + * We can return to the caller if we have found some (this + * ensures forward progress). We'll be coming back to find +--- a/include/linux/dcache.h ++++ b/include/linux/dcache.h +@@ -203,6 +203,7 @@ struct dentry_operations { + + #define DCACHE_CANT_MOUNT 0x0100 + #define DCACHE_GENOCIDE 0x0200 ++#define DCACHE_SHRINK_LIST 0x0400 + + #define DCACHE_NFSFS_RENAMED 0x1000 + /* this dentry has been "silly renamed" and has to be deleted on the last diff --git a/queue-3.2/series b/queue-3.2/series index f42e94c8037..0ff14513788 100644 --- a/queue-3.2/series +++ b/queue-3.2/series @@ -72,3 +72,5 @@ fsnotify-don-t-bug-in-fsnotify_destroy_mark.patch x86-uv-update-boot-messages-for-sgi-uv2-platform.patch recordmcount-fix-handling-of-elf64-big-endian-objects.patch uvcvideo-fix-integer-overflow-in-uvc_ioctl_ctrl_map.patch +dcache-use-a-dispose-list-in-select_parent.patch +fix-shrink_dcache_parent-livelock.patch