diff mbox series

[08/32] RDMA/cm: Convert local_id_table to XArray

Message ID 20190221002107.22625-9-willy@infradead.org (mailing list archive)
State Accepted
Delegated to: Jason Gunthorpe
Headers show
Series Convert the Infiniband subsystem to XArray | expand

Commit Message

Matthew Wilcox Feb. 21, 2019, 12:20 a.m. UTC
Also introduce cm_local_id() to reduce the amount of boilerplate when
converting a local ID to an XArray index.

Signed-off-by: Matthew Wilcox <willy@infradead.org>
---
 drivers/infiniband/core/cm.c | 40 +++++++++++++++---------------------
 1 file changed, 17 insertions(+), 23 deletions(-)

Comments

Jason Gunthorpe Feb. 21, 2019, 6:23 p.m. UTC | #1
On Wed, Feb 20, 2019 at 04:20:43PM -0800, Matthew Wilcox wrote:
> Also introduce cm_local_id() to reduce the amount of boilerplate when
> converting a local ID to an XArray index.
> 
> Signed-off-by: Matthew Wilcox <willy@infradead.org>
>  drivers/infiniband/core/cm.c | 40 +++++++++++++++---------------------
>  1 file changed, 17 insertions(+), 23 deletions(-)
> 
> diff --git a/drivers/infiniband/core/cm.c b/drivers/infiniband/core/cm.c
> index 37980c7564c0..b97592b44ce2 100644
> +++ b/drivers/infiniband/core/cm.c
> @@ -124,7 +124,8 @@ static struct ib_cm {
>  	struct rb_root remote_qp_table;
>  	struct rb_root remote_id_table;
>  	struct rb_root remote_sidr_table;
> -	struct idr local_id_table;
> +	struct xarray local_id_table;
> +	u32 local_id_next;
>  	__be32 random_id_operand;
>  	struct list_head timewait_list;
>  	struct workqueue_struct *wq;
> @@ -598,35 +599,31 @@ static int cm_init_av_by_path(struct sa_path_rec *path,
>  
>  static int cm_alloc_id(struct cm_id_private *cm_id_priv)
>  {
> -	unsigned long flags;
> -	int id;
> -
> -	idr_preload(GFP_KERNEL);
> -	spin_lock_irqsave(&cm.lock, flags);
> +	int err;
> +	u32 id;
>  
> -	id = idr_alloc_cyclic(&cm.local_id_table, cm_id_priv, 0, 0, GFP_NOWAIT);
> -
> -	spin_unlock_irqrestore(&cm.lock, flags);
> -	idr_preload_end();
> +	err = xa_alloc_cyclic_irq(&cm.local_id_table, &id, cm_id_priv,
> +			xa_limit_32b, &cm.local_id_next, GFP_KERNEL);
>  
>  	cm_id_priv->id.local_id = (__force __be32)id ^ cm.random_id_operand;
> -	return id < 0 ? id : 0;
> +	return err;
> +}
> +
> +static u32 cm_local_id(__be32 local_id)
> +{
> +	return (__force u32) (local_id ^ cm.random_id_operand);
>  }
>  
>  static void cm_free_id(__be32 local_id)
>  {
> -	spin_lock_irq(&cm.lock);
> -	idr_remove(&cm.local_id_table,
> -		   (__force int) (local_id ^ cm.random_id_operand));
> -	spin_unlock_irq(&cm.lock);
> +	xa_erase_irq(&cm.local_id_table, cm_local_id(local_id));
>  }
>  
>  static struct cm_id_private * cm_get_id(__be32 local_id, __be32 remote_id)
>  {
>  	struct cm_id_private *cm_id_priv;
>  
> -	cm_id_priv = idr_find(&cm.local_id_table,
> -			      (__force int) (local_id ^ cm.random_id_operand));
> +	cm_id_priv = xa_load(&cm.local_id_table, cm_local_id(local_id));
>  	if (cm_id_priv) {
>  		if (cm_id_priv->id.remote_id == remote_id)
>  			atomic_inc(&cm_id_priv->refcount);
> @@ -2824,9 +2821,8 @@ static struct cm_id_private * cm_acquire_rejected_id(struct cm_rej_msg *rej_msg)
>  			spin_unlock_irq(&cm.lock);
>  			return NULL;
>  		}
> -		cm_id_priv = idr_find(&cm.local_id_table, (__force int)
> -				      (timewait_info->work.local_id ^
> -				       cm.random_id_operand));
> +		cm_id_priv = xa_load(&cm.local_id_table,
> +				cm_local_id(timewait_info->work.local_id));
>  		if (cm_id_priv) {
>  			if (cm_id_priv->id.remote_id == remote_id)
>  				atomic_inc(&cm_id_priv->refcount);
> @@ -4513,7 +4509,7 @@ static int __init ib_cm_init(void)
>  	cm.remote_id_table = RB_ROOT;
>  	cm.remote_qp_table = RB_ROOT;
>  	cm.remote_sidr_table = RB_ROOT;
> -	idr_init(&cm.local_id_table);
> +	xa_init_flags(&cm.local_id_table, XA_FLAGS_ALLOC | XA_FLAGS_LOCK_IRQ);
>  	get_random_bytes(&cm.random_id_operand, sizeof cm.random_id_operand);
>  	INIT_LIST_HEAD(&cm.timewait_list);
>  
> @@ -4539,7 +4535,6 @@ static int __init ib_cm_init(void)
>  error2:
>  	class_unregister(&cm_class);
>  error1:
> -	idr_destroy(&cm.local_id_table);
>  	return ret;
>  }
>  
> @@ -4561,7 +4556,6 @@ static void __exit ib_cm_cleanup(void)
>  	}
>  
>  	class_unregister(&cm_class);
> -	idr_destroy(&cm.local_id_table);

Why not do xa_destroy()?

BTW, I kind of feel like we should have a

  xa_empty_destroy(xa)

which does a  

   if (WARN_ON(!xa_empty()))
       xa_destroy(xa)

Since cases like this are leaking memory bugs to unload the module
with anything in the xarray.

Jason
Matthew Wilcox Feb. 21, 2019, 6:36 p.m. UTC | #2
On Thu, Feb 21, 2019 at 11:23:04AM -0700, Jason Gunthorpe wrote:
> On Wed, Feb 20, 2019 at 04:20:43PM -0800, Matthew Wilcox wrote:
> > @@ -4561,7 +4556,6 @@ static void __exit ib_cm_cleanup(void)
> >  	}
> >  
> >  	class_unregister(&cm_class);
> > -	idr_destroy(&cm.local_id_table);
> 
> Why not do xa_destroy()?
> 
> BTW, I kind of feel like we should have a
> 
>   xa_empty_destroy(xa)
> 
> which does a  
> 
>    if (WARN_ON(!xa_empty()))
>        xa_destroy(xa)
> 
> Since cases like this are leaking memory bugs to unload the module
> with anything in the xarray.

Back In The Day (2016), you had to call idr_destroy() when you were going
to free the data structure holding an IDR, or when unloading a module with
a static IDR in it.  I did a quick estimate, and I'd say about a third of
IDR users didn't.  That was definitely a memory leak because it wouldn't
free any of the preallocated idr_layers which were held in the tree.

Once I converted the IDR to use the radix tree, preallocated
radix_tree_nodes were kept per-cpu and not per-IDR, so the
idr_destroy() became entirely optional if it was empty.  We see a few
WARN/BUG_ON(idr_is_empty()) calls dotted around the kernel as heritage
from these days, but mostly they just stayed as unnecessary calls to
idr_destroy().

For the vast majority of users, these are no-ops.  Because if you're
tearing down an IDR/XArray, if it's not empty then usually it contains
a pointer to something, and idr_destroy()/xa_destroy() can't take care
of it.  There's no generic free() function we can call.  Most IDRs/XArrays
contain the only reference to the pointer.  If it's not empty and we
destroy it, we're losing the only pointer to that memory.

So I chose to delete most of the calls to idr_destroy rather than
convert them to either an xa_empty assertion or a call to xa_destroy.
Yes, if there's a bug in the module, we're going to leak an xa_node.
But we were already leaking a pointer to something else.  We have
CONFIG_DEBUG_KMEMLEAK to help us find these things; we don't need
individual drivers to be doing it too.
Jason Gunthorpe Feb. 21, 2019, 7:08 p.m. UTC | #3
On Thu, Feb 21, 2019 at 10:36:58AM -0800, Matthew Wilcox wrote:
> On Thu, Feb 21, 2019 at 11:23:04AM -0700, Jason Gunthorpe wrote:
> > On Wed, Feb 20, 2019 at 04:20:43PM -0800, Matthew Wilcox wrote:
> > > @@ -4561,7 +4556,6 @@ static void __exit ib_cm_cleanup(void)
> > >  	}
> > >  
> > >  	class_unregister(&cm_class);
> > > -	idr_destroy(&cm.local_id_table);
> > 
> > Why not do xa_destroy()?
> > 
> > BTW, I kind of feel like we should have a
> > 
> >   xa_empty_destroy(xa)
> > 
> > which does a  
> > 
> >    if (WARN_ON(!xa_empty()))
> >        xa_destroy(xa)
> > 
> > Since cases like this are leaking memory bugs to unload the module
> > with anything in the xarray.
> 
> Back In The Day (2016), you had to call idr_destroy() when you were going
> to free the data structure holding an IDR, or when unloading a module with
> a static IDR in it.  I did a quick estimate, and I'd say about a third of
> IDR users didn't.  That was definitely a memory leak because it wouldn't
> free any of the preallocated idr_layers which were held in the tree.
> 
> Once I converted the IDR to use the radix tree, preallocated
> radix_tree_nodes were kept per-cpu and not per-IDR, so the
> idr_destroy() became entirely optional if it was empty.  We see a few
> WARN/BUG_ON(idr_is_empty()) calls dotted around the kernel as heritage
> from these days, but mostly they just stayed as unnecessary calls to
> idr_destroy().
> 
> For the vast majority of users, these are no-ops.  Because if you're
> tearing down an IDR/XArray, if it's not empty then usually it contains
> a pointer to something, and idr_destroy()/xa_destroy() can't take care
> of it.  There's no generic free() function we can call.  Most IDRs/XArrays
> contain the only reference to the pointer.  If it's not empty and we
> destroy it, we're losing the only pointer to that memory.
> 
> So I chose to delete most of the calls to idr_destroy rather than
> convert them to either an xa_empty assertion or a call to xa_destroy.
> Yes, if there's a bug in the module, we're going to leak an xa_node.
> But we were already leaking a pointer to something else.  We have
> CONFIG_DEBUG_KMEMLEAK to help us find these things; we don't need
> individual drivers to be doing it too.

I disagree that CONFIG_DEBUG_KMEMLEAK is a replacement for a WARN_ON,
it is very imprecise and difficult to use feature.

IHMO all the idr_destroys should be WARN_ON checks because, as you
say, it is an unconditional bug to pass that point with pointers still
in the IDR.

It is clearly a bug in the module, why shouldn't we detect it when it
is so cheap to do so?

Jason
diff mbox series

Patch

diff --git a/drivers/infiniband/core/cm.c b/drivers/infiniband/core/cm.c
index 37980c7564c0..b97592b44ce2 100644
--- a/drivers/infiniband/core/cm.c
+++ b/drivers/infiniband/core/cm.c
@@ -124,7 +124,8 @@  static struct ib_cm {
 	struct rb_root remote_qp_table;
 	struct rb_root remote_id_table;
 	struct rb_root remote_sidr_table;
-	struct idr local_id_table;
+	struct xarray local_id_table;
+	u32 local_id_next;
 	__be32 random_id_operand;
 	struct list_head timewait_list;
 	struct workqueue_struct *wq;
@@ -598,35 +599,31 @@  static int cm_init_av_by_path(struct sa_path_rec *path,
 
 static int cm_alloc_id(struct cm_id_private *cm_id_priv)
 {
-	unsigned long flags;
-	int id;
-
-	idr_preload(GFP_KERNEL);
-	spin_lock_irqsave(&cm.lock, flags);
+	int err;
+	u32 id;
 
-	id = idr_alloc_cyclic(&cm.local_id_table, cm_id_priv, 0, 0, GFP_NOWAIT);
-
-	spin_unlock_irqrestore(&cm.lock, flags);
-	idr_preload_end();
+	err = xa_alloc_cyclic_irq(&cm.local_id_table, &id, cm_id_priv,
+			xa_limit_32b, &cm.local_id_next, GFP_KERNEL);
 
 	cm_id_priv->id.local_id = (__force __be32)id ^ cm.random_id_operand;
-	return id < 0 ? id : 0;
+	return err;
+}
+
+static u32 cm_local_id(__be32 local_id)
+{
+	return (__force u32) (local_id ^ cm.random_id_operand);
 }
 
 static void cm_free_id(__be32 local_id)
 {
-	spin_lock_irq(&cm.lock);
-	idr_remove(&cm.local_id_table,
-		   (__force int) (local_id ^ cm.random_id_operand));
-	spin_unlock_irq(&cm.lock);
+	xa_erase_irq(&cm.local_id_table, cm_local_id(local_id));
 }
 
 static struct cm_id_private * cm_get_id(__be32 local_id, __be32 remote_id)
 {
 	struct cm_id_private *cm_id_priv;
 
-	cm_id_priv = idr_find(&cm.local_id_table,
-			      (__force int) (local_id ^ cm.random_id_operand));
+	cm_id_priv = xa_load(&cm.local_id_table, cm_local_id(local_id));
 	if (cm_id_priv) {
 		if (cm_id_priv->id.remote_id == remote_id)
 			atomic_inc(&cm_id_priv->refcount);
@@ -2824,9 +2821,8 @@  static struct cm_id_private * cm_acquire_rejected_id(struct cm_rej_msg *rej_msg)
 			spin_unlock_irq(&cm.lock);
 			return NULL;
 		}
-		cm_id_priv = idr_find(&cm.local_id_table, (__force int)
-				      (timewait_info->work.local_id ^
-				       cm.random_id_operand));
+		cm_id_priv = xa_load(&cm.local_id_table,
+				cm_local_id(timewait_info->work.local_id));
 		if (cm_id_priv) {
 			if (cm_id_priv->id.remote_id == remote_id)
 				atomic_inc(&cm_id_priv->refcount);
@@ -4513,7 +4509,7 @@  static int __init ib_cm_init(void)
 	cm.remote_id_table = RB_ROOT;
 	cm.remote_qp_table = RB_ROOT;
 	cm.remote_sidr_table = RB_ROOT;
-	idr_init(&cm.local_id_table);
+	xa_init_flags(&cm.local_id_table, XA_FLAGS_ALLOC | XA_FLAGS_LOCK_IRQ);
 	get_random_bytes(&cm.random_id_operand, sizeof cm.random_id_operand);
 	INIT_LIST_HEAD(&cm.timewait_list);
 
@@ -4539,7 +4535,6 @@  static int __init ib_cm_init(void)
 error2:
 	class_unregister(&cm_class);
 error1:
-	idr_destroy(&cm.local_id_table);
 	return ret;
 }
 
@@ -4561,7 +4556,6 @@  static void __exit ib_cm_cleanup(void)
 	}
 
 	class_unregister(&cm_class);
-	idr_destroy(&cm.local_id_table);
 }
 
 module_init(ib_cm_init);