]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
net: tcp/dccp: prepare for tw_timer un-pinning
authorValentin Schneider <vschneid@redhat.com>
Thu, 6 Jun 2024 15:11:37 +0000 (17:11 +0200)
committerDavid S. Miller <davem@davemloft.net>
Mon, 10 Jun 2024 10:54:18 +0000 (11:54 +0100)
The TCP timewait timer is proving to be problematic for setups where
scheduler CPU isolation is achieved at runtime via cpusets (as opposed to
statically via isolcpus=domains).

What happens there is a CPU goes through tcp_time_wait(), arming the
time_wait timer, then gets isolated. TCP_TIMEWAIT_LEN later, the timer
fires, causing interference for the now-isolated CPU. This is conceptually
similar to the issue described in commit e02b93124855 ("workqueue: Unbind
kworkers before sending them to exit()")

Move inet_twsk_schedule() to within inet_twsk_hashdance(), with the ehash
lock held. Expand the lock's critical section from inet_twsk_kill() to
inet_twsk_deschedule_put(), serializing the scheduling vs descheduling of
the timer. IOW, this prevents the following race:

     tcp_time_wait()
       inet_twsk_hashdance()
  inet_twsk_deschedule_put()
    del_timer_sync()
       inet_twsk_schedule()

Thanks to Paolo Abeni for suggesting to leverage the ehash lock.

This also restores a comment from commit ec94c2696f0b ("tcp/dccp: avoid
one atomic operation for timewait hashdance") as inet_twsk_hashdance() had
a "Step 1" and "Step 3" comment, but the "Step 2" had gone missing.

inet_twsk_deschedule_put() now acquires the ehash spinlock to synchronize
with inet_twsk_hashdance_schedule().

To ease possible regression search, actual un-pin is done in next patch.

Link: https://lore.kernel.org/all/ZPhpfMjSiHVjQkTk@localhost.localdomain/
Reviewed-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Valentin Schneider <vschneid@redhat.com>
Co-developed-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/inet_timewait_sock.h
net/dccp/minisocks.c
net/ipv4/inet_timewait_sock.c
net/ipv4/tcp_ipv4.c
net/ipv4/tcp_minisocks.c

index 2a536eea9424ea4fad65a1321217b07d2346e638..5b43d220243de994ceb74a94dd8620f504869f30 100644 (file)
@@ -93,8 +93,10 @@ struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk,
                                           struct inet_timewait_death_row *dr,
                                           const int state);
 
-void inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk,
-                        struct inet_hashinfo *hashinfo);
+void inet_twsk_hashdance_schedule(struct inet_timewait_sock *tw,
+                                 struct sock *sk,
+                                 struct inet_hashinfo *hashinfo,
+                                 int timeo);
 
 void __inet_twsk_schedule(struct inet_timewait_sock *tw, int timeo,
                          bool rearm);
index 251a57cf58223b07a7a29e81e049532c46fe74e6..deb52d7d31b48bd193e628db8b2aa794c9a00564 100644 (file)
@@ -59,11 +59,10 @@ void dccp_time_wait(struct sock *sk, int state, int timeo)
                 * we complete the initialization.
                 */
                local_bh_disable();
-               inet_twsk_schedule(tw, timeo);
                /* Linkage updates.
                 * Note that access to tw after this point is illegal.
                 */
-               inet_twsk_hashdance(tw, sk, &dccp_hashinfo);
+               inet_twsk_hashdance_schedule(tw, sk, &dccp_hashinfo, timeo);
                local_bh_enable();
        } else {
                /* Sorry, if we're out of memory, just CLOSE this
index e28075f0006e333897ad379ebc8c87fc3f9643bd..628d33a41ce5fb905e400ae53901fa40ff0a0614 100644 (file)
@@ -96,9 +96,13 @@ static void inet_twsk_add_node_rcu(struct inet_timewait_sock *tw,
  * Enter the time wait state. This is called with locally disabled BH.
  * Essentially we whip up a timewait bucket, copy the relevant info into it
  * from the SK, and mess with hash chains and list linkage.
+ *
+ * The caller must not access @tw anymore after this function returns.
  */
-void inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk,
-                          struct inet_hashinfo *hashinfo)
+void inet_twsk_hashdance_schedule(struct inet_timewait_sock *tw,
+                                 struct sock *sk,
+                                 struct inet_hashinfo *hashinfo,
+                                 int timeo)
 {
        const struct inet_sock *inet = inet_sk(sk);
        const struct inet_connection_sock *icsk = inet_csk(sk);
@@ -129,26 +133,33 @@ void inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk,
 
        spin_lock(lock);
 
+       /* Step 2: Hash TW into tcp ehash chain */
        inet_twsk_add_node_rcu(tw, &ehead->chain);
 
        /* Step 3: Remove SK from hash chain */
        if (__sk_nulls_del_node_init_rcu(sk))
                sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
 
-       spin_unlock(lock);
 
+       /* Ensure above writes are committed into memory before updating the
+        * refcount.
+        * Provides ordering vs later refcount_inc().
+        */
+       smp_wmb();
        /* tw_refcnt is set to 3 because we have :
         * - one reference for bhash chain.
         * - one reference for ehash chain.
         * - one reference for timer.
-        * We can use atomic_set() because prior spin_lock()/spin_unlock()
-        * committed into memory all tw fields.
         * Also note that after this point, we lost our implicit reference
         * so we are not allowed to use tw anymore.
         */
        refcount_set(&tw->tw_refcnt, 3);
+
+       inet_twsk_schedule(tw, timeo);
+
+       spin_unlock(lock);
 }
-EXPORT_SYMBOL_GPL(inet_twsk_hashdance);
+EXPORT_SYMBOL_GPL(inet_twsk_hashdance_schedule);
 
 static void tw_timer_handler(struct timer_list *t)
 {
@@ -217,7 +228,34 @@ EXPORT_SYMBOL_GPL(inet_twsk_alloc);
  */
 void inet_twsk_deschedule_put(struct inet_timewait_sock *tw)
 {
-       if (del_timer_sync(&tw->tw_timer))
+       struct inet_hashinfo *hashinfo = tw->tw_dr->hashinfo;
+       spinlock_t *lock = inet_ehash_lockp(hashinfo, tw->tw_hash);
+
+       /* inet_twsk_purge() walks over all sockets, including tw ones,
+        * and removes them via inet_twsk_deschedule_put() after a
+        * refcount_inc_not_zero().
+        *
+        * inet_twsk_hashdance_schedule() must (re)init the refcount before
+        * arming the timer, i.e. inet_twsk_purge can obtain a reference to
+        * a twsk that did not yet schedule the timer.
+        *
+        * The ehash lock synchronizes these two:
+        * After acquiring the lock, the timer is always scheduled (else
+        * timer_shutdown returns false), because hashdance_schedule releases
+        * the ehash lock only after completing the timer initialization.
+        *
+        * Without grabbing the ehash lock, we get:
+        * 1) cpu x sets twsk refcount to 3
+        * 2) cpu y bumps refcount to 4
+        * 3) cpu y calls inet_twsk_deschedule_put() and shuts timer down
+        * 4) cpu x tries to start timer, but mod_timer is a noop post-shutdown
+        * -> timer refcount is never decremented.
+        */
+       spin_lock(lock);
+       /*  Makes sure hashdance_schedule() has completed */
+       spin_unlock(lock);
+
+       if (timer_shutdown_sync(&tw->tw_timer))
                inet_twsk_kill(tw);
        inet_twsk_put(tw);
 }
index 3613e08ca79496746f99fd65706e6ea4ea26b0e3..e61c7c97474548363959b43bd75c13d96ea17ad8 100644 (file)
@@ -157,7 +157,7 @@ int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
        if (ts_recent_stamp &&
            (!twp || (reuse && time_after32(ktime_get_seconds(),
                                            ts_recent_stamp)))) {
-               /* inet_twsk_hashdance() sets sk_refcnt after putting twsk
+               /* inet_twsk_hashdance_schedule() sets sk_refcnt after putting twsk
                 * and releasing the bucket lock.
                 */
                if (unlikely(!refcount_inc_not_zero(&sktw->sk_refcnt)))
index 4c894e540730ab6cbec6e781b72a4cb3cf4c55b4..fc9a850ed9bd8d2d11d72312dbb9d8b96cbab22f 100644 (file)
@@ -350,11 +350,10 @@ void tcp_time_wait(struct sock *sk, int state, int timeo)
                 * we complete the initialization.
                 */
                local_bh_disable();
-               inet_twsk_schedule(tw, timeo);
                /* Linkage updates.
                 * Note that access to tw after this point is illegal.
                 */
-               inet_twsk_hashdance(tw, sk, net->ipv4.tcp_death_row.hashinfo);
+               inet_twsk_hashdance_schedule(tw, sk, net->ipv4.tcp_death_row.hashinfo, timeo);
                local_bh_enable();
        } else {
                /* Sorry, if we're out of memory, just CLOSE this