Skip to content

Commit 001c112

Browse files
committed
rxrpc: Maintain an extra ref on a conn for the cache list
Overhaul the usage count accounting for the rxrpc_connection struct to make it easier to implement RCU access from the data_ready handler. The problem is that currently we're using a lock to prevent the garbage collector from trying to clean up a connection that we're contemplating unidling. We could just stick incoming packets on the connection we find, but we've then got a problem that we may race when dispatching a work item to process it as we need to give that a ref to prevent the rxrpc_connection struct from disappearing in the meantime. Further, incoming packets may get discarded if attached to an rxrpc_connection struct that is going away. Whilst this is not a total disaster - the client will presumably resend - it would delay processing of the call. This would affect the AFS client filesystem's service manager operation. To this end: (1) We now maintain an extra count on the connection usage count whilst it is on the connection list. This mean it is not in use when its refcount is 1. (2) When trying to reuse an old connection, we only increment the refcount if it is greater than 0. If it is 0, we replace it in the tree with a new candidate connection. (3) Two connection flags are added to indicate whether or not a connection is in the local's client connection tree (used by sendmsg) or the peer's service connection tree (used by data_ready). This makes sure that we don't try and remove a connection if it got replaced. The flags are tested under lock with the removal operation to prevent the reaper from killing the rxrpc_connection struct whilst someone else is trying to effect a replacement. This could probably be alleviated by using memory barriers between the flag set/test and the rb_tree ops. The rb_tree op would still need to be under the lock, however. (4) When trying to reap an old connection, we try to flip the usage count from 1 to 0. If it's not 1 at that point, then it must've come back to life temporarily and we ignore it. Signed-off-by: David Howells <[email protected]>
1 parent d991b4a commit 001c112

File tree

4 files changed

+97
-58
lines changed

4 files changed

+97
-58
lines changed

net/rxrpc/ar-internal.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ struct rxrpc_conn_parameters {
258258
*/
259259
enum rxrpc_conn_flag {
260260
RXRPC_CONN_HAS_IDR, /* Has a client conn ID assigned */
261+
RXRPC_CONN_IN_SERVICE_CONNS, /* Conn is in peer->service_conns */
262+
RXRPC_CONN_IN_CLIENT_CONNS, /* Conn is in local->client_conns */
261263
};
262264

263265
/*
@@ -544,10 +546,10 @@ void __exit rxrpc_destroy_all_calls(void);
544546
*/
545547
extern struct idr rxrpc_client_conn_ids;
546548

547-
void rxrpc_put_client_connection_id(struct rxrpc_connection *);
548549
void rxrpc_destroy_client_conn_ids(void);
549550
int rxrpc_connect_call(struct rxrpc_call *, struct rxrpc_conn_parameters *,
550551
struct sockaddr_rxrpc *, gfp_t);
552+
void rxrpc_unpublish_client_conn(struct rxrpc_connection *);
551553

552554
/*
553555
* conn_event.c
@@ -609,6 +611,7 @@ static inline void rxrpc_queue_conn(struct rxrpc_connection *conn)
609611
struct rxrpc_connection *rxrpc_incoming_connection(struct rxrpc_local *,
610612
struct sockaddr_rxrpc *,
611613
struct sk_buff *);
614+
void rxrpc_unpublish_service_conn(struct rxrpc_connection *);
612615

613616
/*
614617
* input.c

net/rxrpc/conn_client.c

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ static int rxrpc_get_client_connection_id(struct rxrpc_connection *conn,
8484
/*
8585
* Release a connection ID for a client connection from the global pool.
8686
*/
87-
void rxrpc_put_client_connection_id(struct rxrpc_connection *conn)
87+
static void rxrpc_put_client_connection_id(struct rxrpc_connection *conn)
8888
{
8989
if (test_bit(RXRPC_CONN_HAS_IDR, &conn->flags)) {
9090
spin_lock(&rxrpc_conn_id_lock);
@@ -278,12 +278,13 @@ int rxrpc_connect_call(struct rxrpc_call *call,
278278
* lock before dropping the client conn lock.
279279
*/
280280
_debug("new conn");
281+
set_bit(RXRPC_CONN_IN_CLIENT_CONNS, &candidate->flags);
282+
rb_link_node(&candidate->client_node, parent, pp);
283+
rb_insert_color(&candidate->client_node, &local->client_conns);
284+
attached:
281285
conn = candidate;
282286
candidate = NULL;
283287

284-
rb_link_node(&conn->client_node, parent, pp);
285-
rb_insert_color(&conn->client_node, &local->client_conns);
286-
287288
atomic_set(&conn->avail_chans, RXRPC_MAXCALLS - 1);
288289
spin_lock(&conn->channel_lock);
289290
spin_unlock(&local->client_conns_lock);
@@ -307,13 +308,22 @@ int rxrpc_connect_call(struct rxrpc_call *call,
307308
_leave(" = %p {u=%d}", conn, atomic_read(&conn->usage));
308309
return 0;
309310

310-
/* We found a suitable connection already in existence. Discard any
311-
* candidate we may have allocated, and try to get a channel on this
312-
* one.
311+
/* We found a potentially suitable connection already in existence. If
312+
* we can reuse it (ie. its usage count hasn't been reduced to 0 by the
313+
* reaper), discard any candidate we may have allocated, and try to get
314+
* a channel on this one, otherwise we have to replace it.
313315
*/
314316
found_extant_conn:
315317
_debug("found conn");
316-
rxrpc_get_connection(conn);
318+
if (!rxrpc_get_connection_maybe(conn)) {
319+
set_bit(RXRPC_CONN_IN_CLIENT_CONNS, &candidate->flags);
320+
rb_replace_node(&conn->client_node,
321+
&candidate->client_node,
322+
&local->client_conns);
323+
clear_bit(RXRPC_CONN_IN_CLIENT_CONNS, &conn->flags);
324+
goto attached;
325+
}
326+
317327
spin_unlock(&local->client_conns_lock);
318328

319329
rxrpc_put_connection(candidate);
@@ -357,3 +367,19 @@ int rxrpc_connect_call(struct rxrpc_call *call,
357367
_leave(" = -ERESTARTSYS");
358368
return -ERESTARTSYS;
359369
}
370+
371+
/*
372+
* Remove a client connection from the local endpoint's tree, thereby removing
373+
* it as a target for reuse for new client calls.
374+
*/
375+
void rxrpc_unpublish_client_conn(struct rxrpc_connection *conn)
376+
{
377+
struct rxrpc_local *local = conn->params.local;
378+
379+
spin_lock(&local->client_conns_lock);
380+
if (test_and_clear_bit(RXRPC_CONN_IN_CLIENT_CONNS, &conn->flags))
381+
rb_erase(&conn->client_node, &local->client_conns);
382+
spin_unlock(&local->client_conns_lock);
383+
384+
rxrpc_put_client_connection_id(conn);
385+
}

net/rxrpc/conn_object.c

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ struct rxrpc_connection *rxrpc_alloc_connection(gfp_t gfp)
4949
skb_queue_head_init(&conn->rx_queue);
5050
conn->security = &rxrpc_no_security;
5151
spin_lock_init(&conn->state_lock);
52-
atomic_set(&conn->usage, 1);
52+
/* We maintain an extra ref on the connection whilst it is
53+
* on the rxrpc_connections list.
54+
*/
55+
atomic_set(&conn->usage, 2);
5356
conn->debug_id = atomic_inc_return(&rxrpc_debug_id);
5457
atomic_set(&conn->avail_chans, RXRPC_MAXCALLS);
5558
conn->size_align = 4;
@@ -111,7 +114,7 @@ struct rxrpc_connection *rxrpc_find_connection(struct rxrpc_local *local,
111114
return NULL;
112115

113116
found:
114-
rxrpc_get_connection(conn);
117+
conn = rxrpc_get_connection_maybe(conn);
115118
read_unlock_bh(&peer->conn_lock);
116119
_leave(" = %p", conn);
117120
return conn;
@@ -173,10 +176,10 @@ void rxrpc_put_connection(struct rxrpc_connection *conn)
173176
_enter("%p{u=%d,d=%d}",
174177
conn, atomic_read(&conn->usage), conn->debug_id);
175178

176-
ASSERTCMP(atomic_read(&conn->usage), >, 0);
179+
ASSERTCMP(atomic_read(&conn->usage), >, 1);
177180

178181
conn->put_time = ktime_get_seconds();
179-
if (atomic_dec_and_test(&conn->usage)) {
182+
if (atomic_dec_return(&conn->usage) == 1) {
180183
_debug("zombie");
181184
rxrpc_queue_delayed_work(&rxrpc_connection_reap, 0);
182185
}
@@ -216,59 +219,41 @@ static void rxrpc_destroy_connection(struct rcu_head *rcu)
216219
static void rxrpc_connection_reaper(struct work_struct *work)
217220
{
218221
struct rxrpc_connection *conn, *_p;
219-
struct rxrpc_peer *peer;
220-
unsigned long now, earliest, reap_time;
222+
unsigned long reap_older_than, earliest, put_time, now;
221223

222224
LIST_HEAD(graveyard);
223225

224226
_enter("");
225227

226228
now = ktime_get_seconds();
229+
reap_older_than = now - rxrpc_connection_expiry;
227230
earliest = ULONG_MAX;
228231

229232
write_lock(&rxrpc_connection_lock);
230233
list_for_each_entry_safe(conn, _p, &rxrpc_connections, link) {
231-
_debug("reap CONN %d { u=%d,t=%ld }",
232-
conn->debug_id, atomic_read(&conn->usage),
233-
(long) now - (long) conn->put_time);
234-
235-
if (likely(atomic_read(&conn->usage) > 0))
234+
ASSERTCMP(atomic_read(&conn->usage), >, 0);
235+
if (likely(atomic_read(&conn->usage) > 1))
236236
continue;
237237

238-
if (rxrpc_conn_is_client(conn)) {
239-
struct rxrpc_local *local = conn->params.local;
240-
spin_lock(&local->client_conns_lock);
241-
reap_time = conn->put_time + rxrpc_connection_expiry;
242-
243-
if (atomic_read(&conn->usage) > 0) {
244-
;
245-
} else if (reap_time <= now) {
246-
list_move_tail(&conn->link, &graveyard);
247-
rxrpc_put_client_connection_id(conn);
248-
rb_erase(&conn->client_node,
249-
&local->client_conns);
250-
} else if (reap_time < earliest) {
251-
earliest = reap_time;
252-
}
253-
254-
spin_unlock(&local->client_conns_lock);
255-
} else {
256-
peer = conn->params.peer;
257-
write_lock_bh(&peer->conn_lock);
258-
reap_time = conn->put_time + rxrpc_connection_expiry;
259-
260-
if (atomic_read(&conn->usage) > 0) {
261-
;
262-
} else if (reap_time <= now) {
263-
list_move_tail(&conn->link, &graveyard);
264-
rb_erase(&conn->service_node,
265-
&peer->service_conns);
266-
} else if (reap_time < earliest) {
267-
earliest = reap_time;
268-
}
269-
270-
write_unlock_bh(&peer->conn_lock);
238+
put_time = READ_ONCE(conn->put_time);
239+
if (time_after(put_time, reap_older_than)) {
240+
if (time_before(put_time, earliest))
241+
earliest = put_time;
242+
continue;
271243
}
244+
245+
/* The usage count sits at 1 whilst the object is unused on the
246+
* list; we reduce that to 0 to make the object unavailable.
247+
*/
248+
if (atomic_cmpxchg(&conn->usage, 1, 0) != 1)
249+
continue;
250+
251+
if (rxrpc_conn_is_client(conn))
252+
rxrpc_unpublish_client_conn(conn);
253+
else
254+
rxrpc_unpublish_service_conn(conn);
255+
256+
list_move_tail(&conn->link, &graveyard);
272257
}
273258
write_unlock(&rxrpc_connection_lock);
274259

@@ -279,7 +264,6 @@ static void rxrpc_connection_reaper(struct work_struct *work)
279264
(earliest - now) * HZ);
280265
}
281266

282-
/* then destroy all those pulled out */
283267
while (!list_empty(&graveyard)) {
284268
conn = list_entry(graveyard.next, struct rxrpc_connection,
285269
link);

net/rxrpc/conn_service.c

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,12 @@ struct rxrpc_connection *rxrpc_incoming_connection(struct rxrpc_local *local,
104104
}
105105

106106
/* we can now add the new candidate to the list */
107+
set_bit(RXRPC_CONN_IN_SERVICE_CONNS, &candidate->flags);
108+
rb_link_node(&candidate->service_node, p, pp);
109+
rb_insert_color(&candidate->service_node, &peer->service_conns);
110+
attached:
107111
conn = candidate;
108112
candidate = NULL;
109-
rb_link_node(&conn->service_node, p, pp);
110-
rb_insert_color(&conn->service_node, &peer->service_conns);
111113
rxrpc_get_peer(peer);
112114
rxrpc_get_local(local);
113115

@@ -128,11 +130,19 @@ struct rxrpc_connection *rxrpc_incoming_connection(struct rxrpc_local *local,
128130

129131
/* we found the connection in the list immediately */
130132
found_extant_connection:
133+
if (!rxrpc_get_connection_maybe(conn)) {
134+
set_bit(RXRPC_CONN_IN_SERVICE_CONNS, &candidate->flags);
135+
rb_replace_node(&conn->service_node,
136+
&candidate->service_node,
137+
&peer->service_conns);
138+
clear_bit(RXRPC_CONN_IN_SERVICE_CONNS, &conn->flags);
139+
goto attached;
140+
}
141+
131142
if (sp->hdr.securityIndex != conn->security_ix) {
132143
read_unlock_bh(&peer->conn_lock);
133-
goto security_mismatch;
144+
goto security_mismatch_put;
134145
}
135-
rxrpc_get_connection(conn);
136146
read_unlock_bh(&peer->conn_lock);
137147
goto success;
138148

@@ -147,8 +157,24 @@ struct rxrpc_connection *rxrpc_incoming_connection(struct rxrpc_local *local,
147157
kfree(candidate);
148158
goto success;
149159

160+
security_mismatch_put:
161+
rxrpc_put_connection(conn);
150162
security_mismatch:
151163
kfree(candidate);
152164
_leave(" = -EKEYREJECTED");
153165
return ERR_PTR(-EKEYREJECTED);
154166
}
167+
168+
/*
169+
* Remove the service connection from the peer's tree, thereby removing it as a
170+
* target for incoming packets.
171+
*/
172+
void rxrpc_unpublish_service_conn(struct rxrpc_connection *conn)
173+
{
174+
struct rxrpc_peer *peer = conn->params.peer;
175+
176+
write_lock_bh(&peer->conn_lock);
177+
if (test_and_clear_bit(RXRPC_CONN_IN_SERVICE_CONNS, &conn->flags))
178+
rb_erase(&conn->service_node, &peer->service_conns);
179+
write_unlock_bh(&peer->conn_lock);
180+
}

0 commit comments

Comments
 (0)