diff options
author | meem <none@none> | 2006-08-20 10:20:53 -0700 |
---|---|---|
committer | meem <none@none> | 2006-08-20 10:20:53 -0700 |
commit | a2036d4dec789d6fcf6f71ae43cc38d40c4786cc (patch) | |
tree | 78cee26f1c36d603dbc2292d88c42209af3a2042 /usr/src/uts/common/inet/tcp/tcp_fusion.c | |
parent | 70ab954a5d6c4d36858fd6e7e3dd4498d06d2c40 (diff) | |
download | illumos-joyent-a2036d4dec789d6fcf6f71ae43cc38d40c4786cc.tar.gz |
6440123 TCP Fusion loopback connections may hang due to flow control logic error
6458410 read() may spuriously return EAGAIN while unfusing a TCP connection
Diffstat (limited to 'usr/src/uts/common/inet/tcp/tcp_fusion.c')
-rw-r--r-- | usr/src/uts/common/inet/tcp/tcp_fusion.c | 69 |
1 files changed, 50 insertions, 19 deletions
diff --git a/usr/src/uts/common/inet/tcp/tcp_fusion.c b/usr/src/uts/common/inet/tcp/tcp_fusion.c index fda7bff9d2..3f9bf559cc 100644 --- a/usr/src/uts/common/inet/tcp/tcp_fusion.c +++ b/usr/src/uts/common/inet/tcp/tcp_fusion.c @@ -72,16 +72,18 @@ * used for this purpose. When there is urgent data, the sender needs * to push the data up the receiver's streams read queue. In order to * avoid holding the tcp_fuse_lock across putnext(), the sender sets - * the peer tcp's tcp_fuse_syncstr_stopped bit and releases tcp_fuse_lock - * (see macro TCP_FUSE_SYNCSTR_STOP()). If tcp_fuse_rrw() enters after - * this point, it will see that synchronous streams is temporarily - * stopped and it will immediately return EBUSY without accessing the - * tcp_rcv_list or other fields protected by the tcp_fuse_lock. This - * will result in strget() calling getq_noenab() to dequeue data from - * the stream head instead. After the sender has finished pushing up - * all urgent data, it will clear the tcp_fuse_syncstr_stopped bit using - * TCP_FUSE_SYNCSTR_RESUME and the receiver may then resume using - * tcp_fuse_rrw() to retrieve data from tcp_rcv_list. + * the peer tcp's tcp_fuse_syncstr_plugged bit and releases tcp_fuse_lock + * (see macro TCP_FUSE_SYNCSTR_PLUG_DRAIN()). If tcp_fuse_rrw() enters + * after this point, it will see that synchronous streams is plugged and + * will wait on tcp_fuse_plugcv. After the sender has finished pushing up + * all urgent data, it will clear the tcp_fuse_syncstr_plugged bit using + * TCP_FUSE_SYNCSTR_UNPLUG_DRAIN(). This will cause any threads waiting + * on tcp_fuse_plugcv to return EBUSY, and in turn cause strget() to call + * getq_noenab() to dequeue data from the stream head instead. Once the + * data on the stream head has been consumed, tcp_fuse_rrw() may again + * be used to process tcp_rcv_list. However, if TCP_FUSE_SYNCSTR_STOP() + * has been called, all future calls to tcp_fuse_rrw() will return EBUSY, + * effectively disabling synchronous streams. * * The following note applies only to the synchronous streams mode. * @@ -496,7 +498,7 @@ tcp_fuse_output(tcp_t *tcp, mblk_t *mp, uint32_t send_size) * below, synchronous streams will remain stopped until * someone drains the tcp_rcv_list. */ - TCP_FUSE_SYNCSTR_STOP(peer_tcp); + TCP_FUSE_SYNCSTR_PLUG_DRAIN(peer_tcp); tcp_fuse_output_urg(tcp, mp); } @@ -563,6 +565,7 @@ tcp_fuse_output(tcp_t *tcp, mblk_t *mp, uint32_t send_size) } else if (flow_stopped && TCP_UNSENT_BYTES(tcp) <= tcp->tcp_xmit_lowater) { tcp_clrqfull(tcp); + flow_stopped = B_FALSE; } loopback_packets++; @@ -613,7 +616,7 @@ tcp_fuse_output(tcp_t *tcp, mblk_t *mp, uint32_t send_size) * to the presence of urgent data, re-enable it. */ if (urgent) - TCP_FUSE_SYNCSTR_RESUME(peer_tcp); + TCP_FUSE_SYNCSTR_UNPLUG_DRAIN(peer_tcp); } } return (B_TRUE); @@ -733,10 +736,27 @@ tcp_fuse_rrw(queue_t *q, struiod_t *dp) mblk_t *mp; mutex_enter(&tcp->tcp_fuse_lock); + + /* + * If tcp_fuse_syncstr_plugged is set, then another thread is moving + * the underlying data to the stream head. We need to wait until it's + * done, then return EBUSY so that strget() will dequeue data from the + * stream head to ensure data is drained in-order. + */ + if (tcp->tcp_fuse_syncstr_plugged) { + do { + cv_wait(&tcp->tcp_fuse_plugcv, &tcp->tcp_fuse_lock); + } while (tcp->tcp_fuse_syncstr_plugged); + + TCP_STAT(tcp_fusion_rrw_plugged); + TCP_STAT(tcp_fusion_rrw_busy); + return (EBUSY); + } + /* * If someone had turned off tcp_direct_sockfs or if synchronous - * streams is temporarily disabled, we return EBUSY. This causes - * strget() to dequeue data from the stream head instead. + * streams is stopped, we return EBUSY. This causes strget() to + * dequeue data from the stream head instead. */ if (!tcp->tcp_direct_sockfs || tcp->tcp_fuse_syncstr_stopped) { mutex_exit(&tcp->tcp_fuse_lock); @@ -817,7 +837,7 @@ tcp_fuse_rinfop(queue_t *q, infod_t *dp) * currently not accessible. */ if (!tcp->tcp_direct_sockfs || tcp->tcp_fuse_syncstr_stopped || - (mp = tcp->tcp_rcv_list) == NULL) + tcp->tcp_fuse_syncstr_plugged || (mp = tcp->tcp_rcv_list) == NULL) goto done; if (cmd & INFOD_COUNT) { @@ -984,11 +1004,11 @@ tcp_fuse_disable_pair(tcp_t *tcp, boolean_t unfusing) ASSERT(peer_tcp != NULL); /* - * We need to prevent tcp_fuse_rrw() from entering before - * we can disable synchronous streams. + * Force any tcp_fuse_rrw() calls to block until we've moved the data + * onto the stream head. */ - TCP_FUSE_SYNCSTR_STOP(tcp); - TCP_FUSE_SYNCSTR_STOP(peer_tcp); + TCP_FUSE_SYNCSTR_PLUG_DRAIN(tcp); + TCP_FUSE_SYNCSTR_PLUG_DRAIN(peer_tcp); /* * Drain any pending data; the detached check is needed because @@ -1010,6 +1030,17 @@ tcp_fuse_disable_pair(tcp_t *tcp, boolean_t unfusing) (unfusing ? &peer_tcp->tcp_fused_sigurg_mp : NULL)); } + /* + * Make all current and future tcp_fuse_rrw() calls fail with EBUSY. + * To ensure threads don't sneak past the checks in tcp_fuse_rrw(), + * a given stream must be stopped prior to being unplugged (but the + * ordering of operations between the streams is unimportant). + */ + TCP_FUSE_SYNCSTR_STOP(tcp); + TCP_FUSE_SYNCSTR_STOP(peer_tcp); + TCP_FUSE_SYNCSTR_UNPLUG_DRAIN(tcp); + TCP_FUSE_SYNCSTR_UNPLUG_DRAIN(peer_tcp); + /* Lift up any flow-control conditions */ if (tcp->tcp_flow_stopped) { tcp_clrqfull(tcp); |