Re: [PATCH 21/22] multipathd: check paths in order by mpp

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sat, 2024-07-13 at 02:05 -0400, Benjamin Marzinski wrote:
> Instead of checking all of the paths in vecs->pathvec in order, first
> check all the paths in each multipath device. Then check the
> uninitialized paths.
> 
> One issue with checking paths this way is that the multipath device
> can
> be resynced or even removed while a path is being checked. The path
> can
> also be removed. If there is any change to the multipath device,
> multipathd needs to loop through its paths again, because the current
> indexes may not longer be valid.
> 
> To do this change mpp->is_checked to an int called mpp->synced_count,
> and increment it whenever the multipath device gets resynced. After
> each
> path is checked, make sure that the multipath device still exists,
> that
> mpp->synced_count hasn't changed. If either has happened, restart
> checking at the current index in mpvec (which will either be the same
> mpp if it was just resynced, or the next mpp if the last one was
> deleted). Since the multipath device is resynced when its first path
> is
> checked, this restart will happen to every multipath device at least
> once per loop. But the paths themselves aren't rechecked, so it's not
> much overhead.

Why don't we just sync the map once before we start checking the paths,
instead of doing it when the first path is checked? 

> If resyncing a multipath device fails in do_check_mpp(), there may be
> path devices that have pp->mpp set, but are no longer in one of the
> multipath device's pathgroups, and thus will not get checked. This
> almost definitely means the multipath device was deleted. If
> do_check_mpp() failed to resync the device, but it wasn't deleted, it
> will get called again in max_checkint seconds even if it no longer
> has
> mpp->pg set, and the paths will get checked again after that.
> 
> Signed-off-by: Benjamin Marzinski <bmarzins@xxxxxxxxxx>
> ---
>  libmultipath/structs.h     |  2 +-
>  libmultipath/structs_vec.c |  1 +
>  multipathd/main.c          | 54 ++++++++++++++++++++++++++++++++----
> --
>  3 files changed, 48 insertions(+), 9 deletions(-)
> 
> diff --git a/libmultipath/structs.h b/libmultipath/structs.h
> index 457d7836..91509881 100644
> --- a/libmultipath/structs.h
> +++ b/libmultipath/structs.h
> @@ -455,7 +455,7 @@ struct multipath {
>  	int ghost_delay_tick;
>  	int queue_mode;
>  	unsigned int sync_tick;
> -	bool is_checked;
> +	int synced_count;
>  	uid_t uid;
>  	gid_t gid;
>  	mode_t mode;
> diff --git a/libmultipath/structs_vec.c b/libmultipath/structs_vec.c
> index 60360c76..704e0f21 100644
> --- a/libmultipath/structs_vec.c
> +++ b/libmultipath/structs_vec.c
> @@ -513,6 +513,7 @@ update_multipath_table (struct multipath *mpp,
> vector pathvec, int flags)
>  	conf = get_multipath_config();
>  	mpp->sync_tick = conf->max_checkint;
>  	put_multipath_config(conf);
> +	mpp->synced_count++;
>  
>  	r = libmp_mapinfo(DM_MAP_BY_NAME | MAPINFO_MPATH_ONLY,
>  			  (mapid_t) { .str = mpp->alias },
> diff --git a/multipathd/main.c b/multipathd/main.c
> index 27e18a0c..d51bc852 100644
> --- a/multipathd/main.c
> +++ b/multipathd/main.c
> @@ -2356,7 +2356,6 @@ do_check_mpp(struct vectors * vecs, struct
> multipath *mpp)
>  	int i, ret;
>  	struct path *pp;
>  
> -	mpp->is_checked = true;
>  	ret = update_multipath_strings(mpp, vecs->pathvec);
>  	if (ret != DMP_OK) {
>  		condlog(1, "%s: %s", mpp->alias, ret ==
> DMP_NOT_FOUND ?
> @@ -2428,7 +2427,7 @@ do_check_path (struct vectors * vecs, struct
> path * pp)
>  		condlog(0, "%s: path wwid change detected.
> Removing", pp->dev);
>  		return handle_path_wwid_change(pp, vecs)? -1 : 0;
>  	}
> -	if (!pp->mpp->is_checked) {
> +	if (pp->mpp->synced_count == 0) {
>  		do_check_mpp(vecs, pp->mpp);
>  		/* if update_multipath_strings orphaned the path,
> quit early */
>  		if (!pp->mpp)
> @@ -2787,18 +2786,57 @@ check_paths(struct vectors *vecs, unsigned
> int ticks, int *num_paths_p)
>  {
>  	unsigned int paths_checked = 0;
>  	struct timespec diff_time, start_time, end_time;
> +	struct multipath *mpp;
>  	struct path *pp;
>  	int i, rc;
>  
>  	get_monotonic_time(&start_time);
> +
> +	vector_foreach_slot(vecs->mpvec, mpp, i) {
> +		struct pathgroup *pgp;
> +		struct path *pp;
> +		int j, k;
> +		bool check_for_waiters = false;
> +		/* maps can be rechecked, so this is not always 0 */
> +		int synced_count = mpp->synced_count;
> +
> +		vector_foreach_slot (mpp->pg, pgp, j) {
> +			vector_foreach_slot (pgp->paths, pp, k) {
> +				if (!pp->mpp || pp->is_checked)
> +					continue;
> +				pp->is_checked = true;
> +				rc = check_path(vecs, pp, ticks,
> +						start_time.tv_sec);
> +				if (rc > 0)
> +					*num_paths_p += 1;
> +				if (++paths_checked % 128 == 0)
> +					check_for_waiters = true;
> +				/*
> +				 * mpp has been removed or resynced.
> Path may
> +				 * have been removed.
> +				 */
> +				if (VECTOR_SLOT(vecs->mpvec, i) !=
> mpp ||
> +				    synced_count != mpp-
> >synced_count) {
> +					i--;
> +					goto next_mpp;
> +				}

I'm aware that you have implemented the rather vague idea I had
mentioned previously, thanks a lot for that.

This is ok, but the fact that the arrays we are looping over can be
modified deeply inside functions inside the loop feels fragile. I
wonder if it would be possible to clean up our code flow such that
neither mpp nor path objects can be removed at this point the code. 

The sync code could set flags on paths and / or multipath objects that
tell the caller that these objects are no longer valid, and we could
remove them when we're done with the loop. I'm aware that we'd still
need to cover path group changes. Perhaps we should create a temporary
array of all paths in the map (possibly sorted by major/minor number).
We'd ignore newly added paths (they should have been checked recently
anyway, shouldn't they?) and skip over paths that are flagged as being
removed.

I realize I'm making vague suggestions again. Your current work shows
that this is more complicated than I first imagined. Thanks a lot for
considering all the corner cases.

This isn't something I'd expect to change in the current patch set,
just something to think about for the future.


> +			}
> +		}
> +next_mpp:
> +		if (check_for_waiters &&
> +		    (lock_has_waiters(&vecs->lock) ||
> waiting_clients())) {
> +			get_monotonic_time(&end_time);
> +			timespecsub(&end_time, &start_time,
> &diff_time);
> +			if (diff_time.tv_sec > 0)
> +				return CHECKER_RUNNING;
> +		}
> +	}
>  	vector_foreach_slot(vecs->pathvec, pp, i) {
> -		if (pp->is_checked)
> +		if (pp->mpp || pp->is_checked)
>  			continue;
>  		pp->is_checked = true;
> -		if (pp->mpp)
> -			rc = check_path(vecs, pp, ticks,
> start_time.tv_sec);
> -		else
> -			rc = handle_uninitialized_path(vecs, pp,
> ticks);
> +
> +		rc = handle_uninitialized_path(vecs, pp, ticks);
>  		if (rc < 0)
>  			i--;
>  		else
> @@ -2871,7 +2909,7 @@ checkerloop (void *ap)
>  			lock(&vecs->lock);
>  			pthread_testcancel();
>  			vector_foreach_slot(vecs->mpvec, mpp, i)
> -				mpp->is_checked = false;
> +				mpp->synced_count = 0;

Why do we need to reset this? Can't we just increment the counter
whenever we sync, and let it wrap at UINT_MAX?

>  			if (checker_state == CHECKER_STARTING) {
>  				vector_foreach_slot(vecs->mpvec,
> mpp, i)
>  					check_mpp(vecs, mpp, ticks);






[Index of Archives]     [DM Crypt]     [Fedora Desktop]     [ATA RAID]     [Fedora Marketing]     [Fedora Packaging]     [Fedora SELinux]     [Yosemite Discussion]     [KDE Users]     [Fedora Docs]

  Powered by Linux