Skip to content

Commit 8d40763

Browse files
committed
recover from double close
1 parent 966689c commit 8d40763

2 files changed

Lines changed: 77 additions & 18 deletions

File tree

multilistener/multilistener.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
Package multilistener -- listen to all loopback interfaces, or a mixed slice of
33
ipv4 & ipv6 interfaces
44
5-
go std library (net.Listen("tcp", ":8080")) can listen to ALL interfaces
6-
but cannot listen to all local interfaces by default.
5+
go std library (net.Listen("tcp", ":8080")) can listen to ALL interfaces
6+
but cannot listen to all local interfaces by default.
77
88
use multilistener.ListenLocalLoopback to return a single Listener for all ipv4 &
99
ipv6 loopback interfaces
@@ -19,7 +19,7 @@ import (
1919
"sync"
2020
)
2121

22-
// MultiListener implements net.Listener interface
22+
// MultiListener implements net.Listener interface
2323
//
2424
// multiplexes multiple net.Listeners concurrently looping over Accept()
2525
type MultiListener struct {
@@ -64,7 +64,7 @@ func NewLocalLoopback(port string) (*MultiListener, error) {
6464
}
6565

6666
// NewMultiListenerRaw returns a MultiListener wrapper of multiple listeners
67-
//
67+
//
6868
// useful when raw net.Addr or net.Listener is needed
6969
func NewMultiListenerRaw(listeners []net.Listener) (*MultiListener, error) {
7070
dl := &MultiListener{
@@ -79,7 +79,7 @@ func NewMultiListenerRaw(listeners []net.Listener) (*MultiListener, error) {
7979
return dl, nil
8080
}
8181

82-
// NewMultiListener returns multilistener over slice of []string
82+
// NewMultiListener returns multilistener over slice of []string
8383
//
8484
// see net.Dial and net.Listen for the string format of the address
8585
// e.g. "[::1]:8080" for ipv6 and "127.0.0.1:8080" for ipv4
@@ -138,8 +138,13 @@ func (dl *MultiListener) Accept() (net.Conn, error) {
138138

139139
// Close closes all internal channels
140140
//
141-
// do not defer Close() if passing to http.Server
142-
func (dl *MultiListener) Close() error {
141+
// do not defer Close() if passing to http.Server
142+
func (dl *MultiListener) Close() (err error) {
143+
defer func() {
144+
if r := recover(); r != nil {
145+
err = fmt.Errorf("MultiListener.Close() panicked on second call: %v", r)
146+
}
147+
}()
143148
close(dl.closeCh)
144149
// todo clean up ugly
145150
var firstErr error

multilistener/multilistener_test.go

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
// AI claimed IPV6 ::1 C sockets also accepted ipv4. This test confirms go sockets do not
1515
// see also cmd/simple-listener and test with telnet
1616
// I feel like I'm taking crazy pills.
17-
func TestListenOnlyIPV6(t *testing.T){
17+
func TestListenOnlyIPV6(t *testing.T) {
1818
ready := make(chan struct{})
1919
port := "8084"
2020
ml, err := net.Listen("tcp", "[::1]:"+port) // Listen only on IPv6 loopback
@@ -35,7 +35,7 @@ func TestListenOnlyIPV6(t *testing.T){
3535
// server is ready
3636
<-ready
3737
// Attempt to connect to the IPv4 loopback address
38-
testAddr := "127.0.0.1:"+port
38+
testAddr := "127.0.0.1:" + port
3939
t.Logf("Attempting to connect to %s (IPv4) to an IPv6-only listener", testAddr)
4040
resp, err := http.Get("http://" + testAddr)
4141
if err != nil {
@@ -48,21 +48,20 @@ func TestListenOnlyIPV6(t *testing.T){
4848
t.Fatalf("Unexpected success connecting to IPv4 address %s on an IPv6-only listener. Status: %d", testAddr, resp.StatusCode)
4949
}
5050

51-
5251
func TestMultiListener(t *testing.T) {
5352
ready := make(chan struct{})
54-
ml, err := NewLocalLoopback("8081") // Use port 0 to get a random available port
53+
ml, err := NewLocalLoopback("0") // Use port 0 to get a random available port
5554
if err != nil {
5655
t.Fatalf("Failed to create MultiListener: %v", err)
5756
}
5857
// Start an HTTP server on the MultiListener
5958
go func() {
6059
close(ready)
6160
if err := http.Serve(ml, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
62-
if _, err := io.WriteString(w, "Hello from MultiListener!"); err != nil{
61+
if _, err := io.WriteString(w, "Hello from MultiListener!"); err != nil {
6362
t.Errorf("error writeString")
6463
}
65-
})); err != nil{
64+
})); err != nil {
6665
t.Errorf("http serve error")
6766
}
6867
}()
@@ -102,17 +101,72 @@ func TestMultiListener(t *testing.T) {
102101
}
103102
}
104103

105-
106104
// NewLocalLoopback when you want to listen to ipv6 & ipv4 loopback with one listener
107105
func ExampleNewLocalLoopback() {
108-
dual, err := NewLocalLoopback("8080")
106+
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
107+
fmt.Fprint(w, "Hello World!\n")
108+
})
109+
110+
dual, err := NewLocalLoopback("8129")
109111
if err != nil {
110112
panic(err)
111113
}
112114
fmt.Printf("Serving HTTP %+v\n", dual.AllAddr())
113115
fmt.Printf("Preferred Addr: %+v\n", dual.Addr())
114116
go http.Serve(dual, nil) //nolint:errcheck
115117
// Output:
116-
// Serving HTTP [::1]:8080,127.0.0.1:8080
117-
// Preferred Addr: [::1]:8080
118-
}
118+
// Serving HTTP [::1]:8129,127.0.0.1:8129
119+
// Preferred Addr: [::1]:8129
120+
}
121+
122+
func TestMultiListenerDoubleClose(t *testing.T) {
123+
ml, err := NewLocalLoopback("0")
124+
if err != nil {
125+
t.Fatalf("Failed to create MultiListener: %v", err)
126+
}
127+
128+
// First close should be successful
129+
err = ml.Close()
130+
if err != nil {
131+
t.Errorf("First close failed unexpectedly: %v", err)
132+
}
133+
134+
// Second close should ideally not panic and return an error or be a no-op
135+
// We use a defer func to recover from a potential panic
136+
defer func() {
137+
if r := recover(); r != nil {
138+
t.Errorf("MultiListener.Close() panicked on second call: %v", r)
139+
}
140+
}()
141+
142+
err = ml.Close()
143+
if err == nil {
144+
t.Errorf("Second close succeeded unexpectedly, should return an error or be a no-op")
145+
}
146+
t.Logf("Second close returned: %v (expected error or no-op)", err)
147+
}
148+
149+
func TestMultiListenerCloseBeforeServe(t *testing.T) {
150+
//t.Skip()
151+
ml, err := NewLocalLoopback("0")
152+
if err != nil {
153+
t.Fatalf("Failed to create MultiListener: %v", err)
154+
}
155+
156+
// Close the listener immediately after creation, before serving
157+
err = ml.Close()
158+
if err != nil {
159+
t.Fatalf("Failed to close MultiListener before serving: %v", err)
160+
}
161+
162+
// Attempt to serve on a closed listener, should return an error
163+
serveErr := http.Serve(ml, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
164+
t.Errorf("Handler should not be called on a closed listener")
165+
}))
166+
167+
if serveErr == nil {
168+
t.Errorf("http.Serve on a closed listener did not return an error")
169+
} else {
170+
t.Logf("http.Serve on a closed listener returned expected error: %v", serveErr)
171+
}
172+
}

0 commit comments

Comments
 (0)