All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] dm cache: fix issue with background work locking
@ 2026-08-25 19:22 Benjamin Marzinski
  2026-08-25 20:22 ` Matthew Sakai
  2026-08-27  8:36 ` Ming Hung Tsai
  0 siblings, 2 replies; 5+ messages in thread
From: Benjamin Marzinski @ 2026-08-25 19:22 UTC (permalink / raw)
  To: Mikulas Patocka, Mike Snitzer; +Cc: Ming-Hung Tsai, dm-devel

dm cache used a rw_semaphore for background_work_lock. Write locks on
rw_semaphores have strict owner semantics, but there was no guarantee
that the process that locked background_work_lock was the same process
that unlocked it. This can be easily seen using a kernel compiled with
CONFIG_DEBUG_RWSEMS. Given a dm cache device <cache>, run: 'dmsetup
suspend <cache> && dmsetup resume <cache>'. This will tigger a kernel
warning:

DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE))

triggered by cache_resume(). To fix this, switch from a rw_semaphore to
a spinlock and a wait queue. dm cache already hasd a wait queue and
associated counter, migration_wait and nr_allocated_migrations, that was
getting woken up when background work was getting completed, but wasn't
actually used by anything. This is replaced by the background_work queue
and counter.

Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2")
Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
---
 drivers/md/dm-cache-target.c | 55 ++++++++++++++++++------------------
 1 file changed, 28 insertions(+), 27 deletions(-)

diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
index 33dbc71b730f..d8d9c63d2a67 100644
--- a/drivers/md/dm-cache-target.c
+++ b/drivers/md/dm-cache-target.c
@@ -340,8 +340,6 @@ struct cache {
 	struct list_head invalidation_requests;
 
 	sector_t migration_threshold;
-	wait_queue_head_t migration_wait;
-	atomic_t nr_allocated_migrations;
 
 	/*
 	 * The number of in flight migrations that are performing
@@ -397,7 +395,11 @@ struct cache {
 	bool loaded_mappings:1;
 	bool loaded_discards:1;
 
-	struct rw_semaphore background_work_lock;
+	/* background work management */
+	bool background_work_allowed;
+	unsigned background_work_nr;
+	spinlock_t background_work_lock;
+	wait_queue_head_t background_work_wait;
 
 	struct batcher committer;
 	struct work_struct commit_ws;
@@ -488,19 +490,13 @@ static struct dm_cache_migration *alloc_migration(struct cache *cache)
 	memset(mg, 0, sizeof(*mg));
 
 	mg->cache = cache;
-	atomic_inc(&cache->nr_allocated_migrations);
 
 	return mg;
 }
 
 static void free_migration(struct dm_cache_migration *mg)
 {
-	struct cache *cache = mg->cache;
-
-	if (atomic_dec_and_test(&cache->nr_allocated_migrations))
-		wake_up(&cache->migration_wait);
-
-	mempool_free(mg, &cache->migration_pool);
+	mempool_free(mg, &mg->cache->migration_pool);
 }
 
 /*----------------------------------------------------------------*/
@@ -1030,34 +1026,39 @@ static void calc_discard_block_range(struct cache *cache, struct bio *bio,
 
 static void prevent_background_work(struct cache *cache)
 {
-	lockdep_off();
-	down_write(&cache->background_work_lock);
-	lockdep_on();
+	spin_lock_irq(&cache->background_work_lock);
+	cache->background_work_allowed = false;
+	wait_event_lock_irq(cache->background_work_wait,
+			    cache->background_work_nr == 0,
+			    cache->background_work_lock);
+	spin_unlock_irq(&cache->background_work_lock);
 }
 
 static void allow_background_work(struct cache *cache)
 {
-	lockdep_off();
-	up_write(&cache->background_work_lock);
-	lockdep_on();
+	spin_lock_irq(&cache->background_work_lock);
+	cache->background_work_allowed = true;
+	spin_unlock_irq(&cache->background_work_lock);
 }
 
 static bool background_work_begin(struct cache *cache)
 {
 	bool r;
 
-	lockdep_off();
-	r = down_read_trylock(&cache->background_work_lock);
-	lockdep_on();
-
+	spin_lock_irq(&cache->background_work_lock);
+	r = cache->background_work_allowed;
+	if (r)
+		cache->background_work_nr++;
+	spin_unlock_irq(&cache->background_work_lock);
 	return r;
 }
 
 static void background_work_end(struct cache *cache)
 {
-	lockdep_off();
-	up_read(&cache->background_work_lock);
-	lockdep_on();
+	spin_lock_irq(&cache->background_work_lock);
+	if (--cache->background_work_nr == 0)
+		wake_up(&cache->background_work_wait);
+	spin_unlock_irq(&cache->background_work_lock);
 }
 
 /*----------------------------------------------------------------*/
@@ -2507,9 +2508,7 @@ static int cache_create(struct cache_args *ca, struct cache **result)
 
 	spin_lock_init(&cache->lock);
 	bio_list_init(&cache->deferred_bios);
-	atomic_set(&cache->nr_allocated_migrations, 0);
 	atomic_set(&cache->nr_io_migrations, 0);
-	init_waitqueue_head(&cache->migration_wait);
 
 	r = -ENOMEM;
 	atomic_set(&cache->nr_dirty, 0);
@@ -2592,8 +2591,10 @@ static int cache_create(struct cache_args *ca, struct cache **result)
 		     issue_op, cache, cache->wq);
 	dm_iot_init(&cache->tracker);
 
-	init_rwsem(&cache->background_work_lock);
-	prevent_background_work(cache);
+	init_waitqueue_head(&cache->background_work_wait);
+	spin_lock_init(&cache->background_work_lock);
+	cache->background_work_allowed = false;
+	cache->background_work_nr = 0;
 
 	*result = cache;
 	return 0;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH] dm cache: fix issue with background work locking
  2026-08-25 19:22 [PATCH] dm cache: fix issue with background work locking Benjamin Marzinski
@ 2026-08-25 20:22 ` Matthew Sakai
  2026-08-25 23:19   ` Benjamin Marzinski
  2026-08-27  8:36 ` Ming Hung Tsai
  1 sibling, 1 reply; 5+ messages in thread
From: Matthew Sakai @ 2026-08-25 20:22 UTC (permalink / raw)
  To: Benjamin Marzinski, Mikulas Patocka, Mike Snitzer
  Cc: Ming-Hung Tsai, dm-devel



On 8/25/26 3:22 PM, Benjamin Marzinski wrote:
> dm cache used a rw_semaphore for background_work_lock. Write locks on
> rw_semaphores have strict owner semantics, but there was no guarantee
> that the process that locked background_work_lock was the same process
> that unlocked it. This can be easily seen using a kernel compiled with
> CONFIG_DEBUG_RWSEMS. Given a dm cache device <cache>, run: 'dmsetup
> suspend <cache> && dmsetup resume <cache>'. This will tigger a kernel

typo: trigger

> warning:
> 
> DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE))
> 
> triggered by cache_resume(). To fix this, switch from a rw_semaphore to
> a spinlock and a wait queue. dm cache already hasd a wait queue and

typo: has

> associated counter, migration_wait and nr_allocated_migrations, that was
> getting woken up when background work was getting completed, but wasn't
> actually used by anything. This is replaced by the background_work queue
> and counter.
> 
> Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2")
> Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>

Aside from the above, feel free to add

Reviewed-by: Matthew Sakai <msakai@redhat.com>

> ---
>   drivers/md/dm-cache-target.c | 55 ++++++++++++++++++------------------
>   1 file changed, 28 insertions(+), 27 deletions(-)
> 
> diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
> index 33dbc71b730f..d8d9c63d2a67 100644
> --- a/drivers/md/dm-cache-target.c
> +++ b/drivers/md/dm-cache-target.c
> @@ -340,8 +340,6 @@ struct cache {
>   	struct list_head invalidation_requests;
>   
>   	sector_t migration_threshold;
> -	wait_queue_head_t migration_wait;
> -	atomic_t nr_allocated_migrations;
>   
>   	/*
>   	 * The number of in flight migrations that are performing
> @@ -397,7 +395,11 @@ struct cache {
>   	bool loaded_mappings:1;
>   	bool loaded_discards:1;
>   
> -	struct rw_semaphore background_work_lock;
> +	/* background work management */
> +	bool background_work_allowed;
> +	unsigned background_work_nr;
> +	spinlock_t background_work_lock;
> +	wait_queue_head_t background_work_wait;
>   
>   	struct batcher committer;
>   	struct work_struct commit_ws;
> @@ -488,19 +490,13 @@ static struct dm_cache_migration *alloc_migration(struct cache *cache)
>   	memset(mg, 0, sizeof(*mg));
>   
>   	mg->cache = cache;
> -	atomic_inc(&cache->nr_allocated_migrations);
>   
>   	return mg;
>   }
>   
>   static void free_migration(struct dm_cache_migration *mg)
>   {
> -	struct cache *cache = mg->cache;
> -
> -	if (atomic_dec_and_test(&cache->nr_allocated_migrations))
> -		wake_up(&cache->migration_wait);
> -
> -	mempool_free(mg, &cache->migration_pool);
> +	mempool_free(mg, &mg->cache->migration_pool);
>   }
>   
>   /*----------------------------------------------------------------*/
> @@ -1030,34 +1026,39 @@ static void calc_discard_block_range(struct cache *cache, struct bio *bio,
>   
>   static void prevent_background_work(struct cache *cache)
>   {
> -	lockdep_off();
> -	down_write(&cache->background_work_lock);
> -	lockdep_on();
> +	spin_lock_irq(&cache->background_work_lock);
> +	cache->background_work_allowed = false;
> +	wait_event_lock_irq(cache->background_work_wait,
> +			    cache->background_work_nr == 0,
> +			    cache->background_work_lock);
> +	spin_unlock_irq(&cache->background_work_lock);
>   }
>   
>   static void allow_background_work(struct cache *cache)
>   {
> -	lockdep_off();
> -	up_write(&cache->background_work_lock);
> -	lockdep_on();
> +	spin_lock_irq(&cache->background_work_lock);
> +	cache->background_work_allowed = true;
> +	spin_unlock_irq(&cache->background_work_lock);
>   }
>   
>   static bool background_work_begin(struct cache *cache)
>   {
>   	bool r;
>   
> -	lockdep_off();
> -	r = down_read_trylock(&cache->background_work_lock);
> -	lockdep_on();
> -
> +	spin_lock_irq(&cache->background_work_lock);
> +	r = cache->background_work_allowed;
> +	if (r)
> +		cache->background_work_nr++;
> +	spin_unlock_irq(&cache->background_work_lock);
>   	return r;
>   }
>   
>   static void background_work_end(struct cache *cache)
>   {
> -	lockdep_off();
> -	up_read(&cache->background_work_lock);
> -	lockdep_on();
> +	spin_lock_irq(&cache->background_work_lock);
> +	if (--cache->background_work_nr == 0)
> +		wake_up(&cache->background_work_wait);
> +	spin_unlock_irq(&cache->background_work_lock);
>   }
>   
>   /*----------------------------------------------------------------*/
> @@ -2507,9 +2508,7 @@ static int cache_create(struct cache_args *ca, struct cache **result)
>   
>   	spin_lock_init(&cache->lock);
>   	bio_list_init(&cache->deferred_bios);
> -	atomic_set(&cache->nr_allocated_migrations, 0);
>   	atomic_set(&cache->nr_io_migrations, 0);
> -	init_waitqueue_head(&cache->migration_wait);
>   
>   	r = -ENOMEM;
>   	atomic_set(&cache->nr_dirty, 0);
> @@ -2592,8 +2591,10 @@ static int cache_create(struct cache_args *ca, struct cache **result)
>   		     issue_op, cache, cache->wq);
>   	dm_iot_init(&cache->tracker);
>   
> -	init_rwsem(&cache->background_work_lock);
> -	prevent_background_work(cache);
> +	init_waitqueue_head(&cache->background_work_wait);
> +	spin_lock_init(&cache->background_work_lock);
> +	cache->background_work_allowed = false;
> +	cache->background_work_nr = 0;
>   
>   	*result = cache;
>   	return 0;


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] dm cache: fix issue with background work locking
  2026-08-25 20:22 ` Matthew Sakai
@ 2026-08-25 23:19   ` Benjamin Marzinski
  0 siblings, 0 replies; 5+ messages in thread
From: Benjamin Marzinski @ 2026-08-25 23:19 UTC (permalink / raw)
  To: Matthew Sakai; +Cc: Mikulas Patocka, Mike Snitzer, Ming-Hung Tsai, dm-devel

On Tue, Aug 25, 2026 at 04:22:01PM -0400, Matthew Sakai wrote:
> 
> 
> On 8/25/26 3:22 PM, Benjamin Marzinski wrote:
> > dm cache used a rw_semaphore for background_work_lock. Write locks on
> > rw_semaphores have strict owner semantics, but there was no guarantee
> > that the process that locked background_work_lock was the same process
> > that unlocked it. This can be easily seen using a kernel compiled with
> > CONFIG_DEBUG_RWSEMS. Given a dm cache device <cache>, run: 'dmsetup
> > suspend <cache> && dmsetup resume <cache>'. This will tigger a kernel
> 
> typo: trigger
> 
> > warning:
> > 
> > DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE))
> > 
> > triggered by cache_resume(). To fix this, switch from a rw_semaphore to
> > a spinlock and a wait queue. dm cache already hasd a wait queue and
> 
> typo: has
> 
> > associated counter, migration_wait and nr_allocated_migrations, that was
> > getting woken up when background work was getting completed, but wasn't
> > actually used by anything. This is replaced by the background_work queue
> > and counter.
> > 
> > Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2")
> > Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
> 
> Aside from the above, feel free to add
> 
> Reviewed-by: Matthew Sakai <msakai@redhat.com>

Thanks. I'll wait to see if there are more comments first, but I will
respin this to fix the typos.

-Ben

> 
> > ---
> >   drivers/md/dm-cache-target.c | 55 ++++++++++++++++++------------------
> >   1 file changed, 28 insertions(+), 27 deletions(-)
> > 
> > diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
> > index 33dbc71b730f..d8d9c63d2a67 100644
> > --- a/drivers/md/dm-cache-target.c
> > +++ b/drivers/md/dm-cache-target.c
> > @@ -340,8 +340,6 @@ struct cache {
> >   	struct list_head invalidation_requests;
> >   	sector_t migration_threshold;
> > -	wait_queue_head_t migration_wait;
> > -	atomic_t nr_allocated_migrations;
> >   	/*
> >   	 * The number of in flight migrations that are performing
> > @@ -397,7 +395,11 @@ struct cache {
> >   	bool loaded_mappings:1;
> >   	bool loaded_discards:1;
> > -	struct rw_semaphore background_work_lock;
> > +	/* background work management */
> > +	bool background_work_allowed;
> > +	unsigned background_work_nr;
> > +	spinlock_t background_work_lock;
> > +	wait_queue_head_t background_work_wait;
> >   	struct batcher committer;
> >   	struct work_struct commit_ws;
> > @@ -488,19 +490,13 @@ static struct dm_cache_migration *alloc_migration(struct cache *cache)
> >   	memset(mg, 0, sizeof(*mg));
> >   	mg->cache = cache;
> > -	atomic_inc(&cache->nr_allocated_migrations);
> >   	return mg;
> >   }
> >   static void free_migration(struct dm_cache_migration *mg)
> >   {
> > -	struct cache *cache = mg->cache;
> > -
> > -	if (atomic_dec_and_test(&cache->nr_allocated_migrations))
> > -		wake_up(&cache->migration_wait);
> > -
> > -	mempool_free(mg, &cache->migration_pool);
> > +	mempool_free(mg, &mg->cache->migration_pool);
> >   }
> >   /*----------------------------------------------------------------*/
> > @@ -1030,34 +1026,39 @@ static void calc_discard_block_range(struct cache *cache, struct bio *bio,
> >   static void prevent_background_work(struct cache *cache)
> >   {
> > -	lockdep_off();
> > -	down_write(&cache->background_work_lock);
> > -	lockdep_on();
> > +	spin_lock_irq(&cache->background_work_lock);
> > +	cache->background_work_allowed = false;
> > +	wait_event_lock_irq(cache->background_work_wait,
> > +			    cache->background_work_nr == 0,
> > +			    cache->background_work_lock);
> > +	spin_unlock_irq(&cache->background_work_lock);
> >   }
> >   static void allow_background_work(struct cache *cache)
> >   {
> > -	lockdep_off();
> > -	up_write(&cache->background_work_lock);
> > -	lockdep_on();
> > +	spin_lock_irq(&cache->background_work_lock);
> > +	cache->background_work_allowed = true;
> > +	spin_unlock_irq(&cache->background_work_lock);
> >   }
> >   static bool background_work_begin(struct cache *cache)
> >   {
> >   	bool r;
> > -	lockdep_off();
> > -	r = down_read_trylock(&cache->background_work_lock);
> > -	lockdep_on();
> > -
> > +	spin_lock_irq(&cache->background_work_lock);
> > +	r = cache->background_work_allowed;
> > +	if (r)
> > +		cache->background_work_nr++;
> > +	spin_unlock_irq(&cache->background_work_lock);
> >   	return r;
> >   }
> >   static void background_work_end(struct cache *cache)
> >   {
> > -	lockdep_off();
> > -	up_read(&cache->background_work_lock);
> > -	lockdep_on();
> > +	spin_lock_irq(&cache->background_work_lock);
> > +	if (--cache->background_work_nr == 0)
> > +		wake_up(&cache->background_work_wait);
> > +	spin_unlock_irq(&cache->background_work_lock);
> >   }
> >   /*----------------------------------------------------------------*/
> > @@ -2507,9 +2508,7 @@ static int cache_create(struct cache_args *ca, struct cache **result)
> >   	spin_lock_init(&cache->lock);
> >   	bio_list_init(&cache->deferred_bios);
> > -	atomic_set(&cache->nr_allocated_migrations, 0);
> >   	atomic_set(&cache->nr_io_migrations, 0);
> > -	init_waitqueue_head(&cache->migration_wait);
> >   	r = -ENOMEM;
> >   	atomic_set(&cache->nr_dirty, 0);
> > @@ -2592,8 +2591,10 @@ static int cache_create(struct cache_args *ca, struct cache **result)
> >   		     issue_op, cache, cache->wq);
> >   	dm_iot_init(&cache->tracker);
> > -	init_rwsem(&cache->background_work_lock);
> > -	prevent_background_work(cache);
> > +	init_waitqueue_head(&cache->background_work_wait);
> > +	spin_lock_init(&cache->background_work_lock);
> > +	cache->background_work_allowed = false;
> > +	cache->background_work_nr = 0;
> >   	*result = cache;
> >   	return 0;


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] dm cache: fix issue with background work locking
  2026-08-25 19:22 [PATCH] dm cache: fix issue with background work locking Benjamin Marzinski
  2026-08-25 20:22 ` Matthew Sakai
@ 2026-08-27  8:36 ` Ming Hung Tsai
  2026-08-27 11:26   ` Mikulas Patocka
  1 sibling, 1 reply; 5+ messages in thread
From: Ming Hung Tsai @ 2026-08-27  8:36 UTC (permalink / raw)
  To: Benjamin Marzinski; +Cc: Mikulas Patocka, Mike Snitzer, dm-devel

On Wed, Aug 26, 2026 at 3:22 AM Benjamin Marzinski <bmarzins@redhat.com> wrote:
>
> dm cache used a rw_semaphore for background_work_lock. Write locks on
> rw_semaphores have strict owner semantics, but there was no guarantee
> that the process that locked background_work_lock was the same process
> that unlocked it. This can be easily seen using a kernel compiled with
> CONFIG_DEBUG_RWSEMS. Given a dm cache device <cache>, run: 'dmsetup
> suspend <cache> && dmsetup resume <cache>'. This will tigger a kernel
> warning:
>
> DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE))
>
> triggered by cache_resume(). To fix this, switch from a rw_semaphore to
> a spinlock and a wait queue. dm cache already hasd a wait queue and
> associated counter, migration_wait and nr_allocated_migrations, that was
> getting woken up when background work was getting completed, but wasn't
> actually used by anything. This is replaced by the background_work queue
> and counter.
>
> Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2")
> Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
> ---

Thanks Ben. I confirmed the patch fixed it.

Just one minor nit: checkpatch prefers 'unsigned int' for background_work_nr.

Worth noting before the commit b29d4986d0da ("dm cache: significant
rework to leverage dm-bio-prison-v2"), dm cache waited for migrations
via wait_for_migrations(), which did a wait_event() on migration_wait
and nr_allocated_migrations. That commit replaced it with
rw_semaphores and used lockdep_off() to suppress warnings, before
DEBUG_RWSEMS existed.

Reviewed-by: Ming-Hung Tsai <mtsai@redhat.com>

>  drivers/md/dm-cache-target.c | 55 ++++++++++++++++++------------------
>  1 file changed, 28 insertions(+), 27 deletions(-)
>
> diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
> index 33dbc71b730f..d8d9c63d2a67 100644
> --- a/drivers/md/dm-cache-target.c
> +++ b/drivers/md/dm-cache-target.c
> @@ -340,8 +340,6 @@ struct cache {
>         struct list_head invalidation_requests;
>
>         sector_t migration_threshold;
> -       wait_queue_head_t migration_wait;
> -       atomic_t nr_allocated_migrations;
>
>         /*
>          * The number of in flight migrations that are performing
> @@ -397,7 +395,11 @@ struct cache {
>         bool loaded_mappings:1;
>         bool loaded_discards:1;
>
> -       struct rw_semaphore background_work_lock;
> +       /* background work management */
> +       bool background_work_allowed;
> +       unsigned background_work_nr;
> +       spinlock_t background_work_lock;
> +       wait_queue_head_t background_work_wait;
>
>         struct batcher committer;
>         struct work_struct commit_ws;
> @@ -488,19 +490,13 @@ static struct dm_cache_migration *alloc_migration(struct cache *cache)
>         memset(mg, 0, sizeof(*mg));
>
>         mg->cache = cache;
> -       atomic_inc(&cache->nr_allocated_migrations);
>
>         return mg;
>  }
>
>  static void free_migration(struct dm_cache_migration *mg)
>  {
> -       struct cache *cache = mg->cache;
> -
> -       if (atomic_dec_and_test(&cache->nr_allocated_migrations))
> -               wake_up(&cache->migration_wait);
> -
> -       mempool_free(mg, &cache->migration_pool);
> +       mempool_free(mg, &mg->cache->migration_pool);
>  }
>
>  /*----------------------------------------------------------------*/
> @@ -1030,34 +1026,39 @@ static void calc_discard_block_range(struct cache *cache, struct bio *bio,
>
>  static void prevent_background_work(struct cache *cache)
>  {
> -       lockdep_off();
> -       down_write(&cache->background_work_lock);
> -       lockdep_on();
> +       spin_lock_irq(&cache->background_work_lock);
> +       cache->background_work_allowed = false;
> +       wait_event_lock_irq(cache->background_work_wait,
> +                           cache->background_work_nr == 0,
> +                           cache->background_work_lock);
> +       spin_unlock_irq(&cache->background_work_lock);
>  }
>
>  static void allow_background_work(struct cache *cache)
>  {
> -       lockdep_off();
> -       up_write(&cache->background_work_lock);
> -       lockdep_on();
> +       spin_lock_irq(&cache->background_work_lock);
> +       cache->background_work_allowed = true;
> +       spin_unlock_irq(&cache->background_work_lock);
>  }
>
>  static bool background_work_begin(struct cache *cache)
>  {
>         bool r;
>
> -       lockdep_off();
> -       r = down_read_trylock(&cache->background_work_lock);
> -       lockdep_on();
> -
> +       spin_lock_irq(&cache->background_work_lock);
> +       r = cache->background_work_allowed;
> +       if (r)
> +               cache->background_work_nr++;
> +       spin_unlock_irq(&cache->background_work_lock);
>         return r;
>  }
>
>  static void background_work_end(struct cache *cache)
>  {
> -       lockdep_off();
> -       up_read(&cache->background_work_lock);
> -       lockdep_on();
> +       spin_lock_irq(&cache->background_work_lock);
> +       if (--cache->background_work_nr == 0)
> +               wake_up(&cache->background_work_wait);
> +       spin_unlock_irq(&cache->background_work_lock);
>  }
>
>  /*----------------------------------------------------------------*/
> @@ -2507,9 +2508,7 @@ static int cache_create(struct cache_args *ca, struct cache **result)
>
>         spin_lock_init(&cache->lock);
>         bio_list_init(&cache->deferred_bios);
> -       atomic_set(&cache->nr_allocated_migrations, 0);
>         atomic_set(&cache->nr_io_migrations, 0);
> -       init_waitqueue_head(&cache->migration_wait);
>
>         r = -ENOMEM;
>         atomic_set(&cache->nr_dirty, 0);
> @@ -2592,8 +2591,10 @@ static int cache_create(struct cache_args *ca, struct cache **result)
>                      issue_op, cache, cache->wq);
>         dm_iot_init(&cache->tracker);
>
> -       init_rwsem(&cache->background_work_lock);
> -       prevent_background_work(cache);
> +       init_waitqueue_head(&cache->background_work_wait);
> +       spin_lock_init(&cache->background_work_lock);
> +       cache->background_work_allowed = false;
> +       cache->background_work_nr = 0;
>
>         *result = cache;
>         return 0;
> --
> 2.53.0
>


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] dm cache: fix issue with background work locking
  2026-08-27  8:36 ` Ming Hung Tsai
@ 2026-08-27 11:26   ` Mikulas Patocka
  0 siblings, 0 replies; 5+ messages in thread
From: Mikulas Patocka @ 2026-08-27 11:26 UTC (permalink / raw)
  To: Ming Hung Tsai; +Cc: Benjamin Marzinski, Mike Snitzer, dm-devel

[-- Attachment #1: Type: text/plain, Size: 6751 bytes --]



On Thu, 27 Aug 2026, Ming Hung Tsai wrote:

> On Wed, Aug 26, 2026 at 3:22 AM Benjamin Marzinski <bmarzins@redhat.com> wrote:
> >
> > dm cache used a rw_semaphore for background_work_lock. Write locks on
> > rw_semaphores have strict owner semantics, but there was no guarantee
> > that the process that locked background_work_lock was the same process
> > that unlocked it. This can be easily seen using a kernel compiled with
> > CONFIG_DEBUG_RWSEMS. Given a dm cache device <cache>, run: 'dmsetup
> > suspend <cache> && dmsetup resume <cache>'. This will tigger a kernel
> > warning:
> >
> > DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE))
> >
> > triggered by cache_resume(). To fix this, switch from a rw_semaphore to
> > a spinlock and a wait queue. dm cache already hasd a wait queue and
> > associated counter, migration_wait and nr_allocated_migrations, that was
> > getting woken up when background work was getting completed, but wasn't
> > actually used by anything. This is replaced by the background_work queue
> > and counter.
> >
> > Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2")
> > Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com>
> > ---
> 
> Thanks Ben. I confirmed the patch fixed it.
> 
> Just one minor nit: checkpatch prefers 'unsigned int' for background_work_nr.

Hi

Personally, I prefer 'unsigned'. There is no reason why to type more 
tokens if we don't have to.

Mikulas

> Worth noting before the commit b29d4986d0da ("dm cache: significant
> rework to leverage dm-bio-prison-v2"), dm cache waited for migrations
> via wait_for_migrations(), which did a wait_event() on migration_wait
> and nr_allocated_migrations. That commit replaced it with
> rw_semaphores and used lockdep_off() to suppress warnings, before
> DEBUG_RWSEMS existed.
> 
> Reviewed-by: Ming-Hung Tsai <mtsai@redhat.com>
> 
> >  drivers/md/dm-cache-target.c | 55 ++++++++++++++++++------------------
> >  1 file changed, 28 insertions(+), 27 deletions(-)
> >
> > diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
> > index 33dbc71b730f..d8d9c63d2a67 100644
> > --- a/drivers/md/dm-cache-target.c
> > +++ b/drivers/md/dm-cache-target.c
> > @@ -340,8 +340,6 @@ struct cache {
> >         struct list_head invalidation_requests;
> >
> >         sector_t migration_threshold;
> > -       wait_queue_head_t migration_wait;
> > -       atomic_t nr_allocated_migrations;
> >
> >         /*
> >          * The number of in flight migrations that are performing
> > @@ -397,7 +395,11 @@ struct cache {
> >         bool loaded_mappings:1;
> >         bool loaded_discards:1;
> >
> > -       struct rw_semaphore background_work_lock;
> > +       /* background work management */
> > +       bool background_work_allowed;
> > +       unsigned background_work_nr;
> > +       spinlock_t background_work_lock;
> > +       wait_queue_head_t background_work_wait;
> >
> >         struct batcher committer;
> >         struct work_struct commit_ws;
> > @@ -488,19 +490,13 @@ static struct dm_cache_migration *alloc_migration(struct cache *cache)
> >         memset(mg, 0, sizeof(*mg));
> >
> >         mg->cache = cache;
> > -       atomic_inc(&cache->nr_allocated_migrations);
> >
> >         return mg;
> >  }
> >
> >  static void free_migration(struct dm_cache_migration *mg)
> >  {
> > -       struct cache *cache = mg->cache;
> > -
> > -       if (atomic_dec_and_test(&cache->nr_allocated_migrations))
> > -               wake_up(&cache->migration_wait);
> > -
> > -       mempool_free(mg, &cache->migration_pool);
> > +       mempool_free(mg, &mg->cache->migration_pool);
> >  }
> >
> >  /*----------------------------------------------------------------*/
> > @@ -1030,34 +1026,39 @@ static void calc_discard_block_range(struct cache *cache, struct bio *bio,
> >
> >  static void prevent_background_work(struct cache *cache)
> >  {
> > -       lockdep_off();
> > -       down_write(&cache->background_work_lock);
> > -       lockdep_on();
> > +       spin_lock_irq(&cache->background_work_lock);
> > +       cache->background_work_allowed = false;
> > +       wait_event_lock_irq(cache->background_work_wait,
> > +                           cache->background_work_nr == 0,
> > +                           cache->background_work_lock);
> > +       spin_unlock_irq(&cache->background_work_lock);
> >  }
> >
> >  static void allow_background_work(struct cache *cache)
> >  {
> > -       lockdep_off();
> > -       up_write(&cache->background_work_lock);
> > -       lockdep_on();
> > +       spin_lock_irq(&cache->background_work_lock);
> > +       cache->background_work_allowed = true;
> > +       spin_unlock_irq(&cache->background_work_lock);
> >  }
> >
> >  static bool background_work_begin(struct cache *cache)
> >  {
> >         bool r;
> >
> > -       lockdep_off();
> > -       r = down_read_trylock(&cache->background_work_lock);
> > -       lockdep_on();
> > -
> > +       spin_lock_irq(&cache->background_work_lock);
> > +       r = cache->background_work_allowed;
> > +       if (r)
> > +               cache->background_work_nr++;
> > +       spin_unlock_irq(&cache->background_work_lock);
> >         return r;
> >  }
> >
> >  static void background_work_end(struct cache *cache)
> >  {
> > -       lockdep_off();
> > -       up_read(&cache->background_work_lock);
> > -       lockdep_on();
> > +       spin_lock_irq(&cache->background_work_lock);
> > +       if (--cache->background_work_nr == 0)
> > +               wake_up(&cache->background_work_wait);
> > +       spin_unlock_irq(&cache->background_work_lock);
> >  }
> >
> >  /*----------------------------------------------------------------*/
> > @@ -2507,9 +2508,7 @@ static int cache_create(struct cache_args *ca, struct cache **result)
> >
> >         spin_lock_init(&cache->lock);
> >         bio_list_init(&cache->deferred_bios);
> > -       atomic_set(&cache->nr_allocated_migrations, 0);
> >         atomic_set(&cache->nr_io_migrations, 0);
> > -       init_waitqueue_head(&cache->migration_wait);
> >
> >         r = -ENOMEM;
> >         atomic_set(&cache->nr_dirty, 0);
> > @@ -2592,8 +2591,10 @@ static int cache_create(struct cache_args *ca, struct cache **result)
> >                      issue_op, cache, cache->wq);
> >         dm_iot_init(&cache->tracker);
> >
> > -       init_rwsem(&cache->background_work_lock);
> > -       prevent_background_work(cache);
> > +       init_waitqueue_head(&cache->background_work_wait);
> > +       spin_lock_init(&cache->background_work_lock);
> > +       cache->background_work_allowed = false;
> > +       cache->background_work_nr = 0;
> >
> >         *result = cache;
> >         return 0;
> > --
> > 2.53.0
> >
> 

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-08-27 11:26 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-25 19:22 [PATCH] dm cache: fix issue with background work locking Benjamin Marzinski
2026-08-25 20:22 ` Matthew Sakai
2026-08-25 23:19   ` Benjamin Marzinski
2026-08-27  8:36 ` Ming Hung Tsai
2026-08-27 11:26   ` Mikulas Patocka

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.