Skip to content
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

Add CMux.Close() to shutdown server #69

Merged
merged 6 commits into from
Jan 14, 2021
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions cmux.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package cmux

import (
"errors"
"fmt"
"io"
"net"
Expand Down Expand Up @@ -61,6 +62,9 @@ func (e errListenerClosed) Timeout() bool { return false }
// listener is closed.
var ErrListenerClosed = errListenerClosed("mux: listener closed")

// ErrServerClosed is returned from muxListener.Accept when mux server is closed.
var ErrServerClosed = errors.New("mux: server closed")

// for readability of readTimeout
var noTimeout time.Duration

Expand Down Expand Up @@ -93,6 +97,8 @@ type CMux interface {
// Serve starts multiplexing the listener. Serve blocks and perhaps
// should be invoked concurrently within a go routine.
Serve() error
// Closes cmux server and stops accepting any connections on listener
Close()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try and use Close() error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error can inform user about multiple Close() calls. Are there other cases when error can be useful?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The principle interest is in satisfying the interface { Close() error } (i.e. the io.Closer interface) more than anything else.

For instance, net.Listener for instance implements io.Closer.

But it’s definitely not anything critical, just expected that Close() returns an error.

// HandleError registers an error handler that handles listener errors.
HandleError(ErrorHandler)
// sets a timeout for the read of matchers
Expand All @@ -108,9 +114,10 @@ type cMux struct {
root net.Listener
bufLen int
errh ErrorHandler
donec chan struct{}
sls []matchersListener
readTimeout time.Duration
donec chan struct{}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to put things that are protected by a mutex underneath the mutex.

mu sync.Mutex
}

func matchersToMatchWriters(matchers []Matcher) []MatchWriter {
Expand All @@ -133,6 +140,7 @@ func (m *cMux) MatchWithWriters(matchers ...MatchWriter) net.Listener {
ml := muxListener{
Listener: m.root,
connc: make(chan net.Conn, m.bufLen),
donec: make(chan struct{}),
}
m.sls = append(m.sls, matchersListener{ss: matchers, l: ml})
return ml
Expand All @@ -146,7 +154,7 @@ func (m *cMux) Serve() error {
var wg sync.WaitGroup

defer func() {
close(m.donec)
m.closeDoneChans()
wg.Wait()

for _, sl := range m.sls {
Expand Down Expand Up @@ -204,6 +212,30 @@ func (m *cMux) serve(c net.Conn, donec <-chan struct{}, wg *sync.WaitGroup) {
}
}

func (m *cMux) Close() {
m.closeDoneChans()
}

func (m *cMux) closeDoneChans() {
m.mu.Lock()
defer m.mu.Unlock()

select {
case <-m.donec:
// Already closed. Don't close again
default:
close(m.donec)
}
for _, sl := range m.sls {
select {
case <-sl.l.donec:
// Already closed. Don't close again
default:
close(sl.l.donec)
}
}
}

func (m *cMux) HandleError(h ErrorHandler) {
m.errh = h
}
Expand All @@ -223,14 +255,19 @@ func (m *cMux) handleErr(err error) bool {
type muxListener struct {
net.Listener
connc chan net.Conn
donec chan struct{}
}

func (l muxListener) Accept() (net.Conn, error) {
c, ok := <-l.connc
if !ok {
return nil, ErrListenerClosed
select {
case c, ok := <-l.connc:
if !ok {
return nil, ErrListenerClosed
}
return c, nil
case <-l.donec:
return nil, ErrServerClosed
}
return c, nil
}

// MuxConn wraps a net.Conn and provides transparent sniffing of connection data.
Expand Down