[ofa-general] [PATCH 4/4] sdp: do gracefull close instead of always doing abortive close.
Amir Vadai
amirv at mellanox.co.il
Thu Jun 19 07:57:57 PDT 2008
Main changes:
1. when a close/shutdown syscall is called, instead of sending a DREQ, put
last socket ref count and go to TCP_CLOSE state do:
- take a socket reference count
- set state to TCP_TIME_WAIT
- start infiniband tear down
- wait till got RDMA_CM_EVENT_TIMEWAIT_EXIT
- set socket state to TCP_CLOSE
- put last socket ref count - this will call sdp_destruct()
2. Use fin_wait_timeout to timeout a half closed connection which the peer didn't
respond with SDP_MID_DISCONNECT
3. No need for sdp_time_wait
4. Abortive close will immedietly start infiniband teardown - will finilize the
socket closing when CM finish.
5. when sdp_post_sends send SDP_MID_DISCONNECT according to socket state
multiple DISCONNECT could be sent - changed it to be triggered by a flag.
Signed-off-by: Amir Vadai <amirv at mellanox.co.il>
---
drivers/infiniband/ulp/sdp/sdp.h | 20 +++---
drivers/infiniband/ulp/sdp/sdp_bcopy.c | 137 +++++++++++++++++++++++++-------
drivers/infiniband/ulp/sdp/sdp_cma.c | 10 ++-
drivers/infiniband/ulp/sdp/sdp_main.c | 97 +++++++++++++----------
4 files changed, 180 insertions(+), 84 deletions(-)
diff --git a/drivers/infiniband/ulp/sdp/sdp.h b/drivers/infiniband/ulp/sdp/sdp.h
index 5bd4041..3b2dffb 100644
--- a/drivers/infiniband/ulp/sdp/sdp.h
+++ b/drivers/infiniband/ulp/sdp/sdp.h
@@ -125,7 +125,7 @@ struct sdp_sock {
int xmit_size_goal;
int nonagle;
- int time_wait;
+ int fin_wait_timeout;
unsigned keepalive_time;
@@ -133,6 +133,8 @@ struct sdp_sock {
unsigned keepalive_tx_head;
unsigned keepalive_rx_head;
+ int sdp_disconnect;
+
/* Data below will be reset on error */
/* rdma specific */
struct rdma_cm_id *id;
@@ -227,15 +229,12 @@ static inline void sdp_set_error(struct sock *sk, int err)
{
sk->sk_err = -err;
if (sk->sk_socket)
- sk->sk_socket->state = SS_UNCONNECTED;
-
- sdp_set_state(sk, TCP_CLOSE);
+ sk->sk_socket->state = SS_DISCONNECTING;
- if (sdp_sk(sk)->time_wait) {
- sdp_dbg(sk, "%s: destroy in time wait state\n", __func__);
- sdp_sk(sk)->time_wait = 0;
- queue_work(sdp_workqueue, &sdp_sk(sk)->destroy_work);
- }
+ if (sk->sk_state == TCP_SYN_SENT)
+ sdp_set_state(sk, TCP_CLOSE);
+ else
+ sdp_set_state(sk, TCP_TIME_WAIT);
sk->sk_error_report(sk);
}
@@ -245,7 +244,6 @@ extern struct workqueue_struct *sdp_workqueue;
int sdp_cma_handler(struct rdma_cm_id *, struct rdma_cm_event *);
void sdp_reset(struct sock *sk);
void sdp_reset_sk(struct sock *sk, int rc);
-void sdp_time_wait_destroy_sk(struct sdp_sock *ssk);
void sdp_completion_handler(struct ib_cq *cq, void *cq_context);
void sdp_work(struct work_struct *work);
int sdp_post_credits(struct sdp_sock *ssk);
@@ -254,6 +252,8 @@ void sdp_post_recvs(struct sdp_sock *ssk);
int sdp_poll_cq(struct sdp_sock *ssk, struct ib_cq *cq);
void sdp_post_sends(struct sdp_sock *ssk, int nonagle);
void sdp_destroy_work(struct work_struct *work);
+void sdp_cancel_fin_wait_timeout(struct sdp_sock *ssk);
+void sdp_fin_work(struct work_struct *work);
void sdp_time_wait_work(struct work_struct *work);
struct sk_buff *sdp_recv_completion(struct sdp_sock *ssk, int id);
struct sk_buff *sdp_send_completion(struct sdp_sock *ssk, int mseq);
diff --git a/drivers/infiniband/ulp/sdp/sdp_bcopy.c b/drivers/infiniband/ulp/sdp/sdp_bcopy.c
index 3a8d5ac..d3f50b5 100644
--- a/drivers/infiniband/ulp/sdp/sdp_bcopy.c
+++ b/drivers/infiniband/ulp/sdp/sdp_bcopy.c
@@ -96,7 +96,7 @@ void sdp_remove_large_sock(struct sdp_sock *ssk)
}
}
-/* Like tcp_fin */
+/* Like tcp_fin - called when SDP_MID_DISCONNECT is received */
static void sdp_fin(struct sock *sk)
{
sdp_dbg(sk, "%s\n", __func__);
@@ -104,6 +104,42 @@ static void sdp_fin(struct sock *sk)
sk->sk_shutdown |= RCV_SHUTDOWN;
sock_set_flag(sk, SOCK_DONE);
+ switch (sk->sk_state) {
+ case TCP_SYN_RECV:
+ case TCP_ESTABLISHED:
+ sdp_set_state(sk, TCP_CLOSE_WAIT);
+ break;
+
+ case TCP_FIN_WAIT1:
+ /* This case occurs when a simultaneous close
+ * happens, we must ack the received FIN and
+ * enter the CLOSING state.
+ */
+ sdp_set_state(sk, TCP_CLOSING);
+ break;
+
+ case TCP_FIN_WAIT2:
+ /* Received a reply FIN - start Infiniband tear down */
+ sdp_set_state(sk, TCP_TIME_WAIT);
+ sdp_dbg(sk, "%s: Starting Infiniband tear down sending DREQ\n",
+ __func__);
+
+ if (sdp_sk(sk)->id) {
+ rdma_disconnect(sdp_sk(sk)->id);
+ } else {
+ sdp_warn(sk, "%s: sdp_sk(sk)->id is NULL\n", __func__);
+ BUG();
+ }
+ break;
+ case TCP_TIME_WAIT:
+ case TCP_CLOSE:
+ break;
+ default:
+ sdp_warn(sk, "%s: FIN in unexpected state. sk->sk_state=%d\n",
+ __func__, sk->sk_state);
+ break;
+ }
+
sk_mem_reclaim(sk);
@@ -524,7 +560,9 @@ void sdp_post_sends(struct sdp_sock *ssk, int nonagle)
if (unlikely(c < ssk->rx_head - ssk->rx_tail) &&
likely(ssk->bufs > 1) &&
- likely(ssk->tx_head - ssk->tx_tail < SDP_TX_SIZE)) {
+ likely(ssk->tx_head - ssk->tx_tail < SDP_TX_SIZE) &&
+ likely((1 << ssk->isk.sk.sk_state) &
+ (TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2))) {
skb = sdp_stream_alloc_skb(&ssk->isk.sk,
sizeof(struct sdp_bsdh),
GFP_KERNEL);
@@ -533,20 +571,16 @@ void sdp_post_sends(struct sdp_sock *ssk, int nonagle)
sdp_post_send(ssk, skb, SDP_MID_DATA);
}
- if (unlikely((1 << ssk->isk.sk.sk_state) &
- (TCPF_FIN_WAIT1 | TCPF_LAST_ACK)) &&
+ if (unlikely(ssk->sdp_disconnect) &&
!ssk->isk.sk.sk_send_head &&
ssk->bufs > (ssk->remote_credits >= ssk->rx_head - ssk->rx_tail)) {
+ ssk->sdp_disconnect = 0;
skb = sdp_stream_alloc_skb(&ssk->isk.sk,
sizeof(struct sdp_bsdh),
gfp_page);
/* FIXME */
BUG_ON(!skb);
sdp_post_send(ssk, skb, SDP_MID_DISCONN);
- if (ssk->isk.sk.sk_state == TCP_FIN_WAIT1)
- sdp_set_state(&ssk->isk.sk, TCP_FIN_WAIT2);
- else
- sdp_set_state(&ssk->isk.sk, TCP_CLOSING);
}
}
@@ -590,6 +624,7 @@ static void sdp_handle_resize_ack(struct sdp_sock *ssk, struct sdp_chrecvbuf *bu
static int sdp_handle_recv_comp(struct sdp_sock *ssk, struct ib_wc *wc)
{
+ struct sock *sk = &ssk->isk.sk;
int frags;
struct sk_buff *skb;
struct sdp_bsdh *h;
@@ -603,16 +638,15 @@ static int sdp_handle_recv_comp(struct sdp_sock *ssk, struct ib_wc *wc)
if (unlikely(wc->status)) {
if (wc->status != IB_WC_WR_FLUSH_ERR) {
- sdp_dbg(&ssk->isk.sk,
- "Recv completion with error. "
- "Status %d\n", wc->status);
- sdp_reset(&ssk->isk.sk);
+ sdp_dbg(sk, "Recv completion with error. Status %d\n",
+ wc->status);
+ sdp_reset(sk);
}
__kfree_skb(skb);
return 0;
}
- sdp_dbg_data(&ssk->isk.sk, "Recv completion. ID %d Length %d\n",
+ sdp_dbg_data(sk, "Recv completion. ID %d Length %d\n",
(int)wc->wr_id, wc->byte_len);
if (unlikely(wc->byte_len < sizeof(struct sdp_bsdh))) {
printk(KERN_WARNING "SDP BUG! byte_len %d < %zd\n",
@@ -651,7 +685,7 @@ static int sdp_handle_recv_comp(struct sdp_sock *ssk, struct ib_wc *wc)
}
if (unlikely(h->flags & SDP_OOB_PEND))
- sk_send_sigurg(&ssk->isk.sk);
+ sk_send_sigurg(sk);
skb_pull(skb, sizeof(struct sdp_bsdh));
@@ -661,21 +695,36 @@ static int sdp_handle_recv_comp(struct sdp_sock *ssk, struct ib_wc *wc)
__kfree_skb(skb);
break;
}
- skb = sdp_sock_queue_rcv_skb(&ssk->isk.sk, skb);
+
+ if (unlikely(sk->sk_shutdown & RCV_SHUTDOWN)) {
+ /* got data in RCV_SHUTDOWN */
+ if ((1 << sk->sk_state) &
+ (TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2)) {
+ /* go into abortive close */
+ sdp_set_state(sk, TCP_TIME_WAIT);
+
+ sk->sk_prot->disconnect(sk, 0);
+ }
+
+ __kfree_skb(skb);
+ break;
+ }
+ skb = sdp_sock_queue_rcv_skb(sk, skb);
if (unlikely(h->flags & SDP_OOB_PRES))
sdp_urg(ssk, skb);
break;
case SDP_MID_DISCONN:
- /* this will wake recvmsg */
- sdp_sock_queue_rcv_skb(&ssk->isk.sk, skb);
- sdp_fin(&ssk->isk.sk);
+ if (ssk->fin_wait_timeout)
+ sdp_cancel_fin_wait_timeout(ssk);
+
+ __kfree_skb(skb);
+ sdp_fin(sk);
break;
case SDP_MID_CHRCVBUF:
sdp_handle_resize_request(ssk,
- (struct sdp_chrecvbuf *)skb->data);
+ (struct sdp_chrecvbuf *)skb->data);
__kfree_skb(skb);
break;
-
case SDP_MID_CHRCVBUF_ACK:
sdp_handle_resize_ack(ssk, (struct sdp_chrecvbuf *)skb->data);
__kfree_skb(skb);
@@ -685,27 +734,62 @@ static int sdp_handle_recv_comp(struct sdp_sock *ssk, struct ib_wc *wc)
printk(KERN_WARNING "SDP: FIXME MID %d\n", h->mid);
__kfree_skb(skb);
}
+
return 0;
}
static int sdp_handle_send_comp(struct sdp_sock *ssk, struct ib_wc *wc)
{
struct sk_buff *skb;
+ struct sdp_bsdh *h;
skb = sdp_send_completion(ssk, wc->wr_id);
if (unlikely(!skb))
return -1;
- sk_wmem_free_skb(&ssk->isk.sk, skb);
+
if (unlikely(wc->status)) {
if (wc->status != IB_WC_WR_FLUSH_ERR) {
- sdp_dbg(&ssk->isk.sk,
- "Send completion with error. "
+ sdp_dbg(&ssk->isk.sk, "Send completion with error. "
"Status %d\n", wc->status);
sdp_set_error(&ssk->isk.sk, -ECONNRESET);
wake_up(&ssk->wq);
}
+ goto out;
}
+ h = (struct sdp_bsdh *)skb->data;
+
+ if (likely(h->mid != SDP_MID_DISCONN))
+ goto out;
+
+ switch (ssk->isk.sk.sk_state) {
+ case TCP_FIN_WAIT1:
+ /* sdp_set_state(&ssk->isk.sk,
+ TCP_FIN_WAIT2); */
+ break;
+ case TCP_CLOSING:
+ case TCP_LAST_ACK:
+ sdp_set_state(&ssk->isk.sk, TCP_TIME_WAIT);
+
+ sdp_dbg(&ssk->isk.sk,
+ "%s: waiting for Infiniband tear down\n", __func__);
+
+ /* We will not issue a DREQ here - because DREQ is sent on
+ the CM QP - and could reach the peer before the
+ SDP_MID_DISCONNECT This will put the peer in abortive close
+ state - without any real reason.
+ TODO: maybe a timeout whould be used here - what if the
+ peer won't send a DREQ? */
+ break;
+ default:
+ sdp_dbg(&ssk->isk.sk,
+ "%s: sent DISCONNECT from unexpected state %d\n",
+ __func__, ssk->isk.sk.sk_state);
+ }
+
+out:
+ sk_wmem_free_skb(&ssk->isk.sk, skb);
+
return 0;
}
@@ -734,13 +818,6 @@ static void sdp_handle_wc(struct sdp_sock *ssk, struct ib_wc *wc)
return;
}
-
- if (ssk->time_wait && !ssk->isk.sk.sk_send_head &&
- ssk->tx_head == ssk->tx_tail) {
- sdp_dbg(&ssk->isk.sk, "%s: destroy in time wait state\n",
- __func__);
- sdp_time_wait_destroy_sk(ssk);
- }
}
void sdp_completion_handler(struct ib_cq *cq, void *cq_context)
diff --git a/drivers/infiniband/ulp/sdp/sdp_cma.c b/drivers/infiniband/ulp/sdp/sdp_cma.c
index f2fb083..8759bac 100644
--- a/drivers/infiniband/ulp/sdp/sdp_cma.c
+++ b/drivers/infiniband/ulp/sdp/sdp_cma.c
@@ -498,9 +498,17 @@ int sdp_cma_handler(struct rdma_cm_id *id, struct rdma_cm_event *event)
((struct sockaddr_in *)&id->route.addr.src_addr)->sin_addr.s_addr;
rc = sdp_connected_handler(sk, event);
break;
- case RDMA_CM_EVENT_DISCONNECTED:
+ case RDMA_CM_EVENT_DISCONNECTED: /* This means DREQ/DREP received */
sdp_dbg(sk, "RDMA_CM_EVENT_DISCONNECTED\n");
rdma_disconnect(id);
+
+ if (sk->sk_state != TCP_TIME_WAIT) {
+ sdp_set_error(sk, EPIPE);
+ rc = sdp_disconnected_handler(sk);
+ }
+ break;
+ case RDMA_CM_EVENT_TIMWAIT_EXIT:
+ sdp_dbg(sk, "RDMA_CM_EVENT_TIMEWAIT_EXIT\n");
rc = sdp_disconnected_handler(sk);
break;
case RDMA_CM_EVENT_DEVICE_REMOVAL:
diff --git a/drivers/infiniband/ulp/sdp/sdp_main.c b/drivers/infiniband/ulp/sdp/sdp_main.c
index acd3aab..960ec90 100644
--- a/drivers/infiniband/ulp/sdp/sdp_main.c
+++ b/drivers/infiniband/ulp/sdp/sdp_main.c
@@ -342,10 +342,7 @@ void sdp_reset_sk(struct sock *sk, int rc)
memset((void *)&ssk->id, 0, sizeof(*ssk) - offsetof(typeof(*ssk), id));
- if (ssk->time_wait) {
- sdp_dbg(sk, "%s: destroy in time wait state\n", __func__);
- sdp_time_wait_destroy_sk(ssk);
- }
+ queue_work(sdp_workqueue, &ssk->destroy_work);
sk->sk_state_change(sk);
@@ -436,9 +433,10 @@ done:
sdp_dbg(sk, "%s done\n", __func__);
}
-static void sdp_send_active_reset(struct sock *sk, gfp_t priority)
+static void sdp_send_disconnect(struct sock *sk)
{
- sk->sk_prot->disconnect(sk, 0);
+ sdp_sk(sk)->sdp_disconnect = 1;
+ sdp_post_sends(sdp_sk(sk), 0);
}
/*
@@ -452,7 +450,7 @@ static int sdp_close_state(struct sock *sk)
return 0;
if (sk->sk_state == TCP_ESTABLISHED)
- sdp_set_state(sk, TCP_FIN_WAIT1);
+ sdp_set_state(sk, TCP_FIN_WAIT2);/* should be TCP_FIN_WAIT1 */
else if (sk->sk_state == TCP_CLOSE_WAIT)
sdp_set_state(sk, TCP_LAST_ACK);
else
@@ -466,7 +464,13 @@ static void sdp_close(struct sock *sk, long timeout)
struct sk_buff *skb;
int data_was_unread = 0;
+ if ((1 << sk->sk_state) & (TCPF_TIME_WAIT | TCPF_CLOSE)) {
+ sock_put(sk);
+ return;
+ }
+
lock_sock(sk);
+ sock_hold(sk);
sdp_dbg(sk, "%s\n", __func__);
@@ -504,18 +508,23 @@ static void sdp_close(struct sock *sk, long timeout)
if (data_was_unread) {
/* Unread data was tossed, zap the connection. */
NET_INC_STATS_USER(LINUX_MIB_TCPABORTONCLOSE);
- sdp_set_state(sk, TCP_CLOSE);
- sdp_send_active_reset(sk, GFP_KERNEL);
+ sdp_set_state(sk, TCP_TIME_WAIT);
+
+ /* Go into abortive close */
+ sk->sk_prot->disconnect(sk, 0);
} else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
/* Check zero linger _after_ checking for unread data. */
- sk->sk_prot->disconnect(sk, 0);
NET_INC_STATS_USER(LINUX_MIB_TCPABORTONDATA);
+ sdp_set_state(sk, TCP_TIME_WAIT);
+
+ /* Go into abortive close */
+ sk->sk_prot->disconnect(sk, 0);
} else if (sdp_close_state(sk)) {
/* We FIN if the application ate all the data before
* zapping the connection.
*/
- sdp_post_sends(sdp_sk(sk), 0);
+ sdp_send_disconnect(sk);
}
/* TODO: state should move to CLOSE or CLOSE_WAIT etc on disconnect.
@@ -533,7 +542,6 @@ adjudge_to_death:
*/
lock_sock(sk);
- sock_hold(sk);
sock_orphan(sk);
/* This is a (useful) BSD violating of the RFC. There is a
@@ -549,15 +557,8 @@ adjudge_to_death:
* consume significant resources. Let's do it with special
* linger2 option. --ANK
*/
-
- if (sk->sk_state == TCP_FIN_WAIT2 &&
- !sk->sk_send_head &&
- sdp_sk(sk)->tx_head == sdp_sk(sk)->tx_tail) {
- sdp_set_state(sk, TCP_CLOSE);
- }
-
if ((1 << sk->sk_state) & (TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2)) {
- sdp_sk(sk)->time_wait = 1;
+ sdp_sk(sk)->fin_wait_timeout = 1;
/* TODO: liger2 unimplemented.
We should wait 3.5 * rto. How do I know rto? */
/* TODO: tcp_fin_time to get timeout */
@@ -566,18 +567,20 @@ adjudge_to_death:
atomic_inc(sk->sk_prot->orphan_count);
queue_delayed_work(sdp_workqueue, &sdp_sk(sk)->time_wait_work,
TCP_FIN_TIMEOUT);
- goto out;
}
/* TODO: limit number of orphaned sockets.
TCP has sysctl_tcp_mem and sysctl_tcp_max_orphans */
- sock_put(sk);
- /* Otherwise, socket is reprieved until protocol close. */
-out:
- sdp_dbg(sk, "%s: last socket put %d\n", __func__,
- atomic_read(&sk->sk_refcnt));
+ if (sk->sk_state != TCP_CLOSE) {
+ sdp_dbg(sk, "Hold socket till end of Infiniband tear-down\n");
+ /* Hold socket till end of Infiniband tear-down */
+ sock_hold(sk);
+ }
+
release_sock(sk);
+
+ sock_put(sk);
sk_common_release(sk);
}
@@ -635,11 +638,13 @@ static int sdp_disconnect(struct sock *sk, int flags)
struct rdma_cm_id *id;
sdp_dbg(sk, "%s\n", __func__);
- if (ssk->id)
- rc = rdma_disconnect(ssk->id);
- if (old_state != TCP_LISTEN)
+ if (old_state != TCP_LISTEN) {
+ if (ssk->id)
+ rc = rdma_disconnect(ssk->id);
+
return rc;
+ }
sdp_set_state(sk, TCP_CLOSE);
id = ssk->id;
@@ -827,14 +832,23 @@ static int sdp_ioctl(struct sock *sk, int cmd, unsigned long arg)
return put_user(answ, (int __user *)arg);
}
+void sdp_cancel_fin_wait_timeout(struct sdp_sock *ssk)
+{
+ ssk->fin_wait_timeout = 0;
+ cancel_delayed_work(&ssk->time_wait_work);
+ atomic_dec(ssk->isk.sk.sk_prot->orphan_count);
+}
+
void sdp_destroy_work(struct work_struct *work)
{
struct sdp_sock *ssk = container_of(work, struct sdp_sock, destroy_work);
struct sock *sk = &ssk->isk.sk;
sdp_dbg(sk, "%s: refcnt %d\n", __func__, atomic_read(&sk->sk_refcnt));
- cancel_delayed_work(&sdp_sk(sk)->time_wait_work);
- atomic_dec(sk->sk_prot->orphan_count);
+ if (ssk->fin_wait_timeout)
+ sdp_cancel_fin_wait_timeout(ssk);
+
+ sdp_set_state(sk, TCP_CLOSE);
sock_put(sk);
}
@@ -846,26 +860,20 @@ void sdp_time_wait_work(struct work_struct *work)
lock_sock(sk);
sdp_dbg(sk, "%s\n", __func__);
- if (!sdp_sk(sk)->time_wait) {
+ if (!sdp_sk(sk)->fin_wait_timeout) {
release_sock(sk);
return;
}
sdp_dbg(sk, "%s: refcnt %d\n", __func__, atomic_read(&sk->sk_refcnt));
- sdp_set_state(sk, TCP_CLOSE);
- sdp_sk(sk)->time_wait = 0;
+ sdp_sk(sk)->fin_wait_timeout = 0;
release_sock(sk);
atomic_dec(sk->sk_prot->orphan_count);
- sock_put(sk);
-}
-void sdp_time_wait_destroy_sk(struct sdp_sock *ssk)
-{
- ssk->time_wait = 0;
- sdp_set_state(&ssk->isk.sk, TCP_CLOSE);
- queue_work(sdp_workqueue, &ssk->destroy_work);
+ if (sdp_sk(sk)->id)
+ rdma_disconnect(sdp_sk(sk)->id);
}
static int sdp_init_sock(struct sock *sk)
@@ -880,6 +888,9 @@ static int sdp_init_sock(struct sock *sk)
INIT_WORK(&ssk->destroy_work, sdp_destroy_work);
sk->sk_route_caps |= NETIF_F_SG | NETIF_F_NO_CSUM;
+
+ ssk->sdp_disconnect = 0;
+
return 0;
}
@@ -895,7 +906,7 @@ static void sdp_shutdown(struct sock *sk, int how)
return;
if (sk->sk_state == TCP_ESTABLISHED)
- sdp_set_state(sk, TCP_FIN_WAIT1);
+ sdp_set_state(sk, TCP_FIN_WAIT2);/* should be TCP_FIN_WAIT1 */
else if (sk->sk_state == TCP_CLOSE_WAIT)
sdp_set_state(sk, TCP_LAST_ACK);
else
@@ -910,7 +921,7 @@ static void sdp_shutdown(struct sock *sk, int how)
if (ssk->nonagle & TCP_NAGLE_OFF)
ssk->nonagle |= TCP_NAGLE_PUSH;
- sdp_post_sends(ssk, 0);
+ sdp_send_disconnect(sk);
}
static void sdp_mark_push(struct sdp_sock *ssk, struct sk_buff *skb)
--
1.5.2
More information about the general
mailing list