mirror of
https://github.com/SCST-project/scst.git
synced 2026-07-27 10:32:49 +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:
+44
-6
@@ -4771,6 +4771,34 @@ out:
|
|||||||
return res;
|
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 */
|
/* Either add or replace a LUN according to flags argument */
|
||||||
int scst_acg_repl_lun(struct scst_acg *acg, struct kobject *parent,
|
int scst_acg_repl_lun(struct scst_acg *acg, struct kobject *parent,
|
||||||
struct scst_device *dev, uint64_t lun,
|
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;
|
struct scst_acg_dev *acg_dev;
|
||||||
bool del_gen_ua = false;
|
bool del_gen_ua = false;
|
||||||
struct scst_tgt_dev *tgt_dev, *tt;
|
struct scst_tgt_dev *tgt_dev;
|
||||||
struct list_head tgt_dev_list;
|
struct list_head tgt_dev_list;
|
||||||
int res = -EINVAL;
|
int res = -EINVAL;
|
||||||
|
|
||||||
@@ -4805,13 +4833,23 @@ int scst_acg_repl_lun(struct scst_acg *acg, struct kobject *parent,
|
|||||||
}
|
}
|
||||||
mutex_unlock(&scst_mutex);
|
mutex_unlock(&scst_mutex);
|
||||||
|
|
||||||
scst_wait_for_tgt_devs(&tgt_dev_list);
|
if (acg_dev && READ_ONCE(scst_async_lun_replace)) {
|
||||||
synchronize_rcu();
|
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);
|
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)
|
if (acg_dev)
|
||||||
scst_free_acg_dev(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,
|
MODULE_PARM_DESC(auto_cm_assignment,
|
||||||
"Enables the copy managers auto registration. (default: true)");
|
"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 = {
|
struct scst_dev_type scst_null_devtype = {
|
||||||
.name = "none",
|
.name = "none",
|
||||||
.threads_num = -1,
|
.threads_num = -1,
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ extern unsigned int scst_max_dev_cmd_mem;
|
|||||||
|
|
||||||
extern bool scst_forcibly_close_sessions;
|
extern bool scst_forcibly_close_sessions;
|
||||||
extern bool scst_auto_cm_assignment;
|
extern bool scst_auto_cm_assignment;
|
||||||
|
extern bool scst_async_lun_replace;
|
||||||
|
|
||||||
extern mempool_t *scst_mgmt_mempool;
|
extern mempool_t *scst_mgmt_mempool;
|
||||||
extern mempool_t *scst_mgmt_stub_mempool;
|
extern mempool_t *scst_mgmt_stub_mempool;
|
||||||
|
|||||||
@@ -7084,6 +7084,28 @@ out:
|
|||||||
static struct kobj_attribute scst_measure_latency_attr =
|
static struct kobj_attribute scst_measure_latency_attr =
|
||||||
__ATTR(measure_latency, 0644, scst_measure_latency_show, scst_measure_latency_store);
|
__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)
|
static ssize_t scst_threads_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||||
{
|
{
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
@@ -7658,6 +7680,7 @@ static struct kobj_attribute scst_cluster_name_attr =
|
|||||||
|
|
||||||
static struct attribute *scst_sysfs_root_def_attrs[] = {
|
static struct attribute *scst_sysfs_root_def_attrs[] = {
|
||||||
&scst_measure_latency_attr.attr,
|
&scst_measure_latency_attr.attr,
|
||||||
|
&scst_async_lun_replace_attr.attr,
|
||||||
&scst_threads_attr.attr,
|
&scst_threads_attr.attr,
|
||||||
&scst_setup_id_attr.attr,
|
&scst_setup_id_attr.attr,
|
||||||
&scst_max_tasklet_cmd_attr.attr,
|
&scst_max_tasklet_cmd_attr.attr,
|
||||||
|
|||||||
Reference in New Issue
Block a user