diff options
| author | Dmitry Torokhov <[email protected]> | 2023-05-01 15:20:08 -0700 | 
|---|---|---|
| committer | Dmitry Torokhov <[email protected]> | 2023-05-01 15:20:08 -0700 | 
| commit | 9a87ffc99ec8eb8d35eed7c4f816d75f5cc9662e (patch) | |
| tree | d57f3a63479a07b4e0cece029886e76e04feb984 /fs/xfs/xfs_filestream.c | |
| parent | 5dc63e56a9cf8df0b59c234a505a1653f1bdf885 (diff) | |
| parent | 53bea86b5712c7491bb3dae12e271666df0a308c (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 6.4 merge window.
Diffstat (limited to 'fs/xfs/xfs_filestream.c')
| -rw-r--r-- | fs/xfs/xfs_filestream.c | 455 | 
1 files changed, 235 insertions, 220 deletions
diff --git a/fs/xfs/xfs_filestream.c b/fs/xfs/xfs_filestream.c index 34b21a29c39b..22c13933c8f8 100644 --- a/fs/xfs/xfs_filestream.c +++ b/fs/xfs/xfs_filestream.c @@ -12,6 +12,7 @@  #include "xfs_mount.h"  #include "xfs_inode.h"  #include "xfs_bmap.h" +#include "xfs_bmap_util.h"  #include "xfs_alloc.h"  #include "xfs_mru_cache.h"  #include "xfs_trace.h" @@ -22,7 +23,7 @@  struct xfs_fstrm_item {  	struct xfs_mru_cache_elem	mru; -	xfs_agnumber_t			ag; /* AG in use for this directory */ +	struct xfs_perag		*pag; /* AG in use for this directory */  };  enum xfs_fstrm_alloc { @@ -30,117 +31,68 @@ enum xfs_fstrm_alloc {  	XFS_PICK_LOWSPACE = 2,  }; -/* - * Allocation group filestream associations are tracked with per-ag atomic - * counters.  These counters allow xfs_filestream_pick_ag() to tell whether a - * particular AG already has active filestreams associated with it. - */ -int -xfs_filestream_peek_ag( -	xfs_mount_t	*mp, -	xfs_agnumber_t	agno) -{ -	struct xfs_perag *pag; -	int		ret; - -	pag = xfs_perag_get(mp, agno); -	ret = atomic_read(&pag->pagf_fstrms); -	xfs_perag_put(pag); -	return ret; -} - -static int -xfs_filestream_get_ag( -	xfs_mount_t	*mp, -	xfs_agnumber_t	agno) -{ -	struct xfs_perag *pag; -	int		ret; - -	pag = xfs_perag_get(mp, agno); -	ret = atomic_inc_return(&pag->pagf_fstrms); -	xfs_perag_put(pag); -	return ret; -} - -static void -xfs_filestream_put_ag( -	xfs_mount_t	*mp, -	xfs_agnumber_t	agno) -{ -	struct xfs_perag *pag; - -	pag = xfs_perag_get(mp, agno); -	atomic_dec(&pag->pagf_fstrms); -	xfs_perag_put(pag); -} -  static void  xfs_fstrm_free_func(  	void			*data,  	struct xfs_mru_cache_elem *mru)  { -	struct xfs_mount	*mp = data;  	struct xfs_fstrm_item	*item =  		container_of(mru, struct xfs_fstrm_item, mru); +	struct xfs_perag	*pag = item->pag; -	xfs_filestream_put_ag(mp, item->ag); -	trace_xfs_filestream_free(mp, mru->key, item->ag); +	trace_xfs_filestream_free(pag, mru->key); +	atomic_dec(&pag->pagf_fstrms); +	xfs_perag_rele(pag);  	kmem_free(item);  }  /* - * Scan the AGs starting at startag looking for an AG that isn't in use and has - * at least minlen blocks free. + * Scan the AGs starting at start_agno looking for an AG that isn't in use and + * has at least minlen blocks free. If no AG is found to match the allocation + * requirements, pick the AG with the most free space in it.   */  static int  xfs_filestream_pick_ag( -	struct xfs_inode	*ip, -	xfs_agnumber_t		startag, -	xfs_agnumber_t		*agp, +	struct xfs_alloc_arg	*args, +	xfs_ino_t		pino, +	xfs_agnumber_t		start_agno,  	int			flags, -	xfs_extlen_t		minlen) +	xfs_extlen_t		*longest)  { -	struct xfs_mount	*mp = ip->i_mount; -	struct xfs_fstrm_item	*item; +	struct xfs_mount	*mp = args->mp;  	struct xfs_perag	*pag; -	xfs_extlen_t		longest, free = 0, minfree, maxfree = 0; -	xfs_agnumber_t		ag, max_ag = NULLAGNUMBER; -	int			err, trylock, nscan; - -	ASSERT(S_ISDIR(VFS_I(ip)->i_mode)); +	struct xfs_perag	*max_pag = NULL; +	xfs_extlen_t		minlen = *longest; +	xfs_extlen_t		free = 0, minfree, maxfree = 0; +	xfs_agnumber_t		agno; +	bool			first_pass = true; +	int			err;  	/* 2% of an AG's blocks must be free for it to be chosen. */  	minfree = mp->m_sb.sb_agblocks / 50; -	ag = startag; -	*agp = NULLAGNUMBER; - -	/* For the first pass, don't sleep trying to init the per-AG. */ -	trylock = XFS_ALLOC_FLAG_TRYLOCK; - -	for (nscan = 0; 1; nscan++) { -		trace_xfs_filestream_scan(mp, ip->i_ino, ag); - -		pag = xfs_perag_get(mp, ag); - -		if (!pag->pagf_init) { -			err = xfs_alloc_read_agf(pag, NULL, trylock, NULL); -			if (err) { -				if (err != -EAGAIN) { -					xfs_perag_put(pag); -					return err; -				} -				/* Couldn't lock the AGF, skip this AG. */ -				goto next_ag; -			} +restart: +	for_each_perag_wrap(mp, start_agno, agno, pag) { +		trace_xfs_filestream_scan(pag, pino); +		*longest = 0; +		err = xfs_bmap_longest_free_extent(pag, NULL, longest); +		if (err) { +			xfs_perag_rele(pag); +			if (err != -EAGAIN) +				break; +			/* Couldn't lock the AGF, skip this AG. */ +			err = 0; +			continue;  		}  		/* Keep track of the AG with the most free blocks. */  		if (pag->pagf_freeblks > maxfree) {  			maxfree = pag->pagf_freeblks; -			max_ag = ag; +			if (max_pag) +				xfs_perag_rele(max_pag); +			atomic_inc(&pag->pag_active_ref); +			max_pag = pag;  		}  		/* @@ -149,93 +101,73 @@ xfs_filestream_pick_ag(  		 * loop, and it guards against two filestreams being established  		 * in the same AG as each other.  		 */ -		if (xfs_filestream_get_ag(mp, ag) > 1) { -			xfs_filestream_put_ag(mp, ag); -			goto next_ag; -		} - -		longest = xfs_alloc_longest_free_extent(pag, -				xfs_alloc_min_freelist(mp, pag), -				xfs_ag_resv_needed(pag, XFS_AG_RESV_NONE)); -		if (((minlen && longest >= minlen) || -		     (!minlen && pag->pagf_freeblks >= minfree)) && -		    (!pag->pagf_metadata || !(flags & XFS_PICK_USERDATA) || -		     (flags & XFS_PICK_LOWSPACE))) { - -			/* Break out, retaining the reference on the AG. */ -			free = pag->pagf_freeblks; -			xfs_perag_put(pag); -			*agp = ag; -			break; +		if (atomic_inc_return(&pag->pagf_fstrms) <= 1) { +			if (((minlen && *longest >= minlen) || +			     (!minlen && pag->pagf_freeblks >= minfree)) && +			    (!xfs_perag_prefers_metadata(pag) || +			     !(flags & XFS_PICK_USERDATA) || +			     (flags & XFS_PICK_LOWSPACE))) { +				/* Break out, retaining the reference on the AG. */ +				free = pag->pagf_freeblks; +				break; +			}  		}  		/* Drop the reference on this AG, it's not usable. */ -		xfs_filestream_put_ag(mp, ag); -next_ag: -		xfs_perag_put(pag); -		/* Move to the next AG, wrapping to AG 0 if necessary. */ -		if (++ag >= mp->m_sb.sb_agcount) -			ag = 0; - -		/* If a full pass of the AGs hasn't been done yet, continue. */ -		if (ag != startag) -			continue; +		atomic_dec(&pag->pagf_fstrms); +	} -		/* Allow sleeping in xfs_alloc_read_agf() on the 2nd pass. */ -		if (trylock != 0) { -			trylock = 0; -			continue; +	if (err) { +		xfs_perag_rele(pag); +		if (max_pag) +			xfs_perag_rele(max_pag); +		return err; +	} + +	if (!pag) { +		/* +		 * Allow a second pass to give xfs_bmap_longest_free_extent() +		 * another attempt at locking AGFs that it might have skipped +		 * over before we fail. +		 */ +		if (first_pass) { +			first_pass = false; +			goto restart;  		} -		/* Finally, if lowspace wasn't set, set it for the 3rd pass. */ +		/* +		 * We must be low on data space, so run a final lowspace +		 * optimised selection pass if we haven't already. +		 */  		if (!(flags & XFS_PICK_LOWSPACE)) {  			flags |= XFS_PICK_LOWSPACE; -			continue; +			goto restart;  		}  		/* -		 * Take the AG with the most free space, regardless of whether -		 * it's already in use by another filestream. +		 * No unassociated AGs are available, so select the AG with the +		 * most free space, regardless of whether it's already in use by +		 * another filestream. It none suit, just use whatever AG we can +		 * grab.  		 */ -		if (max_ag != NULLAGNUMBER) { -			xfs_filestream_get_ag(mp, max_ag); +		if (!max_pag) { +			for_each_perag_wrap(args->mp, 0, start_agno, args->pag) +				break; +			atomic_inc(&args->pag->pagf_fstrms); +			*longest = 0; +		} else { +			pag = max_pag;  			free = maxfree; -			*agp = max_ag; -			break; +			atomic_inc(&pag->pagf_fstrms);  		} - -		/* take AG 0 if none matched */ -		trace_xfs_filestream_pick(ip, *agp, free, nscan); -		*agp = 0; -		return 0; -	} - -	trace_xfs_filestream_pick(ip, *agp, free, nscan); - -	if (*agp == NULLAGNUMBER) -		return 0; - -	err = -ENOMEM; -	item = kmem_alloc(sizeof(*item), KM_MAYFAIL); -	if (!item) -		goto out_put_ag; - -	item->ag = *agp; - -	err = xfs_mru_cache_insert(mp->m_filestream, ip->i_ino, &item->mru); -	if (err) { -		if (err == -EEXIST) -			err = 0; -		goto out_free_item; +	} else if (max_pag) { +		xfs_perag_rele(max_pag);  	} +	trace_xfs_filestream_pick(pag, pino, free); +	args->pag = pag;  	return 0; -out_free_item: -	kmem_free(item); -out_put_ag: -	xfs_filestream_put_ag(mp, *agp); -	return err;  }  static struct xfs_inode * @@ -263,104 +195,187 @@ out:  }  /* - * Find the right allocation group for a file, either by finding an - * existing file stream or creating a new one. + * Lookup the mru cache for an existing association. If one exists and we can + * use it, return with an active perag reference indicating that the allocation + * will proceed with that association.   * - * Returns NULLAGNUMBER in case of an error. + * If we have no association, or we cannot use the current one and have to + * destroy it, return with longest = 0 to tell the caller to create a new + * association.   */ -xfs_agnumber_t -xfs_filestream_lookup_ag( -	struct xfs_inode	*ip) +static int +xfs_filestream_lookup_association( +	struct xfs_bmalloca	*ap, +	struct xfs_alloc_arg	*args, +	xfs_ino_t		pino, +	xfs_extlen_t		*longest)  { -	struct xfs_mount	*mp = ip->i_mount; -	struct xfs_inode	*pip = NULL; -	xfs_agnumber_t		startag, ag = NULLAGNUMBER; +	struct xfs_mount	*mp = args->mp; +	struct xfs_perag	*pag;  	struct xfs_mru_cache_elem *mru; +	int			error = 0; -	ASSERT(S_ISREG(VFS_I(ip)->i_mode)); - -	pip = xfs_filestream_get_parent(ip); -	if (!pip) -		return NULLAGNUMBER; +	*longest = 0; +	mru = xfs_mru_cache_lookup(mp->m_filestream, pino); +	if (!mru) +		return 0; +	/* +	 * Grab the pag and take an extra active reference for the caller whilst +	 * the mru item cannot go away. This means we'll pin the perag with +	 * the reference we get here even if the filestreams association is torn +	 * down immediately after we mark the lookup as done. +	 */ +	pag = container_of(mru, struct xfs_fstrm_item, mru)->pag; +	atomic_inc(&pag->pag_active_ref); +	xfs_mru_cache_done(mp->m_filestream); -	mru = xfs_mru_cache_lookup(mp->m_filestream, pip->i_ino); -	if (mru) { -		ag = container_of(mru, struct xfs_fstrm_item, mru)->ag; -		xfs_mru_cache_done(mp->m_filestream); +	trace_xfs_filestream_lookup(pag, ap->ip->i_ino); -		trace_xfs_filestream_lookup(mp, ip->i_ino, ag); -		goto out; -	} +	ap->blkno = XFS_AGB_TO_FSB(args->mp, pag->pag_agno, 0); +	xfs_bmap_adjacent(ap);  	/* -	 * Set the starting AG using the rotor for inode32, otherwise -	 * use the directory inode's AG. +	 * If there is very little free space before we start a filestreams +	 * allocation, we're almost guaranteed to fail to find a large enough +	 * free space available so just use the cached AG.  	 */ -	if (xfs_is_inode32(mp)) { -		xfs_agnumber_t	 rotorstep = xfs_rotorstep; -		startag = (mp->m_agfrotor / rotorstep) % mp->m_sb.sb_agcount; -		mp->m_agfrotor = (mp->m_agfrotor + 1) % -		                 (mp->m_sb.sb_agcount * rotorstep); -	} else -		startag = XFS_INO_TO_AGNO(mp, pip->i_ino); +	if (ap->tp->t_flags & XFS_TRANS_LOWMODE) { +		*longest = 1; +		goto out_done; +	} -	if (xfs_filestream_pick_ag(pip, startag, &ag, 0, 0)) -		ag = NULLAGNUMBER; -out: -	xfs_irele(pip); -	return ag; +	error = xfs_bmap_longest_free_extent(pag, args->tp, longest); +	if (error == -EAGAIN) +		error = 0; +	if (error || *longest < args->maxlen) { +		/* We aren't going to use this perag */ +		*longest = 0; +		xfs_perag_rele(pag); +		return error; +	} + +out_done: +	args->pag = pag; +	return 0;  } -/* - * Pick a new allocation group for the current file and its file stream. - * - * This is called when the allocator can't find a suitable extent in the - * current AG, and we have to move the stream into a new AG with more space. - */ -int -xfs_filestream_new_ag( +static int +xfs_filestream_create_association(  	struct xfs_bmalloca	*ap, -	xfs_agnumber_t		*agp) +	struct xfs_alloc_arg	*args, +	xfs_ino_t		pino, +	xfs_extlen_t		*longest)  { -	struct xfs_inode	*ip = ap->ip, *pip; -	struct xfs_mount	*mp = ip->i_mount; -	xfs_extlen_t		minlen = ap->length; -	xfs_agnumber_t		startag = 0; -	int			flags = 0; -	int			err = 0; +	struct xfs_mount	*mp = args->mp;  	struct xfs_mru_cache_elem *mru; +	struct xfs_fstrm_item	*item; +	xfs_agnumber_t		agno = XFS_INO_TO_AGNO(mp, pino); +	int			flags = 0; +	int			error; -	*agp = NULLAGNUMBER; - -	pip = xfs_filestream_get_parent(ip); -	if (!pip) -		goto exit; - -	mru = xfs_mru_cache_remove(mp->m_filestream, pip->i_ino); +	/* Changing parent AG association now, so remove the existing one. */ +	mru = xfs_mru_cache_remove(mp->m_filestream, pino);  	if (mru) {  		struct xfs_fstrm_item *item =  			container_of(mru, struct xfs_fstrm_item, mru); -		startag = (item->ag + 1) % mp->m_sb.sb_agcount; + +		agno = (item->pag->pag_agno + 1) % mp->m_sb.sb_agcount; +		xfs_fstrm_free_func(mp, mru); +	} else if (xfs_is_inode32(mp)) { +		xfs_agnumber_t	 rotorstep = xfs_rotorstep; + +		agno = (mp->m_agfrotor / rotorstep) % mp->m_sb.sb_agcount; +		mp->m_agfrotor = (mp->m_agfrotor + 1) % +				 (mp->m_sb.sb_agcount * rotorstep);  	} +	ap->blkno = XFS_AGB_TO_FSB(args->mp, agno, 0); +	xfs_bmap_adjacent(ap); +  	if (ap->datatype & XFS_ALLOC_USERDATA)  		flags |= XFS_PICK_USERDATA;  	if (ap->tp->t_flags & XFS_TRANS_LOWMODE)  		flags |= XFS_PICK_LOWSPACE; -	err = xfs_filestream_pick_ag(pip, startag, agp, flags, minlen); +	*longest = ap->length; +	error = xfs_filestream_pick_ag(args, pino, agno, flags, longest); +	if (error) +		return error;  	/* -	 * Only free the item here so we skip over the old AG earlier. +	 * We are going to use this perag now, so create an assoication for it. +	 * xfs_filestream_pick_ag() has already bumped the perag fstrms counter +	 * for us, so all we need to do here is take another active reference to +	 * the perag for the cached association. +	 * +	 * If we fail to store the association, we need to drop the fstrms +	 * counter as well as drop the perag reference we take here for the +	 * item. We do not need to return an error for this failure - as long as +	 * we return a referenced AG, the allocation can still go ahead just +	 * fine.  	 */ -	if (mru) -		xfs_fstrm_free_func(mp, mru); +	item = kmem_alloc(sizeof(*item), KM_MAYFAIL); +	if (!item) +		goto out_put_fstrms; + +	atomic_inc(&args->pag->pag_active_ref); +	item->pag = args->pag; +	error = xfs_mru_cache_insert(mp->m_filestream, pino, &item->mru); +	if (error) +		goto out_free_item; +	return 0; + +out_free_item: +	xfs_perag_rele(item->pag); +	kmem_free(item); +out_put_fstrms: +	atomic_dec(&args->pag->pagf_fstrms); +	return 0; +} + +/* + * Search for an allocation group with a single extent large enough for + * the request. First we look for an existing association and use that if it + * is found. Otherwise, we create a new association by selecting an AG that fits + * the allocation criteria. + * + * We return with a referenced perag in args->pag to indicate which AG we are + * allocating into or an error with no references held. + */ +int +xfs_filestream_select_ag( +	struct xfs_bmalloca	*ap, +	struct xfs_alloc_arg	*args, +	xfs_extlen_t		*longest) +{ +	struct xfs_mount	*mp = args->mp; +	struct xfs_inode	*pip; +	xfs_ino_t		ino = 0; +	int			error = 0; + +	*longest = 0; +	args->total = ap->total; +	pip = xfs_filestream_get_parent(ap->ip); +	if (pip) { +		ino = pip->i_ino; +		error = xfs_filestream_lookup_association(ap, args, ino, +				longest); +		xfs_irele(pip); +		if (error) +			return error; +		if (*longest >= args->maxlen) +			goto out_select; +		if (ap->tp->t_flags & XFS_TRANS_LOWMODE) +			goto out_select; +	} + +	error = xfs_filestream_create_association(ap, args, ino, longest); +	if (error) +		return error; -	xfs_irele(pip); -exit: -	if (*agp == NULLAGNUMBER) -		*agp = 0; -	return err; +out_select: +	ap->blkno = XFS_AGB_TO_FSB(mp, args->pag->pag_agno, 0); +	return 0;  }  void  |