diff options
Diffstat (limited to 'net/unix/garbage.c')
| -rw-r--r-- | net/unix/garbage.c | 62 |
1 files changed, 47 insertions, 15 deletions
diff --git a/net/unix/garbage.c b/net/unix/garbage.c index 2a27b84f740b..abb3ab34cb1e 100644 --- a/net/unix/garbage.c +++ b/net/unix/garbage.c | |||
| @@ -80,6 +80,7 @@ | |||
| 80 | #include <linux/file.h> | 80 | #include <linux/file.h> |
| 81 | #include <linux/proc_fs.h> | 81 | #include <linux/proc_fs.h> |
| 82 | #include <linux/mutex.h> | 82 | #include <linux/mutex.h> |
| 83 | #include <linux/wait.h> | ||
| 83 | 84 | ||
| 84 | #include <net/sock.h> | 85 | #include <net/sock.h> |
| 85 | #include <net/af_unix.h> | 86 | #include <net/af_unix.h> |
| @@ -91,6 +92,7 @@ | |||
| 91 | static LIST_HEAD(gc_inflight_list); | 92 | static LIST_HEAD(gc_inflight_list); |
| 92 | static LIST_HEAD(gc_candidates); | 93 | static LIST_HEAD(gc_candidates); |
| 93 | static DEFINE_SPINLOCK(unix_gc_lock); | 94 | static DEFINE_SPINLOCK(unix_gc_lock); |
| 95 | static DECLARE_WAIT_QUEUE_HEAD(unix_gc_wait); | ||
| 94 | 96 | ||
| 95 | unsigned int unix_tot_inflight; | 97 | unsigned int unix_tot_inflight; |
| 96 | 98 | ||
| @@ -186,8 +188,17 @@ static void scan_inflight(struct sock *x, void (*func)(struct unix_sock *), | |||
| 186 | */ | 188 | */ |
| 187 | struct sock *sk = unix_get_socket(*fp++); | 189 | struct sock *sk = unix_get_socket(*fp++); |
| 188 | if (sk) { | 190 | if (sk) { |
| 189 | hit = true; | 191 | struct unix_sock *u = unix_sk(sk); |
| 190 | func(unix_sk(sk)); | 192 | |
| 193 | /* | ||
| 194 | * Ignore non-candidates, they could | ||
| 195 | * have been added to the queues after | ||
| 196 | * starting the garbage collection | ||
| 197 | */ | ||
| 198 | if (u->gc_candidate) { | ||
| 199 | hit = true; | ||
| 200 | func(u); | ||
| 201 | } | ||
| 191 | } | 202 | } |
| 192 | } | 203 | } |
| 193 | if (hit && hitlist != NULL) { | 204 | if (hit && hitlist != NULL) { |
| @@ -249,24 +260,29 @@ static void inc_inflight_move_tail(struct unix_sock *u) | |||
| 249 | { | 260 | { |
| 250 | atomic_long_inc(&u->inflight); | 261 | atomic_long_inc(&u->inflight); |
| 251 | /* | 262 | /* |
| 252 | * If this is still a candidate, move it to the end of the | 263 | * If this still might be part of a cycle, move it to the end |
| 253 | * list, so that it's checked even if it was already passed | 264 | * of the list, so that it's checked even if it was already |
| 254 | * over | 265 | * passed over |
| 255 | */ | 266 | */ |
| 256 | if (u->gc_candidate) | 267 | if (u->gc_maybe_cycle) |
| 257 | list_move_tail(&u->link, &gc_candidates); | 268 | list_move_tail(&u->link, &gc_candidates); |
| 258 | } | 269 | } |
| 259 | 270 | ||
| 260 | /* The external entry point: unix_gc() */ | 271 | static bool gc_in_progress = false; |
| 261 | 272 | ||
| 262 | void unix_gc(void) | 273 | void wait_for_unix_gc(void) |
| 263 | { | 274 | { |
| 264 | static bool gc_in_progress = false; | 275 | wait_event(unix_gc_wait, gc_in_progress == false); |
| 276 | } | ||
| 265 | 277 | ||
| 278 | /* The external entry point: unix_gc() */ | ||
| 279 | void unix_gc(void) | ||
| 280 | { | ||
| 266 | struct unix_sock *u; | 281 | struct unix_sock *u; |
| 267 | struct unix_sock *next; | 282 | struct unix_sock *next; |
| 268 | struct sk_buff_head hitlist; | 283 | struct sk_buff_head hitlist; |
| 269 | struct list_head cursor; | 284 | struct list_head cursor; |
| 285 | LIST_HEAD(not_cycle_list); | ||
| 270 | 286 | ||
| 271 | spin_lock(&unix_gc_lock); | 287 | spin_lock(&unix_gc_lock); |
| 272 | 288 | ||
| @@ -282,10 +298,14 @@ void unix_gc(void) | |||
| 282 | * | 298 | * |
| 283 | * Holding unix_gc_lock will protect these candidates from | 299 | * Holding unix_gc_lock will protect these candidates from |
| 284 | * being detached, and hence from gaining an external | 300 | * being detached, and hence from gaining an external |
| 285 | * reference. This also means, that since there are no | 301 | * reference. Since there are no possible receivers, all |
| 286 | * possible receivers, the receive queues of these sockets are | 302 | * buffers currently on the candidates' queues stay there |
| 287 | * static during the GC, even though the dequeue is done | 303 | * during the garbage collection. |
| 288 | * before the detach without atomicity guarantees. | 304 | * |
| 305 | * We also know that no new candidate can be added onto the | ||
| 306 | * receive queues. Other, non candidate sockets _can_ be | ||
| 307 | * added to queue, so we must make sure only to touch | ||
| 308 | * candidates. | ||
| 289 | */ | 309 | */ |
| 290 | list_for_each_entry_safe(u, next, &gc_inflight_list, link) { | 310 | list_for_each_entry_safe(u, next, &gc_inflight_list, link) { |
| 291 | long total_refs; | 311 | long total_refs; |
| @@ -299,6 +319,7 @@ void unix_gc(void) | |||
| 299 | if (total_refs == inflight_refs) { | 319 | if (total_refs == inflight_refs) { |
| 300 | list_move_tail(&u->link, &gc_candidates); | 320 | list_move_tail(&u->link, &gc_candidates); |
| 301 | u->gc_candidate = 1; | 321 | u->gc_candidate = 1; |
| 322 | u->gc_maybe_cycle = 1; | ||
| 302 | } | 323 | } |
| 303 | } | 324 | } |
| 304 | 325 | ||
| @@ -325,14 +346,24 @@ void unix_gc(void) | |||
| 325 | list_move(&cursor, &u->link); | 346 | list_move(&cursor, &u->link); |
| 326 | 347 | ||
| 327 | if (atomic_long_read(&u->inflight) > 0) { | 348 | if (atomic_long_read(&u->inflight) > 0) { |
| 328 | list_move_tail(&u->link, &gc_inflight_list); | 349 | list_move_tail(&u->link, ¬_cycle_list); |
| 329 | u->gc_candidate = 0; | 350 | u->gc_maybe_cycle = 0; |
| 330 | scan_children(&u->sk, inc_inflight_move_tail, NULL); | 351 | scan_children(&u->sk, inc_inflight_move_tail, NULL); |
| 331 | } | 352 | } |
| 332 | } | 353 | } |
| 333 | list_del(&cursor); | 354 | list_del(&cursor); |
| 334 | 355 | ||
| 335 | /* | 356 | /* |
| 357 | * not_cycle_list contains those sockets which do not make up a | ||
| 358 | * cycle. Restore these to the inflight list. | ||
| 359 | */ | ||
| 360 | while (!list_empty(¬_cycle_list)) { | ||
| 361 | u = list_entry(not_cycle_list.next, struct unix_sock, link); | ||
| 362 | u->gc_candidate = 0; | ||
| 363 | list_move_tail(&u->link, &gc_inflight_list); | ||
| 364 | } | ||
| 365 | |||
| 366 | /* | ||
| 336 | * Now gc_candidates contains only garbage. Restore original | 367 | * Now gc_candidates contains only garbage. Restore original |
| 337 | * inflight counters for these as well, and remove the skbuffs | 368 | * inflight counters for these as well, and remove the skbuffs |
| 338 | * which are creating the cycle(s). | 369 | * which are creating the cycle(s). |
| @@ -351,6 +382,7 @@ void unix_gc(void) | |||
| 351 | /* All candidates should have been detached by now. */ | 382 | /* All candidates should have been detached by now. */ |
| 352 | BUG_ON(!list_empty(&gc_candidates)); | 383 | BUG_ON(!list_empty(&gc_candidates)); |
| 353 | gc_in_progress = false; | 384 | gc_in_progress = false; |
| 385 | wake_up(&unix_gc_wait); | ||
| 354 | 386 | ||
| 355 | out: | 387 | out: |
| 356 | spin_unlock(&unix_gc_lock); | 388 | spin_unlock(&unix_gc_lock); |
