drbd: Keep the listening socket open while trying to connect to the peer
authorPhilipp Reisner <philipp.reisner@linbit.com>
Thu, 12 Jul 2012 12:22:37 +0000 (14:22 +0200)
committerPhilipp Reisner <philipp.reisner@linbit.com>
Thu, 8 Nov 2012 15:58:31 +0000 (16:58 +0100)
Signed-off-by: Philipp Reisner <philipp.reisner@linbit.com>
Signed-off-by: Lars Ellenberg <lars.ellenberg@linbit.com>
drivers/block/drbd/drbd_receiver.c

index 46c5579..9aac1c4 100644 (file)
@@ -666,7 +666,32 @@ out:
        return sock;
 }
 
-static struct socket *prepare_listen_socket(struct drbd_tconn *tconn)
+struct accept_wait_data {
+       struct drbd_tconn *tconn;
+       struct socket *s_listen;
+       struct completion door_bell;
+       void (*original_sk_state_change)(struct sock *sk);
+
+};
+
+static void incomming_connection(struct sock *sk)
+{
+       struct accept_wait_data *ad = sk->sk_user_data;
+       struct drbd_tconn *tconn = ad->tconn;
+
+       if (sk->sk_state != TCP_ESTABLISHED)
+               conn_warn(tconn, "unexpected tcp state change. sk_state = %d\n", sk->sk_state);
+
+       write_lock_bh(&sk->sk_callback_lock);
+       sk->sk_state_change = ad->original_sk_state_change;
+       sk->sk_user_data = NULL;
+       write_unlock_bh(&sk->sk_callback_lock);
+
+       sk->sk_state_change(sk);
+       complete(&ad->door_bell);
+}
+
+static int prepare_listen_socket(struct drbd_tconn *tconn, struct accept_wait_data *ad)
 {
        int err, sndbuf_size, rcvbuf_size, my_addr_len;
        struct sockaddr_in6 my_addr;
@@ -678,7 +703,7 @@ static struct socket *prepare_listen_socket(struct drbd_tconn *tconn)
        nc = rcu_dereference(tconn->net_conf);
        if (!nc) {
                rcu_read_unlock();
-               return NULL;
+               return -EIO;
        }
        sndbuf_size = nc->sndbuf_size;
        rcvbuf_size = nc->rcvbuf_size;
@@ -703,12 +728,19 @@ static struct socket *prepare_listen_socket(struct drbd_tconn *tconn)
        if (err < 0)
                goto out;
 
+       ad->s_listen = s_listen;
+       write_lock_bh(&s_listen->sk->sk_callback_lock);
+       ad->original_sk_state_change = s_listen->sk->sk_state_change;
+       s_listen->sk->sk_state_change = incomming_connection;
+       s_listen->sk->sk_user_data = ad;
+       write_unlock_bh(&s_listen->sk->sk_callback_lock);
+
        what = "listen";
        err = s_listen->ops->listen(s_listen, 5);
        if (err < 0)
                goto out;
 
-       return s_listen;
+       return 0;
 out:
        if (s_listen)
                sock_release(s_listen);
@@ -719,14 +751,13 @@ out:
                }
        }
 
-       return NULL;
+       return -EIO;
 }
 
-static struct socket *drbd_wait_for_connect(struct drbd_tconn *tconn)
+static struct socket *drbd_wait_for_connect(struct drbd_tconn *tconn, struct accept_wait_data *ad)
 {
        int timeo, connect_int, err = 0;
        struct socket *s_estab = NULL;
-       struct socket *s_listen;
        struct net_conf *nc;
 
        rcu_read_lock();
@@ -741,18 +772,11 @@ static struct socket *drbd_wait_for_connect(struct drbd_tconn *tconn)
        timeo = connect_int * HZ;
        timeo += (random32() & 1) ? timeo / 7 : -timeo / 7; /* 28.5% random jitter */
 
-       s_listen = prepare_listen_socket(tconn);
-       if (!s_listen)
-               goto out;
-
-       s_listen->sk->sk_rcvtimeo = timeo;
-       s_listen->sk->sk_sndtimeo = timeo;
-
-       err = kernel_accept(s_listen, &s_estab, 0);
+       err = wait_for_completion_interruptible_timeout(&ad->door_bell, timeo);
+       if (err <= 0)
+               return NULL;
 
-out:
-       if (s_listen)
-               sock_release(s_listen);
+       err = kernel_accept(ad->s_listen, &s_estab, 0);
        if (err < 0) {
                if (err != -EAGAIN && err != -EINTR && err != -ERESTARTSYS) {
                        conn_err(tconn, "accept failed, err = %d\n", err);
@@ -855,6 +879,10 @@ static int conn_connect(struct drbd_tconn *tconn)
        int vnr, timeout, try, h, ok;
        bool discard_my_data;
        enum drbd_state_rv rv;
+       struct accept_wait_data ad = {
+               .tconn = tconn,
+               .door_bell = COMPLETION_INITIALIZER_ONSTACK(ad.door_bell),
+       };
 
        if (conn_request_state(tconn, NS(conn, C_WF_CONNECTION), CS_VERBOSE) < SS_SUCCESS)
                return -2;
@@ -873,6 +901,9 @@ static int conn_connect(struct drbd_tconn *tconn)
        /* Assume that the peer only understands protocol 80 until we know better.  */
        tconn->agreed_pro_version = 80;
 
+       if (prepare_listen_socket(tconn, &ad))
+               return 0;
+
        do {
                struct socket *s;
 
@@ -911,7 +942,7 @@ static int conn_connect(struct drbd_tconn *tconn)
                }
 
 retry:
-               s = drbd_wait_for_connect(tconn);
+               s = drbd_wait_for_connect(tconn, &ad);
                if (s) {
                        try = receive_first_packet(tconn, s);
                        drbd_socket_okay(&sock.socket);
@@ -957,6 +988,9 @@ retry:
                }
        } while (1);
 
+       if (ad.s_listen)
+               sock_release(ad.s_listen);
+
        sock.socket->sk->sk_reuse = 1; /* SO_REUSEADDR */
        msock.socket->sk->sk_reuse = 1; /* SO_REUSEADDR */
 
@@ -1052,6 +1086,8 @@ retry:
        return h;
 
 out_release_sockets:
+       if (ad.s_listen)
+               sock_release(ad.s_listen);
        if (sock.socket)
                sock_release(sock.socket);
        if (msock.socket)