Skip to content

Commit 3e6ff8c

Browse files
valschneidergregkh
authored andcommitted
net: tcp/dccp: prepare for tw_timer un-pinning
[ Upstream commit b334b92 ] 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 e02b931 ("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 ec94c26 ("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> Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent b69aade commit 3e6ff8c

5 files changed

Lines changed: 52 additions & 14 deletions

File tree

include/net/inet_timewait_sock.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,10 @@ struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk,
9393
struct inet_timewait_death_row *dr,
9494
const int state);
9595

96-
void inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk,
97-
struct inet_hashinfo *hashinfo);
96+
void inet_twsk_hashdance_schedule(struct inet_timewait_sock *tw,
97+
struct sock *sk,
98+
struct inet_hashinfo *hashinfo,
99+
int timeo);
98100

99101
void __inet_twsk_schedule(struct inet_timewait_sock *tw, int timeo,
100102
bool rearm);

net/dccp/minisocks.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,10 @@ void dccp_time_wait(struct sock *sk, int state, int timeo)
5959
* we complete the initialization.
6060
*/
6161
local_bh_disable();
62-
inet_twsk_schedule(tw, timeo);
6362
/* Linkage updates.
6463
* Note that access to tw after this point is illegal.
6564
*/
66-
inet_twsk_hashdance(tw, sk, &dccp_hashinfo);
65+
inet_twsk_hashdance_schedule(tw, sk, &dccp_hashinfo, timeo);
6766
local_bh_enable();
6867
} else {
6968
/* Sorry, if we're out of memory, just CLOSE this

net/ipv4/inet_timewait_sock.c

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,13 @@ static void inet_twsk_add_node_rcu(struct inet_timewait_sock *tw,
9696
* Enter the time wait state. This is called with locally disabled BH.
9797
* Essentially we whip up a timewait bucket, copy the relevant info into it
9898
* from the SK, and mess with hash chains and list linkage.
99+
*
100+
* The caller must not access @tw anymore after this function returns.
99101
*/
100-
void inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk,
101-
struct inet_hashinfo *hashinfo)
102+
void inet_twsk_hashdance_schedule(struct inet_timewait_sock *tw,
103+
struct sock *sk,
104+
struct inet_hashinfo *hashinfo,
105+
int timeo)
102106
{
103107
const struct inet_sock *inet = inet_sk(sk);
104108
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,
129133

130134
spin_lock(lock);
131135

136+
/* Step 2: Hash TW into tcp ehash chain */
132137
inet_twsk_add_node_rcu(tw, &ehead->chain);
133138

134139
/* Step 3: Remove SK from hash chain */
135140
if (__sk_nulls_del_node_init_rcu(sk))
136141
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
137142

138-
spin_unlock(lock);
139143

144+
/* Ensure above writes are committed into memory before updating the
145+
* refcount.
146+
* Provides ordering vs later refcount_inc().
147+
*/
148+
smp_wmb();
140149
/* tw_refcnt is set to 3 because we have :
141150
* - one reference for bhash chain.
142151
* - one reference for ehash chain.
143152
* - one reference for timer.
144-
* We can use atomic_set() because prior spin_lock()/spin_unlock()
145-
* committed into memory all tw fields.
146153
* Also note that after this point, we lost our implicit reference
147154
* so we are not allowed to use tw anymore.
148155
*/
149156
refcount_set(&tw->tw_refcnt, 3);
157+
158+
inet_twsk_schedule(tw, timeo);
159+
160+
spin_unlock(lock);
150161
}
151-
EXPORT_SYMBOL_GPL(inet_twsk_hashdance);
162+
EXPORT_SYMBOL_GPL(inet_twsk_hashdance_schedule);
152163

153164
static void tw_timer_handler(struct timer_list *t)
154165
{
@@ -217,7 +228,34 @@ EXPORT_SYMBOL_GPL(inet_twsk_alloc);
217228
*/
218229
void inet_twsk_deschedule_put(struct inet_timewait_sock *tw)
219230
{
220-
if (del_timer_sync(&tw->tw_timer))
231+
struct inet_hashinfo *hashinfo = tw->tw_dr->hashinfo;
232+
spinlock_t *lock = inet_ehash_lockp(hashinfo, tw->tw_hash);
233+
234+
/* inet_twsk_purge() walks over all sockets, including tw ones,
235+
* and removes them via inet_twsk_deschedule_put() after a
236+
* refcount_inc_not_zero().
237+
*
238+
* inet_twsk_hashdance_schedule() must (re)init the refcount before
239+
* arming the timer, i.e. inet_twsk_purge can obtain a reference to
240+
* a twsk that did not yet schedule the timer.
241+
*
242+
* The ehash lock synchronizes these two:
243+
* After acquiring the lock, the timer is always scheduled (else
244+
* timer_shutdown returns false), because hashdance_schedule releases
245+
* the ehash lock only after completing the timer initialization.
246+
*
247+
* Without grabbing the ehash lock, we get:
248+
* 1) cpu x sets twsk refcount to 3
249+
* 2) cpu y bumps refcount to 4
250+
* 3) cpu y calls inet_twsk_deschedule_put() and shuts timer down
251+
* 4) cpu x tries to start timer, but mod_timer is a noop post-shutdown
252+
* -> timer refcount is never decremented.
253+
*/
254+
spin_lock(lock);
255+
/* Makes sure hashdance_schedule() has completed */
256+
spin_unlock(lock);
257+
258+
if (timer_shutdown_sync(&tw->tw_timer))
221259
inet_twsk_kill(tw);
222260
inet_twsk_put(tw);
223261
}

net/ipv4/tcp_ipv4.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
159159
if (ts_recent_stamp &&
160160
(!twp || (reuse && time_after32(ktime_get_seconds(),
161161
ts_recent_stamp)))) {
162-
/* inet_twsk_hashdance() sets sk_refcnt after putting twsk
162+
/* inet_twsk_hashdance_schedule() sets sk_refcnt after putting twsk
163163
* and releasing the bucket lock.
164164
*/
165165
if (unlikely(!refcount_inc_not_zero(&sktw->sk_refcnt)))

net/ipv4/tcp_minisocks.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,10 @@ void tcp_time_wait(struct sock *sk, int state, int timeo)
350350
* we complete the initialization.
351351
*/
352352
local_bh_disable();
353-
inet_twsk_schedule(tw, timeo);
354353
/* Linkage updates.
355354
* Note that access to tw after this point is illegal.
356355
*/
357-
inet_twsk_hashdance(tw, sk, net->ipv4.tcp_death_row.hashinfo);
356+
inet_twsk_hashdance_schedule(tw, sk, net->ipv4.tcp_death_row.hashinfo, timeo);
358357
local_bh_enable();
359358
} else {
360359
/* Sorry, if we're out of memory, just CLOSE this

0 commit comments

Comments
 (0)