device-mapper-multipath-0.9.9-17
Add 0083-libmultipath-add-purge_disconnected-configuration-op.patch
Add 0084-multipathd-implement-purge-functionality-for-disconn.patch
* Fixes RHEL-141287 ("Add purge_disconnected support to multipathd")
Resolves: RHEL-141287
This commit is contained in:
parent
91c21a51b4
commit
420113ceaf
279
0083-libmultipath-add-purge_disconnected-configuration-op.patch
Normal file
279
0083-libmultipath-add-purge_disconnected-configuration-op.patch
Normal file
@ -0,0 +1,279 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Brian Bunker <brian@purestorage.com>
|
||||
Date: Fri, 9 Jan 2026 16:50:43 -0800
|
||||
Subject: [PATCH] libmultipath: add purge_disconnected configuration option
|
||||
|
||||
Add a new configuration option 'purge_disconnected' that can be set
|
||||
per multipath device, hardware entry, or globally. This option will
|
||||
be used to control whether multipathd should automatically remove
|
||||
paths that are in a disconnected state.
|
||||
|
||||
The option is disabled by default (PURGE_DISCONNECTED_OFF).
|
||||
|
||||
This patch only adds the configuration infrastructure. The actual
|
||||
purge functionality will be implemented in a subsequent patch.
|
||||
|
||||
Signed-off-by: Brian Bunker <brian@purestorage.com>
|
||||
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
|
||||
Reviewed-by: Martin Wilck <mwilck@suse.com>
|
||||
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
|
||||
---
|
||||
libmultipath/config.c | 2 ++
|
||||
libmultipath/config.h | 3 +++
|
||||
libmultipath/configure.c | 1 +
|
||||
libmultipath/defaults.h | 1 +
|
||||
libmultipath/dict.c | 14 ++++++++++++++
|
||||
libmultipath/propsel.c | 16 ++++++++++++++++
|
||||
libmultipath/propsel.h | 1 +
|
||||
libmultipath/structs.h | 12 ++++++++++++
|
||||
multipath/multipath.conf.5.in | 22 ++++++++++++++++++++++
|
||||
9 files changed, 72 insertions(+)
|
||||
|
||||
diff --git a/libmultipath/config.c b/libmultipath/config.c
|
||||
index 3d5943d3..5c87fbe0 100644
|
||||
--- a/libmultipath/config.c
|
||||
+++ b/libmultipath/config.c
|
||||
@@ -470,6 +470,7 @@ merge_hwe (struct hwentry * dst, struct hwentry * src)
|
||||
merge_num(marginal_path_err_rate_threshold);
|
||||
merge_num(marginal_path_err_recheck_gap_time);
|
||||
merge_num(marginal_path_double_failed_time);
|
||||
+ merge_num(purge_disconnected);
|
||||
|
||||
snprintf(id, sizeof(id), "%s/%s", dst->vendor, dst->product);
|
||||
reconcile_features_with_options(id, &dst->features,
|
||||
@@ -517,6 +518,7 @@ merge_mpe(struct mpentry *dst, struct mpentry *src)
|
||||
merge_num(skip_kpartx);
|
||||
merge_num(max_sectors_kb);
|
||||
merge_num(ghost_delay);
|
||||
+ merge_num(purge_disconnected);
|
||||
merge_num(uid);
|
||||
merge_num(gid);
|
||||
merge_num(mode);
|
||||
diff --git a/libmultipath/config.h b/libmultipath/config.h
|
||||
index 158cebf0..c9e4029e 100644
|
||||
--- a/libmultipath/config.h
|
||||
+++ b/libmultipath/config.h
|
||||
@@ -89,6 +89,7 @@ struct hwentry {
|
||||
int marginal_path_err_rate_threshold;
|
||||
int marginal_path_err_recheck_gap_time;
|
||||
int marginal_path_double_failed_time;
|
||||
+ int purge_disconnected;
|
||||
int skip_kpartx;
|
||||
int max_sectors_kb;
|
||||
int ghost_delay;
|
||||
@@ -131,6 +132,7 @@ struct mpentry {
|
||||
int marginal_path_err_rate_threshold;
|
||||
int marginal_path_err_recheck_gap_time;
|
||||
int marginal_path_double_failed_time;
|
||||
+ int purge_disconnected;
|
||||
int skip_kpartx;
|
||||
int max_sectors_kb;
|
||||
int ghost_delay;
|
||||
@@ -189,6 +191,7 @@ struct config {
|
||||
int marginal_path_err_rate_threshold;
|
||||
int marginal_path_err_recheck_gap_time;
|
||||
int marginal_path_double_failed_time;
|
||||
+ int purge_disconnected;
|
||||
int uxsock_timeout;
|
||||
int strict_timing;
|
||||
int retrigger_tries;
|
||||
diff --git a/libmultipath/configure.c b/libmultipath/configure.c
|
||||
index b5c701fa..e92c905a 100644
|
||||
--- a/libmultipath/configure.c
|
||||
+++ b/libmultipath/configure.c
|
||||
@@ -355,6 +355,7 @@ int setup_map(struct multipath *mpp, char **params, struct vectors *vecs)
|
||||
select_max_sectors_kb(conf, mpp);
|
||||
select_ghost_delay(conf, mpp);
|
||||
select_flush_on_last_del(conf, mpp);
|
||||
+ select_purge_disconnected(conf, mpp);
|
||||
|
||||
sysfs_set_scsi_tmo(conf, mpp);
|
||||
marginal_pathgroups = conf->marginal_pathgroups;
|
||||
diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h
|
||||
index 3602636c..ed31e475 100644
|
||||
--- a/libmultipath/defaults.h
|
||||
+++ b/libmultipath/defaults.h
|
||||
@@ -57,6 +57,7 @@
|
||||
#define DEFAULT_ALL_TG_PT ALL_TG_PT_OFF
|
||||
#define DEFAULT_RECHECK_WWID RECHECK_WWID_OFF
|
||||
#define DEFAULT_AUTO_RESIZE AUTO_RESIZE_NEVER
|
||||
+#define DEFAULT_PURGE_DISCONNECTED PURGE_DISCONNECTED_OFF
|
||||
/* Enable no foreign libraries by default */
|
||||
#define DEFAULT_ENABLE_FOREIGN "NONE"
|
||||
|
||||
diff --git a/libmultipath/dict.c b/libmultipath/dict.c
|
||||
index 546103f2..968163d7 100644
|
||||
--- a/libmultipath/dict.c
|
||||
+++ b/libmultipath/dict.c
|
||||
@@ -941,6 +941,16 @@ declare_hw_snprint(skip_kpartx, print_yes_no_undef)
|
||||
declare_mp_handler(skip_kpartx, set_yes_no_undef)
|
||||
declare_mp_snprint(skip_kpartx, print_yes_no_undef)
|
||||
|
||||
+declare_def_handler(purge_disconnected, set_yes_no_undef)
|
||||
+declare_def_snprint_defint(purge_disconnected, print_yes_no_undef,
|
||||
+ DEFAULT_PURGE_DISCONNECTED)
|
||||
+declare_ovr_handler(purge_disconnected, set_yes_no_undef)
|
||||
+declare_ovr_snprint(purge_disconnected, print_yes_no_undef)
|
||||
+declare_hw_handler(purge_disconnected, set_yes_no_undef)
|
||||
+declare_hw_snprint(purge_disconnected, print_yes_no_undef)
|
||||
+declare_mp_handler(purge_disconnected, set_yes_no_undef)
|
||||
+declare_mp_snprint(purge_disconnected, print_yes_no_undef)
|
||||
+
|
||||
declare_def_range_handler(remove_retries, 0, INT_MAX)
|
||||
declare_def_snprint(remove_retries, print_int)
|
||||
|
||||
@@ -2227,6 +2237,7 @@ init_keywords(vector keywords)
|
||||
install_keyword("retrigger_delay", &def_retrigger_delay_handler, &snprint_def_retrigger_delay);
|
||||
install_keyword("missing_uev_wait_timeout", &def_uev_wait_timeout_handler, &snprint_def_uev_wait_timeout);
|
||||
install_keyword("skip_kpartx", &def_skip_kpartx_handler, &snprint_def_skip_kpartx);
|
||||
+ install_keyword("purge_disconnected", &def_purge_disconnected_handler, &snprint_def_purge_disconnected);
|
||||
install_keyword("disable_changed_wwids", &deprecated_disable_changed_wwids_handler, &snprint_deprecated);
|
||||
install_keyword("remove_retries", &def_remove_retries_handler, &snprint_def_remove_retries);
|
||||
install_keyword("max_sectors_kb", &def_max_sectors_kb_handler, &snprint_def_max_sectors_kb);
|
||||
@@ -2310,6 +2321,7 @@ init_keywords(vector keywords)
|
||||
install_keyword("marginal_path_err_recheck_gap_time", &hw_marginal_path_err_recheck_gap_time_handler, &snprint_hw_marginal_path_err_recheck_gap_time);
|
||||
install_keyword("marginal_path_double_failed_time", &hw_marginal_path_double_failed_time_handler, &snprint_hw_marginal_path_double_failed_time);
|
||||
install_keyword("skip_kpartx", &hw_skip_kpartx_handler, &snprint_hw_skip_kpartx);
|
||||
+ install_keyword("purge_disconnected", &hw_purge_disconnected_handler, &snprint_hw_purge_disconnected);
|
||||
install_keyword("max_sectors_kb", &hw_max_sectors_kb_handler, &snprint_hw_max_sectors_kb);
|
||||
install_keyword("ghost_delay", &hw_ghost_delay_handler, &snprint_hw_ghost_delay);
|
||||
install_keyword("all_tg_pt", &hw_all_tg_pt_handler, &snprint_hw_all_tg_pt);
|
||||
@@ -2355,6 +2367,7 @@ init_keywords(vector keywords)
|
||||
install_keyword("marginal_path_double_failed_time", &ovr_marginal_path_double_failed_time_handler, &snprint_ovr_marginal_path_double_failed_time);
|
||||
|
||||
install_keyword("skip_kpartx", &ovr_skip_kpartx_handler, &snprint_ovr_skip_kpartx);
|
||||
+ install_keyword("purge_disconnected", &ovr_purge_disconnected_handler, &snprint_ovr_purge_disconnected);
|
||||
install_keyword("max_sectors_kb", &ovr_max_sectors_kb_handler, &snprint_ovr_max_sectors_kb);
|
||||
install_keyword("ghost_delay", &ovr_ghost_delay_handler, &snprint_ovr_ghost_delay);
|
||||
install_keyword("all_tg_pt", &ovr_all_tg_pt_handler, &snprint_ovr_all_tg_pt);
|
||||
@@ -2400,6 +2413,7 @@ init_keywords(vector keywords)
|
||||
install_keyword("marginal_path_err_recheck_gap_time", &mp_marginal_path_err_recheck_gap_time_handler, &snprint_mp_marginal_path_err_recheck_gap_time);
|
||||
install_keyword("marginal_path_double_failed_time", &mp_marginal_path_double_failed_time_handler, &snprint_mp_marginal_path_double_failed_time);
|
||||
install_keyword("skip_kpartx", &mp_skip_kpartx_handler, &snprint_mp_skip_kpartx);
|
||||
+ install_keyword("purge_disconnected", &mp_purge_disconnected_handler, &snprint_mp_purge_disconnected);
|
||||
install_keyword("max_sectors_kb", &mp_max_sectors_kb_handler, &snprint_mp_max_sectors_kb);
|
||||
install_keyword("ghost_delay", &mp_ghost_delay_handler, &snprint_mp_ghost_delay);
|
||||
install_sublevel_end();
|
||||
diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c
|
||||
index 5ad0b78c..9b7aba43 100644
|
||||
--- a/libmultipath/propsel.c
|
||||
+++ b/libmultipath/propsel.c
|
||||
@@ -1367,6 +1367,22 @@ out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
+int select_purge_disconnected(struct config *conf, struct multipath *mp)
|
||||
+{
|
||||
+ const char *origin;
|
||||
+
|
||||
+ mp_set_mpe(purge_disconnected);
|
||||
+ mp_set_ovr(purge_disconnected);
|
||||
+ mp_set_hwe(purge_disconnected);
|
||||
+ mp_set_conf(purge_disconnected);
|
||||
+ mp_set_default(purge_disconnected, DEFAULT_PURGE_DISCONNECTED);
|
||||
+out:
|
||||
+ condlog(3, "%s: purge_disconnected = %s %s", mp->alias,
|
||||
+ (mp->purge_disconnected == PURGE_DISCONNECTED_ON) ? "yes" : "no",
|
||||
+ origin);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
int select_max_sectors_kb(struct config *conf, struct multipath * mp)
|
||||
{
|
||||
const char *origin;
|
||||
diff --git a/libmultipath/propsel.h b/libmultipath/propsel.h
|
||||
index 73615c2f..ee83253e 100644
|
||||
--- a/libmultipath/propsel.h
|
||||
+++ b/libmultipath/propsel.h
|
||||
@@ -37,6 +37,7 @@ int select_marginal_path_err_rate_threshold(struct config *conf, struct multipat
|
||||
int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multipath *mp);
|
||||
int select_marginal_path_double_failed_time(struct config *conf, struct multipath *mp);
|
||||
int select_ghost_delay(struct config *conf, struct multipath * mp);
|
||||
+int select_purge_disconnected(struct config *conf, struct multipath *mp);
|
||||
void reconcile_features_with_options(const char *id, char **features,
|
||||
int* no_path_retry,
|
||||
int *retain_hwhandler);
|
||||
diff --git a/libmultipath/structs.h b/libmultipath/structs.h
|
||||
index 2ff195f3..e3f2ffb9 100644
|
||||
--- a/libmultipath/structs.h
|
||||
+++ b/libmultipath/structs.h
|
||||
@@ -186,6 +186,17 @@ enum auto_resize_state {
|
||||
AUTO_RESIZE_GROW_SHRINK,
|
||||
};
|
||||
|
||||
+/*
|
||||
+ * purge_disconnected configuration option (per multipath device)
|
||||
+ * Controls whether paths that become disconnected at the storage target
|
||||
+ * should be automatically removed from the system via sysfs.
|
||||
+ */
|
||||
+enum purge_disconnected_states {
|
||||
+ PURGE_DISCONNECTED_UNDEF = YNU_UNDEF,
|
||||
+ PURGE_DISCONNECTED_OFF = YNU_NO, /* Don't purge */
|
||||
+ PURGE_DISCONNECTED_ON = YNU_YES, /* Purge disconnected paths */
|
||||
+};
|
||||
+
|
||||
#define PROTOCOL_UNSET -1
|
||||
|
||||
enum scsi_protocol {
|
||||
@@ -453,6 +464,7 @@ struct multipath {
|
||||
int ghost_delay;
|
||||
int ghost_delay_tick;
|
||||
int queue_mode;
|
||||
+ int purge_disconnected;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
mode_t mode;
|
||||
diff --git a/multipath/multipath.conf.5.in b/multipath/multipath.conf.5.in
|
||||
index 31740a1f..af85092e 100644
|
||||
--- a/multipath/multipath.conf.5.in
|
||||
+++ b/multipath/multipath.conf.5.in
|
||||
@@ -1304,6 +1304,22 @@ The default is: \fBno\fR
|
||||
.
|
||||
.
|
||||
.TP
|
||||
+.B purge_disconnected
|
||||
+If set to
|
||||
+.I yes
|
||||
+, multipathd will automatically remove devices that are in a disconnected state.
|
||||
+A path is considered disconnected when the TUR (Test Unit Ready) path checker
|
||||
+receives the SCSI sense code "LOGICAL UNIT NOT SUPPORTED" (sense key 0x5,
|
||||
+ASC/ASCQ 0x25/0x00). This typically indicates that the LUN has been unmapped
|
||||
+or is no longer presented by the storage array. This option helps clean up
|
||||
+stale device entries that would otherwise remain in the system.
|
||||
+.RS
|
||||
+.TP
|
||||
+The default is: \fBno\fR
|
||||
+.RE
|
||||
+.
|
||||
+.
|
||||
+.TP
|
||||
.B disable_changed_wwids
|
||||
(Deprecated) This option is not supported anymore, and will be ignored.
|
||||
.RE
|
||||
@@ -1601,6 +1617,8 @@ section:
|
||||
.TP
|
||||
.B skip_kpartx
|
||||
.TP
|
||||
+.B purge_disconnected
|
||||
+.TP
|
||||
.B max_sectors_kb
|
||||
.TP
|
||||
.B ghost_delay
|
||||
@@ -1786,6 +1804,8 @@ section:
|
||||
.TP
|
||||
.B skip_kpartx
|
||||
.TP
|
||||
+.B purge_disconnected
|
||||
+.TP
|
||||
.B max_sectors_kb
|
||||
.TP
|
||||
.B ghost_delay
|
||||
@@ -1870,6 +1890,8 @@ the values are taken from the \fIdevices\fR or \fIdefaults\fR sections:
|
||||
.TP
|
||||
.B skip_kpartx
|
||||
.TP
|
||||
+.B purge_disconnected
|
||||
+.TP
|
||||
.B max_sectors_kb
|
||||
.TP
|
||||
.B ghost_delay
|
||||
752
0084-multipathd-implement-purge-functionality-for-disconn.patch
Normal file
752
0084-multipathd-implement-purge-functionality-for-disconn.patch
Normal file
@ -0,0 +1,752 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Brian Bunker <brian@purestorage.com>
|
||||
Date: Fri, 9 Jan 2026 16:50:43 -0800
|
||||
Subject: [PATCH] multipathd: implement purge functionality for disconnected
|
||||
paths
|
||||
|
||||
Implement automatic purging of paths that have been disconnected at the
|
||||
storage target (e.g., LUN unmapped). This builds on the purge_disconnected
|
||||
configuration option added in the previous patch.
|
||||
|
||||
This adds:
|
||||
- New PATH_DISCONNECTED checker state to signal disconnection
|
||||
- TUR checker support for detecting LUN NOT SUPPORTED (ASC/ASCQ 0x25/0x00)
|
||||
- Purge thread (purgeloop) that removes paths via sysfs delete attribute
|
||||
- State machine to track disconnection and delay purging
|
||||
- Conversion of PATH_DISCONNECTED to PATH_DOWN for normal processing
|
||||
|
||||
The purge thread runs independently and processes paths that have been
|
||||
marked for purging by the checker thread. Paths are only purged after
|
||||
remaining disconnected for delay_wait_checks intervals to avoid removing
|
||||
paths that are temporarily flapping.
|
||||
|
||||
Signed-off-by: Brian Bunker <brian@purestorage.com>
|
||||
Signed-off-by: Krishna Kant <krishna.kant@purestorage.com>
|
||||
Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
|
||||
Reviewed-by: Martin Wilck <mwilck@suse.com>
|
||||
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
|
||||
---
|
||||
libmultipath/checkers.c | 2 +
|
||||
libmultipath/checkers.h | 15 +-
|
||||
libmultipath/checkers/tur.c | 10 ++
|
||||
libmultipath/discovery.c | 17 ++
|
||||
libmultipath/io_err_stat.c | 1 +
|
||||
libmultipath/print.c | 2 +
|
||||
libmultipath/structs.h | 14 ++
|
||||
multipathd/Makefile | 2 +-
|
||||
multipathd/main.c | 74 +++++++-
|
||||
multipathd/purge.c | 326 ++++++++++++++++++++++++++++++++++++
|
||||
multipathd/purge.h | 41 +++++
|
||||
11 files changed, 496 insertions(+), 8 deletions(-)
|
||||
create mode 100644 multipathd/purge.c
|
||||
create mode 100644 multipathd/purge.h
|
||||
|
||||
diff --git a/libmultipath/checkers.c b/libmultipath/checkers.c
|
||||
index fdb91e17..5eca2b44 100644
|
||||
--- a/libmultipath/checkers.c
|
||||
+++ b/libmultipath/checkers.c
|
||||
@@ -41,6 +41,7 @@ static const char *checker_state_names[PATH_MAX_STATE] = {
|
||||
[PATH_TIMEOUT] = "timeout",
|
||||
[PATH_REMOVED] = "removed",
|
||||
[PATH_DELAYED] = "delayed",
|
||||
+ [PATH_DISCONNECTED] = "disconnected",
|
||||
};
|
||||
|
||||
static LIST_HEAD(checkers);
|
||||
@@ -344,6 +345,7 @@ static const char *generic_msg[CHECKER_GENERIC_MSGTABLE_SIZE] = {
|
||||
[CHECKER_MSGID_DOWN] = " reports path is down",
|
||||
[CHECKER_MSGID_GHOST] = " reports path is ghost",
|
||||
[CHECKER_MSGID_UNSUPPORTED] = " doesn't support this device",
|
||||
+ [CHECKER_MSGID_DISCONNECTED] = " no access to this device",
|
||||
};
|
||||
|
||||
const char *checker_message(const struct checker *c)
|
||||
diff --git a/libmultipath/checkers.h b/libmultipath/checkers.h
|
||||
index ea1e8af6..a0df88db 100644
|
||||
--- a/libmultipath/checkers.h
|
||||
+++ b/libmultipath/checkers.h
|
||||
@@ -65,6 +65,15 @@
|
||||
* delay_watch_checks checks, when it comes back up again, it will not
|
||||
* be marked as up until it has been up for delay_wait_checks checks.
|
||||
* During this time, it is marked as "delayed"
|
||||
+ *
|
||||
+ * PATH_DISCONNECTED is a special ephemeral state used to signal that a path
|
||||
+ * has been disconnected at the storage target (e.g., LUN unmapped). When a
|
||||
+ * checker returns PATH_DISCONNECTED:
|
||||
+ * 1. The path's pp->disconnected field is set to track purge state
|
||||
+ * 2. The state is immediately converted to PATH_DOWN for normal processing
|
||||
+ * 3. If purge_disconnected is enabled, the path will be removed via sysfs
|
||||
+ * This state should never be stored in pp->state or pp->chkrstate; it exists
|
||||
+ * only as a transient return value from checkers to trigger special handling.
|
||||
*/
|
||||
enum path_check_state {
|
||||
PATH_WILD = 0,
|
||||
@@ -77,6 +86,7 @@ enum path_check_state {
|
||||
PATH_TIMEOUT,
|
||||
PATH_REMOVED,
|
||||
PATH_DELAYED,
|
||||
+ PATH_DISCONNECTED, /* Ephemeral: mapped to PATH_DOWN */
|
||||
PATH_MAX_STATE
|
||||
};
|
||||
|
||||
@@ -112,9 +122,10 @@ enum {
|
||||
CHECKER_MSGID_DOWN,
|
||||
CHECKER_MSGID_GHOST,
|
||||
CHECKER_MSGID_UNSUPPORTED,
|
||||
+ CHECKER_MSGID_DISCONNECTED,
|
||||
CHECKER_GENERIC_MSGTABLE_SIZE,
|
||||
- CHECKER_FIRST_MSGID = 100, /* lowest msgid for checkers */
|
||||
- CHECKER_MSGTABLE_SIZE = 100, /* max msg table size for checkers */
|
||||
+ CHECKER_FIRST_MSGID = 100, /* lowest msgid for checkers */
|
||||
+ CHECKER_MSGTABLE_SIZE = 100, /* max msg table size for checkers */
|
||||
};
|
||||
|
||||
struct checker_class;
|
||||
diff --git a/libmultipath/checkers/tur.c b/libmultipath/checkers/tur.c
|
||||
index 2800446d..7fd9e959 100644
|
||||
--- a/libmultipath/checkers/tur.c
|
||||
+++ b/libmultipath/checkers/tur.c
|
||||
@@ -196,6 +196,16 @@ retry:
|
||||
*msgid = MSG_TUR_TRANSITIONING;
|
||||
return PATH_PENDING;
|
||||
}
|
||||
+ } else if (key == 0x5) {
|
||||
+ /* Illegal request */
|
||||
+ if (asc == 0x25 && ascq == 0x00) {
|
||||
+ /*
|
||||
+ * LUN NOT SUPPORTED: unmapped at target.
|
||||
+ * Signals pp->disconnected, becomes PATH_DOWN.
|
||||
+ */
|
||||
+ *msgid = CHECKER_MSGID_DISCONNECTED;
|
||||
+ return PATH_DISCONNECTED;
|
||||
+ }
|
||||
}
|
||||
*msgid = CHECKER_MSGID_DOWN;
|
||||
return PATH_DOWN;
|
||||
diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c
|
||||
index a1284e73..289c97c7 100644
|
||||
--- a/libmultipath/discovery.c
|
||||
+++ b/libmultipath/discovery.c
|
||||
@@ -2461,8 +2461,25 @@ int pathinfo(struct path *pp, struct config *conf, int mask)
|
||||
pp->state == PATH_UNCHECKED ||
|
||||
pp->state == PATH_WILD)
|
||||
pp->chkrstate = pp->state = newstate;
|
||||
+ /*
|
||||
+ * PATH_TIMEOUT and PATH_DISCONNECTED are ephemeral
|
||||
+ * states that should never be stored in pp->state.
|
||||
+ * Convert them to PATH_DOWN immediately.
|
||||
+ */
|
||||
if (pp->state == PATH_TIMEOUT)
|
||||
pp->state = PATH_DOWN;
|
||||
+ if (pp->state == PATH_DISCONNECTED) {
|
||||
+ int purge_enabled = pp->mpp &&
|
||||
+ pp->mpp->purge_disconnected ==
|
||||
+ PURGE_DISCONNECTED_ON;
|
||||
+ if (purge_enabled &&
|
||||
+ pp->disconnected == NOT_DISCONNECTED) {
|
||||
+ condlog(2, "%s: mark path for purge",
|
||||
+ pp->dev);
|
||||
+ pp->disconnected = DISCONNECTED_READY_FOR_PURGE;
|
||||
+ }
|
||||
+ pp->state = PATH_DOWN;
|
||||
+ }
|
||||
if (pp->state == PATH_UP && !pp->size) {
|
||||
condlog(3, "%s: device size is 0, "
|
||||
"path unusable", pp->dev);
|
||||
diff --git a/libmultipath/io_err_stat.c b/libmultipath/io_err_stat.c
|
||||
index 1c594451..ca23187d 100644
|
||||
--- a/libmultipath/io_err_stat.c
|
||||
+++ b/libmultipath/io_err_stat.c
|
||||
@@ -397,6 +397,7 @@ static void account_async_io_state(struct io_err_stat_path *pp, int rc)
|
||||
switch (rc) {
|
||||
case PATH_DOWN:
|
||||
case PATH_TIMEOUT:
|
||||
+ case PATH_DISCONNECTED:
|
||||
pp->io_err_nr++;
|
||||
break;
|
||||
case PATH_UNCHECKED:
|
||||
diff --git a/libmultipath/print.c b/libmultipath/print.c
|
||||
index fbed2dd5..1946728f 100644
|
||||
--- a/libmultipath/print.c
|
||||
+++ b/libmultipath/print.c
|
||||
@@ -547,6 +547,8 @@ snprint_chk_state (struct strbuf *buff, const struct path * pp)
|
||||
return append_strbuf_str(buff, "i/o timeout");
|
||||
case PATH_DELAYED:
|
||||
return append_strbuf_str(buff, "delayed");
|
||||
+ case PATH_DISCONNECTED:
|
||||
+ return append_strbuf_str(buff, "disconnected");
|
||||
default:
|
||||
return append_strbuf_str(buff, "undef");
|
||||
}
|
||||
diff --git a/libmultipath/structs.h b/libmultipath/structs.h
|
||||
index e3f2ffb9..dc184a93 100644
|
||||
--- a/libmultipath/structs.h
|
||||
+++ b/libmultipath/structs.h
|
||||
@@ -197,6 +197,18 @@ enum purge_disconnected_states {
|
||||
PURGE_DISCONNECTED_ON = YNU_YES, /* Purge disconnected paths */
|
||||
};
|
||||
|
||||
+/*
|
||||
+ * Path disconnection state (per path)
|
||||
+ * Tracks whether a path has been marked for purge and whether it's already queued.
|
||||
+ */
|
||||
+enum path_disconnected_state {
|
||||
+ NOT_DISCONNECTED, /* Path is not disconnected */
|
||||
+ DISCONNECTED_READY_FOR_PURGE, /* Path is disconnected and ready to be
|
||||
+ queued for purge */
|
||||
+ DISCONNECTED_QUEUED_FOR_PURGE, /* Path is disconnected and already
|
||||
+ queued for purge */
|
||||
+};
|
||||
+
|
||||
#define PROTOCOL_UNSET -1
|
||||
|
||||
enum scsi_protocol {
|
||||
@@ -376,6 +388,8 @@ struct path {
|
||||
int state;
|
||||
int dmstate;
|
||||
int chkrstate;
|
||||
+ enum path_disconnected_state disconnected; /* Marked for purge due to
|
||||
+ disconnection */
|
||||
int failcount;
|
||||
int priority;
|
||||
int pgindex;
|
||||
diff --git a/multipathd/Makefile b/multipathd/Makefile
|
||||
index 61cf1af6..bedb09bc 100644
|
||||
--- a/multipathd/Makefile
|
||||
+++ b/multipathd/Makefile
|
||||
@@ -36,7 +36,7 @@ endif
|
||||
|
||||
CLI_OBJS := multipathc.o cli.o
|
||||
OBJS := main.o pidfile.o uxlsnr.o uxclnt.o cli.o cli_handlers.o waiter.o \
|
||||
- dmevents.o init_unwinder.o
|
||||
+ dmevents.o init_unwinder.o purge.o
|
||||
ifeq ($(FPIN_SUPPORT),1)
|
||||
OBJS += fpin_handlers.o
|
||||
endif
|
||||
diff --git a/multipathd/main.c b/multipathd/main.c
|
||||
index e9165350..7cb57e3c 100644
|
||||
--- a/multipathd/main.c
|
||||
+++ b/multipathd/main.c
|
||||
@@ -83,6 +83,7 @@
|
||||
#include "dmevents.h"
|
||||
#include "io_err_stat.h"
|
||||
#include "foreign.h"
|
||||
+#include "purge.h"
|
||||
#include "../third-party/valgrind/drd.h"
|
||||
#include "init_unwinder.h"
|
||||
|
||||
@@ -135,11 +136,11 @@ static bool __delayed_reconfig;
|
||||
pid_t daemon_pid;
|
||||
static pthread_mutex_t config_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t config_cond;
|
||||
-static pthread_t check_thr, uevent_thr, uxlsnr_thr, uevq_thr, dmevent_thr,
|
||||
- fpin_thr, fpin_consumer_thr;
|
||||
-static bool check_thr_started, uevent_thr_started, uxlsnr_thr_started,
|
||||
- uevq_thr_started, dmevent_thr_started, fpin_thr_started,
|
||||
- fpin_consumer_thr_started;
|
||||
+static pthread_t check_thr, purge_thr, uevent_thr, uxlsnr_thr, uevq_thr,
|
||||
+ dmevent_thr, fpin_thr, fpin_consumer_thr;
|
||||
+static bool check_thr_started, purge_thr_started, uevent_thr_started,
|
||||
+ uxlsnr_thr_started, uevq_thr_started, dmevent_thr_started,
|
||||
+ fpin_thr_started, fpin_consumer_thr_started;
|
||||
static int pid_fd = -1;
|
||||
|
||||
static inline enum daemon_status get_running_state(void)
|
||||
@@ -2484,6 +2485,28 @@ check_path (struct vectors * vecs, struct path * pp, unsigned int ticks)
|
||||
if (newstate == PATH_REMOVED)
|
||||
newstate = PATH_DOWN;
|
||||
|
||||
+ /*
|
||||
+ * PATH_DISCONNECTED is an ephemeral state used to signal that a path
|
||||
+ * has been disconnected at the storage target (LUN unmapped). We use
|
||||
+ * it to set pp->disconnected for purge tracking, then immediately
|
||||
+ * convert it to PATH_DOWN for normal path failure handling.
|
||||
+ *
|
||||
+ * This ensures PATH_DISCONNECTED never gets stored in pp->state or
|
||||
+ * pp->chkrstate - it exists only as a transient signal from the
|
||||
+ * checker to trigger special handling before becoming PATH_DOWN.
|
||||
+ */
|
||||
+ if (newstate == PATH_DISCONNECTED) {
|
||||
+ if (pp->mpp &&
|
||||
+ pp->mpp->purge_disconnected == PURGE_DISCONNECTED_ON &&
|
||||
+ pp->disconnected == NOT_DISCONNECTED) {
|
||||
+ condlog(2, "%s: mark (%s) path for purge", pp->dev,
|
||||
+ checker_state_name(newstate));
|
||||
+ pp->disconnected = DISCONNECTED_READY_FOR_PURGE;
|
||||
+ }
|
||||
+ /* Always convert to PATH_DOWN for normal processing */
|
||||
+ newstate = PATH_DOWN;
|
||||
+ }
|
||||
+
|
||||
if (newstate == PATH_WILD || newstate == PATH_UNCHECKED) {
|
||||
condlog(2, "%s: unusable path (%s) - checker failed",
|
||||
pp->dev, checker_state_name(newstate));
|
||||
@@ -2800,6 +2823,7 @@ checkerloop (void *ap)
|
||||
int num_paths = 0, strict_timing, rc = 0, i = 0;
|
||||
unsigned int ticks = 0;
|
||||
enum checker_state checker_state = CHECKER_STARTING;
|
||||
+ LIST_HEAD(purge_list);
|
||||
|
||||
if (set_config_state(DAEMON_RUNNING) != DAEMON_RUNNING)
|
||||
/* daemon shutdown */
|
||||
@@ -2878,6 +2902,12 @@ unlock:
|
||||
}
|
||||
}
|
||||
|
||||
+ /*
|
||||
+ * Cleanup handler to free purge_list if thread is cancelled.
|
||||
+ * This prevents memory leaks during shutdown.
|
||||
+ */
|
||||
+ pthread_cleanup_push(cleanup_purge_list, &purge_list);
|
||||
+
|
||||
pthread_cleanup_push(cleanup_lock, &vecs->lock);
|
||||
lock(&vecs->lock);
|
||||
pthread_testcancel();
|
||||
@@ -2886,6 +2916,11 @@ unlock:
|
||||
missing_uev_wait_tick(vecs);
|
||||
ghost_delay_tick(vecs);
|
||||
partial_retrigger_tick(vecs->pathvec);
|
||||
+ /*
|
||||
+ * Build purge list for disconnected paths.
|
||||
+ * The caller will queue it after releasing vecs->lock.
|
||||
+ */
|
||||
+ build_purge_list(vecs, &purge_list);
|
||||
lock_cleanup_pop(vecs->lock);
|
||||
|
||||
if (count)
|
||||
@@ -2900,6 +2935,26 @@ unlock:
|
||||
lock_cleanup_pop(vecs->lock);
|
||||
}
|
||||
|
||||
+ /*
|
||||
+ * Queue purge work for disconnected paths.
|
||||
+ * This is done after releasing vecs->lock to avoid holding
|
||||
+ * the lock while signaling the purge thread.
|
||||
+ */
|
||||
+ if (!list_empty(&purge_list)) {
|
||||
+ pthread_cleanup_push(cleanup_mutex, &purge_mutex);
|
||||
+ pthread_mutex_lock(&purge_mutex);
|
||||
+ pthread_testcancel();
|
||||
+ list_splice_tail_init(&purge_list, &purge_queue);
|
||||
+ pthread_cond_signal(&purge_cond);
|
||||
+ pthread_cleanup_pop(1);
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * Pop cleanup handler. Execute it (arg=1) to free purge_list
|
||||
+ * at the end of each iteration.
|
||||
+ */
|
||||
+ pthread_cleanup_pop(1);
|
||||
+
|
||||
get_monotonic_time(&end_time);
|
||||
timespecsub(&end_time, &start_time, &diff_time);
|
||||
if (num_paths) {
|
||||
@@ -3396,6 +3451,8 @@ static void cleanup_threads(void)
|
||||
|
||||
if (check_thr_started)
|
||||
pthread_cancel(check_thr);
|
||||
+ if (purge_thr_started)
|
||||
+ pthread_cancel(purge_thr);
|
||||
if (uevent_thr_started)
|
||||
pthread_cancel(uevent_thr);
|
||||
if (uxlsnr_thr_started)
|
||||
@@ -3412,6 +3469,8 @@ static void cleanup_threads(void)
|
||||
|
||||
if (check_thr_started)
|
||||
pthread_join(check_thr, NULL);
|
||||
+ if (purge_thr_started)
|
||||
+ pthread_join(purge_thr, NULL);
|
||||
if (uevent_thr_started)
|
||||
pthread_join(uevent_thr, NULL);
|
||||
if (uxlsnr_thr_started)
|
||||
@@ -3664,6 +3723,11 @@ child (__attribute__((unused)) void *param)
|
||||
goto failed;
|
||||
} else
|
||||
check_thr_started = true;
|
||||
+ if ((rc = pthread_create(&purge_thr, &misc_attr, purgeloop, vecs))) {
|
||||
+ condlog(0, "failed to create purge loop thread: %d", rc);
|
||||
+ goto failed;
|
||||
+ } else
|
||||
+ purge_thr_started = true;
|
||||
if ((rc = pthread_create(&uevq_thr, &misc_attr, uevqloop, vecs))) {
|
||||
condlog(0, "failed to create uevent dispatcher: %d", rc);
|
||||
goto failed;
|
||||
diff --git a/multipathd/purge.c b/multipathd/purge.c
|
||||
new file mode 100644
|
||||
index 00000000..44f0c905
|
||||
--- /dev/null
|
||||
+++ b/multipathd/purge.c
|
||||
@@ -0,0 +1,326 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
+/*
|
||||
+ * Copyright (C) 2025 Brian Bunker <brian@purestorage.com>
|
||||
+ * Copyright (C) 2025 Krishna Kant <krishna.kant@purestorage.com>
|
||||
+ */
|
||||
+
|
||||
+#include <pthread.h>
|
||||
+#include <sys/mman.h>
|
||||
+#include <sys/stat.h>
|
||||
+#include <errno.h>
|
||||
+#include <string.h>
|
||||
+#include <unistd.h>
|
||||
+#include <libudev.h>
|
||||
+#include <urcu.h>
|
||||
+
|
||||
+#include "vector.h"
|
||||
+#include "structs.h"
|
||||
+#include "structs_vec.h"
|
||||
+#include "debug.h"
|
||||
+#include "util.h"
|
||||
+#include "lock.h"
|
||||
+#include "sysfs.h"
|
||||
+#include "list.h"
|
||||
+#include "purge.h"
|
||||
+
|
||||
+pthread_mutex_t purge_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
+pthread_cond_t purge_cond = PTHREAD_COND_INITIALIZER;
|
||||
+LIST_HEAD(purge_queue);
|
||||
+
|
||||
+/*
|
||||
+ * Information needed to purge a path. We copy this data while holding
|
||||
+ * vecs->lock, then release the lock before doing the actual sysfs write.
|
||||
+ * This prevents blocking other operations while waiting for sysfs I/O.
|
||||
+ *
|
||||
+ * The udev device reference captures the sysfs path (including H:C:T:L).
|
||||
+ * The duplicated fd prevents device name/number reuse: the kernel will not
|
||||
+ * reuse the device's minor number (which maps to the device name) for a new
|
||||
+ * device while we hold an open file descriptor, even if the original device
|
||||
+ * has been removed. This protects against deleting a new device that reused
|
||||
+ * the same name after the original was removed externally.
|
||||
+ */
|
||||
+struct purge_path_info {
|
||||
+ struct list_head node; /* List linkage */
|
||||
+ struct udev_device *udev; /* Udev device (refcounted) */
|
||||
+ int fd; /* Dup'd fd prevents device reuse */
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * Attempt to delete a path by writing to the SCSI device's sysfs delete
|
||||
+ * attribute. This triggers kernel-level device removal. The actual cleanup
|
||||
+ * of the path structure from pathvec happens later when a uevent arrives
|
||||
+ * (handled by uev_remove_path).
|
||||
+ *
|
||||
+ * This function does NOT require vecs->lock to be held, as it operates on
|
||||
+ * copied data. This function may block while writing to sysfs, which is
|
||||
+ * why it's called without holding any locks.
|
||||
+ *
|
||||
+ * Protection against device reuse:
|
||||
+ * The duplicated fd in purge_path_info prevents the kernel from reusing
|
||||
+ * the device's minor number (and thus the device name like /dev/sdd) for
|
||||
+ * a new device, even if the original device has been removed externally.
|
||||
+ * This ensures we cannot accidentally delete a new device that reused the
|
||||
+ * same name. The kernel maintains this guarantee as long as we hold the
|
||||
+ * open file descriptor.
|
||||
+ */
|
||||
+static void delete_path_sysfs(struct purge_path_info *info)
|
||||
+{
|
||||
+ struct udev_device *ud;
|
||||
+ const char *devname;
|
||||
+
|
||||
+ if (!info->udev)
|
||||
+ goto out;
|
||||
+
|
||||
+ devname = udev_device_get_devnode(info->udev);
|
||||
+
|
||||
+ /*
|
||||
+ * Get the SCSI device parent. This is where we'll write to the
|
||||
+ * "delete" attribute to trigger device removal.
|
||||
+ */
|
||||
+ ud = udev_device_get_parent_with_subsystem_devtype(info->udev, "scsi",
|
||||
+ "scsi_device");
|
||||
+ if (!ud) {
|
||||
+ condlog(3, "%s: failed to purge, no SCSI parent found", devname);
|
||||
+ goto out;
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * Write "1" to the SCSI device's delete attribute to trigger
|
||||
+ * kernel-level device removal.
|
||||
+ */
|
||||
+ if (sysfs_attr_set_value(ud, "delete", "1", 1) < 0)
|
||||
+ condlog(3, "%s: failed to purge", devname);
|
||||
+ else
|
||||
+ condlog(2, "%s: purged", devname);
|
||||
+
|
||||
+out:
|
||||
+ return;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Prepare purge info for a path while holding vecs->lock.
|
||||
+ * Takes a reference on the udev device and duplicates the fd.
|
||||
+ * Returns allocated purge_path_info on success, NULL on failure.
|
||||
+ *
|
||||
+ * We require a valid fd because it prevents the kernel from reusing
|
||||
+ * the device's minor number (and device name) for a new device while
|
||||
+ * we hold it open. This protects against accidentally deleting a new
|
||||
+ * device that reused the same name after the original was removed.
|
||||
+ */
|
||||
+static struct purge_path_info *prepare_purge_path_info(struct path *pp)
|
||||
+{
|
||||
+ struct purge_path_info *info = NULL;
|
||||
+
|
||||
+ if (!pp->udev || !pp->mpp)
|
||||
+ goto out;
|
||||
+
|
||||
+ /*
|
||||
+ * We require a valid fd to prevent device name reuse.
|
||||
+ * Without it, we cannot safely purge the device.
|
||||
+ */
|
||||
+ if (pp->fd < 0) {
|
||||
+ condlog(3, "%s: no fd available, cannot safely purge", pp->dev);
|
||||
+ goto out;
|
||||
+ }
|
||||
+
|
||||
+ info = calloc(1, sizeof(*info));
|
||||
+ if (!info)
|
||||
+ goto out;
|
||||
+
|
||||
+ INIT_LIST_HEAD(&info->node);
|
||||
+ info->udev = udev_device_ref(pp->udev);
|
||||
+ if (!info->udev)
|
||||
+ goto out_free;
|
||||
+
|
||||
+ info->fd = dup(pp->fd);
|
||||
+ if (info->fd < 0) {
|
||||
+ condlog(3, "%s: failed to dup fd: %s, cannot safely purge",
|
||||
+ pp->dev, strerror(errno));
|
||||
+ goto out_unref;
|
||||
+ }
|
||||
+
|
||||
+ return info;
|
||||
+
|
||||
+out_unref:
|
||||
+ udev_device_unref(info->udev);
|
||||
+out_free:
|
||||
+ free(info);
|
||||
+ info = NULL;
|
||||
+out:
|
||||
+ return info;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Clean up and free purge info.
|
||||
+ */
|
||||
+static void free_purge_path_info(struct purge_path_info *info)
|
||||
+{
|
||||
+ if (!info)
|
||||
+ return;
|
||||
+
|
||||
+ if (info->fd >= 0)
|
||||
+ close(info->fd);
|
||||
+ if (info->udev)
|
||||
+ udev_device_unref(info->udev);
|
||||
+ free(info);
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Build a list of purge_path_info for all paths marked for purge.
|
||||
+ * This should be called while holding vecs->lock. It clears the
|
||||
+ * disconnected flag and prepares purge info for each path, adding
|
||||
+ * them to tmpq.
|
||||
+ */
|
||||
+void build_purge_list(struct vectors *vecs, struct list_head *tmpq)
|
||||
+{
|
||||
+ struct path *pp;
|
||||
+ unsigned int i;
|
||||
+
|
||||
+ vector_foreach_slot (vecs->pathvec, pp, i) {
|
||||
+ struct purge_path_info *info;
|
||||
+
|
||||
+ if (pp->disconnected != DISCONNECTED_READY_FOR_PURGE)
|
||||
+ continue;
|
||||
+
|
||||
+ /*
|
||||
+ * Mark as queued whether we succeed or fail.
|
||||
+ * On success, we're purging it now.
|
||||
+ * On failure, retrying is unlikely to help until
|
||||
+ * the checker re-evaluates the path.
|
||||
+ */
|
||||
+ pp->disconnected = DISCONNECTED_QUEUED_FOR_PURGE;
|
||||
+
|
||||
+ info = prepare_purge_path_info(pp);
|
||||
+ if (info) {
|
||||
+ condlog(2, "%s: queuing path for purge", pp->dev);
|
||||
+ list_add_tail(&info->node, tmpq);
|
||||
+ } else
|
||||
+ condlog(3, "%s: failed to prepare purge info", pp->dev);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static void rcu_unregister(__attribute__((unused)) void *param)
|
||||
+{
|
||||
+ rcu_unregister_thread();
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Cleanup handler for a single purge_path_info.
|
||||
+ * Used to prevent memory leaks if thread is cancelled while processing.
|
||||
+ */
|
||||
+static void cleanup_purge_path_info(void *arg)
|
||||
+{
|
||||
+ struct purge_path_info *info = arg;
|
||||
+
|
||||
+ free_purge_path_info(info);
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Cleanup handler for purge list. Frees all purge_path_info entries.
|
||||
+ * Can be called as a pthread cleanup handler or directly.
|
||||
+ */
|
||||
+void cleanup_purge_list(void *arg)
|
||||
+{
|
||||
+ struct list_head *purge_list = arg;
|
||||
+ struct purge_path_info *info, *tmp;
|
||||
+
|
||||
+ list_for_each_entry_safe(info, tmp, purge_list, node)
|
||||
+ {
|
||||
+ list_del_init(&info->node);
|
||||
+ free_purge_path_info(info);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Cleanup handler for the global purge queue.
|
||||
+ * Used during shutdown to free any remaining queued items.
|
||||
+ */
|
||||
+static void cleanup_global_purge_queue(void *arg __attribute__((unused)))
|
||||
+{
|
||||
+ pthread_mutex_lock(&purge_mutex);
|
||||
+ cleanup_purge_list(&purge_queue);
|
||||
+ pthread_mutex_unlock(&purge_mutex);
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Main purge thread loop.
|
||||
+ *
|
||||
+ * This thread waits for purge_path_info structs to be queued by the checker
|
||||
+ * thread, then processes them by writing to their sysfs delete attributes.
|
||||
+ * The checker thread builds the list while holding vecs->lock, so this
|
||||
+ * thread doesn't need to grab that lock at all.
|
||||
+ *
|
||||
+ * Uses list_splice_tail_init() like uevent_dispatch() to safely transfer
|
||||
+ * items from the global queue to a local list for processing.
|
||||
+ *
|
||||
+ * Cleanup handlers are registered for both the local purge_list and the
|
||||
+ * global purge_queue (similar to uevent_listen), and for each individual
|
||||
+ * purge_path_info after it's popped off the list (similar to service_uevq).
|
||||
+ * This ensures no memory leaks if the thread is cancelled at any point.
|
||||
+ */
|
||||
+void *purgeloop(void *ap __attribute__((unused)))
|
||||
+{
|
||||
+ pthread_cleanup_push(rcu_unregister, NULL);
|
||||
+ rcu_register_thread();
|
||||
+ mlockall(MCL_CURRENT | MCL_FUTURE);
|
||||
+
|
||||
+ /*
|
||||
+ * Cleanup handler for global purge_queue.
|
||||
+ * This handles items that were queued but not yet moved to purge_list.
|
||||
+ */
|
||||
+ pthread_cleanup_push(cleanup_global_purge_queue, NULL);
|
||||
+
|
||||
+ while (1) {
|
||||
+ LIST_HEAD(purge_list);
|
||||
+ struct purge_path_info *info;
|
||||
+
|
||||
+ /*
|
||||
+ * Cleanup handler for local purge_list.
|
||||
+ * This handles items that were moved from purge_queue but
|
||||
+ * not yet processed.
|
||||
+ */
|
||||
+ pthread_cleanup_push(cleanup_purge_list, &purge_list);
|
||||
+
|
||||
+ /*
|
||||
+ * Cleanup handler for purge_mutex.
|
||||
+ * Note: pthread_cond_wait() reacquires the mutex before
|
||||
+ * returning, even on cancellation, so this cleanup handler
|
||||
+ * will properly unlock it if we're cancelled.
|
||||
+ */
|
||||
+ pthread_cleanup_push(cleanup_mutex, &purge_mutex);
|
||||
+ pthread_mutex_lock(&purge_mutex);
|
||||
+ pthread_testcancel();
|
||||
+ while (list_empty(&purge_queue)) {
|
||||
+ condlog(4, "purgeloop waiting for work");
|
||||
+ pthread_cond_wait(&purge_cond, &purge_mutex);
|
||||
+ }
|
||||
+ list_splice_tail_init(&purge_queue, &purge_list);
|
||||
+ pthread_cleanup_pop(1);
|
||||
+
|
||||
+ /*
|
||||
+ * Process all paths in the list without holding any locks.
|
||||
+ * The sysfs operations may block, but that's fine since we're
|
||||
+ * not holding vecs->lock.
|
||||
+ *
|
||||
+ * After popping each info off the list, we immediately push
|
||||
+ * a cleanup handler for it. This ensures it gets freed even
|
||||
+ * if we're cancelled inside delete_path_sysfs().
|
||||
+ */
|
||||
+ while ((info = list_pop_entry(&purge_list, typeof(*info), node))) {
|
||||
+ pthread_cleanup_push(cleanup_purge_path_info, info);
|
||||
+ delete_path_sysfs(info);
|
||||
+ pthread_cleanup_pop(1);
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * Pop cleanup handler without executing it (0) since we've
|
||||
+ * already freed everything above. The handler only runs if
|
||||
+ * the thread is cancelled during processing.
|
||||
+ */
|
||||
+ pthread_cleanup_pop(0);
|
||||
+ }
|
||||
+
|
||||
+ pthread_cleanup_pop(1);
|
||||
+ pthread_cleanup_pop(1);
|
||||
+ return NULL;
|
||||
+}
|
||||
diff --git a/multipathd/purge.h b/multipathd/purge.h
|
||||
new file mode 100644
|
||||
index 00000000..1fe755f3
|
||||
--- /dev/null
|
||||
+++ b/multipathd/purge.h
|
||||
@@ -0,0 +1,41 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
+/*
|
||||
+ * Copyright (C) 2025 Brian Bunker <brian@purestorage.com>
|
||||
+ * Copyright (C) 2025 Krishna Kant <krishna.kant@purestorage.com>
|
||||
+ */
|
||||
+
|
||||
+#ifndef PURGE_H_INCLUDED
|
||||
+#define PURGE_H_INCLUDED
|
||||
+
|
||||
+#include <pthread.h>
|
||||
+#include "list.h"
|
||||
+
|
||||
+struct vectors;
|
||||
+
|
||||
+/*
|
||||
+ * Purge thread synchronization.
|
||||
+ * The checker thread builds a list of paths to purge and queues them here.
|
||||
+ * The purge thread picks up the queue and processes it.
|
||||
+ */
|
||||
+extern pthread_mutex_t purge_mutex;
|
||||
+extern pthread_cond_t purge_cond;
|
||||
+extern struct list_head purge_queue;
|
||||
+
|
||||
+/*
|
||||
+ * Build a list of paths to purge and add them to tmpq. Called by checker
|
||||
+ * thread while holding vecs->lock.
|
||||
+ */
|
||||
+void build_purge_list(struct vectors *vecs, struct list_head *tmpq);
|
||||
+
|
||||
+/*
|
||||
+ * Cleanup handler for purge list. Frees all purge_path_info entries.
|
||||
+ * Can be called as a pthread cleanup handler or directly for shutdown cleanup.
|
||||
+ */
|
||||
+void cleanup_purge_list(void *arg);
|
||||
+
|
||||
+/*
|
||||
+ * Main purge thread loop
|
||||
+ */
|
||||
+void *purgeloop(void *ap);
|
||||
+
|
||||
+#endif /* PURGE_H_INCLUDED */
|
||||
@ -1,6 +1,6 @@
|
||||
Name: device-mapper-multipath
|
||||
Version: 0.9.9
|
||||
Release: 16%{?dist}
|
||||
Release: 17%{?dist}
|
||||
Summary: Tools to manage multipath devices using device-mapper
|
||||
License: GPLv2
|
||||
URL: http://christophe.varoqui.free.fr/
|
||||
@ -92,6 +92,8 @@ Patch0079: 0079-mpathpersist-Fix-REPORT-CAPABILITIES-output.patch
|
||||
Patch0080: 0080-multipath-tools-update-NFINIDAT-InfiniBox-config-in-.patch
|
||||
Patch0081: 0081-multipathd-make-multipathd-show-status-busy-checker-.patch
|
||||
Patch0082: 0082-multipathd-print-path-offline-message-even-without-a.patch
|
||||
Patch0083: 0083-libmultipath-add-purge_disconnected-configuration-op.patch
|
||||
Patch0084: 0084-multipathd-implement-purge-functionality-for-disconn.patch
|
||||
|
||||
# runtime
|
||||
Requires: %{name}-libs = %{version}-%{release}
|
||||
@ -301,6 +303,12 @@ fi
|
||||
%{_pkgconfdir}/libdmmp.pc
|
||||
|
||||
%changelog
|
||||
* Thu Jan 29 2026 Benjamin Marzinski <bmarzins@redhat.com> - 0.9.9-17
|
||||
- Add 0083-libmultipath-add-purge_disconnected-configuration-op.patch
|
||||
- Add 0084-multipathd-implement-purge-functionality-for-disconn.patch
|
||||
* Fixes RHEL-141287 ("Add purge_disconnected support to multipathd")
|
||||
- Resolves: RHEL-141287
|
||||
|
||||
* Thu Jan 22 2026 Benjamin Marzinski <bmarzins@redhat.com> - 0.9.9-16
|
||||
- Add 0081-multipathd-make-multipathd-show-status-busy-checker-.patch
|
||||
* Fixes RHEL-136405 ("improve Busy checking for multipathd show status
|
||||
|
||||
Loading…
Reference in New Issue
Block a user