@@ -115,6 +115,8 @@
#include <net/checksum.h>
#include <linux/security.h>
+#define POLL_OUT_ALL (POLLOUT | POLLWRNORM | POLLWRBAND)
+
static struct hlist_head unix_socket_table[UNIX_HASH_SIZE + 1];
static DEFINE_SPINLOCK(unix_table_lock);
static atomic_long_t unix_nr_socks;
@@ -303,6 +305,53 @@ found:
return s;
}
+static int unix_peer_wake_relay(wait_queue_t *q, unsigned mode, int flags,
+ void *key)
+{
+ struct unix_sock *u;
+ wait_queue_head_t *u_sleep;
+
+ u = container_of(q, struct unix_sock, peer_wake);
+
+ u_sleep = sk_sleep(&u->sk);
+ if (u_sleep)
+ wake_up_interruptible_poll(u_sleep, key);
+
+ return 0;
+}
+
+static inline int unix_peer_wake_connect(struct sock *me, struct sock *peer)
+{
+ struct unix_sock *u, *u_peer;
+
+ u = unix_sk(me);
+ u_peer = unix_sk(peer);
+
+ if (u->peer_wake.private)
+ return 0;
+
+ u->peer_wake.private = peer;
+ add_wait_queue(&u_peer->peer_wait, &u->peer_wake);
+
+ return 1;
+}
+
+static inline int unix_peer_wake_disconnect(struct sock *me, struct sock *peer)
+{
+ struct unix_sock *u, *u_peer;
+
+ u = unix_sk(me);
+ u_peer = unix_sk(peer);
+
+ if (u->peer_wake.private != peer)
+ return 0;
+
+ remove_wait_queue(&u_peer->peer_wait, &u->peer_wake);
+ u->peer_wake.private = NULL;
+
+ return 1;
+}
+
static inline int unix_writable(struct sock *sk)
{
return (atomic_read(&sk->sk_wmem_alloc) << 2) <= sk->sk_sndbuf;
@@ -317,7 +366,7 @@ static void unix_write_space(struct sock
wq = rcu_dereference(sk->sk_wq);
if (wq_has_sleeper(wq))
wake_up_interruptible_sync_poll(&wq->wait,
- POLLOUT | POLLWRNORM | POLLWRBAND);
+ POLL_OUT_ALL);
sk_wake_async(sk, SOCK_WAKE_SPACE, POLL_OUT);
}
rcu_read_unlock();
@@ -409,6 +458,8 @@ static void unix_release_sock(struct soc
skpair->sk_state_change(skpair);
sk_wake_async(skpair, SOCK_WAKE_WAITD, POLL_HUP);
}
+
+ unix_peer_wake_disconnect(sk, skpair);
sock_put(skpair); /* It may now die */
unix_peer(sk) = NULL;
}
@@ -630,6 +681,7 @@ static struct sock *unix_create1(struct
INIT_LIST_HEAD(&u->link);
mutex_init(&u->readlock); /* single task reading lock */
init_waitqueue_head(&u->peer_wait);
+ init_waitqueue_func_entry(&u->peer_wake, unix_peer_wake_relay);
unix_insert_socket(unix_sockets_unbound, sk);
out:
if (sk == NULL)
@@ -953,7 +1005,7 @@ static int unix_dgram_connect(struct soc
struct sockaddr_un *sunaddr = (struct sockaddr_un *)addr;
struct sock *other;
unsigned hash;
- int err;
+ int err, disconned;
if (addr->sa_family != AF_UNSPEC) {
err = unix_mkname(sunaddr, alen, &hash);
@@ -1001,6 +1053,11 @@ restart:
if (unix_peer(sk)) {
struct sock *old_peer = unix_peer(sk);
unix_peer(sk) = other;
+
+ disconned = unix_peer_wake_disconnect(sk, other);
+ if (disconned)
+ wake_up_interruptible_poll(sk_sleep(sk), POLL_OUT_ALL);
+
unix_state_double_unlock(sk, other);
if (other != old_peer)
@@ -1439,7 +1496,7 @@ static int unix_dgram_sendmsg(struct kio
struct sk_buff *skb;
long timeo;
struct scm_cookie tmp_scm;
- int max_level;
+ int max_level, disconned;
if (NULL == siocb->scm)
siocb->scm = &tmp_scm;
@@ -1525,6 +1582,12 @@ restart:
unix_state_lock(sk);
if (unix_peer(sk) == other) {
unix_peer(sk) = NULL;
+
+ disconned = unix_peer_wake_disconnect(sk, other);
+ if (disconned)
+ wake_up_interruptible_poll(sk_sleep(sk),
+ POLL_OUT_ALL);
+
unix_state_unlock(sk);
unix_dgram_disconnected(sk, other);
@@ -1783,8 +1846,7 @@ static int unix_dgram_recvmsg(struct kio
goto out_unlock;
}
- wake_up_interruptible_sync_poll(&u->peer_wait,
- POLLOUT | POLLWRNORM | POLLWRBAND);
+ wake_up_interruptible_sync_poll(&u->peer_wait, POLL_OUT_ALL);
if (msg->msg_name)
unix_copy_addr(msg, skb->sk);
@@ -2127,7 +2189,7 @@ static unsigned int unix_poll(struct fil
* connection. This prevents stuck sockets.
*/
if (unix_writable(sk))
- mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+ mask |= POLL_OUT_ALL;
return mask;
}
@@ -2137,6 +2199,7 @@ static unsigned int unix_dgram_poll(stru
{
struct sock *sk = sock->sk, *other;
unsigned int mask, writable;
+ int need_wakeup;
sock_poll_wait(file, sk_sleep(sk), wait);
mask = 0;
@@ -2163,22 +2226,50 @@ static unsigned int unix_dgram_poll(stru
}
/* No write status requested, avoid expensive OUT tests. */
- if (wait && !(wait->key & (POLLWRBAND | POLLWRNORM | POLLOUT)))
+ if (wait && !(wait->key & POLL_OUT_ALL))
return mask;
writable = unix_writable(sk);
- other = unix_peer_get(sk);
- if (other) {
- if (unix_peer(other) != sk) {
- sock_poll_wait(file, &unix_sk(other)->peer_wait, wait);
- if (unix_recvq_full(other))
+ if (writable) {
+ /*
+ * If sk is asymmetrically connected to a peer,
+ * whether or not data can actually be written depends
+ * on the number of messages already enqueued for
+ * reception on the peer socket, so check for this
+ * condition, too.
+ *
+ * In case the socket is deemed not writeable because
+ * of this, link it onto the peer_wait queue of the
+ * peer to ensure that a wake up happens even if no
+ * datagram already enqueued for processing was sent
+ * using sk.
+ *
+ * If a thread finds the socket writable but wasn't
+ * the one which connected, other threads might be
+ * sleeping so do a wake up after disconnecting.
+ */
+
+ need_wakeup = 0;
+ unix_state_lock(sk);
+
+ other = unix_peer(sk);
+ if (other && unix_peer(other) != sk) {
+ need_wakeup = !unix_peer_wake_connect(sk, other);
+
+ if (unix_recvq_full(other)) {
writable = 0;
+ need_wakeup = 0;
+ } else
+ unix_peer_wake_disconnect(sk, other);
}
- sock_put(other);
+
+ unix_state_unlock(sk);
+ if (need_wakeup)
+ wake_up_interruptible_poll(sk_sleep(sk), POLL_OUT_ALL);
}
if (writable)
- mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+ mask |= POLL_OUT_ALL;
else
set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
@@ -58,6 +58,7 @@ struct unix_sock {
unsigned int gc_maybe_cycle : 1;
unsigned char recursion_level;
struct socket_wq peer_wq;
+ wait_queue_t peer_wake;
};
#define unix_sk(__sk) ((struct unix_sock *)__sk)