|
@@ -81,6 +81,10 @@
|
|
|
* - For each work item attached to the log intent item,
|
|
|
* * Perform the described action.
|
|
|
* * Attach the work item to the log done item.
|
|
|
+ * * If the result of doing the work was -EAGAIN, ->finish work
|
|
|
+ * wants a new transaction. See the "Requesting a Fresh
|
|
|
+ * Transaction while Finishing Deferred Work" section below for
|
|
|
+ * details.
|
|
|
*
|
|
|
* The key here is that we must log an intent item for all pending
|
|
|
* work items every time we roll the transaction, and that we must log
|
|
@@ -88,6 +92,34 @@
|
|
|
* we can perform complex remapping operations, chaining intent items
|
|
|
* as needed.
|
|
|
*
|
|
|
+ * Requesting a Fresh Transaction while Finishing Deferred Work
|
|
|
+ *
|
|
|
+ * If ->finish_item decides that it needs a fresh transaction to
|
|
|
+ * finish the work, it must ask its caller (xfs_defer_finish) for a
|
|
|
+ * continuation. The most likely cause of this circumstance are the
|
|
|
+ * refcount adjust functions deciding that they've logged enough items
|
|
|
+ * to be at risk of exceeding the transaction reservation.
|
|
|
+ *
|
|
|
+ * To get a fresh transaction, we want to log the existing log done
|
|
|
+ * item to prevent the log intent item from replaying, immediately log
|
|
|
+ * a new log intent item with the unfinished work items, roll the
|
|
|
+ * transaction, and re-call ->finish_item wherever it left off. The
|
|
|
+ * log done item and the new log intent item must be in the same
|
|
|
+ * transaction or atomicity cannot be guaranteed; defer_finish ensures
|
|
|
+ * that this happens.
|
|
|
+ *
|
|
|
+ * This requires some coordination between ->finish_item and
|
|
|
+ * defer_finish. Upon deciding to request a new transaction,
|
|
|
+ * ->finish_item should update the current work item to reflect the
|
|
|
+ * unfinished work. Next, it should reset the log done item's list
|
|
|
+ * count to the number of items finished, and return -EAGAIN.
|
|
|
+ * defer_finish sees the -EAGAIN, logs the new log intent item
|
|
|
+ * with the remaining work items, and leaves the xfs_defer_pending
|
|
|
+ * item at the head of the dop_work queue. Then it rolls the
|
|
|
+ * transaction and picks up processing where it left off. It is
|
|
|
+ * required that ->finish_item must be careful to leave enough
|
|
|
+ * transaction reservation to fit the new log intent item.
|
|
|
+ *
|
|
|
* This is an example of remapping the extent (E, E+B) into file X at
|
|
|
* offset A and dealing with the extent (C, C+B) already being mapped
|
|
|
* there:
|
|
@@ -104,21 +136,26 @@
|
|
|
* | Intent to add rmap (X, E, A, B) |
|
|
|
* +-------------------------------------------------+
|
|
|
* | Reduce refcount for extent (C, B) | t2
|
|
|
- * | Done reducing refcount for extent (C, B) |
|
|
|
+ * | Done reducing refcount for extent (C, 9) |
|
|
|
+ * | Intent to reduce refcount for extent (C+9, B-9) |
|
|
|
+ * | (ran out of space after 9 refcount updates) |
|
|
|
+ * +-------------------------------------------------+
|
|
|
+ * | Reduce refcount for extent (C+9, B+9) | t3
|
|
|
+ * | Done reducing refcount for extent (C+9, B-9) |
|
|
|
* | Increase refcount for extent (E, B) |
|
|
|
* | Done increasing refcount for extent (E, B) |
|
|
|
* | Intent to free extent (C, B) |
|
|
|
* | Intent to free extent (F, 1) (refcountbt block) |
|
|
|
* | Intent to remove rmap (F, 1, REFC) |
|
|
|
* +-------------------------------------------------+
|
|
|
- * | Remove rmap (X, C, A, B) | t3
|
|
|
+ * | Remove rmap (X, C, A, B) | t4
|
|
|
* | Done removing rmap (X, C, A, B) |
|
|
|
* | Add rmap (X, E, A, B) |
|
|
|
* | Done adding rmap (X, E, A, B) |
|
|
|
* | Remove rmap (F, 1, REFC) |
|
|
|
* | Done removing rmap (F, 1, REFC) |
|
|
|
* +-------------------------------------------------+
|
|
|
- * | Free extent (C, B) | t4
|
|
|
+ * | Free extent (C, B) | t5
|
|
|
* | Done freeing extent (C, B) |
|
|
|
* | Free extent (D, 1) |
|
|
|
* | Done freeing extent (D, 1) |
|
|
@@ -141,6 +178,9 @@
|
|
|
* - Intent to free extent (C, B)
|
|
|
* - Intent to free extent (F, 1) (refcountbt block)
|
|
|
* - Intent to remove rmap (F, 1, REFC)
|
|
|
+ *
|
|
|
+ * Note that the continuation requested between t2 and t3 is likely to
|
|
|
+ * reoccur.
|
|
|
*/
|
|
|
|
|
|
static const struct xfs_defer_op_type *defer_op_types[XFS_DEFER_OPS_TYPE_MAX];
|
|
@@ -323,7 +363,16 @@ xfs_defer_finish(
|
|
|
dfp->dfp_count--;
|
|
|
error = dfp->dfp_type->finish_item(*tp, dop, li,
|
|
|
dfp->dfp_done, &state);
|
|
|
- if (error) {
|
|
|
+ if (error == -EAGAIN) {
|
|
|
+ /*
|
|
|
+ * Caller wants a fresh transaction;
|
|
|
+ * put the work item back on the list
|
|
|
+ * and jump out.
|
|
|
+ */
|
|
|
+ list_add(li, &dfp->dfp_work);
|
|
|
+ dfp->dfp_count++;
|
|
|
+ break;
|
|
|
+ } else if (error) {
|
|
|
/*
|
|
|
* Clean up after ourselves and jump out.
|
|
|
* xfs_defer_cancel will take care of freeing
|
|
@@ -335,9 +384,25 @@ xfs_defer_finish(
|
|
|
goto out;
|
|
|
}
|
|
|
}
|
|
|
- /* Done with the dfp, free it. */
|
|
|
- list_del(&dfp->dfp_list);
|
|
|
- kmem_free(dfp);
|
|
|
+ if (error == -EAGAIN) {
|
|
|
+ /*
|
|
|
+ * Caller wants a fresh transaction, so log a
|
|
|
+ * new log intent item to replace the old one
|
|
|
+ * and roll the transaction. See "Requesting
|
|
|
+ * a Fresh Transaction while Finishing
|
|
|
+ * Deferred Work" above.
|
|
|
+ */
|
|
|
+ dfp->dfp_intent = dfp->dfp_type->create_intent(*tp,
|
|
|
+ dfp->dfp_count);
|
|
|
+ dfp->dfp_done = NULL;
|
|
|
+ list_for_each(li, &dfp->dfp_work)
|
|
|
+ dfp->dfp_type->log_item(*tp, dfp->dfp_intent,
|
|
|
+ li);
|
|
|
+ } else {
|
|
|
+ /* Done with the dfp, free it. */
|
|
|
+ list_del(&dfp->dfp_list);
|
|
|
+ kmem_free(dfp);
|
|
|
+ }
|
|
|
|
|
|
if (cleanup_fn)
|
|
|
cleanup_fn(*tp, state, error);
|