All of lore.kernel.org
 help / color / mirror / Atom feed
From: netdev-bot+sashiko@kernel.org
To: achender@kernel.org
Cc: netdev@vger.kernel.org, linux-rdma@vger.kernel.org,
	pabeni@redhat.com, edumazet@google.com, kuba@kernel.org,
	horms@kernel.org, nicoyip.dev@gmail.com
Subject: Re: [PATCH net-next v2 2/9] net/rds: make rds_destroy_pending() cover single-connection destroy
Date: Wed, 16 Sep 2026 04:36:44 +0000	[thread overview]
Message-ID: <178953340453.22033.3272399861070164947@kernel.org> (raw)
In-Reply-To: <20260912035027.27447-3-achender@kernel.org>

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:

Critical: 0 · High: 0 (2) · Medium: 0 · Low: 1 (2)

- [Low] The commit message and both new comments assert an invariant the
  tree does not hold.

Pre-existing issues:
- [High] Deadlock in the very single-connection destroy path this patch
  is written for.
- [High] Reported concern (judged resolved later in the series):
  rds_destroy_pending() now dereferences conn->c_destroy_in_prog, but…
- [Low] Reported concern (judged invalid at the end of the series):
  rds_conn_destroy()'s header comment at net/rds/connection.c:558-565…
- [Low] Reported concern (judged a false positive): that
  rds_conn_path_drop(cp, destroy=true) (net/rds/connection.c:997-1008)…

--

net/rds: make rds_destroy_pending() cover single-connection destroy

This adds a per-connection c_destroy_in_prog flag, set by
rds_conn_destroy() before the unhash plus synchronize_rcu() sequence, and
tests it in rds_destroy_pending() so that a single-connection destroy is
covered by the same predicate the work-requeueing sites already consult.
The now-unreferenced RDS_DESTROY_PENDING cp_flags bit and its dead test in
rds_ib_is_unloading() are removed.

A few questions below.

> diff --git a/net/rds/connection.c b/net/rds/connection.c
> index b6c4beb50eaf0..50e1b6bfceeab 100644
> --- a/net/rds/connection.c
> +++ b/net/rds/connection.c
> @@ -574,6 +574,14 @@ void rds_conn_destroy(struct rds_connection *conn)
>  		 "%pI4\n", conn, &conn->c_laddr,
>  		 &conn->c_faddr);
>  
> +	/* Make rds_destroy_pending() true for this conn.  Together with
> +	 * the synchronize_rcu() below this stops the work-requeueing
> +	 * sites (which all test rds_destroy_pending() under
> +	 * rcu_read_lock()) from queueing new work on the path
> +	 * workqueues once we start cancelling and destroying them.
> +	 */

[Severity: Low]
Is the "which all test rds_destroy_pending() under rcu_read_lock()" part
accurate for the current tree?  rds_send_worker() and rds_recv_worker() in
net/rds/threads.c re-arm their own works with no predicate test and no RCU
read-side section:

net/rds/threads.c:rds_send_worker() {
	...
	switch (ret) {
	case -EAGAIN:
		rds_stats_inc(s_send_immediate_retry);
		queue_delayed_work(cp->cp_wq, &cp->cp_send_w, 0);
		break;
	case -ENOMEM:
		rds_stats_inc(s_send_delayed_retry);
		queue_delayed_work(cp->cp_wq, &cp->cp_send_w, 2);
	...
}

rds_recv_worker() does the same for cp_recv_w.

Those two do look safe, but for a different reason than the one documented
here: rds_conn_path_destroy() runs cancel_delayed_work_sync(&cp->cp_send_w)
and cancel_delayed_work_sync(&cp->cp_recv_w), and __cancel_work_sync()
disables the work for the duration of the cancel, so a self-requeue from the
still-running worker is dropped and the worker cannot requeue once the sync
cancel has returned.

The commit message makes the same universal claim:

    since all of those sites guard the queueing with rds_destroy_pending()
    under rcu_read_lock() (the last stragglers were converted by the
    previous patch)

Could both the comment and the commit message be reworded so the two
self-requeue sites in threads.c are not covered by the statement?

> +	WRITE_ONCE(conn->c_destroy_in_prog, true);
> +
>  	/* Ensure conn will not be scheduled for reconnect */
>  	spin_lock_irq(&rds_conn_lock);
>  	hlist_del_init_rcu(&conn->c_hash_node);

[Severity: High]
This isn't a bug introduced by this patch, but the single-connection destroy
path the commit message names as the motivation looks like it deadlocks
before the new flag matters.

rds_ib_cm_connect_complete() calls rds_conn_destroy() synchronously from
inside the RDMA CM event handler, which still holds conn->c_cm_lock (that is
c_path[0].cp_cm_lock, see #define c_cm_lock c_path[0].cp_cm_lock in
net/rds/rds_single_path.h):

rds_rdma_cm_event_handler_cmn()
  mutex_lock(&conn->c_cm_lock);
  rds_ib_cm_connect_complete()
    rds_conn_destroy()
      rds_conn_path_destroy()
        rds_conn_path_drop(cp, true);   /* cp_state = RDS_CONN_ERROR,
                                           queue_work(cp_down_w) */
        flush_work(&cp->cp_down_w);

The queued pass then blocks on the mutex the flushing thread still holds:

rds_shutdown_worker()
  rds_conn_shutdown(cp)
    /* DOWN->DOWN transition fails, state is RDS_CONN_ERROR */
    mutex_lock(&cp->cp_cm_lock);

The handler cannot reach its mutex_unlock() until rds_conn_destroy() returns,
and rds_conn_destroy() cannot return until cp_down_w completes.  Can this
hang both the rdma_cm event worker and the path workqueue worker, with the
connection left in DISCONNECTING/ERROR?

There appears to be a second cycle on the same path: if the shutdown pass
ever got past the mutex, rds_ib_conn_path_shutdown() calls
rdma_destroy_id(ic->i_cm_id) on the very cm_id whose event is being handled,
which waits on id_priv->handler_mutex held by the same stuck thread.

The deciding input is peer supplied: the branch is taken when the peer's
accept private data decodes to a protocol version below 3.1, with
RDS_PROTOCOL_VERSION == RDS_PROTOCOL_COMPAT_VERSION == 0x0301.

> diff --git a/net/rds/rds.h b/net/rds/rds.h
> index 2db49573dacd5..50b08c28ab865 100644
> --- a/net/rds/rds.h
> +++ b/net/rds/rds.h

[ ... ]

> @@ -148,6 +147,14 @@ struct rds_connection {
>  				c_pad_to_32:29;
>  	int			c_npaths;
>  	bool			c_with_sport_idx;
> +	/* Set once, by rds_conn_destroy(), before it cancels the path
> +	 * works; read through rds_destroy_pending().  A site that arms
> +	 * a path work must test the predicate and queue the work inside
> +	 * one rcu_read_lock() section: the synchronize_rcu() that
> +	 * follows the store is what keeps a queue issued after the
> +	 * cancellation from landing on a destroyed workqueue.
> +	 */
> +	bool			c_destroy_in_prog;
>  	struct rds_connection	*c_passive;
>  	struct rds_transport	*c_trans;
>  
> @@ -994,7 +1001,8 @@ void __rds_put_mr_final(struct kref *kref);
>  
>  static inline bool rds_destroy_pending(struct rds_connection *conn)
>  {
> -	return !check_net(rds_conn_net(conn)) ||
> +	return READ_ONCE(conn->c_destroy_in_prog) ||
> +	       !check_net(rds_conn_net(conn)) ||
>  	       (conn->c_trans->t_unloading && conn->c_trans->t_unloading(conn));
>  }

[Severity: High]
This isn't a bug introduced by this patch, but rds_destroy_pending() now
dereferences the conn to read c_destroy_in_prog, while at this commit
rds_conn_destroy() still frees the conn synchronously right after the paths
are torn down:

net/rds/connection.c:rds_conn_destroy() {
	...
	kfree(conn->c_path);
	kmem_cache_free(rds_conn_slab, conn);
}

Several sites that evaluate the predicate hold raw, unreferenced conn
pointers: rs->rs_conn in rds_sendmsg(), tc->t_cpath->cp_conn reached from
sk->sk_user_data in the TCP callbacks, and ic->conn on the IB side.  Can
such a reader evaluate READ_ONCE(conn->c_destroy_in_prog) on freed slab
memory and, on a stale false, go on to queue work on a cp_wq that
destroy_workqueue() has already freed?

Later patches in this series do supply the liveness the new guard needs:
"net/rds: split connection destroy into quiesce and kref-governed free"
moves conn_free, destroy_workqueue, kfree(c_path) and kmem_cache_free into
rds_conn_destroy_fini() at the last rds_conn_put(); "net/rds: hold
connection references in lookup, sockets and c_passive" gives rs->rs_conn
its own reference; and "net/rds: pin the connection across RDMA-CM event
handling" pins the conn around the CM handler.  Would it be clearer to order
this patch after those, or to note the dependency in the commit message?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260912035027.27447-1-achender%40kernel.org

  parent reply	other threads:[~2026-09-16  4:36 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-12  3:50 [PATCH net-next v2 0/9] net/rds: make connection lifetime reference-counted Allison Henderson
2026-09-12  3:50 ` [PATCH net-next v2 1/9] net/rds: guard every work-requeueing site with rds_destroy_pending() Allison Henderson
2026-09-13  3:50   ` sashiko-bot
2026-09-16  4:36   ` netdev-bot+sashiko
2026-09-12  3:50 ` [PATCH net-next v2 2/9] net/rds: make rds_destroy_pending() cover single-connection destroy Allison Henderson
2026-09-13  3:50   ` sashiko-bot
2026-09-16  4:36   ` netdev-bot+sashiko [this message]
2026-09-12  3:50 ` [PATCH net-next v2 3/9] net/rds: split connection destroy into quiesce and kref-governed free Allison Henderson
2026-09-13  3:50   ` sashiko-bot
2026-09-16  4:36   ` netdev-bot+sashiko
2026-09-12  3:50 ` [PATCH net-next v2 4/9] net/rds: wait for connections to be freed on transport unload Allison Henderson
2026-09-13  3:50   ` sashiko-bot
2026-09-16  4:36   ` netdev-bot+sashiko
2026-09-12  3:50 ` [PATCH net-next v2 5/9] net/rds: unlink transport nodes before a possibly deferred connection free Allison Henderson
2026-09-13  3:50   ` sashiko-bot
2026-09-16  4:36   ` netdev-bot+sashiko
2026-09-12  3:50 ` [PATCH net-next v2 6/9] net/rds: hold connection references in lookup, sockets and c_passive Allison Henderson
2026-09-13  3:50   ` sashiko-bot
2026-09-16  4:36   ` netdev-bot+sashiko
2026-09-12  3:50 ` [PATCH net-next v2 7/9] net/rds: pin the connection across RDMA-CM event handling Allison Henderson
2026-09-13  3:50   ` sashiko-bot
2026-09-16  4:36   ` netdev-bot+sashiko
2026-09-12  3:50 ` [PATCH net-next v2 8/9] net/rds: drop rds_conn_count in favor of t_conn_count Allison Henderson
2026-09-13  3:50   ` sashiko-bot
2026-09-16  4:36   ` netdev-bot+sashiko
2026-09-12  3:50 ` [PATCH net-next v2 9/9] net/rds: hold a connection reference from struct rds_incoming Allison Henderson
2026-09-13  3:50   ` sashiko-bot
2026-09-16  4:36   ` netdev-bot+sashiko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178953340453.22033.3272399861070164947@kernel.org \
    --to=netdev-bot+sashiko@kernel.org \
    --cc=achender@kernel.org \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-rdma@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=nicoyip.dev@gmail.com \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.