btrfs: limit async_work allocation and worker func duration
authorMaxim Patlasov <mpatlasov@virtuozzo.com>
Mon, 12 Dec 2016 22:32:44 +0000 (14:32 -0800)
committerSasha Levin <alexander.levin@verizon.com>
Fri, 13 Jan 2017 01:56:57 +0000 (20:56 -0500)
[ Upstream commit 2939e1a86f758b55cdba73e29397dd3d94df13bc ]

Problem statement: unprivileged user who has read-write access to more than
one btrfs subvolume may easily consume all kernel memory (eventually
triggering oom-killer).

Reproducer (./mkrmdir below essentially loops over mkdir/rmdir):

[root@kteam1 ~]# cat prep.sh

DEV=/dev/sdb
mkfs.btrfs -f $DEV
mount $DEV /mnt
for i in `seq 1 16`
do
mkdir /mnt/$i
btrfs subvolume create /mnt/SV_$i
ID=`btrfs subvolume list /mnt |grep "SV_$i$" |cut -d ' ' -f 2`
mount -t btrfs -o subvolid=$ID $DEV /mnt/$i
chmod a+rwx /mnt/$i
done

[root@kteam1 ~]# sh prep.sh

[maxim@kteam1 ~]$ for i in `seq 1 16`; do ./mkrmdir /mnt/$i 2000 2000 & done

[root@kteam1 ~]# for i in `seq 1 4`; do grep "kmalloc-128" /proc/slabinfo | grep -v dma; sleep 60; done
kmalloc-128        10144  10144    128   32    1 : tunables    0    0    0 : slabdata    317    317      0
kmalloc-128       9992352 9992352    128   32    1 : tunables    0    0    0 : slabdata 312261 312261      0
kmalloc-128       24226752 24226752    128   32    1 : tunables    0    0    0 : slabdata 757086 757086      0
kmalloc-128       42754240 42754240    128   32    1 : tunables    0    0    0 : slabdata 1336070 1336070      0

The huge numbers above come from insane number of async_work-s allocated
and queued by btrfs_wq_run_delayed_node.

The problem is caused by btrfs_wq_run_delayed_node() queuing more and more
works if the number of delayed items is above BTRFS_DELAYED_BACKGROUND. The
worker func (btrfs_async_run_delayed_root) processes at least
BTRFS_DELAYED_BATCH items (if they are present in the list). So, the machinery
works as expected while the list is almost empty. As soon as it is getting
bigger, worker func starts to process more than one item at a time, it takes
longer, and the chances to have async_works queued more than needed is getting
higher.

The problem above is worsened by another flaw of delayed-inode implementation:
if async_work was queued in a throttling branch (number of items >=
BTRFS_DELAYED_WRITEBACK), corresponding worker func won't quit until
the number of items < BTRFS_DELAYED_BACKGROUND / 2. So, it is possible that
the func occupies CPU infinitely (up to 30sec in my experiments): while the
func is trying to drain the list, the user activity may add more and more
items to the list.

The patch fixes both problems in straightforward way: refuse queuing too
many works in btrfs_wq_run_delayed_node and bail out of worker func if
at least BTRFS_DELAYED_WRITEBACK items are processed.

Changed in v2: remove support of thresh == NO_THRESHOLD.

Signed-off-by: Maxim Patlasov <mpatlasov@virtuozzo.com>
Signed-off-by: Chris Mason <clm@fb.com>
Cc: stable@vger.kernel.org # v3.15+
Signed-off-by: Sasha Levin <alexander.levin@verizon.com>
fs/btrfs/async-thread.c
fs/btrfs/async-thread.h
fs/btrfs/delayed-inode.c

index 1848705506ff6ad1ec3f9e8a02e2725fdbed5141..0ce4de6430efa2d0f7068f456d5517c4a9d220a9 100644 (file)
@@ -64,6 +64,20 @@ void btrfs_##name(struct work_struct *arg)                           \
        normal_work_helper(work);                                       \
 }
 
+bool btrfs_workqueue_normal_congested(struct btrfs_workqueue *wq)
+{
+       /*
+        * We could compare wq->normal->pending with num_online_cpus()
+        * to support "thresh == NO_THRESHOLD" case, but it requires
+        * moving up atomic_inc/dec in thresh_queue/exec_hook. Let's
+        * postpone it until someone needs the support of that case.
+        */
+       if (wq->normal->thresh == NO_THRESHOLD)
+               return false;
+
+       return atomic_read(&wq->normal->pending) > wq->normal->thresh * 2;
+}
+
 BTRFS_WORK_HELPER(worker_helper);
 BTRFS_WORK_HELPER(delalloc_helper);
 BTRFS_WORK_HELPER(flush_delalloc_helper);
index ec2ee477f8ba003ee7af4030da1b976891073dcf..8c4564204f25a986fa3a330461f05258e5407b4d 100644 (file)
@@ -78,4 +78,5 @@ void btrfs_queue_work(struct btrfs_workqueue *wq,
 void btrfs_destroy_workqueue(struct btrfs_workqueue *wq);
 void btrfs_workqueue_set_max(struct btrfs_workqueue *wq, int max);
 void btrfs_set_work_high_priority(struct btrfs_work *work);
+bool btrfs_workqueue_normal_congested(struct btrfs_workqueue *wq);
 #endif
index bc2d048a9eb924075dcf71db5592335dfb739caa..8265b0754dca2cd34274107a000986963959db44 100644 (file)
@@ -1371,7 +1371,8 @@ release_path:
        total_done++;
 
        btrfs_release_prepared_delayed_node(delayed_node);
-       if (async_work->nr == 0 || total_done < async_work->nr)
+       if ((async_work->nr == 0 && total_done < BTRFS_DELAYED_WRITEBACK) ||
+           total_done < async_work->nr)
                goto again;
 
 free_path:
@@ -1387,7 +1388,8 @@ static int btrfs_wq_run_delayed_node(struct btrfs_delayed_root *delayed_root,
 {
        struct btrfs_async_delayed_work *async_work;
 
-       if (atomic_read(&delayed_root->items) < BTRFS_DELAYED_BACKGROUND)
+       if (atomic_read(&delayed_root->items) < BTRFS_DELAYED_BACKGROUND ||
+           btrfs_workqueue_normal_congested(fs_info->delayed_workers))
                return 0;
 
        async_work = kmalloc(sizeof(*async_work), GFP_NOFS);