aboutsummaryrefslogtreecommitdiffstats
path: root/fs/xfs
diff options
context:
space:
mode:
authorBrian Foster <bfoster@redhat.com>2018-09-28 23:45:26 -0400
committerDave Chinner <david@fromorbit.com>2018-09-28 23:45:26 -0400
commit95808459b110f16b50f03a70ecfa72bb14bd8a96 (patch)
tree10f08200523405f5b534af39552d3061a348e118 /fs/xfs
parent23420d05e67d23728e116321c4afe084ebfa6427 (diff)
xfs: refactor xfs_buf_log_item reference count handling
The xfs_buf_log_item structure has a reference counter with slightly tricky semantics. In the common case, a buffer is logged and committed in a transaction, committed to the on-disk log (added to the AIL) and then finally written back and removed from the AIL. The bli refcount covers two potentially overlapping timeframes: 1. the bli is held in an active transaction 2. the bli is pinned by the log The caveat to this approach is that the reference counter does not purely dictate the lifetime of the bli. IOW, when a dirty buffer is physically logged and unpinned, the bli refcount may go to zero as the log item is inserted into the AIL. Only once the buffer is written back can the bli finally be freed. The above semantics means that it is not enough for the various refcount decrementing contexts to release the bli on decrement to zero. xfs_trans_brelse(), transaction commit (->iop_unlock()) and unpin (->iop_unpin()) must all drop the associated reference and make additional checks to determine if the current context is responsible for freeing the item. For example, if a transaction holds but does not dirty a particular bli, the commit may drop the refcount to zero. If the bli itself is clean, it is also not AIL resident and must be freed at this time. The same is true for xfs_trans_brelse(). If the transaction dirties a bli and then aborts or an unpin results in an abort due to a log I/O error, the last reference count holder is expected to explicitly remove the item from the AIL and release it (since an abort means filesystem shutdown and metadata writeback will never occur). This leads to fairly complex checks being replicated in a few different places. Since ->iop_unlock() and xfs_trans_brelse() are nearly identical, refactor the logic into a common helper that implements and documents the semantics in one place. This patch does not change behavior. Signed-off-by: Brian Foster <bfoster@redhat.com> Reviewed-by: Dave Chinner <dchinner@redhat.com> Signed-off-by: Dave Chinner <david@fromorbit.com>
Diffstat (limited to 'fs/xfs')
-rw-r--r--fs/xfs/xfs_buf_item.c90
-rw-r--r--fs/xfs/xfs_buf_item.h1
-rw-r--r--fs/xfs/xfs_trans_buf.c23
3 files changed, 56 insertions, 58 deletions
diff --git a/fs/xfs/xfs_buf_item.c b/fs/xfs/xfs_buf_item.c
index 42fce70b474d..12d8455bfbb2 100644
--- a/fs/xfs/xfs_buf_item.c
+++ b/fs/xfs/xfs_buf_item.c
@@ -532,6 +532,49 @@ xfs_buf_item_push(
532} 532}
533 533
534/* 534/*
535 * Drop the buffer log item refcount and take appropriate action. This helper
536 * determines whether the bli must be freed or not, since a decrement to zero
537 * does not necessarily mean the bli is unused.
538 *
539 * Return true if the bli is freed, false otherwise.
540 */
541bool
542xfs_buf_item_put(
543 struct xfs_buf_log_item *bip)
544{
545 struct xfs_log_item *lip = &bip->bli_item;
546 bool aborted;
547 bool dirty;
548
549 /* drop the bli ref and return if it wasn't the last one */
550 if (!atomic_dec_and_test(&bip->bli_refcount))
551 return false;
552
553 /*
554 * We dropped the last ref and must free the item if clean or aborted.
555 * If the bli is dirty and non-aborted, the buffer was clean in the
556 * transaction but still awaiting writeback from previous changes. In
557 * that case, the bli is freed on buffer writeback completion.
558 */
559 aborted = test_bit(XFS_LI_ABORTED, &lip->li_flags) ||
560 XFS_FORCED_SHUTDOWN(lip->li_mountp);
561 dirty = bip->bli_flags & XFS_BLI_DIRTY;
562 if (dirty && !aborted)
563 return false;
564
565 /*
566 * The bli is aborted or clean. An aborted item may be in the AIL
567 * regardless of dirty state. For example, consider an aborted
568 * transaction that invalidated a dirty bli and cleared the dirty
569 * state.
570 */
571 if (aborted)
572 xfs_trans_ail_remove(lip, SHUTDOWN_LOG_IO_ERROR);
573 xfs_buf_item_relse(bip->bli_buf);
574 return true;
575}
576
577/*
535 * Release the buffer associated with the buf log item. If there is no dirty 578 * Release the buffer associated with the buf log item. If there is no dirty
536 * logged data associated with the buffer recorded in the buf log item, then 579 * logged data associated with the buffer recorded in the buf log item, then
537 * free the buf log item and remove the reference to it in the buffer. 580 * free the buf log item and remove the reference to it in the buffer.
@@ -556,13 +599,12 @@ xfs_buf_item_unlock(
556{ 599{
557 struct xfs_buf_log_item *bip = BUF_ITEM(lip); 600 struct xfs_buf_log_item *bip = BUF_ITEM(lip);
558 struct xfs_buf *bp = bip->bli_buf; 601 struct xfs_buf *bp = bip->bli_buf;
559 bool freed; 602 bool released;
560 bool aborted;
561 bool hold = bip->bli_flags & XFS_BLI_HOLD; 603 bool hold = bip->bli_flags & XFS_BLI_HOLD;
562 bool dirty = bip->bli_flags & XFS_BLI_DIRTY;
563 bool stale = bip->bli_flags & XFS_BLI_STALE; 604 bool stale = bip->bli_flags & XFS_BLI_STALE;
564#if defined(DEBUG) || defined(XFS_WARN) 605#if defined(DEBUG) || defined(XFS_WARN)
565 bool ordered = bip->bli_flags & XFS_BLI_ORDERED; 606 bool ordered = bip->bli_flags & XFS_BLI_ORDERED;
607 bool dirty = bip->bli_flags & XFS_BLI_DIRTY;
566#endif 608#endif
567 609
568 trace_xfs_buf_item_unlock(bip); 610 trace_xfs_buf_item_unlock(bip);
@@ -575,8 +617,6 @@ xfs_buf_item_unlock(
575 (ordered && dirty && !xfs_buf_item_dirty_format(bip))); 617 (ordered && dirty && !xfs_buf_item_dirty_format(bip)));
576 ASSERT(!stale || (bip->__bli_format.blf_flags & XFS_BLF_CANCEL)); 618 ASSERT(!stale || (bip->__bli_format.blf_flags & XFS_BLF_CANCEL));
577 619
578 aborted = test_bit(XFS_LI_ABORTED, &lip->li_flags);
579
580 /* 620 /*
581 * Clear the buffer's association with this transaction and 621 * Clear the buffer's association with this transaction and
582 * per-transaction state from the bli, which has been copied above. 622 * per-transaction state from the bli, which has been copied above.
@@ -585,40 +625,16 @@ xfs_buf_item_unlock(
585 bip->bli_flags &= ~(XFS_BLI_LOGGED | XFS_BLI_HOLD | XFS_BLI_ORDERED); 625 bip->bli_flags &= ~(XFS_BLI_LOGGED | XFS_BLI_HOLD | XFS_BLI_ORDERED);
586 626
587 /* 627 /*
588 * Drop the transaction's bli reference and deal with the item if we had 628 * Unref the item and unlock the buffer unless held or stale. Stale
589 * the last one. We must free the item if clean or aborted since it 629 * buffers remain locked until final unpin unless the bli is freed by
590 * wasn't pinned by the log and this is the last chance to do so. If the 630 * the unref call. The latter implies shutdown because buffer
591 * bli is freed and dirty (but non-aborted), the buffer was not dirty in 631 * invalidation dirties the bli and transaction.
592 * this transaction but modified by a previous one and still awaiting
593 * writeback. In that case, the bli is freed on buffer writeback
594 * completion.
595 */ 632 */
596 freed = atomic_dec_and_test(&bip->bli_refcount); 633 released = xfs_buf_item_put(bip);
597 if (freed) { 634 if (hold || (stale && !released))
598 ASSERT(!aborted || XFS_FORCED_SHUTDOWN(lip->li_mountp));
599 /*
600 * An aborted item may be in the AIL regardless of dirty state.
601 * For example, consider an aborted transaction that invalidated
602 * a dirty bli and cleared the dirty state.
603 */
604 if (aborted)
605 xfs_trans_ail_remove(lip, SHUTDOWN_LOG_IO_ERROR);
606 if (aborted || !dirty)
607 xfs_buf_item_relse(bp);
608 } else if (stale) {
609 /*
610 * Stale buffers remain locked until final unpin unless the bli
611 * was freed in the branch above. A freed stale bli implies an
612 * abort because buffer invalidation dirties the bli and
613 * transaction.
614 */
615 ASSERT(!freed);
616 return; 635 return;
617 } 636 ASSERT(!stale || test_bit(XFS_LI_ABORTED, &lip->li_flags));
618 ASSERT(!stale || (aborted && freed)); 637 xfs_buf_relse(bp);
619
620 if (!hold)
621 xfs_buf_relse(bp);
622} 638}
623 639
624/* 640/*
diff --git a/fs/xfs/xfs_buf_item.h b/fs/xfs/xfs_buf_item.h
index 3f7d7b72e7e6..90f65f891fab 100644
--- a/fs/xfs/xfs_buf_item.h
+++ b/fs/xfs/xfs_buf_item.h
@@ -51,6 +51,7 @@ struct xfs_buf_log_item {
51 51
52int xfs_buf_item_init(struct xfs_buf *, struct xfs_mount *); 52int xfs_buf_item_init(struct xfs_buf *, struct xfs_mount *);
53void xfs_buf_item_relse(struct xfs_buf *); 53void xfs_buf_item_relse(struct xfs_buf *);
54bool xfs_buf_item_put(struct xfs_buf_log_item *);
54void xfs_buf_item_log(struct xfs_buf_log_item *, uint, uint); 55void xfs_buf_item_log(struct xfs_buf_log_item *, uint, uint);
55bool xfs_buf_item_dirty_format(struct xfs_buf_log_item *); 56bool xfs_buf_item_dirty_format(struct xfs_buf_log_item *);
56void xfs_buf_attach_iodone(struct xfs_buf *, 57void xfs_buf_attach_iodone(struct xfs_buf *,
diff --git a/fs/xfs/xfs_trans_buf.c b/fs/xfs/xfs_trans_buf.c
index 7498f87ceed3..286a287ac57a 100644
--- a/fs/xfs/xfs_trans_buf.c
+++ b/fs/xfs/xfs_trans_buf.c
@@ -339,8 +339,6 @@ xfs_trans_brelse(
339 struct xfs_buf *bp) 339 struct xfs_buf *bp)
340{ 340{
341 struct xfs_buf_log_item *bip = bp->b_log_item; 341 struct xfs_buf_log_item *bip = bp->b_log_item;
342 bool freed;
343 bool dirty;
344 342
345 ASSERT(bp->b_transp == tp); 343 ASSERT(bp->b_transp == tp);
346 344
@@ -379,25 +377,8 @@ xfs_trans_brelse(
379 xfs_trans_del_item(&bip->bli_item); 377 xfs_trans_del_item(&bip->bli_item);
380 bip->bli_flags &= ~XFS_BLI_HOLD; 378 bip->bli_flags &= ~XFS_BLI_HOLD;
381 379
382 /* 380 /* drop the reference to the bli */
383 * Drop the reference to the bli. At this point, the bli must be either 381 xfs_buf_item_put(bip);
384 * freed or dirty (or both). If freed, there are a couple cases where we
385 * are responsible to free the item. If the bli is clean, we're the last
386 * user of it. If the fs has shut down, the bli may be dirty and AIL
387 * resident, but won't ever be written back. We therefore may also need
388 * to remove it from the AIL before freeing it.
389 */
390 freed = atomic_dec_and_test(&bip->bli_refcount);
391 dirty = bip->bli_flags & XFS_BLI_DIRTY;
392 ASSERT(freed || dirty);
393 if (freed) {
394 bool abort = XFS_FORCED_SHUTDOWN(tp->t_mountp);
395 ASSERT(abort || !test_bit(XFS_LI_IN_AIL, &bip->bli_item.li_flags));
396 if (abort)
397 xfs_trans_ail_remove(&bip->bli_item, SHUTDOWN_LOG_IO_ERROR);
398 if (!dirty || abort)
399 xfs_buf_item_relse(bp);
400 }
401 382
402 bp->b_transp = NULL; 383 bp->b_transp = NULL;
403 xfs_buf_relse(bp); 384 xfs_buf_relse(bp);