tls-autodetect: fix client-initiated shutdown

This commit is contained in:
Přemysl Eric Janouch 2018-07-22 13:45:32 +02:00
parent 6caa4ab928
commit 525734eeb3
Signed by: p
GPG Key ID: A0420B94F92B9493

View File

@ -80,14 +80,15 @@ type connCloseWrite interface {
} }
type client struct { type client struct {
transport net.Conn // underlying connection transport net.Conn // underlying connection
tls *tls.Conn // TLS, if detected tls *tls.Conn // TLS, if detected
conn connCloseWrite // high-level connection conn connCloseWrite // high-level connection
inQ []byte // unprocessed input inQ []byte // unprocessed input
outQ []byte // unprocessed output outQ []byte // unprocessed output
writing bool // whether a writing goroutine is running reading bool // whether a reading goroutine is running
inShutdown bool // whether we're closing connection writing bool // whether a writing goroutine is running
killTimer *time.Timer // timeout closing bool // whether we're closing the connection
killTimer *time.Timer // timeout
} }
type preparedEvent struct { type preparedEvent struct {
@ -164,7 +165,7 @@ func forceShutdown(reason string) {
// --- Client ------------------------------------------------------------------ // --- Client ------------------------------------------------------------------
func (c *client) send(line string) { func (c *client) send(line string) {
if !c.inShutdown { if !c.closing {
c.outQ = append(c.outQ, (line + "\r\n")...) c.outQ = append(c.outQ, (line + "\r\n")...)
c.flushOutQ() c.flushOutQ()
} }
@ -172,7 +173,7 @@ func (c *client) send(line string) {
// Tear down the client connection, trying to do so in a graceful manner. // Tear down the client connection, trying to do so in a graceful manner.
func (c *client) closeLink() { func (c *client) closeLink() {
if c.inShutdown { if c.closing {
return return
} }
if c.conn == nil { if c.conn == nil {
@ -180,13 +181,13 @@ func (c *client) closeLink() {
return return
} }
// Since we send this goodbye, we don't need to call CloseWrite. // Since we send this goodbye, we don't need to call CloseWrite here.
c.send("Goodbye") c.send("Goodbye")
c.killTimer = time.AfterFunc(3*time.Second, func() { c.killTimer = time.AfterFunc(3*time.Second, func() {
timeouts <- c timeouts <- c
}) })
c.inShutdown = true c.closing = true
} }
// Close the connection and forget about the client. // Close the connection and forget about the client.
@ -218,6 +219,7 @@ func (c *client) onPrepared(host string, isTLS bool) {
// TODO: Save the host in the client structure. // TODO: Save the host in the client structure.
go read(c) go read(c)
c.reading = true
} }
// Handle the results from trying to read from the client connection. // Handle the results from trying to read from the client connection.
@ -236,28 +238,34 @@ func (c *client) onRead(data []byte, readErr error) {
} }
// TODO: Inform the client about the inQ overrun in the farewell message. // TODO: Inform the client about the inQ overrun in the farewell message.
// TODO: We should stop receiving any more data from this client. // TODO: We should stop receiving any more data from this client, or at
// least stop extending the inQ if we don't want to signal tho goroutine.
if len(c.inQ) > 8192 { if len(c.inQ) > 8192 {
c.closeLink() c.closeLink()
return return
} }
if readErr == io.EOF { if readErr != nil {
if c.inShutdown { c.reading = false
if readErr != io.EOF {
log.Println(readErr)
c.destroy()
} else if c.closing {
// Disregarding whether a clean shutdown has happened or not.
log.Println("client finished shutdown")
c.destroy() c.destroy()
} else { } else {
log.Println("client EOF")
c.closeLink() c.closeLink()
} }
} else if readErr != nil {
log.Println(readErr)
c.destroy()
} }
} }
// Spawn a goroutine to flush the outQ if possible and necessary. If the // Spawn a goroutine to flush the outQ if possible and necessary. If the
// connection is not ready yet, it needs to be retried as soon as it becomes. // connection is not ready yet, it needs to be retried as soon as it becomes.
func (c *client) flushOutQ() { func (c *client) flushOutQ() {
if c.conn != nil && !c.writing { if !c.writing && c.conn != nil {
go write(c, c.outQ) go write(c, c.outQ)
c.writing = true c.writing = true
} }
@ -273,14 +281,8 @@ func (c *client) onWrite(written int, writeErr error) {
c.destroy() c.destroy()
} else if len(c.outQ) > 0 { } else if len(c.outQ) > 0 {
c.flushOutQ() c.flushOutQ()
} else if c.inShutdown { } else if c.closing {
if c.conn != nil { if c.reading {
// FIXME: This is only correct for when /we/ initiate the shutdown,
// otherwise we should perhaps just Close. Though even if we
// Close, there's a/ no writer to fail on it, and b/ the reader
// has already exited, too, which is why the client stays alive
// up until the timeout. It seems that in that case we need to
// call c.destroy().
c.conn.CloseWrite() c.conn.CloseWrite()
} else { } else {
c.destroy() c.destroy()
@ -341,7 +343,6 @@ func prepare(client *client) {
} }
prepared <- preparedEvent{client, host, isTLS} prepared <- preparedEvent{client, host, isTLS}
} }
func read(client *client) { func read(client *client) {