Re: [PATCH mptcp-next 6/6] mptcp: defer sk_data_ready to the worker
Geliang Tang <[email protected]> Thu, 30 Jul 2026 08:57:11 +0800
| Newsgroups | dev.linux.lists.mptcp |
|---|---|
| Message-ID | <[email protected]> |
Hi Paolo, On Wed, 2026-07-29 at 10:55 +0200, Paolo Abeni wrote: > On 7/27/26 1:29 PM, Geliang Tang wrote: > > From: Geliang Tang <[email protected]> > > > > When MPTCP carries TLS, the data path runs under mptcp_data_lock(). > > Reaching sk->sk_data_ready(sk) synchronously ends up at > > tls_strp_check_rcv() -> mptcp_recv_skb() -> mptcp_move_skbs(), > > which > > calls mptcp_data_lock() on the same sk and recurses on > > sk_lock.slock. > > Is mptcp_move_skbs() really needed in mptcp_recv_skb()? Anyway it > looks > like that is not the only constraint: AFAICS, before the > mptcp_recv_skb() > calls, the TLS code would call __mptcp_read_sock() which in turns > calls > mptcp_rcv_space_adjust() and mptcp_cleanup_rbuf() that requires > holding > the msk socket lock in process context, while the mptcp/TLS caller is > in > BH scope. > > > Fix this by deferring sk->sk_data_ready(sk) to mptcp_worker() via a > > new MPTCP_WORK_DATA_READY bit, re-using the existing > > mptcp_schedule_work()/mptcp_cancel_work() infrastructure. The > > wakeup > > bit is consumed after the SOCK_DEAD && TCP_CLOSE destroy branch, so > > a > > socket that reaches the destroy path drops the pending wakeup > > rather > > than running it post-free. > > I think that unconditionally adding the work latency for non-TLS > application > is a no-go. > > Instead I *think* that the constraints in __mptcp_read_sock() could > be relaxed > with something alike the following (completely untested): Thank you for your patch. It is very useful, but when running TLS tests, it deadlocks with the mptcp_data_lock in mptcp_inq_hint(). TLS calls mptcp_inq(), and my implementation of mptcp_inq() is a wrapper around mptcp_inq_hint(): static int mptcp_inq(struct sock *sk) { int answ; if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) { answ = 0; } else { answ = mptcp_inq_hint(sk); if (answ && (sk->sk_state == TCP_CLOSE || (sk->sk_shutdown & RCV_SHUTDOWN))) answ--; } return answ; } To eliminate this deadlock, I had to remove the mptcp_data_lock() from mptcp_inq_hint() and replace it with READ_ONCE(). I'm not sure if this is problematic: @@ static unsigned int mptcp_inq_hint(struct sock *sk) const struct mptcp_sock *msk = mptcp_sk(sk); u64 hint_val; - /* Avoid races vs ack_seq updates. */ - mptcp_data_lock(sk); - hint_val = msk->ack_seq - msk->copied_seq; - mptcp_data_unlock(sk); + hint_val = READ_ONCE(msk->ack_seq) - READ_ONCE(msk->copied_seq); if (hint_val >= INT_MAX) return INT_MAX; Additionally, mptcp_read_done() also needs similar modifications to those made in __mptcp_read_sock(): @@ static void mptcp_read_done(struct sock *sk, size_t len) mptcp_eat_recv_skb(sk, skb); } - mptcp_rcv_space_adjust(msk, len - left); - /* Clean up data we have read: This will do ACK frames. */ - if (left != len) - mptcp_cleanup_rbuf(msk, len - left); + if (left != len) { + msk->read_copied = len - left; + set_bit(MPTCP_WORK_READ_COMPLETE, &msk->flags); + mptcp_schedule_work(sk); + } } Currently, the implementation of mptcp_read_done() is as follows: static void mptcp_read_done(struct sock *sk, size_t len) { struct mptcp_sock *msk = mptcp_sk(sk); struct sk_buff *skb; size_t left; u32 offset; msk_owned_by_me(msk); if (sk->sk_state == TCP_LISTEN) return; left = len; while (left && (skb = mptcp_recv_skb(sk, &offset)) != NULL) { int used; used = min_t(size_t, skb->len - offset, left); msk->bytes_consumed += used; msk->copied_seq += used; left -= used; if (skb->len > offset + used) break; mptcp_eat_recv_skb(sk, skb); } /* Clean up data we have read: This will do ACK frames. */ if (left != len) { msk->read_copied = len - left; set_bit(MPTCP_WORK_READ_COMPLETE, &msk->flags); mptcp_schedule_work(sk); } } Another issue, unrelated to this patch, is that I have defined a .read_done interface for TLS, with tcp_read_done() and mptcp_read_done() corresponding to TCP and MPTCP, respectively. This .read_done interface is very similar to .read_sock, and .read_sock is a generic interface in struct proto_ops. I'm wondering whether we could add a .read_done interface to struct proto_ops, so that in TLS we could call the protocol-specific .read_done via sk->sk_socket->ops- >read_done(). I'm not sure if this is a good idea or whether upstream would accept it. I'd like to hear your opinion. Thank you very much. -Geliang > --- > diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c > index ca644ec53eed..b09e267f5d46 100644 > --- a/net/mptcp/protocol.c > +++ b/net/mptcp/protocol.c > @@ -2986,6 +2986,15 @@ static void mptcp_do_fastclose(struct sock > *sk) > } > } > > +static void mptcp_read_complete(struct sock *sk) > +{ > + struct mptcp_sock *msk = mptcp_sk(sk); > + > + mptcp_cleanup_rbuf(msk, msk->read_copied); > + mptcp_rcv_space_adjust(msk, msk->read_copied); > + msk->read_copied = 0; > +} > + > static void mptcp_worker(struct work_struct *work) > { > struct mptcp_sock *msk = container_of(work, struct > mptcp_sock, work); > @@ -3026,6 +3035,9 @@ static void mptcp_worker(struct work_struct > *work) > if (test_and_clear_bit(MPTCP_WORK_RTX, &msk->flags)) > __mptcp_retrans(sk); > > + if (test_and_clear_bit(MPTCP_WORK_READ_COMPLETE, &msk- > >flags)) > + __mptcp_read_complete(sk); > + > fail_tout = msk->first ? READ_ONCE(mptcp_subflow_ctx(msk- > >first)->fail_tout) : 0; > if (fail_tout && time_after(jiffies, fail_tout)) > mptcp_mp_fail_no_response(msk); > @@ -4381,9 +4393,6 @@ static struct sk_buff *mptcp_recv_skb(struct > sock *sk, u32 *off) > struct sk_buff *skb; > u32 offset; > > - if (!list_empty(&msk->backlog_list)) > - mptcp_move_skbs(sk); > - > while ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) { > offset = MPTCP_SKB_CB(skb)->offset; > if (offset < skb->len) { > @@ -4395,10 +4404,7 @@ static struct sk_buff *mptcp_recv_skb(struct > sock *sk, u32 *off) > return NULL; > } > > -/* > - * Note: > - * - It is assumed that the socket was locked by the caller. > - */ > +/* Can be invoked in BH scope */ > static int __mptcp_read_sock(struct sock *sk, read_descriptor_t > *desc, > sk_read_actor_t recv_actor, bool noack) > { > @@ -4441,11 +4447,14 @@ static int __mptcp_read_sock(struct sock *sk, > read_descriptor_t *desc, > if (noack) > goto out; > > - mptcp_rcv_space_adjust(msk, copied); > - > + /* The backlog flushing is only needed when some data is > actually > + * moved and will take place in the workers's release > callback. > + */ > if (copied > 0) { > mptcp_recv_skb(sk, &offset); > - mptcp_cleanup_rbuf(msk, copied); > + msk->read_copied = copied; > + set_bit(MPTCP_WORK_READ_COMPLETE, &msk->flags); > + mptcp_schedule_work(sk); > } > out: > return copied; > diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h > index 4a2d40cd7b13..89902a98d383 100644 > --- a/net/mptcp/protocol.h > +++ b/net/mptcp/protocol.h > @@ -115,6 +115,7 @@ > #define MPTCP_WORK_RTX 1 > #define MPTCP_FALLBACK_DONE 2 > #define MPTCP_WORK_CLOSE_SUBFLOW 3 > +#define MPTCP_WORK_READ_COMPLETE 4 > > /* MPTCP socket release cb flags */ > #define MPTCP_PUSH_PENDING 1 > @@ -305,6 +306,7 @@ struct mptcp_sock { > u32 last_data_sent; > u32 last_data_recv; > u32 last_ack_recv; > + int read_copied; > unsigned long timer_ival; > u32 token; > unsigned long flags;