Skip to content

Commit df7b22c

Browse files
Close TCP, TLS connections gracefully to avoid data loss (#123)
TCP connections, including TLS connections, acknowledge received data. Although a simple `net.Conn.Close()` will put all previously written data on the network, the receiving server may disregard data that it can't successfully acknowledge. Graceful acknowledgement and closure can be facilitated by the client closing writes first, and reading any available data before fully closing the connection.
1 parent ab7b158 commit df7b22c

File tree

3 files changed

+52
-7
lines changed

3 files changed

+52
-7
lines changed

.changelog/123.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
TCP, TLS outputs: Close connections gracefully to avoid data loss.
3+
```

internal/output/tcp/tcp.go

+26-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package tcp
66

77
import (
88
"context"
9+
"errors"
10+
"io"
911
"net"
1012
"time"
1113

@@ -18,7 +20,7 @@ func init() {
1820

1921
type Output struct {
2022
opts *output.Options
21-
conn net.Conn
23+
conn *net.TCPConn
2224
}
2325

2426
func New(opts *output.Options) (output.Output, error) {
@@ -33,7 +35,7 @@ func (o *Output) DialContext(ctx context.Context) error {
3335
return err
3436
}
3537

36-
o.conn = conn
38+
o.conn = conn.(*net.TCPConn)
3739
return nil
3840
}
3941

@@ -42,10 +44,29 @@ func (o *Output) Conn() net.Conn {
4244
}
4345

4446
func (o *Output) Close() error {
45-
if o.conn == nil {
46-
return nil
47+
if o.conn != nil {
48+
if err := o.conn.CloseWrite(); err != nil {
49+
return err
50+
}
51+
52+
// drain to facilitate graceful close on the other side
53+
deadline := time.Now().Add(5 * time.Second)
54+
if err := o.conn.SetReadDeadline(deadline); err != nil {
55+
return err
56+
}
57+
buffer := make([]byte, 1024)
58+
for {
59+
_, err := o.conn.Read(buffer)
60+
if errors.Is(err, io.EOF) {
61+
break
62+
} else if err != nil {
63+
return err
64+
}
65+
}
66+
67+
return o.conn.Close()
4768
}
48-
return o.conn.Close()
69+
return nil
4970
}
5071

5172
func (o *Output) Write(b []byte) (int, error) {

internal/output/tls/tls.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ package tcp
77
import (
88
"context"
99
"crypto/tls"
10+
"errors"
11+
"io"
1012
"net"
1113
"time"
1214

@@ -19,7 +21,7 @@ func init() {
1921

2022
type Output struct {
2123
opts *output.Options
22-
conn net.Conn
24+
conn *tls.Conn
2325
}
2426

2527
func New(opts *output.Options) (output.Output, error) {
@@ -39,12 +41,31 @@ func (o *Output) DialContext(ctx context.Context) error {
3941
return err
4042
}
4143

42-
o.conn = conn
44+
o.conn = conn.(*tls.Conn)
4345
return nil
4446
}
4547

4648
func (o *Output) Close() error {
4749
if o.conn != nil {
50+
if err := o.conn.CloseWrite(); err != nil {
51+
return err
52+
}
53+
54+
// drain to facilitate graceful close on the other side
55+
deadline := time.Now().Add(5 * time.Second)
56+
if err := o.conn.SetReadDeadline(deadline); err != nil {
57+
return err
58+
}
59+
buffer := make([]byte, 1024)
60+
for {
61+
_, err := o.conn.Read(buffer)
62+
if errors.Is(err, io.EOF) {
63+
break
64+
} else if err != nil {
65+
return err
66+
}
67+
}
68+
4869
return o.conn.Close()
4970
}
5071
return nil

0 commit comments

Comments
 (0)