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.
This commit is contained in:
Brian M
2026-03-25 14:55:45 -07:00
committed by Gleb Chesnokov
parent 0731c421fd
commit a4a55aab41
4 changed files with 73 additions and 6 deletions

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,