* [PATCH 0/3] dm-delay synchronization cleanup
@ 2024-05-07 21:16 Benjamin Marzinski
2024-05-07 21:16 ` [PATCH 1/3] dm-delay: fix workqueue delay_timer race Benjamin Marzinski
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Benjamin Marzinski @ 2024-05-07 21:16 UTC (permalink / raw)
To: Mikulas Patocka, Mike Snitzer, Heinz Mauelshagen; +Cc: dm-devel
This patchset fixes a possible crash in delay_dtr() caused by the
delay_timer queueing work on a destroyed workqueue. It also cuts down
on the amount of locking and waiting the dm-delay does.
Benjamin Marzinski (3):
dm-delay: fix workqueue delay_timer race
dm-delay: change locking to avoid contention
dm-delay: remove timer_lock
drivers/md/dm-delay.c | 52 +++++++++++++++++++++++--------------------
1 file changed, 28 insertions(+), 24 deletions(-)
--
2.45.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/3] dm-delay: fix workqueue delay_timer race
2024-05-07 21:16 [PATCH 0/3] dm-delay synchronization cleanup Benjamin Marzinski
@ 2024-05-07 21:16 ` Benjamin Marzinski
2024-05-07 21:16 ` [PATCH 2/3] dm-delay: change locking to avoid contention Benjamin Marzinski
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Benjamin Marzinski @ 2024-05-07 21:16 UTC (permalink / raw)
To: Mikulas Patocka, Mike Snitzer, Heinz Mauelshagen; +Cc: dm-devel
delay_timer could be pending when delay_dtr() is called. It needs to be
shut down before kdelayd_wq is destroyed, so it won't try queueing more
work to kdelayd_wq while that's getting destroyed.
Also the del_timer_sync() call in delay_presuspend() doesn't protect
against the timer getting immediately rearmed by the queued call to
flush_delayed_bios(), but there's no real harm if that does happen.
timer_delete() is less work, and is basically just as likely to stop a
pointless call to flush_delayed_bios().
Fixes: 26b9f228703f ("dm: delay target")
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
drivers/md/dm-delay.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/md/dm-delay.c b/drivers/md/dm-delay.c
index 1c424983c042..584e3b10802d 100644
--- a/drivers/md/dm-delay.c
+++ b/drivers/md/dm-delay.c
@@ -154,8 +154,10 @@ static void delay_dtr(struct dm_target *ti)
{
struct delay_c *dc = ti->private;
- if (dc->kdelayd_wq)
+ if (dc->kdelayd_wq) {
+ timer_shutdown_sync(&dc->delay_timer);
destroy_workqueue(dc->kdelayd_wq);
+ }
if (dc->read.dev)
dm_put_device(ti, dc->read.dev);
@@ -334,7 +336,7 @@ static void delay_presuspend(struct dm_target *ti)
mutex_unlock(&delayed_bios_lock);
if (!delay_is_fast(dc))
- del_timer_sync(&dc->delay_timer);
+ timer_delete(&dc->delay_timer);
flush_delayed_bios(dc, true);
}
--
2.45.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 2/3] dm-delay: change locking to avoid contention
2024-05-07 21:16 [PATCH 0/3] dm-delay synchronization cleanup Benjamin Marzinski
2024-05-07 21:16 ` [PATCH 1/3] dm-delay: fix workqueue delay_timer race Benjamin Marzinski
@ 2024-05-07 21:16 ` Benjamin Marzinski
2024-05-07 21:16 ` [PATCH 3/3] dm-delay: remove timer_lock Benjamin Marzinski
2024-05-09 14:24 ` [PATCH 0/3] dm-delay synchronization cleanup Mike Snitzer
3 siblings, 0 replies; 6+ messages in thread
From: Benjamin Marzinski @ 2024-05-07 21:16 UTC (permalink / raw)
To: Mikulas Patocka, Mike Snitzer, Heinz Mauelshagen; +Cc: dm-devel
The delayed_bios list is protected by one mutex shared by all dm-delay
devices. This mutex must be held whenever a bio is added or expired bios
are removed from the list. Since a large number of expired bios could
be on the list, flush_delayed_bios() can schedule while holding the
mutex. This means a flush_delayed_bios() call on any dm-delay device can
slow down delay_map() calls on any other dm-delay device.
To keep dm-delay devices from slowing each other down and keep
processing delay bios from slowing adding delayed bios, the global mutex
has been removed, and each dm-delay device now has two locks.
delayed_bios_lock is a spinlock that must be held whenever the
delayed_bios list is accessed. process_bios_lock is a mutex that must be
held whenever a process has temporarily pulled bios off the delayed_bios
list to check which ones should be processed. It must be held until all
the bios that won't be processed are returned to the list. This is what
flush_delayed_bios() now does. The mutex is necessary to guarantee that
delay_presuspend() sees the entire list of delayed bios when it calls
flush_delayed_bios().
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
drivers/md/dm-delay.c | 36 +++++++++++++++++++++++-------------
1 file changed, 23 insertions(+), 13 deletions(-)
diff --git a/drivers/md/dm-delay.c b/drivers/md/dm-delay.c
index 584e3b10802d..b1829c106a37 100644
--- a/drivers/md/dm-delay.c
+++ b/drivers/md/dm-delay.c
@@ -29,6 +29,8 @@ struct delay_class {
struct delay_c {
struct timer_list delay_timer;
struct mutex timer_lock;
+ struct mutex process_bios_lock; /* hold while removing bios to be processed from list */
+ spinlock_t delayed_bios_lock; /* hold on all accesses to delayed_bios list */
struct workqueue_struct *kdelayd_wq;
struct work_struct flush_expired_bios;
struct list_head delayed_bios;
@@ -49,8 +51,6 @@ struct dm_delay_info {
unsigned long expires;
};
-static DEFINE_MUTEX(delayed_bios_lock);
-
static void handle_delayed_timer(struct timer_list *t)
{
struct delay_c *dc = from_timer(dc, t, delay_timer);
@@ -89,12 +89,16 @@ static void flush_delayed_bios(struct delay_c *dc, bool flush_all)
{
struct dm_delay_info *delayed, *next;
struct bio_list flush_bio_list;
+ LIST_HEAD(local_list);
unsigned long next_expires = 0;
bool start_timer = false;
bio_list_init(&flush_bio_list);
- mutex_lock(&delayed_bios_lock);
- list_for_each_entry_safe(delayed, next, &dc->delayed_bios, list) {
+ mutex_lock(&dc->process_bios_lock);
+ spin_lock(&dc->delayed_bios_lock);
+ list_replace_init(&dc->delayed_bios, &local_list);
+ spin_unlock(&dc->delayed_bios_lock);
+ list_for_each_entry_safe(delayed, next, &local_list, list) {
cond_resched();
if (flush_all || time_after_eq(jiffies, delayed->expires)) {
struct bio *bio = dm_bio_from_per_bio_data(delayed,
@@ -114,7 +118,10 @@ static void flush_delayed_bios(struct delay_c *dc, bool flush_all)
}
}
}
- mutex_unlock(&delayed_bios_lock);
+ spin_lock(&dc->delayed_bios_lock);
+ list_splice(&local_list, &dc->delayed_bios);
+ spin_unlock(&dc->delayed_bios_lock);
+ mutex_unlock(&dc->process_bios_lock);
if (start_timer)
queue_timeout(dc, next_expires);
@@ -128,13 +135,13 @@ static int flush_worker_fn(void *data)
while (!kthread_should_stop()) {
flush_delayed_bios(dc, false);
- mutex_lock(&delayed_bios_lock);
+ spin_lock(&dc->delayed_bios_lock);
if (unlikely(list_empty(&dc->delayed_bios))) {
set_current_state(TASK_INTERRUPTIBLE);
- mutex_unlock(&delayed_bios_lock);
+ spin_unlock(&dc->delayed_bios_lock);
schedule();
} else {
- mutex_unlock(&delayed_bios_lock);
+ spin_unlock(&dc->delayed_bios_lock);
cond_resched();
}
}
@@ -168,6 +175,7 @@ static void delay_dtr(struct dm_target *ti)
if (dc->worker)
kthread_stop(dc->worker);
+ mutex_destroy(&dc->process_bios_lock);
mutex_destroy(&dc->timer_lock);
kfree(dc);
@@ -227,6 +235,8 @@ static int delay_ctr(struct dm_target *ti, unsigned int argc, char **argv)
ti->private = dc;
INIT_LIST_HEAD(&dc->delayed_bios);
mutex_init(&dc->timer_lock);
+ mutex_init(&dc->process_bios_lock);
+ spin_lock_init(&dc->delayed_bios_lock);
dc->may_delay = true;
dc->argc = argc;
@@ -310,14 +320,14 @@ static int delay_bio(struct delay_c *dc, struct delay_class *c, struct bio *bio)
delayed->context = dc;
delayed->expires = expires = jiffies + msecs_to_jiffies(c->delay);
- mutex_lock(&delayed_bios_lock);
+ spin_lock(&dc->delayed_bios_lock);
if (unlikely(!dc->may_delay)) {
- mutex_unlock(&delayed_bios_lock);
+ spin_unlock(&dc->delayed_bios_lock);
return DM_MAPIO_REMAPPED;
}
c->ops++;
list_add_tail(&delayed->list, &dc->delayed_bios);
- mutex_unlock(&delayed_bios_lock);
+ spin_unlock(&dc->delayed_bios_lock);
if (delay_is_fast(dc))
wake_up_process(dc->worker);
@@ -331,9 +341,9 @@ static void delay_presuspend(struct dm_target *ti)
{
struct delay_c *dc = ti->private;
- mutex_lock(&delayed_bios_lock);
+ spin_lock(&dc->delayed_bios_lock);
dc->may_delay = false;
- mutex_unlock(&delayed_bios_lock);
+ spin_unlock(&dc->delayed_bios_lock);
if (!delay_is_fast(dc))
timer_delete(&dc->delay_timer);
--
2.45.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 3/3] dm-delay: remove timer_lock
2024-05-07 21:16 [PATCH 0/3] dm-delay synchronization cleanup Benjamin Marzinski
2024-05-07 21:16 ` [PATCH 1/3] dm-delay: fix workqueue delay_timer race Benjamin Marzinski
2024-05-07 21:16 ` [PATCH 2/3] dm-delay: change locking to avoid contention Benjamin Marzinski
@ 2024-05-07 21:16 ` Benjamin Marzinski
2024-05-09 14:24 ` [PATCH 0/3] dm-delay synchronization cleanup Mike Snitzer
3 siblings, 0 replies; 6+ messages in thread
From: Benjamin Marzinski @ 2024-05-07 21:16 UTC (permalink / raw)
To: Mikulas Patocka, Mike Snitzer, Heinz Mauelshagen; +Cc: dm-devel
Instead of manually checking the timer details in queue_timeout(), call
timer_reduce() to start the timer or reduce the expiration time. This
avoids needing a lock.
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
drivers/md/dm-delay.c | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/drivers/md/dm-delay.c b/drivers/md/dm-delay.c
index b1829c106a37..297ae7ffcd0b 100644
--- a/drivers/md/dm-delay.c
+++ b/drivers/md/dm-delay.c
@@ -28,7 +28,6 @@ struct delay_class {
struct delay_c {
struct timer_list delay_timer;
- struct mutex timer_lock;
struct mutex process_bios_lock; /* hold while removing bios to be processed from list */
spinlock_t delayed_bios_lock; /* hold on all accesses to delayed_bios list */
struct workqueue_struct *kdelayd_wq;
@@ -60,12 +59,7 @@ static void handle_delayed_timer(struct timer_list *t)
static void queue_timeout(struct delay_c *dc, unsigned long expires)
{
- mutex_lock(&dc->timer_lock);
-
- if (!timer_pending(&dc->delay_timer) || expires < dc->delay_timer.expires)
- mod_timer(&dc->delay_timer, expires);
-
- mutex_unlock(&dc->timer_lock);
+ timer_reduce(&dc->delay_timer, expires);
}
static inline bool delay_is_fast(struct delay_c *dc)
@@ -176,7 +170,6 @@ static void delay_dtr(struct dm_target *ti)
kthread_stop(dc->worker);
mutex_destroy(&dc->process_bios_lock);
- mutex_destroy(&dc->timer_lock);
kfree(dc);
}
@@ -234,7 +227,6 @@ static int delay_ctr(struct dm_target *ti, unsigned int argc, char **argv)
ti->private = dc;
INIT_LIST_HEAD(&dc->delayed_bios);
- mutex_init(&dc->timer_lock);
mutex_init(&dc->process_bios_lock);
spin_lock_init(&dc->delayed_bios_lock);
dc->may_delay = true;
--
2.45.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 0/3] dm-delay synchronization cleanup
2024-05-07 21:16 [PATCH 0/3] dm-delay synchronization cleanup Benjamin Marzinski
` (2 preceding siblings ...)
2024-05-07 21:16 ` [PATCH 3/3] dm-delay: remove timer_lock Benjamin Marzinski
@ 2024-05-09 14:24 ` Mike Snitzer
2024-05-09 16:07 ` Benjamin Marzinski
3 siblings, 1 reply; 6+ messages in thread
From: Mike Snitzer @ 2024-05-09 14:24 UTC (permalink / raw)
To: Benjamin Marzinski
Cc: Mikulas Patocka, Heinz Mauelshagen, dm-devel, Joel Colledge
On Tue, May 07, 2024 at 05:16:22PM -0400, Benjamin Marzinski wrote:
> This patchset fixes a possible crash in delay_dtr() caused by the
> delay_timer queueing work on a destroyed workqueue. It also cuts down
> on the amount of locking and waiting the dm-delay does.
>
> Benjamin Marzinski (3):
> dm-delay: fix workqueue delay_timer race
> dm-delay: change locking to avoid contention
> dm-delay: remove timer_lock
>
> drivers/md/dm-delay.c | 52 +++++++++++++++++++++++--------------------
> 1 file changed, 28 insertions(+), 24 deletions(-)
>
> --
> 2.45.0
>
>
Hey Ben,
I just staged 5 dm-delay patches in dm-6.10. 1 from Joel and 4 from
you. Please confirm this is what you expected and looks good:
https://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm.git/log/?h=dm-6.10
Thanks,
Mike
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 0/3] dm-delay synchronization cleanup
2024-05-09 14:24 ` [PATCH 0/3] dm-delay synchronization cleanup Mike Snitzer
@ 2024-05-09 16:07 ` Benjamin Marzinski
0 siblings, 0 replies; 6+ messages in thread
From: Benjamin Marzinski @ 2024-05-09 16:07 UTC (permalink / raw)
To: Mike Snitzer; +Cc: Mikulas Patocka, Heinz Mauelshagen, dm-devel, Joel Colledge
On Thu, May 09, 2024 at 10:24:52AM -0400, Mike Snitzer wrote:
> On Tue, May 07, 2024 at 05:16:22PM -0400, Benjamin Marzinski wrote:
> > This patchset fixes a possible crash in delay_dtr() caused by the
> > delay_timer queueing work on a destroyed workqueue. It also cuts down
> > on the amount of locking and waiting the dm-delay does.
> >
> > Benjamin Marzinski (3):
> > dm-delay: fix workqueue delay_timer race
> > dm-delay: change locking to avoid contention
> > dm-delay: remove timer_lock
> >
> > drivers/md/dm-delay.c | 52 +++++++++++++++++++++++--------------------
> > 1 file changed, 28 insertions(+), 24 deletions(-)
> >
> > --
> > 2.45.0
> >
> >
>
> Hey Ben,
>
> I just staged 5 dm-delay patches in dm-6.10. 1 from Joel and 4 from
> you. Please confirm this is what you expected and looks good:
> https://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm.git/log/?h=dm-6.10
Well, I was half expecting to be told that it's kinda pointless to
optimize a target whoses purpose is to slow things down, but yeah, this
matches my dev branch.
Thanks.
-Ben
>
> Thanks,
> Mike
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2024-05-09 16:07 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-05-07 21:16 [PATCH 0/3] dm-delay synchronization cleanup Benjamin Marzinski
2024-05-07 21:16 ` [PATCH 1/3] dm-delay: fix workqueue delay_timer race Benjamin Marzinski
2024-05-07 21:16 ` [PATCH 2/3] dm-delay: change locking to avoid contention Benjamin Marzinski
2024-05-07 21:16 ` [PATCH 3/3] dm-delay: remove timer_lock Benjamin Marzinski
2024-05-09 14:24 ` [PATCH 0/3] dm-delay synchronization cleanup Mike Snitzer
2024-05-09 16:07 ` Benjamin Marzinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).