@ -4,6 +4,7 @@ import (
"io"
"net"
"sync"
"sync/atomic"
"time"
)
@ -11,10 +12,6 @@ type listener struct {
net . Listener
}
type gracefulConn interface {
gracefulShutdown ( )
}
// WrapListener wraps an arbitrary net.Listener for use with graceful shutdowns.
// All net.Conn's Accept()ed by this listener will be auto-wrapped as if
// WrapConn() were called on them.
@ -24,11 +21,17 @@ func WrapListener(l net.Listener) net.Listener {
func ( l listener ) Accept ( ) ( net . Conn , error ) {
conn , err := l . Listener . Accept ( )
if err != nil {
return nil , err
}
return WrapConn ( conn ) , err
}
return WrapConn ( conn ) , nil
type conn struct {
mu sync . Mutex
cs * connSet
net . Conn
id uint64
busy , die bool
dead bool
hijacked bool
}
/ *
@ -39,10 +42,9 @@ terminating the process.
In order to use this function , you must call SetReadDeadline ( ) before the call
to Read ( ) you might make to read a new request off the wire . The connection is
eligible for abrupt closing at any point between when the call to
SetReadDeadline ( ) returns and when the call to Read returns with new data . It
does not matter what deadline is given to SetReadDeadline ( ) -- the default HTTP
server provided by this package sets a deadline far into the future when a
deadline is not provided , for instance .
SetReadDeadline ( ) returns and when the call to Read returns with new data . It
does not matter what deadline is given to SetReadDeadline ( ) -- if a deadline is
inappropriate , providing one extremely far into the future will suffice .
Unfortunately , this means that it ' s difficult to use SetReadDeadline ( ) in a
great many perfectly reasonable circumstances , such as to extend a deadline
@ -50,152 +52,125 @@ after more data has been read, without the connection being eligible for
"graceful" termination at an undesirable time . Since this package was written
explicitly to target net / http , which does not as of this writing do any of this ,
fixing the semantics here does not seem especially urgent .
As an optimization for net / http over TCP , if the input connection supports the
ReadFrom ( ) function , the returned connection will as well . This allows the net
package to use sendfile ( 2 ) on certain platforms in certain circumstances .
* /
func WrapConn ( c net . Conn ) net . Conn {
wg . Add ( 1 )
nc := conn {
Conn : c ,
closing : make ( chan struct { } ) ,
if c == nil {
return nil
}
if _ , ok := c . ( io . ReaderFrom ) ; ok {
c = & sendfile { nc }
} else {
c = & nc
wg . Add ( 1 )
return & conn {
Conn : c ,
id : atomic . AddUint64 ( & idleSet . id , 1 ) ,
}
}
go c . ( gracefulConn ) . gracefulShutdown ( )
func ( c * conn ) Read ( b [ ] byte ) ( n int , err error ) {
c . mu . Lock ( )
if ! c . hijacked {
defer func ( ) {
c . mu . Lock ( )
if c . hijacked {
// It's a little unclear to me how this case
// would happen, but we *did* drop the lock, so
// let's play it safe.
return
}
if c . dead {
// Dead sockets don't tell tales. This is to
// prevent the case where a Read manages to suck
// an entire request off the wire in a race with
// someone trying to close idle connections.
// Whoever grabs the conn lock first wins, and
// if that's the closing process, we need to
// "take back" the read.
n = 0
err = io . EOF
} else {
idleSet . markBusy ( c )
}
c . mu . Unlock ( )
} ( )
}
c . mu . Unlock ( )
return c
return c . Conn . Read ( b )
}
type connstate int
/ *
State diagram . ( Waiting ) is the starting state .
( Waiting ) -- -- - Read ( ) -- -- - > Working -- - +
| ^ / | ^ Read ( )
| \ / | + -- -- +
kill SetReadDeadline ( ) kill
| | + -- -- - +
V V V Read ( )
Dead <- SetReadDeadline ( ) -- Dying -- -- +
^
|
+ -- Close ( ) -- - [ from any state ]
func ( c * conn ) SetReadDeadline ( t time . Time ) error {
c . mu . Lock ( )
if ! c . hijacked {
defer c . markIdle ( )
}
c . mu . Unlock ( )
return c . Conn . SetReadDeadline ( t )
}
* /
func ( c * conn ) Close ( ) error {
kill := false
c . mu . Lock ( )
kill , c . dead = ! c . dead , true
idleSet . markBusy ( c )
c . mu . Unlock ( )
if kill {
defer wg . Done ( )
}
return c . Conn . Close ( )
}
const (
// Waiting for more data, and eligible for killing
csWaiting connstate = iota
// In the middle of a connection
csWorking
// Kill has been requested, but waiting on request to finish up
csDying
// Connection is gone forever. Also used when a connection gets hijacked
csDead
)
type writerOnly struct {
w io . Writer
}
type conn struct {
net . Conn
m sync . Mutex
state connstate
closing chan struct { }
func ( w writerOnly ) Write ( buf [ ] byte ) ( int , error ) {
return w . w . Write ( buf )
}
type sendfile struct { conn }
func ( c * conn ) gracefulShutdown ( ) {
select {
case <- kill :
case <- c . closing :
return
}
c . m . Lock ( )
defer c . m . Unlock ( )
switch c . state {
case csWaiting :
c . unlockedClose ( true )
case csWorking :
c . state = csDying
func ( c * conn ) ReadFrom ( r io . Reader ) ( int64 , error ) {
if rf , ok := c . Conn . ( io . ReaderFrom ) ; ok {
return rf . ReadFrom ( r )
}
return io . Copy ( writerOnly { c } , r )
}
func ( c * conn ) unlockedClose ( closeConn bool ) {
if closeConn {
func ( c * conn ) markIdle ( ) {
kill := false
c . mu . Lock ( )
idleSet . markIdle ( c )
if c . die {
kill , c . dead = ! c . dead , true
}
c . mu . Unlock ( )
if kill {
defer wg . Done ( )
c . Conn . Close ( )
}
close ( c . closing )
wg . Done ( )
c . state = csDead
}
// We do some hijinks to support hijacking. The semantics here is that any
// connection that gets hijacked is dead to us: we return the raw net.Conn and
// stop tracking the connection entirely.
type hijackConn interface {
hijack ( ) net . Conn
}
func ( c * conn ) hijack ( ) net . Conn {
c . m . Lock ( )
defer c . m . Unlock ( )
if c . state != csDead {
close ( c . closing )
wg . Done ( )
c . state = csDead
func ( c * conn ) closeIfIdle ( ) {
kill := false
c . mu . Lock ( )
c . die = true
if ! c . busy && ! c . hijacked {
kill , c . dead = ! c . dead , true
}
return c . Conn
}
func ( c * conn ) Read ( b [ ] byte ) ( n int , err error ) {
defer func ( ) {
c . m . Lock ( )
defer c . m . Unlock ( )
if c . state == csWaiting {
c . state = csWorking
} else if c . state == csDead {
n = 0
err = io . EOF
}
} ( )
c . mu . Unlock ( )
return c . Conn . Read ( b )
}
func ( c * conn ) Close ( ) error {
defer func ( ) {
c . m . Lock ( )
defer c . m . Unlock ( )
if c . state != csDead {
c . unlockedClose ( false )
}
} ( )
return c . Conn . Close ( )
}
func ( c * conn ) SetReadDeadline ( t time . Time ) error {
defer func ( ) {
c . m . Lock ( )
defer c . m . Unlock ( )
switch c . state {
case csDying :
c . unlockedClose ( false )
case csWorking :
c . state = csWaiting
}
} ( )
return c . Conn . SetReadDeadline ( t )
if kill {
defer wg . Done ( )
c . Conn . Close ( )
}
}
func ( s * sendfile ) ReadFrom ( r io . Reader ) ( int64 , error ) {
// conn.Conn.KHAAAAAAAANNNNNN
return s . conn . Conn . ( io . ReaderFrom ) . ReadFrom ( r )
func ( c * conn ) hijack ( ) net . Conn {
c . mu . Lock ( )
idleSet . markBusy ( c )
c . hijacked = true
c . mu . Unlock ( )
return c . Conn
}