writeback, cgroup: release dying cgwbs by switching attached inodes
[sfrench/cifs-2.6.git] / mm / backing-dev.c
index 54c5dc4b8c24eb99d7f7405f2fb50ca60800bfe9..271f2ca862c82ca1e95b4e22598cae7e17d8c45e 100644 (file)
@@ -371,12 +371,16 @@ static void wb_exit(struct bdi_writeback *wb)
 #include <linux/memcontrol.h>
 
 /*
- * cgwb_lock protects bdi->cgwb_tree, blkcg->cgwb_list, and memcg->cgwb_list.
- * bdi->cgwb_tree is also RCU protected.
+ * cgwb_lock protects bdi->cgwb_tree, blkcg->cgwb_list, offline_cgwbs and
+ * memcg->cgwb_list.  bdi->cgwb_tree is also RCU protected.
  */
 static DEFINE_SPINLOCK(cgwb_lock);
 static struct workqueue_struct *cgwb_release_wq;
 
+static LIST_HEAD(offline_cgwbs);
+static void cleanup_offline_cgwbs_workfn(struct work_struct *work);
+static DECLARE_WORK(cleanup_offline_cgwbs_work, cleanup_offline_cgwbs_workfn);
+
 static void cgwb_release_workfn(struct work_struct *work)
 {
        struct bdi_writeback *wb = container_of(work, struct bdi_writeback,
@@ -395,6 +399,11 @@ static void cgwb_release_workfn(struct work_struct *work)
 
        fprop_local_destroy_percpu(&wb->memcg_completions);
        percpu_ref_exit(&wb->refcnt);
+
+       spin_lock_irq(&cgwb_lock);
+       list_del(&wb->offline_node);
+       spin_unlock_irq(&cgwb_lock);
+
        wb_exit(wb);
        WARN_ON_ONCE(!list_empty(&wb->b_attached));
        kfree_rcu(wb, rcu);
@@ -414,6 +423,7 @@ static void cgwb_kill(struct bdi_writeback *wb)
        WARN_ON(!radix_tree_delete(&wb->bdi->cgwb_tree, wb->memcg_css->id));
        list_del(&wb->memcg_node);
        list_del(&wb->blkcg_node);
+       list_add(&wb->offline_node, &offline_cgwbs);
        percpu_ref_kill(&wb->refcnt);
 }
 
@@ -635,6 +645,54 @@ static void cgwb_bdi_unregister(struct backing_dev_info *bdi)
        mutex_unlock(&bdi->cgwb_release_mutex);
 }
 
+/*
+ * cleanup_offline_cgwbs_workfn - try to release dying cgwbs
+ *
+ * Try to release dying cgwbs by switching attached inodes to the nearest
+ * living ancestor's writeback. Processed wbs are placed at the end
+ * of the list to guarantee the forward progress.
+ */
+static void cleanup_offline_cgwbs_workfn(struct work_struct *work)
+{
+       struct bdi_writeback *wb;
+       LIST_HEAD(processed);
+
+       spin_lock_irq(&cgwb_lock);
+
+       while (!list_empty(&offline_cgwbs)) {
+               wb = list_first_entry(&offline_cgwbs, struct bdi_writeback,
+                                     offline_node);
+               list_move(&wb->offline_node, &processed);
+
+               /*
+                * If wb is dirty, cleaning up the writeback by switching
+                * attached inodes will result in an effective removal of any
+                * bandwidth restrictions, which isn't the goal.  Instead,
+                * it can be postponed until the next time, when all io
+                * will be likely completed.  If in the meantime some inodes
+                * will get re-dirtied, they should be eventually switched to
+                * a new cgwb.
+                */
+               if (wb_has_dirty_io(wb))
+                       continue;
+
+               if (!wb_tryget(wb))
+                       continue;
+
+               spin_unlock_irq(&cgwb_lock);
+               while (cleanup_offline_cgwb(wb))
+                       cond_resched();
+               spin_lock_irq(&cgwb_lock);
+
+               wb_put(wb);
+       }
+
+       if (!list_empty(&processed))
+               list_splice_tail(&processed, &offline_cgwbs);
+
+       spin_unlock_irq(&cgwb_lock);
+}
+
 /**
  * wb_memcg_offline - kill all wb's associated with a memcg being offlined
  * @memcg: memcg being offlined
@@ -651,6 +709,8 @@ void wb_memcg_offline(struct mem_cgroup *memcg)
                cgwb_kill(wb);
        memcg_cgwb_list->next = NULL;   /* prevent new wb's */
        spin_unlock_irq(&cgwb_lock);
+
+       queue_work(system_unbound_wq, &cleanup_offline_cgwbs_work);
 }
 
 /**