-
Notifications
You must be signed in to change notification settings - Fork 205
Ensure Close()
waits for goroutines when called concurrently
#331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,6 @@ import ( | |
"fmt" | ||
"net" | ||
"sync" | ||
"sync/atomic" | ||
) | ||
|
||
// connErrPair pairs conn and error which is returned by accept on sub-listeners. | ||
|
@@ -38,8 +37,8 @@ type multiListener struct { | |
// connCh passes accepted connections, from child listeners to parent. | ||
connCh chan connErrPair | ||
// stopCh communicates from parent to child listeners. | ||
stopCh chan struct{} | ||
closed atomic.Bool | ||
stopCh chan struct{} | ||
closeOnce sync.Once | ||
} | ||
|
||
// compile time check to ensure *multiListener implements net.Listener | ||
|
@@ -152,29 +151,27 @@ func (ml *multiListener) Accept() (net.Conn, error) { | |
// the go-routines to exit. | ||
func (ml *multiListener) Close() error { | ||
// Make sure this can be called repeatedly without explosions. | ||
if !ml.closed.CompareAndSwap(false, true) { | ||
return fmt.Errorf("use of closed network connection") | ||
} | ||
|
||
// Tell all sub-listeners to stop. | ||
close(ml.stopCh) | ||
|
||
// Closing the listeners causes Accept() to immediately return an error in | ||
// the sub-listener go-routines. | ||
for _, l := range ml.listeners { | ||
_ = l.Close() | ||
} | ||
ml.closeOnce.Do(func() { | ||
// Tell all sub-listeners to stop. | ||
close(ml.stopCh) | ||
|
||
// Closing the listeners causes Accept() to immediately return an error in | ||
// the sub-listener go-routines. | ||
for _, l := range ml.listeners { | ||
_ = l.Close() | ||
} | ||
|
||
// Wait for all the sub-listener go-routines to exit. | ||
ml.wg.Wait() | ||
close(ml.connCh) | ||
// Wait for all the sub-listener go-routines to exit. | ||
ml.wg.Wait() | ||
close(ml.connCh) | ||
|
||
// Drain any already-queued connections. | ||
for connErr := range ml.connCh { | ||
if connErr.conn != nil { | ||
_ = connErr.conn.Close() | ||
// Drain any already-queued connections. | ||
for connErr := range ml.connCh { | ||
if connErr.conn != nil { | ||
_ = connErr.conn.Close() | ||
} | ||
} | ||
} | ||
}) | ||
return nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it doesn't make sense to return an error for double close. Either we return the actual underlying errors or no error at all. Any reason to create "artificial" errors? I.e. nothing bad happened because of the second call, why return an error? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An alternative implementation here would still synchronize the shutdown of goroutines, but call |
||
} | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why this loop is necessary. Channel is not buffered and we've told the publishing goroutines to stop just above via
close(ml.stopCh)
- they will each close the connection they are trying to push, if any.