[PATCH] xfs: free the EFI entries from AIL on forced shutdown
Mark Tinguely
tinguely at sgi.com
Thu Mar 20 14:47:45 CDT 2014
On 03/17/14 00:29, Dave Chinner wrote:
> On Fri, Mar 14, 2014 at 11:37:01AM -0500, Mark Tinguely wrote:
>> The extent free intention (EFI) and extent free done (EFD)
>> log items are in separate transactions. It is possible that
>> the EFI can be pushed to the AIL before a forced shutdown
>> where it gets stuck for following reasons:
>>
>> No EFD. If freeing the extent fails in xfs_bmap_finish() or
>> xfs_recover_process_efi(), then the corresponding extent
>> free done (EFD) entry will not be created.
>
> You don't handle the xfs_bmap_finish() case properly - the patch
> only addresses the case where the EFD was created and contains a
> reference to the EFI. The only time we can have an EFI in the AIL
> without an EFD pointing to it is if xfs_trans_reserve() call in
> xfs_bmap_finish() fails due to a shutdown. That failure case
> leaks the EFI reference that is supposed to be passed to the EFD...
>
>> EFD IOP Abort processing. If xfs_trans_cancel() is called with
>> an abort flag, or if the xfs_trans_commit() is called when the
>> file system is in forced shutdown or if the log buffer write fails,
>> then the EFD iop commands will not remove the EFI from the AIL.
>
> Which they should, because after xfs_trans_get_efd(), the EFD owns
> the reference to the EFI. Hence aborting the EFD should release the
> EFI reference it owns.
Nod.
>
>> Index: b/fs/xfs/xfs_extfree_item.c
>> ===================================================================
>> --- a/fs/xfs/xfs_extfree_item.c
>> +++ b/fs/xfs/xfs_extfree_item.c
>> @@ -420,8 +420,15 @@ STATIC void
>> xfs_efd_item_unlock(
>> struct xfs_log_item *lip)
>> {
>> - if (lip->li_flags& XFS_LI_ABORTED)
>> + /*
>> + * Clear the EFI if on AIL when aborting xfs_bmap_finish.
>> + * The forced shutdown will force the log, so other EFDs
>> + * should not be processed from the CIL.
>> + */
>> + if (lip->li_flags& XFS_LI_ABORTED) {
>> + xfs_efi_clear_ail(lip->li_ailp);
>> xfs_efd_item_free(EFD_ITEM(lip));
>> + }
>> }
>
> So, we abort one EFI, so we kill every EFI in the filesystem? We
> want to be able to cancel and rollback transactions eventually, and
> this will prevent us from being able to do that as it will kill EFIs
> unrelated to the EFD being aborted.
>
> AFAICT, all this needs to do is:
>
> if (!(lip->li_flags& XFS_LI_ABORTED))
> return;
>
> xfs_efi_release(efdp->efd_efip, efdp->efd_format.efd_nextents);
> xfs_efd_item_free(efdp);
>
> i.e. before freeing the EFD, release the reference to the EFI
> that it was passed.
Nod.
>> @@ -439,10 +446,15 @@ xfs_efd_item_committed(
>> struct xfs_efd_log_item *efdp = EFD_ITEM(lip);
>>
>> /*
>> - * If we got a log I/O error, it's always the case that the LR with the
>> - * EFI got unpinned and freed before the EFD got aborted.
>> + * If we got a log I/O error and the EFI is also in this buffer, it
>> + * will be unpinned and freed before the EFD got aborted. But the EFI
>> + * is in an earlier transaction and could be on the AIL when the log
>> + * I/O error happened for this EFD. In that case, manually remove the
>> + * remaining EFIs from the AIL.
>> */
>> - if (!(lip->li_flags& XFS_LI_ABORTED))
>> + if (lip->li_flags& XFS_LI_ABORTED)
>> + xfs_efi_clear_ail(lip->li_ailp);
>> + else
>> xfs_efi_release(efdp->efd_efip, efdp->efd_format.efd_nextents);
>
> Same here - we can simply always call xfs_efi_release() and we'll
> end up doing the right thing w.r.t. the EFi reference count.
>
> And if we do the right thing with the EFI reference count when
> aborting the EFI (i.e. call __xfs_efi_release() rather than freeing
> it) the aborting of transactions will correctly free all the
> references to the EFI and remove them from the AIL.
This is fine if the EFI made it to the AIL before the log abort - which
it may or may not have done.
Log errors will not force the log. The call to
xfs_trans_committed_bulk() with the abort flag set will not place the
EFI on the AIL (takes the iop_unpin/continue path).
Your proposal of changing xfs_efi_committed() from freeing the EFI to a
call to __xfs_efi_release() and changing xfs_efd_committed() to
xfs_efi_release() will do the right thing for the counters and removing
both the EFI/EFD entries, but it will also want to remove the
non-existent EFI entry from the AIL. I do not like adding the EFI to the
AIL in the abort path.
> The only case we
> then have to handle specially is the xfs_trans_reserve failure in
> xfs_bmap_finish(), where we need to release the EFI directly in the
> error path.
any reason to not move the efd creation earlier to not special case it?
> That gets rid of the "big hammer" error handling for normal runtime
> shutdown that xfs_efi_clear_ail(). i.e. we need to fix the reference
> count handling, not work around it.
>
>
>> Index: b/fs/xfs/xfs_log_recover.c
>> ===================================================================
>> --- a/fs/xfs/xfs_log_recover.c
>> +++ b/fs/xfs/xfs_log_recover.c
>> @@ -3634,20 +3634,23 @@ xlog_recover_process_data(
>> /*
>> * Process an extent free intent item that was recovered from
>> * the log. We need to free the extents that it describes.
>> + * The caller will free all EFI entries on error.
>> */
>> STATIC int
>> xlog_recover_process_efi(
>> - xfs_mount_t *mp,
>> - xfs_efi_log_item_t *efip)
>> + struct xfs_mount *mp,
>> + struct xfs_efi_log_item *efip)
>> {
>> - xfs_efd_log_item_t *efdp;
>> - xfs_trans_t *tp;
>> + struct xfs_efd_log_item *efdp;
>> + struct xfs_trans *tp;
>> int i;
>> int error = 0;
>> xfs_extent_t *extp;
>> xfs_fsblock_t startblock_fsb;
>>
>> ASSERT(!test_bit(XFS_EFI_RECOVERED,&efip->efi_flags));
>> + /* All paths need the XFS_EFI_RECOVERED flag set. Do it here. */
>> + set_bit(XFS_EFI_RECOVERED,&efip->efi_flags);
>>
>> /*
>> * First check the validity of the extents described by the
>> @@ -3662,12 +3665,6 @@ xlog_recover_process_efi(
>> (extp->ext_len == 0) ||
>> (startblock_fsb>= mp->m_sb.sb_dblocks) ||
>> (extp->ext_len>= mp->m_sb.sb_agblocks)) {
>> - /*
>> - * This will pull the EFI from the AIL and
>> - * free the memory associated with it.
>> - */
>> - set_bit(XFS_EFI_RECOVERED,&efip->efi_flags);
>> - xfs_efi_release(efip, efip->efi_format.efi_nextents);
>> return XFS_ERROR(EIO);
>> }
>> }
>> @@ -3687,7 +3684,6 @@ xlog_recover_process_efi(
>> extp->ext_len);
>> }
>>
>> - set_bit(XFS_EFI_RECOVERED,&efip->efi_flags);
>> error = xfs_trans_commit(tp, 0);
>> return error;
>
> This is basically saying "xfs_efi_release() should drop both the AIL
> and the EFD reference as we really only only have one reference". I
> think we should simply remove the XFS_EFI_RECOVERED bit from the
> reference counting code, and simply subtract one of the EFI
> references in xlog_recover_efi_pass2() where we insert the EFI into
> the AIL, so that it behaves exactly like the runtime case when
> later processing the EFDs.
>
> That is, in xlog_recover_efd_pass2() when we find a matching EFD we
> simply call xfs_efi_release() and that does all the freeing because
> we're removing the EFD reference which is the only remaining
> reference.
>
> Then in xlog_recover_process_efi(), we simply call xfs_efi_release()
> for the error cases to drop the last reference to the EFI, otherwise
> the commit of the EFD will free it because it calls
> xfs_efi_release() appropriately.
Works for me.
>
>>
>> @@ -3718,8 +3714,8 @@ STATIC int
>> xlog_recover_process_efis(
>> struct xlog *log)
>> {
>> - xfs_log_item_t *lip;
>> - xfs_efi_log_item_t *efip;
>> + struct xfs_log_item *lip;
>> + struct xfs_efi_log_item *efip;
>> int error = 0;
>> struct xfs_ail_cursor cur;
>> struct xfs_ail *ailp;
>> @@ -3753,12 +3749,13 @@ xlog_recover_process_efis(
>> error = xlog_recover_process_efi(log->l_mp, efip);
>> spin_lock(&ailp->xa_lock);
>> if (error)
>> - goto out;
>> + break;
>> lip = xfs_trans_ail_cursor_next(ailp,&cur);
>> }
>> -out:
>> xfs_trans_ail_cursor_done(ailp,&cur);
>> spin_unlock(&ailp->xa_lock);
>> + if (error)
>> + xfs_efi_clear_ail(ailp);
>> return error;
>> }
>
> If we fix the reference counting, then I think all we need here is:
>
> int error = 0;
>
> while (ail cursor next) {
> ....
> if (!error)
> error = xlog_recover_process_efi()
> else
> xfs_efi_release(efip)
> ....
> }
>
> So that any error will trigger freeing of all the other unprocessed
> EFIs on the AIL that are pending recovery without needing any more
> special looping....
>
Nod.
--Mark.
More information about the xfs
mailing list