[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