]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/btrfs/transaction.c
Merge branches 'irq-core-for-linus' and 'core-locking-for-linus' of git://git.kernel...
[karo-tx-linux.git] / fs / btrfs / transaction.c
index 325d9a5f0128d90573fb41c799765dc6c1c22d61..1fffbc017bdfe0451cc2e67ccf5fd85623a12aee 100644 (file)
@@ -279,6 +279,58 @@ static noinline int wait_for_commit(struct btrfs_root *root,
        return 0;
 }
 
+int btrfs_wait_for_commit(struct btrfs_root *root, u64 transid)
+{
+       struct btrfs_transaction *cur_trans = NULL, *t;
+       int ret;
+
+       mutex_lock(&root->fs_info->trans_mutex);
+
+       ret = 0;
+       if (transid) {
+               if (transid <= root->fs_info->last_trans_committed)
+                       goto out_unlock;
+
+               /* find specified transaction */
+               list_for_each_entry(t, &root->fs_info->trans_list, list) {
+                       if (t->transid == transid) {
+                               cur_trans = t;
+                               break;
+                       }
+                       if (t->transid > transid)
+                               break;
+               }
+               ret = -EINVAL;
+               if (!cur_trans)
+                       goto out_unlock;  /* bad transid */
+       } else {
+               /* find newest transaction that is committing | committed */
+               list_for_each_entry_reverse(t, &root->fs_info->trans_list,
+                                           list) {
+                       if (t->in_commit) {
+                               if (t->commit_done)
+                                       goto out_unlock;
+                               cur_trans = t;
+                               break;
+                       }
+               }
+               if (!cur_trans)
+                       goto out_unlock;  /* nothing committing|committed */
+       }
+
+       cur_trans->use_count++;
+       mutex_unlock(&root->fs_info->trans_mutex);
+
+       wait_for_commit(root, cur_trans);
+
+       mutex_lock(&root->fs_info->trans_mutex);
+       put_transaction(cur_trans);
+       ret = 0;
+out_unlock:
+       mutex_unlock(&root->fs_info->trans_mutex);
+       return ret;
+}
+
 #if 0
 /*
  * rate limit against the drop_snapshot code.  This helps to slow down new
@@ -402,6 +454,7 @@ static int __btrfs_end_transaction(struct btrfs_trans_handle *trans,
        WARN_ON(cur_trans->num_writers < 1);
        cur_trans->num_writers--;
 
+       smp_mb();
        if (waitqueue_active(&cur_trans->writer_wait))
                wake_up(&cur_trans->writer_wait);
        put_transaction(cur_trans);
@@ -1006,11 +1059,127 @@ int btrfs_transaction_blocked(struct btrfs_fs_info *info)
        return ret;
 }
 
+/*
+ * wait for the current transaction commit to start and block subsequent
+ * transaction joins
+ */
+static void wait_current_trans_commit_start(struct btrfs_root *root,
+                                           struct btrfs_transaction *trans)
+{
+       DEFINE_WAIT(wait);
+
+       if (trans->in_commit)
+               return;
+
+       while (1) {
+               prepare_to_wait(&root->fs_info->transaction_blocked_wait, &wait,
+                               TASK_UNINTERRUPTIBLE);
+               if (trans->in_commit) {
+                       finish_wait(&root->fs_info->transaction_blocked_wait,
+                                   &wait);
+                       break;
+               }
+               mutex_unlock(&root->fs_info->trans_mutex);
+               schedule();
+               mutex_lock(&root->fs_info->trans_mutex);
+               finish_wait(&root->fs_info->transaction_blocked_wait, &wait);
+       }
+}
+
+/*
+ * wait for the current transaction to start and then become unblocked.
+ * caller holds ref.
+ */
+static void wait_current_trans_commit_start_and_unblock(struct btrfs_root *root,
+                                        struct btrfs_transaction *trans)
+{
+       DEFINE_WAIT(wait);
+
+       if (trans->commit_done || (trans->in_commit && !trans->blocked))
+               return;
+
+       while (1) {
+               prepare_to_wait(&root->fs_info->transaction_wait, &wait,
+                               TASK_UNINTERRUPTIBLE);
+               if (trans->commit_done ||
+                   (trans->in_commit && !trans->blocked)) {
+                       finish_wait(&root->fs_info->transaction_wait,
+                                   &wait);
+                       break;
+               }
+               mutex_unlock(&root->fs_info->trans_mutex);
+               schedule();
+               mutex_lock(&root->fs_info->trans_mutex);
+               finish_wait(&root->fs_info->transaction_wait,
+                           &wait);
+       }
+}
+
+/*
+ * commit transactions asynchronously. once btrfs_commit_transaction_async
+ * returns, any subsequent transaction will not be allowed to join.
+ */
+struct btrfs_async_commit {
+       struct btrfs_trans_handle *newtrans;
+       struct btrfs_root *root;
+       struct delayed_work work;
+};
+
+static void do_async_commit(struct work_struct *work)
+{
+       struct btrfs_async_commit *ac =
+               container_of(work, struct btrfs_async_commit, work.work);
+
+       btrfs_commit_transaction(ac->newtrans, ac->root);
+       kfree(ac);
+}
+
+int btrfs_commit_transaction_async(struct btrfs_trans_handle *trans,
+                                  struct btrfs_root *root,
+                                  int wait_for_unblock)
+{
+       struct btrfs_async_commit *ac;
+       struct btrfs_transaction *cur_trans;
+
+       ac = kmalloc(sizeof(*ac), GFP_NOFS);
+       BUG_ON(!ac);
+
+       INIT_DELAYED_WORK(&ac->work, do_async_commit);
+       ac->root = root;
+       ac->newtrans = btrfs_join_transaction(root, 0);
+
+       /* take transaction reference */
+       mutex_lock(&root->fs_info->trans_mutex);
+       cur_trans = trans->transaction;
+       cur_trans->use_count++;
+       mutex_unlock(&root->fs_info->trans_mutex);
+
+       btrfs_end_transaction(trans, root);
+       schedule_delayed_work(&ac->work, 0);
+
+       /* wait for transaction to start and unblock */
+       mutex_lock(&root->fs_info->trans_mutex);
+       if (wait_for_unblock)
+               wait_current_trans_commit_start_and_unblock(root, cur_trans);
+       else
+               wait_current_trans_commit_start(root, cur_trans);
+       put_transaction(cur_trans);
+       mutex_unlock(&root->fs_info->trans_mutex);
+
+       return 0;
+}
+
+/*
+ * btrfs_transaction state sequence:
+ *    in_commit = 0, blocked = 0  (initial)
+ *    in_commit = 1, blocked = 1
+ *    blocked = 0
+ *    commit_done = 1
+ */
 int btrfs_commit_transaction(struct btrfs_trans_handle *trans,
                             struct btrfs_root *root)
 {
        unsigned long joined = 0;
-       unsigned long timeout = 1;
        struct btrfs_transaction *cur_trans;
        struct btrfs_transaction *prev_trans = NULL;
        DEFINE_WAIT(wait);
@@ -1057,6 +1226,8 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans,
 
        trans->transaction->in_commit = 1;
        trans->transaction->blocked = 1;
+       wake_up(&root->fs_info->transaction_blocked_wait);
+
        if (cur_trans->list.prev != &root->fs_info->trans_list) {
                prev_trans = list_entry(cur_trans->list.prev,
                                        struct btrfs_transaction, list);
@@ -1081,11 +1252,6 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans,
                        snap_pending = 1;
 
                WARN_ON(cur_trans != trans->transaction);
-               if (cur_trans->num_writers > 1)
-                       timeout = MAX_SCHEDULE_TIMEOUT;
-               else if (should_grow)
-                       timeout = 1;
-
                mutex_unlock(&root->fs_info->trans_mutex);
 
                if (flush_on_commit || snap_pending) {
@@ -1107,8 +1273,10 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans,
                                TASK_UNINTERRUPTIBLE);
 
                smp_mb();
-               if (cur_trans->num_writers > 1 || should_grow)
-                       schedule_timeout(timeout);
+               if (cur_trans->num_writers > 1)
+                       schedule_timeout(MAX_SCHEDULE_TIMEOUT);
+               else if (should_grow)
+                       schedule_timeout(1);
 
                mutex_lock(&root->fs_info->trans_mutex);
                finish_wait(&cur_trans->writer_wait, &wait);