[3/3] xfs: log EFIs for all btree blocks being used to stage a btree
diff mbox series

Message ID 157063980635.2914891.10711621853635545427.stgit@magnolia
State New
Headers show
Series
  • xfs: prepare repair for bulk loading
Related show

Commit Message

Darrick J. Wong Oct. 9, 2019, 4:50 p.m. UTC
From: Darrick J. Wong <darrick.wong@oracle.com>

We need to log EFIs for every extent that we allocate for the purpose of
staging a new btree so that if we fail then the blocks will be freed
during log recovery.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
 fs/xfs/scrub/repair.c     |   37 +++++++++++++++++++++++++++++++++++--
 fs/xfs/scrub/repair.h     |    4 +++-
 fs/xfs/xfs_extfree_item.c |    2 --
 3 files changed, 38 insertions(+), 5 deletions(-)

Comments

Brian Foster Oct. 25, 2019, 2:24 p.m. UTC | #1
On Wed, Oct 09, 2019 at 09:50:06AM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <darrick.wong@oracle.com>
> 
> We need to log EFIs for every extent that we allocate for the purpose of
> staging a new btree so that if we fail then the blocks will be freed
> during log recovery.
> 
> Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
> ---

Ok, so now I'm getting to the stuff we discussed earlier around pinning
the log tail and whatnot. I have no issue with getting this in as is in
the meantime, so long as we eventually account for those kind of issues
before we consider this non-experimental.

>  fs/xfs/scrub/repair.c     |   37 +++++++++++++++++++++++++++++++++++--
>  fs/xfs/scrub/repair.h     |    4 +++-
>  fs/xfs/xfs_extfree_item.c |    2 --
>  3 files changed, 38 insertions(+), 5 deletions(-)
> 
> 
> diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c
> index beebd484c5f3..49cea124148b 100644
> --- a/fs/xfs/scrub/repair.c
> +++ b/fs/xfs/scrub/repair.c
> @@ -25,6 +25,8 @@
>  #include "xfs_ag_resv.h"
>  #include "xfs_quota.h"
>  #include "xfs_bmap.h"
> +#include "xfs_defer.h"
> +#include "xfs_extfree_item.h"
>  #include "scrub/scrub.h"
>  #include "scrub/common.h"
>  #include "scrub/trace.h"
> @@ -412,7 +414,8 @@ int
>  xrep_newbt_add_reservation(
>  	struct xrep_newbt		*xnr,
>  	xfs_fsblock_t			fsbno,
> -	xfs_extlen_t			len)
> +	xfs_extlen_t			len,
> +	void				*priv)
>  {
>  	struct xrep_newbt_resv	*resv;
>  
> @@ -424,6 +427,7 @@ xrep_newbt_add_reservation(
>  	resv->fsbno = fsbno;
>  	resv->len = len;
>  	resv->used = 0;
> +	resv->priv = priv;
>  	list_add_tail(&resv->list, &xnr->reservations);
>  	return 0;
>  }
> @@ -434,6 +438,7 @@ xrep_newbt_reserve_space(
>  	struct xrep_newbt	*xnr,
>  	uint64_t		nr_blocks)
>  {
> +	const struct xfs_defer_op_type *efi_type = &xfs_extent_free_defer_type;

Heh. I feel like we should be able to do something a little cleaner
here, but I'm not sure what off the top of my head. Maybe a helper to
look up a generic "intent type" in the defer_op_types table and somewhat
abstract away the dfops-specific nature of the current interfaces..?
Maybe we should consider renaming xfs_defer_op_type to something more
generic too (xfs_intent_type ?). (This could all be a separate patch
btw.)

>  	struct xfs_scrub	*sc = xnr->sc;
>  	xfs_alloctype_t		type;
>  	xfs_fsblock_t		alloc_hint = xnr->alloc_hint;

Also variable names look misaligned with the above (here and in the
destroy function below).

> @@ -442,6 +447,7 @@ xrep_newbt_reserve_space(
>  	type = sc->ip ? XFS_ALLOCTYPE_START_BNO : XFS_ALLOCTYPE_NEAR_BNO;
>  
>  	while (nr_blocks > 0 && !error) {
> +		struct xfs_extent_free_item	efi_item;
>  		struct xfs_alloc_arg	args = {
>  			.tp		= sc->tp,
>  			.mp		= sc->mp,
> @@ -453,6 +459,7 @@ xrep_newbt_reserve_space(
>  			.prod		= nr_blocks,
>  			.resv		= xnr->resv,
>  		};
> +		void			*efi;
>  
>  		error = xfs_alloc_vextent(&args);
>  		if (error)
> @@ -465,7 +472,20 @@ xrep_newbt_reserve_space(
>  				XFS_FSB_TO_AGBNO(sc->mp, args.fsbno),
>  				args.len, xnr->oinfo.oi_owner);
>  
> -		error = xrep_newbt_add_reservation(xnr, args.fsbno, args.len);
> +		/*
> +		 * Log a deferred free item for each extent we allocate so that
> +		 * we can get all of the space back if we crash before we can
> +		 * commit the new btree.
> +		 */
> +		efi_item.xefi_startblock = args.fsbno;
> +		efi_item.xefi_blockcount = args.len;
> +		efi_item.xefi_oinfo = xnr->oinfo;
> +		efi_item.xefi_skip_discard = true;
> +		efi = efi_type->create_intent(sc->tp, 1);
> +		efi_type->log_item(sc->tp, efi, &efi_item.xefi_list);
> +
> +		error = xrep_newbt_add_reservation(xnr, args.fsbno, args.len,
> +				efi);
>  		if (error)
>  			break;
>  
> @@ -487,6 +507,7 @@ xrep_newbt_destroy(
>  	struct xrep_newbt	*xnr,
>  	int			error)
>  {
> +	const struct xfs_defer_op_type *efi_type = &xfs_extent_free_defer_type;
>  	struct xfs_scrub	*sc = xnr->sc;
>  	struct xrep_newbt_resv	*resv, *n;
>  
> @@ -494,6 +515,17 @@ xrep_newbt_destroy(
>  		goto junkit;
>  
>  	list_for_each_entry_safe(resv, n, &xnr->reservations, list) {
> +		struct xfs_efd_log_item *efd;
> +
> +		/*
> +		 * Log a deferred free done for each extent we allocated now
> +		 * that we've linked the block into the filesystem.  We cheat
> +		 * since we know that log recovery has never looked at the
> +		 * extents attached to an EFD.
> +		 */
> +		efd = efi_type->create_done(sc->tp, resv->priv, 0);
> +		set_bit(XFS_LI_DIRTY, &efd->efd_item.li_flags);
> +

So here we've presumably succeeded so we drop the intent and actually
free any blocks that we didn't happen to use.

>  		/* Free every block we didn't use. */
>  		resv->fsbno += resv->used;
>  		resv->len -= resv->used;
> @@ -515,6 +547,7 @@ xrep_newbt_destroy(
>  
>  junkit:
>  	list_for_each_entry_safe(resv, n, &xnr->reservations, list) {
> +		efi_type->abort_intent(resv->priv);

And in this case we've failed so we abort the intent.. but what actually
happens to the blocks we might have allocated? Do we need to free those
somewhere or is that also handled by the above path somehow?

Brian

>  		list_del(&resv->list);
>  		kmem_free(resv);
>  	}
> diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h
> index ab6c1199ecc0..cb86281de28b 100644
> --- a/fs/xfs/scrub/repair.h
> +++ b/fs/xfs/scrub/repair.h
> @@ -67,6 +67,8 @@ struct xrep_newbt_resv {
>  	/* Link to list of extents that we've reserved. */
>  	struct list_head	list;
>  
> +	void			*priv;
> +
>  	/* FSB of the block we reserved. */
>  	xfs_fsblock_t		fsbno;
>  
> @@ -112,7 +114,7 @@ void xrep_newbt_init_ag(struct xrep_newbt *xba, struct xfs_scrub *sc,
>  void xrep_newbt_init_inode(struct xrep_newbt *xba, struct xfs_scrub *sc,
>  		int whichfork, const struct xfs_owner_info *oinfo);
>  int xrep_newbt_add_reservation(struct xrep_newbt *xba, xfs_fsblock_t fsbno,
> -		xfs_extlen_t len);
> +		xfs_extlen_t len, void *priv);
>  int xrep_newbt_reserve_space(struct xrep_newbt *xba, uint64_t nr_blocks);
>  void xrep_newbt_destroy(struct xrep_newbt *xba, int error);
>  int xrep_newbt_alloc_block(struct xfs_btree_cur *cur, struct xrep_newbt *xba,
> diff --git a/fs/xfs/xfs_extfree_item.c b/fs/xfs/xfs_extfree_item.c
> index e44efc41a041..1e49936afbfb 100644
> --- a/fs/xfs/xfs_extfree_item.c
> +++ b/fs/xfs/xfs_extfree_item.c
> @@ -328,8 +328,6 @@ xfs_trans_get_efd(
>  {
>  	struct xfs_efd_log_item		*efdp;
>  
> -	ASSERT(nextents > 0);
> -
>  	if (nextents > XFS_EFD_MAX_FAST_EXTENTS) {
>  		efdp = kmem_zalloc(sizeof(struct xfs_efd_log_item) +
>  				(nextents - 1) * sizeof(struct xfs_extent),
>
Darrick J. Wong Oct. 25, 2019, 4:22 p.m. UTC | #2
On Fri, Oct 25, 2019 at 10:24:01AM -0400, Brian Foster wrote:
> On Wed, Oct 09, 2019 at 09:50:06AM -0700, Darrick J. Wong wrote:
> > From: Darrick J. Wong <darrick.wong@oracle.com>
> > 
> > We need to log EFIs for every extent that we allocate for the purpose of
> > staging a new btree so that if we fail then the blocks will be freed
> > during log recovery.
> > 
> > Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
> > ---
> 
> Ok, so now I'm getting to the stuff we discussed earlier around pinning
> the log tail and whatnot. I have no issue with getting this in as is in
> the meantime, so long as we eventually account for those kind of issues
> before we consider this non-experimental.

<nod> I also wondered in the past if it would be smarter just to freeze
the whole filesystem (like I currently do for rmapbt rebuilding) so that
repair is the only thing that gets to touch the log, but I bet that will
be unpopular. :)

> >  fs/xfs/scrub/repair.c     |   37 +++++++++++++++++++++++++++++++++++--
> >  fs/xfs/scrub/repair.h     |    4 +++-
> >  fs/xfs/xfs_extfree_item.c |    2 --
> >  3 files changed, 38 insertions(+), 5 deletions(-)
> > 
> > 
> > diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c
> > index beebd484c5f3..49cea124148b 100644
> > --- a/fs/xfs/scrub/repair.c
> > +++ b/fs/xfs/scrub/repair.c
> > @@ -25,6 +25,8 @@
> >  #include "xfs_ag_resv.h"
> >  #include "xfs_quota.h"
> >  #include "xfs_bmap.h"
> > +#include "xfs_defer.h"
> > +#include "xfs_extfree_item.h"
> >  #include "scrub/scrub.h"
> >  #include "scrub/common.h"
> >  #include "scrub/trace.h"
> > @@ -412,7 +414,8 @@ int
> >  xrep_newbt_add_reservation(
> >  	struct xrep_newbt		*xnr,
> >  	xfs_fsblock_t			fsbno,
> > -	xfs_extlen_t			len)
> > +	xfs_extlen_t			len,
> > +	void				*priv)
> >  {
> >  	struct xrep_newbt_resv	*resv;
> >  
> > @@ -424,6 +427,7 @@ xrep_newbt_add_reservation(
> >  	resv->fsbno = fsbno;
> >  	resv->len = len;
> >  	resv->used = 0;
> > +	resv->priv = priv;
> >  	list_add_tail(&resv->list, &xnr->reservations);
> >  	return 0;
> >  }
> > @@ -434,6 +438,7 @@ xrep_newbt_reserve_space(
> >  	struct xrep_newbt	*xnr,
> >  	uint64_t		nr_blocks)
> >  {
> > +	const struct xfs_defer_op_type *efi_type = &xfs_extent_free_defer_type;
> 
> Heh. I feel like we should be able to do something a little cleaner
> here, but I'm not sure what off the top of my head. Maybe a helper to
> look up a generic "intent type" in the defer_op_types table and somewhat
> abstract away the dfops-specific nature of the current interfaces..?
> Maybe we should consider renaming xfs_defer_op_type to something more
> generic too (xfs_intent_type ?). (This could all be a separate patch
> btw.)

I dunno.  I also feel like this is borderline misuse of the defer ops
code since we're overriding the default behavior to walk an EFI through
the state machine manually... so perhaps it's ok to wait until we have a
second reasonable user to abstract some of this away?

> >  	struct xfs_scrub	*sc = xnr->sc;
> >  	xfs_alloctype_t		type;
> >  	xfs_fsblock_t		alloc_hint = xnr->alloc_hint;
> 
> Also variable names look misaligned with the above (here and in the
> destroy function below).

Yeah, I'll see if I can fix that.

> > @@ -442,6 +447,7 @@ xrep_newbt_reserve_space(
> >  	type = sc->ip ? XFS_ALLOCTYPE_START_BNO : XFS_ALLOCTYPE_NEAR_BNO;
> >  
> >  	while (nr_blocks > 0 && !error) {
> > +		struct xfs_extent_free_item	efi_item;
> >  		struct xfs_alloc_arg	args = {
> >  			.tp		= sc->tp,
> >  			.mp		= sc->mp,
> > @@ -453,6 +459,7 @@ xrep_newbt_reserve_space(
> >  			.prod		= nr_blocks,
> >  			.resv		= xnr->resv,
> >  		};
> > +		void			*efi;
> >  
> >  		error = xfs_alloc_vextent(&args);
> >  		if (error)
> > @@ -465,7 +472,20 @@ xrep_newbt_reserve_space(
> >  				XFS_FSB_TO_AGBNO(sc->mp, args.fsbno),
> >  				args.len, xnr->oinfo.oi_owner);
> >  
> > -		error = xrep_newbt_add_reservation(xnr, args.fsbno, args.len);
> > +		/*
> > +		 * Log a deferred free item for each extent we allocate so that
> > +		 * we can get all of the space back if we crash before we can
> > +		 * commit the new btree.
> > +		 */
> > +		efi_item.xefi_startblock = args.fsbno;
> > +		efi_item.xefi_blockcount = args.len;
> > +		efi_item.xefi_oinfo = xnr->oinfo;
> > +		efi_item.xefi_skip_discard = true;
> > +		efi = efi_type->create_intent(sc->tp, 1);
> > +		efi_type->log_item(sc->tp, efi, &efi_item.xefi_list);
> > +
> > +		error = xrep_newbt_add_reservation(xnr, args.fsbno, args.len,
> > +				efi);
> >  		if (error)
> >  			break;
> >  
> > @@ -487,6 +507,7 @@ xrep_newbt_destroy(
> >  	struct xrep_newbt	*xnr,
> >  	int			error)
> >  {
> > +	const struct xfs_defer_op_type *efi_type = &xfs_extent_free_defer_type;
> >  	struct xfs_scrub	*sc = xnr->sc;
> >  	struct xrep_newbt_resv	*resv, *n;
> >  
> > @@ -494,6 +515,17 @@ xrep_newbt_destroy(
> >  		goto junkit;
> >  
> >  	list_for_each_entry_safe(resv, n, &xnr->reservations, list) {
> > +		struct xfs_efd_log_item *efd;
> > +
> > +		/*
> > +		 * Log a deferred free done for each extent we allocated now
> > +		 * that we've linked the block into the filesystem.  We cheat
> > +		 * since we know that log recovery has never looked at the
> > +		 * extents attached to an EFD.
> > +		 */
> > +		efd = efi_type->create_done(sc->tp, resv->priv, 0);
> > +		set_bit(XFS_LI_DIRTY, &efd->efd_item.li_flags);
> > +
> 
> So here we've presumably succeeded so we drop the intent and actually
> free any blocks that we didn't happen to use.

Correct.

> >  		/* Free every block we didn't use. */
> >  		resv->fsbno += resv->used;
> >  		resv->len -= resv->used;
> > @@ -515,6 +547,7 @@ xrep_newbt_destroy(
> >  
> >  junkit:
> >  	list_for_each_entry_safe(resv, n, &xnr->reservations, list) {
> > +		efi_type->abort_intent(resv->priv);
> 
> And in this case we've failed so we abort the intent.. but what actually
> happens to the blocks we might have allocated? Do we need to free those
> somewhere or is that also handled by the above path somehow?

Hmm, my assumption here was that the error code would be passed all the
way back to xchk_teardown, which in cancelling the dirty transaction
would shut down the filesystem, and log recovery would take care of it
for us.

However, I suppose we could at least try to "recover" the intent to free
the EFIs and clear the EFI; and only fall back to aborting if that also
fails since then we'll know everything's dead in the water.

--D

> Brian
> 
> >  		list_del(&resv->list);
> >  		kmem_free(resv);
> >  	}
> > diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h
> > index ab6c1199ecc0..cb86281de28b 100644
> > --- a/fs/xfs/scrub/repair.h
> > +++ b/fs/xfs/scrub/repair.h
> > @@ -67,6 +67,8 @@ struct xrep_newbt_resv {
> >  	/* Link to list of extents that we've reserved. */
> >  	struct list_head	list;
> >  
> > +	void			*priv;
> > +
> >  	/* FSB of the block we reserved. */
> >  	xfs_fsblock_t		fsbno;
> >  
> > @@ -112,7 +114,7 @@ void xrep_newbt_init_ag(struct xrep_newbt *xba, struct xfs_scrub *sc,
> >  void xrep_newbt_init_inode(struct xrep_newbt *xba, struct xfs_scrub *sc,
> >  		int whichfork, const struct xfs_owner_info *oinfo);
> >  int xrep_newbt_add_reservation(struct xrep_newbt *xba, xfs_fsblock_t fsbno,
> > -		xfs_extlen_t len);
> > +		xfs_extlen_t len, void *priv);
> >  int xrep_newbt_reserve_space(struct xrep_newbt *xba, uint64_t nr_blocks);
> >  void xrep_newbt_destroy(struct xrep_newbt *xba, int error);
> >  int xrep_newbt_alloc_block(struct xfs_btree_cur *cur, struct xrep_newbt *xba,
> > diff --git a/fs/xfs/xfs_extfree_item.c b/fs/xfs/xfs_extfree_item.c
> > index e44efc41a041..1e49936afbfb 100644
> > --- a/fs/xfs/xfs_extfree_item.c
> > +++ b/fs/xfs/xfs_extfree_item.c
> > @@ -328,8 +328,6 @@ xfs_trans_get_efd(
> >  {
> >  	struct xfs_efd_log_item		*efdp;
> >  
> > -	ASSERT(nextents > 0);
> > -
> >  	if (nextents > XFS_EFD_MAX_FAST_EXTENTS) {
> >  		efdp = kmem_zalloc(sizeof(struct xfs_efd_log_item) +
> >  				(nextents - 1) * sizeof(struct xfs_extent),
> > 
>
Brian Foster Oct. 25, 2019, 5:36 p.m. UTC | #3
On Fri, Oct 25, 2019 at 09:22:25AM -0700, Darrick J. Wong wrote:
> On Fri, Oct 25, 2019 at 10:24:01AM -0400, Brian Foster wrote:
> > On Wed, Oct 09, 2019 at 09:50:06AM -0700, Darrick J. Wong wrote:
> > > From: Darrick J. Wong <darrick.wong@oracle.com>
> > > 
> > > We need to log EFIs for every extent that we allocate for the purpose of
> > > staging a new btree so that if we fail then the blocks will be freed
> > > during log recovery.
> > > 
> > > Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
> > > ---
> > 
> > Ok, so now I'm getting to the stuff we discussed earlier around pinning
> > the log tail and whatnot. I have no issue with getting this in as is in
> > the meantime, so long as we eventually account for those kind of issues
> > before we consider this non-experimental.
> 
> <nod> I also wondered in the past if it would be smarter just to freeze
> the whole filesystem (like I currently do for rmapbt rebuilding) so that
> repair is the only thing that gets to touch the log, but I bet that will
> be unpopular. :)
> 
> > >  fs/xfs/scrub/repair.c     |   37 +++++++++++++++++++++++++++++++++++--
> > >  fs/xfs/scrub/repair.h     |    4 +++-
> > >  fs/xfs/xfs_extfree_item.c |    2 --
> > >  3 files changed, 38 insertions(+), 5 deletions(-)
> > > 
> > > 
> > > diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c
> > > index beebd484c5f3..49cea124148b 100644
> > > --- a/fs/xfs/scrub/repair.c
> > > +++ b/fs/xfs/scrub/repair.c
> > > @@ -25,6 +25,8 @@
> > >  #include "xfs_ag_resv.h"
> > >  #include "xfs_quota.h"
> > >  #include "xfs_bmap.h"
> > > +#include "xfs_defer.h"
> > > +#include "xfs_extfree_item.h"
> > >  #include "scrub/scrub.h"
> > >  #include "scrub/common.h"
> > >  #include "scrub/trace.h"
> > > @@ -412,7 +414,8 @@ int
> > >  xrep_newbt_add_reservation(
> > >  	struct xrep_newbt		*xnr,
> > >  	xfs_fsblock_t			fsbno,
> > > -	xfs_extlen_t			len)
> > > +	xfs_extlen_t			len,
> > > +	void				*priv)
> > >  {
> > >  	struct xrep_newbt_resv	*resv;
> > >  
> > > @@ -424,6 +427,7 @@ xrep_newbt_add_reservation(
> > >  	resv->fsbno = fsbno;
> > >  	resv->len = len;
> > >  	resv->used = 0;
> > > +	resv->priv = priv;
> > >  	list_add_tail(&resv->list, &xnr->reservations);
> > >  	return 0;
> > >  }
> > > @@ -434,6 +438,7 @@ xrep_newbt_reserve_space(
> > >  	struct xrep_newbt	*xnr,
> > >  	uint64_t		nr_blocks)
> > >  {
> > > +	const struct xfs_defer_op_type *efi_type = &xfs_extent_free_defer_type;
> > 
> > Heh. I feel like we should be able to do something a little cleaner
> > here, but I'm not sure what off the top of my head. Maybe a helper to
> > look up a generic "intent type" in the defer_op_types table and somewhat
> > abstract away the dfops-specific nature of the current interfaces..?
> > Maybe we should consider renaming xfs_defer_op_type to something more
> > generic too (xfs_intent_type ?). (This could all be a separate patch
> > btw.)
> 
> I dunno.  I also feel like this is borderline misuse of the defer ops
> code since we're overriding the default behavior to walk an EFI through
> the state machine manually... so perhaps it's ok to wait until we have a
> second reasonable user to abstract some of this away?
> 

I think that's reasonable. I was also wondering whether some of the
dfops functionality might be worth refactoring for this, but I'm not
totally sure what that would look like.

> > >  	struct xfs_scrub	*sc = xnr->sc;
> > >  	xfs_alloctype_t		type;
> > >  	xfs_fsblock_t		alloc_hint = xnr->alloc_hint;
> > 
> > Also variable names look misaligned with the above (here and in the
> > destroy function below).
> 
> Yeah, I'll see if I can fix that.
> 
> > > @@ -442,6 +447,7 @@ xrep_newbt_reserve_space(
> > >  	type = sc->ip ? XFS_ALLOCTYPE_START_BNO : XFS_ALLOCTYPE_NEAR_BNO;
> > >  
> > >  	while (nr_blocks > 0 && !error) {
> > > +		struct xfs_extent_free_item	efi_item;
> > >  		struct xfs_alloc_arg	args = {
> > >  			.tp		= sc->tp,
> > >  			.mp		= sc->mp,
> > > @@ -453,6 +459,7 @@ xrep_newbt_reserve_space(
> > >  			.prod		= nr_blocks,
> > >  			.resv		= xnr->resv,
> > >  		};
> > > +		void			*efi;
> > >  
> > >  		error = xfs_alloc_vextent(&args);
> > >  		if (error)
> > > @@ -465,7 +472,20 @@ xrep_newbt_reserve_space(
> > >  				XFS_FSB_TO_AGBNO(sc->mp, args.fsbno),
> > >  				args.len, xnr->oinfo.oi_owner);
> > >  
> > > -		error = xrep_newbt_add_reservation(xnr, args.fsbno, args.len);
> > > +		/*
> > > +		 * Log a deferred free item for each extent we allocate so that
> > > +		 * we can get all of the space back if we crash before we can
> > > +		 * commit the new btree.
> > > +		 */
> > > +		efi_item.xefi_startblock = args.fsbno;
> > > +		efi_item.xefi_blockcount = args.len;
> > > +		efi_item.xefi_oinfo = xnr->oinfo;
> > > +		efi_item.xefi_skip_discard = true;
> > > +		efi = efi_type->create_intent(sc->tp, 1);
> > > +		efi_type->log_item(sc->tp, efi, &efi_item.xefi_list);
> > > +
> > > +		error = xrep_newbt_add_reservation(xnr, args.fsbno, args.len,
> > > +				efi);
> > >  		if (error)
> > >  			break;
> > >  
> > > @@ -487,6 +507,7 @@ xrep_newbt_destroy(
> > >  	struct xrep_newbt	*xnr,
> > >  	int			error)
> > >  {
> > > +	const struct xfs_defer_op_type *efi_type = &xfs_extent_free_defer_type;
> > >  	struct xfs_scrub	*sc = xnr->sc;
> > >  	struct xrep_newbt_resv	*resv, *n;
> > >  
> > > @@ -494,6 +515,17 @@ xrep_newbt_destroy(
> > >  		goto junkit;
> > >  
> > >  	list_for_each_entry_safe(resv, n, &xnr->reservations, list) {
> > > +		struct xfs_efd_log_item *efd;
> > > +
> > > +		/*
> > > +		 * Log a deferred free done for each extent we allocated now
> > > +		 * that we've linked the block into the filesystem.  We cheat
> > > +		 * since we know that log recovery has never looked at the
> > > +		 * extents attached to an EFD.
> > > +		 */
> > > +		efd = efi_type->create_done(sc->tp, resv->priv, 0);
> > > +		set_bit(XFS_LI_DIRTY, &efd->efd_item.li_flags);
> > > +
> > 
> > So here we've presumably succeeded so we drop the intent and actually
> > free any blocks that we didn't happen to use.
> 
> Correct.
> 
> > >  		/* Free every block we didn't use. */
> > >  		resv->fsbno += resv->used;
> > >  		resv->len -= resv->used;
> > > @@ -515,6 +547,7 @@ xrep_newbt_destroy(
> > >  
> > >  junkit:
> > >  	list_for_each_entry_safe(resv, n, &xnr->reservations, list) {
> > > +		efi_type->abort_intent(resv->priv);
> > 
> > And in this case we've failed so we abort the intent.. but what actually
> > happens to the blocks we might have allocated? Do we need to free those
> > somewhere or is that also handled by the above path somehow?
> 
> Hmm, my assumption here was that the error code would be passed all the
> way back to xchk_teardown, which in cancelling the dirty transaction
> would shut down the filesystem, and log recovery would take care of it
> for us.
> 

Ok.. that seems perfectly reasonable to me in a general sense. I'd just
prefer to see a "shutdown cleans up the mess" comment somewhere so the
expected error handling behavior is clear in the code. From a scrub
perspective, it sounds like we are saying a failed repair attempt is
going to shutdown and leave around a dirty log. I wonder how that would
play out in practice given that the filesystem is presumably corrupted,
so if we can't mount the fs after that point for whatever reason we've
basically added to the mess since repair will need to zero the log. We
could also crash at any point too if the fs is corrupted so I dunno..

> However, I suppose we could at least try to "recover" the intent to free
> the EFIs and clear the EFI; and only fall back to aborting if that also
> fails since then we'll know everything's dead in the water.
> 

That could be something for a follow on patch too depending on the
above..

Brian

> --D
> 
> > Brian
> > 
> > >  		list_del(&resv->list);
> > >  		kmem_free(resv);
> > >  	}
> > > diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h
> > > index ab6c1199ecc0..cb86281de28b 100644
> > > --- a/fs/xfs/scrub/repair.h
> > > +++ b/fs/xfs/scrub/repair.h
> > > @@ -67,6 +67,8 @@ struct xrep_newbt_resv {
> > >  	/* Link to list of extents that we've reserved. */
> > >  	struct list_head	list;
> > >  
> > > +	void			*priv;
> > > +
> > >  	/* FSB of the block we reserved. */
> > >  	xfs_fsblock_t		fsbno;
> > >  
> > > @@ -112,7 +114,7 @@ void xrep_newbt_init_ag(struct xrep_newbt *xba, struct xfs_scrub *sc,
> > >  void xrep_newbt_init_inode(struct xrep_newbt *xba, struct xfs_scrub *sc,
> > >  		int whichfork, const struct xfs_owner_info *oinfo);
> > >  int xrep_newbt_add_reservation(struct xrep_newbt *xba, xfs_fsblock_t fsbno,
> > > -		xfs_extlen_t len);
> > > +		xfs_extlen_t len, void *priv);
> > >  int xrep_newbt_reserve_space(struct xrep_newbt *xba, uint64_t nr_blocks);
> > >  void xrep_newbt_destroy(struct xrep_newbt *xba, int error);
> > >  int xrep_newbt_alloc_block(struct xfs_btree_cur *cur, struct xrep_newbt *xba,
> > > diff --git a/fs/xfs/xfs_extfree_item.c b/fs/xfs/xfs_extfree_item.c
> > > index e44efc41a041..1e49936afbfb 100644
> > > --- a/fs/xfs/xfs_extfree_item.c
> > > +++ b/fs/xfs/xfs_extfree_item.c
> > > @@ -328,8 +328,6 @@ xfs_trans_get_efd(
> > >  {
> > >  	struct xfs_efd_log_item		*efdp;
> > >  
> > > -	ASSERT(nextents > 0);
> > > -
> > >  	if (nextents > XFS_EFD_MAX_FAST_EXTENTS) {
> > >  		efdp = kmem_zalloc(sizeof(struct xfs_efd_log_item) +
> > >  				(nextents - 1) * sizeof(struct xfs_extent),
> > > 
> >

Patch
diff mbox series

diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c
index beebd484c5f3..49cea124148b 100644
--- a/fs/xfs/scrub/repair.c
+++ b/fs/xfs/scrub/repair.c
@@ -25,6 +25,8 @@ 
 #include "xfs_ag_resv.h"
 #include "xfs_quota.h"
 #include "xfs_bmap.h"
+#include "xfs_defer.h"
+#include "xfs_extfree_item.h"
 #include "scrub/scrub.h"
 #include "scrub/common.h"
 #include "scrub/trace.h"
@@ -412,7 +414,8 @@  int
 xrep_newbt_add_reservation(
 	struct xrep_newbt		*xnr,
 	xfs_fsblock_t			fsbno,
-	xfs_extlen_t			len)
+	xfs_extlen_t			len,
+	void				*priv)
 {
 	struct xrep_newbt_resv	*resv;
 
@@ -424,6 +427,7 @@  xrep_newbt_add_reservation(
 	resv->fsbno = fsbno;
 	resv->len = len;
 	resv->used = 0;
+	resv->priv = priv;
 	list_add_tail(&resv->list, &xnr->reservations);
 	return 0;
 }
@@ -434,6 +438,7 @@  xrep_newbt_reserve_space(
 	struct xrep_newbt	*xnr,
 	uint64_t		nr_blocks)
 {
+	const struct xfs_defer_op_type *efi_type = &xfs_extent_free_defer_type;
 	struct xfs_scrub	*sc = xnr->sc;
 	xfs_alloctype_t		type;
 	xfs_fsblock_t		alloc_hint = xnr->alloc_hint;
@@ -442,6 +447,7 @@  xrep_newbt_reserve_space(
 	type = sc->ip ? XFS_ALLOCTYPE_START_BNO : XFS_ALLOCTYPE_NEAR_BNO;
 
 	while (nr_blocks > 0 && !error) {
+		struct xfs_extent_free_item	efi_item;
 		struct xfs_alloc_arg	args = {
 			.tp		= sc->tp,
 			.mp		= sc->mp,
@@ -453,6 +459,7 @@  xrep_newbt_reserve_space(
 			.prod		= nr_blocks,
 			.resv		= xnr->resv,
 		};
+		void			*efi;
 
 		error = xfs_alloc_vextent(&args);
 		if (error)
@@ -465,7 +472,20 @@  xrep_newbt_reserve_space(
 				XFS_FSB_TO_AGBNO(sc->mp, args.fsbno),
 				args.len, xnr->oinfo.oi_owner);
 
-		error = xrep_newbt_add_reservation(xnr, args.fsbno, args.len);
+		/*
+		 * Log a deferred free item for each extent we allocate so that
+		 * we can get all of the space back if we crash before we can
+		 * commit the new btree.
+		 */
+		efi_item.xefi_startblock = args.fsbno;
+		efi_item.xefi_blockcount = args.len;
+		efi_item.xefi_oinfo = xnr->oinfo;
+		efi_item.xefi_skip_discard = true;
+		efi = efi_type->create_intent(sc->tp, 1);
+		efi_type->log_item(sc->tp, efi, &efi_item.xefi_list);
+
+		error = xrep_newbt_add_reservation(xnr, args.fsbno, args.len,
+				efi);
 		if (error)
 			break;
 
@@ -487,6 +507,7 @@  xrep_newbt_destroy(
 	struct xrep_newbt	*xnr,
 	int			error)
 {
+	const struct xfs_defer_op_type *efi_type = &xfs_extent_free_defer_type;
 	struct xfs_scrub	*sc = xnr->sc;
 	struct xrep_newbt_resv	*resv, *n;
 
@@ -494,6 +515,17 @@  xrep_newbt_destroy(
 		goto junkit;
 
 	list_for_each_entry_safe(resv, n, &xnr->reservations, list) {
+		struct xfs_efd_log_item *efd;
+
+		/*
+		 * Log a deferred free done for each extent we allocated now
+		 * that we've linked the block into the filesystem.  We cheat
+		 * since we know that log recovery has never looked at the
+		 * extents attached to an EFD.
+		 */
+		efd = efi_type->create_done(sc->tp, resv->priv, 0);
+		set_bit(XFS_LI_DIRTY, &efd->efd_item.li_flags);
+
 		/* Free every block we didn't use. */
 		resv->fsbno += resv->used;
 		resv->len -= resv->used;
@@ -515,6 +547,7 @@  xrep_newbt_destroy(
 
 junkit:
 	list_for_each_entry_safe(resv, n, &xnr->reservations, list) {
+		efi_type->abort_intent(resv->priv);
 		list_del(&resv->list);
 		kmem_free(resv);
 	}
diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h
index ab6c1199ecc0..cb86281de28b 100644
--- a/fs/xfs/scrub/repair.h
+++ b/fs/xfs/scrub/repair.h
@@ -67,6 +67,8 @@  struct xrep_newbt_resv {
 	/* Link to list of extents that we've reserved. */
 	struct list_head	list;
 
+	void			*priv;
+
 	/* FSB of the block we reserved. */
 	xfs_fsblock_t		fsbno;
 
@@ -112,7 +114,7 @@  void xrep_newbt_init_ag(struct xrep_newbt *xba, struct xfs_scrub *sc,
 void xrep_newbt_init_inode(struct xrep_newbt *xba, struct xfs_scrub *sc,
 		int whichfork, const struct xfs_owner_info *oinfo);
 int xrep_newbt_add_reservation(struct xrep_newbt *xba, xfs_fsblock_t fsbno,
-		xfs_extlen_t len);
+		xfs_extlen_t len, void *priv);
 int xrep_newbt_reserve_space(struct xrep_newbt *xba, uint64_t nr_blocks);
 void xrep_newbt_destroy(struct xrep_newbt *xba, int error);
 int xrep_newbt_alloc_block(struct xfs_btree_cur *cur, struct xrep_newbt *xba,
diff --git a/fs/xfs/xfs_extfree_item.c b/fs/xfs/xfs_extfree_item.c
index e44efc41a041..1e49936afbfb 100644
--- a/fs/xfs/xfs_extfree_item.c
+++ b/fs/xfs/xfs_extfree_item.c
@@ -328,8 +328,6 @@  xfs_trans_get_efd(
 {
 	struct xfs_efd_log_item		*efdp;
 
-	ASSERT(nextents > 0);
-
 	if (nextents > XFS_EFD_MAX_FAST_EXTENTS) {
 		efdp = kmem_zalloc(sizeof(struct xfs_efd_log_item) +
 				(nextents - 1) * sizeof(struct xfs_extent),