From a4a55aab41246ab8ce249d1ed71970dc4a16a400 Mon Sep 17 00:00:00 2001 From: Brian M Date: Wed, 25 Mar 2026 14:55:45 -0700 Subject: [PATCH] scst: add async_lun_replace to defer tgt_dev cleanup after LUN replace When a LUN is replaced, scst_acg_repl_lun polls tgt_dev_cmd_count every 100ms waiting for in-flight commands on the old tgt_devs to drain before freeing them. This is synchronous: the sysfs write to luns/mgmt blocks until the drain completes. If the old device becomes unreachable before the LUN replace (e.g. due to a transport failure), in-flight commands may be stuck in error recovery for up to the transport's recovery timeout, blocking the replace for that entire window. Add a bool module parameter async_lun_replace (default false). When enabled, scst_acg_repl_lun schedules the tgt_dev drain and free on system_wq and returns immediately. Falls back to synchronous behaviour on allocation failure. This is safe because __scst_acg_del_lun removes the old tgt_devs from all session and device lookup paths before the work is scheduled. New commands from the initiator use the new tgt_devs; only in-flight commands still hold references via cmd->tgt_dev, and tgt_dev_cmd_count tracks exactly those. synchronize_rcu ensures no RCU reader holds a stale pointer before scst_free_tgt_dev is called. --- scst/src/scst_lib.c | 50 +++++++++++++++++++++++++++++++++++++------ scst/src/scst_main.c | 5 +++++ scst/src/scst_priv.h | 1 + scst/src/scst_sysfs.c | 23 ++++++++++++++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 8ac079a72..e26582a99 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -4771,6 +4771,34 @@ out: return res; } +struct scst_async_repl_work { + struct work_struct work; + struct list_head tgt_dev_list; +}; + +static void scst_wait_and_free_tgt_devs(struct list_head *tgt_dev_list) +{ + struct scst_tgt_dev *tgt_dev, *tt; + + scst_wait_for_tgt_devs(tgt_dev_list); + synchronize_rcu(); + + mutex_lock(&scst_mutex); + list_for_each_entry_safe(tgt_dev, tt, tgt_dev_list, + extra_tgt_dev_list_entry) + scst_free_tgt_dev(tgt_dev); + mutex_unlock(&scst_mutex); +} + +static void scst_async_repl_work_fn(struct work_struct *work) +{ + struct scst_async_repl_work *w = + container_of(work, struct scst_async_repl_work, work); + + scst_wait_and_free_tgt_devs(&w->tgt_dev_list); + kfree(w); +} + /* Either add or replace a LUN according to flags argument */ int scst_acg_repl_lun(struct scst_acg *acg, struct kobject *parent, struct scst_device *dev, uint64_t lun, @@ -4778,7 +4806,7 @@ int scst_acg_repl_lun(struct scst_acg *acg, struct kobject *parent, { struct scst_acg_dev *acg_dev; bool del_gen_ua = false; - struct scst_tgt_dev *tgt_dev, *tt; + struct scst_tgt_dev *tgt_dev; struct list_head tgt_dev_list; int res = -EINVAL; @@ -4805,13 +4833,23 @@ int scst_acg_repl_lun(struct scst_acg *acg, struct kobject *parent, } mutex_unlock(&scst_mutex); - scst_wait_for_tgt_devs(&tgt_dev_list); - synchronize_rcu(); + if (acg_dev && READ_ONCE(scst_async_lun_replace)) { + struct scst_async_repl_work *w = + kzalloc(sizeof(*w), GFP_KERNEL); + if (w) { + INIT_WORK(&w->work, scst_async_repl_work_fn); + INIT_LIST_HEAD(&w->tgt_dev_list); + list_splice_init(&tgt_dev_list, &w->tgt_dev_list); + schedule_work(&w->work); + goto relock; + } + /* fall back to synchronous path on allocation failure */ + } + scst_wait_and_free_tgt_devs(&tgt_dev_list); + +relock: mutex_lock(&scst_mutex); - list_for_each_entry_safe(tgt_dev, tt, &tgt_dev_list, extra_tgt_dev_list_entry) - scst_free_tgt_dev(tgt_dev); - if (acg_dev) scst_free_acg_dev(acg_dev); diff --git a/scst/src/scst_main.c b/scst/src/scst_main.c index b82b9da22..c20a1c3f6 100644 --- a/scst/src/scst_main.c +++ b/scst/src/scst_main.c @@ -188,6 +188,11 @@ module_param_named(auto_cm_assignment, scst_auto_cm_assignment, bool, 0644); MODULE_PARM_DESC(auto_cm_assignment, "Enables the copy managers auto registration. (default: true)"); +bool scst_async_lun_replace; +module_param_named(async_lun_replace, scst_async_lun_replace, bool, 0644); +MODULE_PARM_DESC(async_lun_replace, + "If enabled, LUN replace operations do not wait synchronously for in-flight commands on the old tgt_dev to drain; cleanup is deferred to a workqueue. (default: false)"); + struct scst_dev_type scst_null_devtype = { .name = "none", .threads_num = -1, diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 226724e9f..325be2006 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -154,6 +154,7 @@ extern unsigned int scst_max_dev_cmd_mem; extern bool scst_forcibly_close_sessions; extern bool scst_auto_cm_assignment; +extern bool scst_async_lun_replace; extern mempool_t *scst_mgmt_mempool; extern mempool_t *scst_mgmt_stub_mempool; diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 18bf3e13b..282de4ab5 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -7084,6 +7084,28 @@ out: static struct kobj_attribute scst_measure_latency_attr = __ATTR(measure_latency, 0644, scst_measure_latency_show, scst_measure_latency_store); +static ssize_t scst_async_lun_replace_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%d\n", READ_ONCE(scst_async_lun_replace) ? 1 : 0); +} + +static ssize_t scst_async_lun_replace_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + bool val; + + if (kstrtobool(buf, &val)) + return -EINVAL; + WRITE_ONCE(scst_async_lun_replace, val); + return count; +} + +static struct kobj_attribute scst_async_lun_replace_attr = + __ATTR(async_lun_replace, 0644, scst_async_lun_replace_show, scst_async_lun_replace_store); + static ssize_t scst_threads_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { ssize_t ret; @@ -7658,6 +7680,7 @@ static struct kobj_attribute scst_cluster_name_attr = static struct attribute *scst_sysfs_root_def_attrs[] = { &scst_measure_latency_attr.attr, + &scst_async_lun_replace_attr.attr, &scst_threads_attr.attr, &scst_setup_id_attr.attr, &scst_max_tasklet_cmd_attr.attr,