mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-25 07:51:28 +00:00
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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user