Skip to content

Commit 76cabbd

Browse files
committed
recover from double close
1 parent 966689c commit 76cabbd

2 files changed

Lines changed: 69 additions & 14 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: 57 additions & 7 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,7 +48,6 @@ 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{})
5453
ml, err := NewLocalLoopback("8081") // Use port 0 to get a random available port
@@ -59,10 +58,10 @@ func TestMultiListener(t *testing.T) {
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,7 +101,6 @@ 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() {
108106
dual, err := NewLocalLoopback("8080")
@@ -115,4 +113,56 @@ func ExampleNewLocalLoopback() {
115113
// Output:
116114
// Serving HTTP [::1]:8080,127.0.0.1:8080
117115
// Preferred Addr: [::1]:8080
118-
}
116+
}
117+
118+
func TestMultiListenerDoubleClose(t *testing.T) {
119+
ml, err := NewLocalLoopback("8082")
120+
if err != nil {
121+
t.Fatalf("Failed to create MultiListener: %v", err)
122+
}
123+
124+
// First close should be successful
125+
err = ml.Close()
126+
if err != nil {
127+
t.Errorf("First close failed unexpectedly: %v", err)
128+
}
129+
130+
// Second close should ideally not panic and return an error or be a no-op
131+
// We use a defer func to recover from a potential panic
132+
defer func() {
133+
if r := recover(); r != nil {
134+
t.Errorf("MultiListener.Close() panicked on second call: %v", r)
135+
}
136+
}()
137+
138+
err = ml.Close()
139+
if err == nil {
140+
t.Errorf("Second close succeeded unexpectedly, should return an error or be a no-op")
141+
}
142+
t.Logf("Second close returned: %v (expected error or no-op)", err)
143+
}
144+
145+
func TestMultiListenerCloseBeforeServe(t *testing.T) {
146+
t.Skip()
147+
ml, err := NewLocalLoopback("8083")
148+
if err != nil {
149+
t.Fatalf("Failed to create MultiListener: %v", err)
150+
}
151+
152+
// Close the listener immediately after creation, before serving
153+
err = ml.Close()
154+
if err != nil {
155+
t.Fatalf("Failed to close MultiListener before serving: %v", err)
156+
}
157+
158+
// Attempt to serve on a closed listener, should return an error
159+
serveErr := http.Serve(ml, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
160+
t.Errorf("Handler should not be called on a closed listener")
161+
}))
162+
163+
if serveErr == nil {
164+
t.Errorf("http.Serve on a closed listener did not return an error")
165+
} else {
166+
t.Logf("http.Serve on a closed listener returned expected error: %v", serveErr)
167+
}
168+
}

0 commit comments

Comments
 (0)