af_unix: Try not to hold unix_gc_lock during accept().
authorKuniyuki Iwashima <kuniyu@amazon.com>
Sat, 13 Apr 2024 02:19:28 +0000 (19:19 -0700)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 16 Apr 2024 11:50:57 +0000 (13:50 +0200)
Commit dcf70df2048d ("af_unix: Fix up unix_edge.successor for embryo
socket.") added spin_lock(&unix_gc_lock) in accept() path, and it
caused regression in a stress test as reported by kernel test robot.

If the embryo socket is not part of the inflight graph, we need not
hold the lock.

To decide that in O(1) time and avoid the regression in the normal
use case,

  1. add a new stat unix_sk(sk)->scm_stat.nr_unix_fds

  2. count the number of inflight AF_UNIX sockets in the receive
     queue under unix_state_lock()

  3. move unix_update_edges() call under unix_state_lock()

  4. avoid locking if nr_unix_fds is 0 in unix_update_edges()

Reported-by: kernel test robot <oliver.sang@intel.com>
Closes: https://lore.kernel.org/oe-lkp/202404101427.92a08551-oliver.sang@intel.com
Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Link: https://lore.kernel.org/r/20240413021928.20946-1-kuniyu@amazon.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
include/net/af_unix.h
net/unix/af_unix.c
net/unix/garbage.c

index 7311b77edfc7bf21d3ab27b4aa879654f6a93fa2..872ff2a5037229a6f628b761df08515a5bd9b752 100644 (file)
@@ -67,6 +67,7 @@ struct unix_skb_parms {
 
 struct scm_stat {
        atomic_t nr_fds;
+       unsigned long nr_unix_fds;
 };
 
 #define UNIXCB(skb)    (*(struct unix_skb_parms *)&((skb)->cb))
index 142d210b5b035a90f8a96a453a6c318ec85184f3..ed16d5f66df8d6ee4b647339161efb0c0f5ff2f5 100644 (file)
@@ -1719,12 +1719,12 @@ static int unix_accept(struct socket *sock, struct socket *newsock, int flags,
        }
 
        tsk = skb->sk;
-       unix_update_edges(unix_sk(tsk));
        skb_free_datagram(sk, skb);
        wake_up_interruptible(&unix_sk(sk)->peer_wait);
 
        /* attach accepted sock to socket */
        unix_state_lock(tsk);
+       unix_update_edges(unix_sk(tsk));
        newsock->state = SS_CONNECTED;
        unix_sock_inherit_flags(sock, newsock);
        sock_graft(tsk, newsock);
index 12a4ec27e0d4d87b340dd863eae088db20d99aac..95240a59808f24ebae40373bb85b5987e69fead5 100644 (file)
@@ -209,6 +209,7 @@ void unix_add_edges(struct scm_fp_list *fpl, struct unix_sock *receiver)
                unix_add_edge(fpl, edge);
        } while (i < fpl->count_unix);
 
+       receiver->scm_stat.nr_unix_fds += fpl->count_unix;
        WRITE_ONCE(unix_tot_inflight, unix_tot_inflight + fpl->count_unix);
 out:
        WRITE_ONCE(fpl->user->unix_inflight, fpl->user->unix_inflight + fpl->count);
@@ -222,6 +223,7 @@ out:
 
 void unix_del_edges(struct scm_fp_list *fpl)
 {
+       struct unix_sock *receiver;
        int i = 0;
 
        spin_lock(&unix_gc_lock);
@@ -235,6 +237,8 @@ void unix_del_edges(struct scm_fp_list *fpl)
                unix_del_edge(fpl, edge);
        } while (i < fpl->count_unix);
 
+       receiver = fpl->edges[0].successor;
+       receiver->scm_stat.nr_unix_fds -= fpl->count_unix;
        WRITE_ONCE(unix_tot_inflight, unix_tot_inflight - fpl->count_unix);
 out:
        WRITE_ONCE(fpl->user->unix_inflight, fpl->user->unix_inflight - fpl->count);
@@ -246,10 +250,18 @@ out:
 
 void unix_update_edges(struct unix_sock *receiver)
 {
-       spin_lock(&unix_gc_lock);
-       unix_update_graph(unix_sk(receiver->listener)->vertex);
-       receiver->listener = NULL;
-       spin_unlock(&unix_gc_lock);
+       /* nr_unix_fds is only updated under unix_state_lock().
+        * If it's 0 here, the embryo socket is not part of the
+        * inflight graph, and GC will not see it, so no lock needed.
+        */
+       if (!receiver->scm_stat.nr_unix_fds) {
+               receiver->listener = NULL;
+       } else {
+               spin_lock(&unix_gc_lock);
+               unix_update_graph(unix_sk(receiver->listener)->vertex);
+               receiver->listener = NULL;
+               spin_unlock(&unix_gc_lock);
+       }
 }
 
 int unix_prepare_fpl(struct scm_fp_list *fpl)