[Devel] [PATCH RHEL9 COMMIT] dm-qcow2: Pause IO and notify about ENOSPC events
Konstantin Khorenko
khorenko at virtuozzo.com
Wed Mar 2 20:04:13 MSK 2022
The commit is pushed to "branch-rh9-5.14.0-42.vz9.14.x-ovz" and will appear at https://src.openvz.org/scm/ovz/vzkernel.git
after rh9-5.14.0-42.vz9.14.3
------>
commit cfba20c4f81debbe208326fc38cf6d08f333abeb
Author: Kirill Tkhai <ktkhai at virtuozzo.com>
Date: Wed Mar 2 20:04:13 2022 +0300
dm-qcow2: Pause IO and notify about ENOSPC events
https://jira.sw.ru/browse/PSBM-132049
Signed-off-by: Kirill Tkhai <ktkhai at virtuozzo.com>
Feature: dm-qcow2: block device over QCOW2 files driver
---
drivers/md/dm-qcow2-cmd.c | 23 +++++++++++++++++++++++
drivers/md/dm-qcow2-map.c | 41 ++++++++++++++++++++++++++++++++++++++++-
drivers/md/dm-qcow2-target.c | 40 ++++++++++++++++++++++++++++++++++++++++
drivers/md/dm-qcow2.h | 8 ++++++++
4 files changed, 111 insertions(+), 1 deletion(-)
diff --git a/drivers/md/dm-qcow2-cmd.c b/drivers/md/dm-qcow2-cmd.c
index 7c91daa5af2d..1db79f6e54e7 100644
--- a/drivers/md/dm-qcow2-cmd.c
+++ b/drivers/md/dm-qcow2-cmd.c
@@ -316,6 +316,22 @@ static int qcow2_set_fault_injection(struct qcow2_target *tgt,
return 0;
}
+static int qcow2_get_event(struct qcow2_target *tgt, char *result, unsigned int maxlen)
+{
+ unsigned int sz = 0;
+ int ret = 0;
+
+ spin_lock_irq(&tgt->event_lock);
+ if (tgt->event_enospc) {
+ ret = (DMEMIT("event_ENOSPC\n")) ? 1 : 0;
+ if (ret)
+ tgt->event_enospc = false;
+ }
+ spin_unlock_irq(&tgt->event_lock);
+
+ return ret;
+}
+
int qcow2_message(struct dm_target *ti, unsigned int argc, char **argv,
char *result, unsigned int maxlen)
{
@@ -352,6 +368,13 @@ int qcow2_message(struct dm_target *ti, unsigned int argc, char **argv,
}
ret = qcow2_set_fault_injection(tgt, val, val2);
goto out;
+ } else if (!strcmp(argv[0], "get_event")) {
+ if (argc != 1) {
+ ret = -EINVAL;
+ goto out;
+ }
+ ret = qcow2_get_event(tgt, result, maxlen);
+ goto out;
}
ret = mutex_lock_killable(&tgt->ctl_mutex);
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 60dd5c76ba7c..5c0bf98ec8ad 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -3995,7 +3995,33 @@ void do_qcow2_fsync_work(struct work_struct *ws)
current_restore_flags(pflags, PF_LOCAL_THROTTLE|PF_MEMALLOC_NOIO);
}
-static void qrq_endio(struct qcow2_target *tgt, struct qio *unused,
+static bool qcow2_try_delay_enospc(struct qcow2_target *tgt, struct qcow2_rq *qrq, struct qio *qio)
+{
+ bool delayed = true;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tgt->event_lock, flags);
+ if (unlikely(tgt->wants_suspend)) {
+ delayed = false;
+ goto unlock;
+ }
+
+ init_qrq_and_embedded_qio(tgt, qrq->rq, qrq, qio);
+
+ pr_err_once("qcow2: underlying disk is almost full\n");
+ tgt->event_enospc = true;
+ list_add_tail(&qio->link, &tgt->enospc_qios);
+unlock:
+ spin_unlock_irqrestore(&tgt->event_lock, flags);
+
+ if (delayed)
+ mod_timer(&tgt->enospc_timer, jiffies + ENOSPC_TIMEOUT_JI);
+ schedule_work(&tgt->event_work);
+
+ return delayed;
+}
+
+static void qrq_endio(struct qcow2_target *tgt, struct qio *qio,
void *qrq_ptr, blk_status_t bi_status)
{
struct qcow2_rq *qrq = qrq_ptr;
@@ -4003,6 +4029,19 @@ static void qrq_endio(struct qcow2_target *tgt, struct qio *unused,
if (qrq->bvec)
kfree(qrq->bvec);
+ /*
+ * Here is exit point for rq, and here we handle ENOSPC.
+ * Embedded qios will be reinitialized like they've just
+ * came from upper dm level, and later resubmitted after
+ * timeout. Note, that we do not handle merge here: merge
+ * callers receive -ENOSPC synchronous without intermediaries.
+ */
+ if (unlikely(bi_status == BLK_STS_NOSPC)) {
+ WARN_ON_ONCE(!op_is_write(qio->bi_op));
+ if (qcow2_try_delay_enospc(tgt, qrq, qio))
+ return;
+ }
+
mempool_free(qrq, tgt->qrq_pool);
dm_complete_request(rq, bi_status);
}
diff --git a/drivers/md/dm-qcow2-target.c b/drivers/md/dm-qcow2-target.c
index 99a0359391ce..6c550cbe2579 100644
--- a/drivers/md/dm-qcow2-target.c
+++ b/drivers/md/dm-qcow2-target.c
@@ -23,6 +23,14 @@ static void qcow2_set_service_operations(struct dm_target *ti, bool allowed)
tgt->service_operations_allowed = allowed;
mutex_unlock(&tgt->ctl_mutex);
}
+static void qcow2_set_wants_suspend(struct dm_target *ti, bool wants)
+{
+ struct qcow2_target *tgt = to_qcow2_target(ti);
+
+ spin_lock_irq(&tgt->event_lock);
+ tgt->wants_suspend = wants;
+ spin_unlock_irq(&tgt->event_lock);
+}
static int rw_pages_sync(unsigned int rw, struct qcow2 *qcow2,
u64 index, struct page *pages[], int nr)
@@ -411,6 +419,26 @@ static void inflight_ref_exit1(struct percpu_ref *ref)
complete(&tgt->inflight_ref_comp);
}
+void ploop_enospc_timer(struct timer_list *timer)
+{
+ struct qcow2_target *tgt = from_timer(tgt, timer, enospc_timer);
+ unsigned long flags;
+ LIST_HEAD(list);
+
+ spin_lock_irqsave(&tgt->event_lock, flags);
+ list_splice_init(&tgt->enospc_qios, &list);
+ spin_unlock_irqrestore(&tgt->event_lock, flags);
+
+ submit_embedded_qios(tgt, &list);
+}
+
+static void qcow2_event_work(struct work_struct *ws)
+{
+ struct qcow2_target *tgt = container_of(ws, struct qcow2_target, event_work);
+
+ dm_table_event(tgt->ti->table);
+}
+
static struct qcow2_target *alloc_qcow2_target(struct dm_target *ti)
{
percpu_ref_func_t *release;
@@ -448,8 +476,12 @@ static struct qcow2_target *alloc_qcow2_target(struct dm_target *ti)
}
init_completion(&tgt->inflight_ref_comp);
+ spin_lock_init(&tgt->event_lock);
mutex_init(&tgt->ctl_mutex);
init_waitqueue_head(&tgt->service_wq);
+ INIT_WORK(&tgt->event_work, qcow2_event_work);
+ INIT_LIST_HEAD(&tgt->enospc_qios);
+ timer_setup(&tgt->enospc_timer, ploop_enospc_timer, 0);
ti->private = tgt;
tgt->ti = ti;
qcow2_set_service_operations(ti, false);
@@ -871,10 +903,16 @@ static void qcow2_status(struct dm_target *ti, status_type_t type,
static void qcow2_presuspend(struct dm_target *ti)
{
+ struct qcow2_target *tgt = to_qcow2_target(ti);
+
qcow2_set_service_operations(ti, false);
+ qcow2_set_wants_suspend(ti, true);
+ del_timer_sync(&tgt->enospc_timer);
+ ploop_enospc_timer(&tgt->enospc_timer);
}
static void qcow2_presuspend_undo(struct dm_target *ti)
{
+ qcow2_set_wants_suspend(ti, false);
qcow2_set_service_operations(ti, true);
}
static void qcow2_postsuspend(struct dm_target *ti)
@@ -920,6 +958,8 @@ static int qcow2_preresume(struct dm_target *ti)
if (ret)
pr_err("qcow2: Can't set features\n");
}
+ if (!ret)
+ qcow2_set_wants_suspend(ti, false);
return ret;
}
diff --git a/drivers/md/dm-qcow2.h b/drivers/md/dm-qcow2.h
index 14899608b30f..f560ea9b3030 100644
--- a/drivers/md/dm-qcow2.h
+++ b/drivers/md/dm-qcow2.h
@@ -18,6 +18,7 @@
#define MIN_QIOS 512
#define WB_TIMEOUT_JI (60 * HZ)
+#define ENOSPC_TIMEOUT_JI (20 * HZ)
#define PREALLOC_SIZE (128ULL * 1024 * 1024)
struct QCowHeader {
@@ -119,12 +120,19 @@ struct qcow2_target {
unsigned int inflight_ref_index:1;
bool service_operations_allowed;
+ bool wants_suspend;
bool md_writeback_error;
bool truncate_error;
+ bool event_enospc;
atomic_t service_qios;
struct wait_queue_head service_wq;
+ struct list_head enospc_qios; /* Delayed after ENOSPC */
+ struct timer_list enospc_timer;
+
+ struct work_struct event_work;
+ spinlock_t event_lock;
struct mutex ctl_mutex;
};
More information about the Devel
mailing list