bdi: Shutdown writeback on all cgwbs in cgwb_bdi_destroy()
[sfrench/cifs-2.6.git] / mm / backing-dev.c
index c6f2a37028c205db8143ebe58677c790c66a0faf..b67be4fc12c429d4fa5b8a0905dee84ccc973564 100644 (file)
@@ -294,6 +294,8 @@ static int wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi,
 
        memset(wb, 0, sizeof(*wb));
 
+       if (wb != &bdi->wb)
+               bdi_get(bdi);
        wb->bdi = bdi;
        wb->last_old_flush = jiffies;
        INIT_LIST_HEAD(&wb->b_dirty);
@@ -314,8 +316,10 @@ static int wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi,
        wb->dirty_sleep = jiffies;
 
        wb->congested = wb_congested_get_create(bdi, blkcg_id, gfp);
-       if (!wb->congested)
-               return -ENOMEM;
+       if (!wb->congested) {
+               err = -ENOMEM;
+               goto out_put_bdi;
+       }
 
        err = fprop_local_init_percpu(&wb->completions, gfp);
        if (err)
@@ -335,9 +339,14 @@ out_destroy_stat:
        fprop_local_destroy_percpu(&wb->completions);
 out_put_cong:
        wb_congested_put(wb->congested);
+out_put_bdi:
+       if (wb != &bdi->wb)
+               bdi_put(bdi);
        return err;
 }
 
+static void cgwb_remove_from_bdi_list(struct bdi_writeback *wb);
+
 /*
  * Remove bdi from the global list and shutdown any threads we have running
  */
@@ -347,10 +356,18 @@ static void wb_shutdown(struct bdi_writeback *wb)
        spin_lock_bh(&wb->work_lock);
        if (!test_and_clear_bit(WB_registered, &wb->state)) {
                spin_unlock_bh(&wb->work_lock);
+               /*
+                * Wait for wb shutdown to finish if someone else is just
+                * running wb_shutdown(). Otherwise we could proceed to wb /
+                * bdi destruction before wb_shutdown() is finished.
+                */
+               wait_on_bit(&wb->state, WB_shutting_down, TASK_UNINTERRUPTIBLE);
                return;
        }
+       set_bit(WB_shutting_down, &wb->state);
        spin_unlock_bh(&wb->work_lock);
 
+       cgwb_remove_from_bdi_list(wb);
        /*
         * Drain work list and shutdown the delayed_work.  !WB_registered
         * tells wb_workfn() that @wb is dying and its work_list needs to
@@ -359,6 +376,12 @@ static void wb_shutdown(struct bdi_writeback *wb)
        mod_delayed_work(bdi_wq, &wb->dwork, 0);
        flush_delayed_work(&wb->dwork);
        WARN_ON(!list_empty(&wb->work_list));
+       /*
+        * Make sure bit gets cleared after shutdown is finished. Matches with
+        * the barrier provided by test_and_clear_bit() above.
+        */
+       smp_wmb();
+       clear_bit(WB_shutting_down, &wb->state);
 }
 
 static void wb_exit(struct bdi_writeback *wb)
@@ -372,6 +395,8 @@ static void wb_exit(struct bdi_writeback *wb)
 
        fprop_local_destroy_percpu(&wb->completions);
        wb_congested_put(wb->congested);
+       if (wb != &wb->bdi->wb)
+               bdi_put(wb->bdi);
 }
 
 #ifdef CONFIG_CGROUP_WRITEBACK
@@ -438,7 +463,7 @@ retry:
                return NULL;
 
        atomic_set(&new_congested->refcnt, 0);
-       new_congested->bdi = bdi;
+       new_congested->__bdi = bdi;
        new_congested->blkcg_id = blkcg_id;
        goto retry;
 
@@ -466,10 +491,10 @@ void wb_congested_put(struct bdi_writeback_congested *congested)
        }
 
        /* bdi might already have been destroyed leaving @congested unlinked */
-       if (congested->bdi) {
+       if (congested->__bdi) {
                rb_erase(&congested->rb_node,
-                        &congested->bdi->cgwb_congested_tree);
-               congested->bdi = NULL;
+                        &congested->__bdi->cgwb_congested_tree);
+               congested->__bdi = NULL;
        }
 
        spin_unlock_irqrestore(&cgwb_lock, flags);
@@ -482,10 +507,6 @@ static void cgwb_release_workfn(struct work_struct *work)
                                                release_work);
        struct backing_dev_info *bdi = wb->bdi;
 
-       spin_lock_irq(&cgwb_lock);
-       list_del_rcu(&wb->bdi_node);
-       spin_unlock_irq(&cgwb_lock);
-
        wb_shutdown(wb);
 
        css_put(wb->memcg_css);
@@ -517,6 +538,13 @@ static void cgwb_kill(struct bdi_writeback *wb)
        percpu_ref_kill(&wb->refcnt);
 }
 
+static void cgwb_remove_from_bdi_list(struct bdi_writeback *wb)
+{
+       spin_lock_irq(&cgwb_lock);
+       list_del_rcu(&wb->bdi_node);
+       spin_unlock_irq(&cgwb_lock);
+}
+
 static int cgwb_create(struct backing_dev_info *bdi,
                       struct cgroup_subsys_state *memcg_css, gfp_t gfp)
 {
@@ -684,12 +712,21 @@ static void cgwb_bdi_destroy(struct backing_dev_info *bdi)
 {
        struct radix_tree_iter iter;
        void **slot;
+       struct bdi_writeback *wb;
 
        WARN_ON(test_bit(WB_registered, &bdi->wb.state));
 
        spin_lock_irq(&cgwb_lock);
        radix_tree_for_each_slot(slot, &bdi->cgwb_tree, &iter, 0)
                cgwb_kill(*slot);
+
+       while (!list_empty(&bdi->wb_list)) {
+               wb = list_first_entry(&bdi->wb_list, struct bdi_writeback,
+                                     bdi_node);
+               spin_unlock_irq(&cgwb_lock);
+               wb_shutdown(wb);
+               spin_lock_irq(&cgwb_lock);
+       }
        spin_unlock_irq(&cgwb_lock);
 
        /*
@@ -752,11 +789,18 @@ static void cgwb_bdi_exit(struct backing_dev_info *bdi)
                        rb_entry(rbn, struct bdi_writeback_congested, rb_node);
 
                rb_erase(rbn, &bdi->cgwb_congested_tree);
-               congested->bdi = NULL;  /* mark @congested unlinked */
+               congested->__bdi = NULL;        /* mark @congested unlinked */
        }
        spin_unlock_irq(&cgwb_lock);
 }
 
+static void cgwb_bdi_register(struct backing_dev_info *bdi)
+{
+       spin_lock_irq(&cgwb_lock);
+       list_add_tail_rcu(&bdi->wb.bdi_node, &bdi->wb_list);
+       spin_unlock_irq(&cgwb_lock);
+}
+
 #else  /* CONFIG_CGROUP_WRITEBACK */
 
 static int cgwb_bdi_init(struct backing_dev_info *bdi)
@@ -784,6 +828,16 @@ static void cgwb_bdi_exit(struct backing_dev_info *bdi)
        wb_congested_put(bdi->wb_congested);
 }
 
+static void cgwb_bdi_register(struct backing_dev_info *bdi)
+{
+       list_add_tail_rcu(&bdi->wb.bdi_node, &bdi->wb_list);
+}
+
+static void cgwb_remove_from_bdi_list(struct bdi_writeback *wb)
+{
+       list_del_rcu(&wb->bdi_node);
+}
+
 #endif /* CONFIG_CGROUP_WRITEBACK */
 
 int bdi_init(struct backing_dev_info *bdi)
@@ -802,8 +856,6 @@ int bdi_init(struct backing_dev_info *bdi)
 
        ret = cgwb_bdi_init(bdi);
 
-       list_add_tail_rcu(&bdi->wb.bdi_node, &bdi->wb_list);
-
        return ret;
 }
 EXPORT_SYMBOL(bdi_init);
@@ -839,6 +891,7 @@ int bdi_register(struct backing_dev_info *bdi, struct device *parent,
        if (IS_ERR(dev))
                return PTR_ERR(dev);
 
+       cgwb_bdi_register(bdi);
        bdi->dev = dev;
 
        bdi_debug_register(bdi, dev_name(dev));