2
0
mirror of https://github.com/soheilhy/cmux.git synced 2026-08-31 15:54:31 +08:00

2 Commits

Author SHA1 Message Date
Soheil Hassas Yeganeh
711042c095 Use IPv4 for the listener to avoid v6 failures on Travis. 2017-12-04 12:32:43 -05:00
Soheil Hassas Yeganeh
ac00452023 Fix TestClose for Go10.
Depending on the Go version used, reading from a closed pipe can return
net.OpError or io.ErrClosedPipe. Simply check the string content of the
error.
2017-12-04 12:18:06 -05:00
13 changed files with 65 additions and 260 deletions

View File

@@ -1,50 +0,0 @@
name: CI
on:
push:
branches: [master]
pull_request:
permissions:
contents: read
jobs:
library:
name: library (go ${{ matrix.go }})
runs-on: ubuntu-latest
strategy:
matrix:
go: ["1.23", "1.24"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: gofmt
run: test -z "$(gofmt -s -l .)" || (gofmt -s -l . && exit 1)
- name: vet
run: go vet ./...
- name: build
run: go build ./...
- name: test
run: go test -race ./...
example:
name: example
runs-on: ubuntu-latest
defaults:
run:
working-directory: example
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: gofmt
run: test -z "$(gofmt -s -l .)" || (gofmt -s -l . && exit 1)
- name: vet
run: go vet ./...
- name: build
run: go build ./...
- name: test
run: go test -race ./...

29
.travis.yml Normal file
View File

@@ -0,0 +1,29 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- tip
matrix:
allow_failures:
- go: tip
gobuild_args: -race
before_install:
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go get -u github.com/kisielk/errcheck; fi
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go get -u github.com/golang/lint/golint; fi
before_script:
- '! gofmt -s -l . | read'
- echo $TRAVIS_GO_VERSION
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then golint ./...; fi
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then errcheck ./...; fi
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go tool vet .; fi
- if [[ $TRAVIS_GO_VERSION == 1.6* ]]; then go tool vet --shadow .; fi
script:
- go test -bench . -v ./...
- go test -race -bench . -v ./...

View File

@@ -25,7 +25,7 @@ trpcL := m.Match(cmux.Any()) // Any means anything that is not yet matched.
// Create your protocol servers.
grpcS := grpc.NewServer()
grpchello.RegisterGreeterServer(grpcS, &server{})
grpchello.RegisterGreeterServer(grpcs, &server{})
httpS := &http.Server{
Handler: &helloHTTP1Handler{},

52
cmux.go
View File

@@ -15,7 +15,6 @@
package cmux
import (
"errors"
"fmt"
"io"
"net"
@@ -62,9 +61,6 @@ 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
@@ -97,8 +93,6 @@ 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()
// HandleError registers an error handler that handles listener errors.
HandleError(ErrorHandler)
// sets a timeout for the read of matchers
@@ -114,18 +108,16 @@ type cMux struct {
root net.Listener
bufLen int
errh ErrorHandler
donec chan struct{}
sls []matchersListener
readTimeout time.Duration
donec chan struct{}
mu sync.Mutex
}
func matchersToMatchWriters(matchers []Matcher) []MatchWriter {
mws := make([]MatchWriter, 0, len(matchers))
for _, m := range matchers {
cm := m
mws = append(mws, func(w io.Writer, r io.Reader) bool {
return cm(r)
return m(r)
})
}
return mws
@@ -140,7 +132,6 @@ 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
@@ -154,7 +145,7 @@ func (m *cMux) Serve() error {
var wg sync.WaitGroup
defer func() {
m.closeDoneChans()
close(m.donec)
wg.Wait()
for _, sl := range m.sls {
@@ -212,30 +203,6 @@ 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
}
@@ -255,19 +222,14 @@ 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) {
select {
case c, ok := <-l.connc:
if !ok {
return nil, ErrListenerClosed
}
return c, nil
case <-l.donec:
return nil, ErrServerClosed
c, ok := <-l.connc
if !ok {
return nil, ErrListenerClosed
}
return c, nil
}
// MuxConn wraps a net.Conn and provides transparent sniffing of connection data.

View File

@@ -22,6 +22,7 @@ import (
"fmt"
"go/build"
"io"
"io/ioutil"
"log"
"net"
"net/http"
@@ -127,7 +128,7 @@ func runTestHTTPServer(errCh chan<- error, l net.Listener) {
mu.Unlock()
},
}
if err := s.Serve(l); err != ErrListenerClosed && err != ErrServerClosed {
if err := s.Serve(l); err != ErrListenerClosed {
errCh <- err
}
}
@@ -193,7 +194,7 @@ func runTestHTTPClient(t *testing.T, proto string, addr net.Addr) {
}
}()
b, err := io.ReadAll(r.Body)
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
@@ -217,7 +218,7 @@ func runTestRPCServer(errCh chan<- error, l net.Listener) {
for {
c, err := l.Accept()
if err != nil {
if err != ErrListenerClosed && err != ErrServerClosed {
if err != ErrListenerClosed {
errCh <- err
}
return
@@ -320,7 +321,7 @@ func TestTimeout(t *testing.T) {
func TestRead(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error, 1)
errCh := make(chan error)
defer func() {
select {
case err := <-errCh:
@@ -334,11 +335,10 @@ func TestRead(t *testing.T) {
writer, reader := net.Pipe()
go func() {
if _, err := io.WriteString(writer, strings.Repeat(payload, mult)); err != nil {
errCh <- err
return
t.Fatal(err)
}
if err := writer.Close(); err != nil {
errCh <- err
t.Fatal(err)
}
}()
@@ -379,7 +379,7 @@ func TestRead(t *testing.T) {
func TestAny(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error, 1)
errCh := make(chan error)
defer func() {
select {
case err := <-errCh:
@@ -403,7 +403,7 @@ func TestTLS(t *testing.T) {
generateTLSCert(t)
defer cleanupTLSCert(t)
defer leakCheck(t)()
errCh := make(chan error, 1)
errCh := make(chan error)
defer func() {
select {
case err := <-errCh:
@@ -428,7 +428,7 @@ func TestTLS(t *testing.T) {
func TestHTTP2(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error, 1)
errCh := make(chan error)
defer func() {
select {
case err := <-errCh:
@@ -439,11 +439,10 @@ func TestHTTP2(t *testing.T) {
writer, reader := net.Pipe()
go func() {
if _, err := io.WriteString(writer, http2.ClientPreface); err != nil {
errCh <- err
return
t.Fatal(err)
}
if err := writer.Close(); err != nil {
errCh <- err
t.Fatal(err)
}
}()
@@ -494,7 +493,7 @@ func testHTTP2MatchHeaderField(
notMatchValue string,
) {
defer leakCheck(t)()
errCh := make(chan error, 1)
errCh := make(chan error)
defer func() {
select {
case err := <-errCh:
@@ -506,14 +505,12 @@ func testHTTP2MatchHeaderField(
writer, reader := net.Pipe()
go func() {
if _, err := io.WriteString(writer, http2.ClientPreface); err != nil {
errCh <- err
return
t.Fatal(err)
}
var buf bytes.Buffer
enc := hpack.NewEncoder(&buf)
if err := enc.WriteField(hpack.HeaderField{Name: name, Value: headerValue}); err != nil {
errCh <- err
return
t.Fatal(err)
}
framer := http2.NewFramer(writer, nil)
err := framer.WriteHeaders(http2.HeadersFrameParam{
@@ -523,11 +520,10 @@ func testHTTP2MatchHeaderField(
EndHeaders: true,
})
if err != nil {
errCh <- err
return
t.Fatal(err)
}
if err := writer.Close(); err != nil {
errCh <- err
t.Fatal(err)
}
}()
@@ -562,7 +558,7 @@ func testHTTP2MatchHeaderField(
func TestHTTPGoRPC(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error, 1)
errCh := make(chan error)
defer func() {
select {
case err := <-errCh:
@@ -587,7 +583,7 @@ func TestHTTPGoRPC(t *testing.T) {
func TestErrorHandler(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error, 1)
errCh := make(chan error)
defer func() {
select {
case err := <-errCh:
@@ -626,38 +622,9 @@ func TestErrorHandler(t *testing.T) {
}
}
func TestMultipleMatchers(t *testing.T) {
func TestClose(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error, 1)
defer func() {
select {
case err := <-errCh:
t.Fatal(err)
default:
}
}()
l, cleanup := testListener(t)
defer cleanup()
matcher := func(r io.Reader) bool {
return true
}
unmatcher := func(r io.Reader) bool {
return false
}
muxl := New(l)
lis := muxl.Match(unmatcher, matcher, unmatcher)
go runTestHTTPServer(errCh, lis)
go safeServe(errCh, muxl)
runTestHTTP1Client(t, l.Addr())
}
func TestListenerClose(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error, 1)
errCh := make(chan error)
defer func() {
select {
case err := <-errCh:
@@ -689,7 +656,7 @@ func TestListenerClose(t *testing.T) {
// Second connection either goes through or it is closed.
if _, err := anyl.Accept(); err != nil {
if err != ErrListenerClosed && err != ErrServerClosed {
if err != ErrListenerClosed {
t.Fatal(err)
}
// The error is either io.ErrClosedPipe or net.OpError wrapping
@@ -700,31 +667,6 @@ func TestListenerClose(t *testing.T) {
}
}
func TestClose(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error, 1)
defer func() {
select {
case err := <-errCh:
t.Fatal(err)
default:
}
}()
l, cleanup := testListener(t)
defer cleanup()
muxl := New(l)
anyl := muxl.Match(Any())
go safeServe(errCh, muxl)
muxl.Close()
if _, err := anyl.Accept(); err != ErrServerClosed {
t.Fatal(err)
}
}
// Cribbed from google.golang.org/grpc/test/end2end_test.go.
// interestingGoroutines returns all goroutines we care about for the purpose

View File

@@ -1,19 +0,0 @@
module github.com/soheilhy/cmux/example
go 1.25.0
require (
github.com/soheilhy/cmux v0.0.0-00010101000000-000000000000
golang.org/x/net v0.53.0
google.golang.org/grpc v1.80.0
google.golang.org/grpc/examples v0.0.0-20260605180800-0f3086db7a75
)
require (
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.36.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
replace github.com/soheilhy/cmux => ../

View File

@@ -1,40 +0,0 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/grpc/examples v0.0.0-20260605180800-0f3086db7a75 h1:AEIy9rWQCaqGLD5thytE2ox41wjT4vOe43/yh2enp+A=
google.golang.org/grpc/examples v0.0.0-20260605180800-0f3086db7a75/go.mod h1:Cj6Qdy58KTzw400M0aa1PA3rNLrhQgPFdqXxShtOXmM=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=

View File

@@ -15,7 +15,6 @@
package cmux_test
import (
"context"
"fmt"
"io"
"log"
@@ -26,10 +25,10 @@ import (
"google.golang.org/grpc"
"golang.org/x/net/context"
"golang.org/x/net/websocket"
"github.com/soheilhy/cmux"
"google.golang.org/grpc/examples/helloworld/helloworld"
grpchello "google.golang.org/grpc/examples/helloworld/helloworld"
)
@@ -87,9 +86,7 @@ func serveRPC(l net.Listener) {
}
}
type grpcServer struct {
helloworld.UnimplementedGreeterServer
}
type grpcServer struct{}
func (s *grpcServer) SayHello(ctx context.Context, in *grpchello.HelloRequest) (
*grpchello.HelloReply, error) {

7
go.mod
View File

@@ -1,7 +0,0 @@
module github.com/soheilhy/cmux
go 1.23.0
require golang.org/x/net v0.42.0
require golang.org/x/text v0.27.0 // indirect

4
go.sum
View File

@@ -1,4 +0,0 @@
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=

View File

@@ -18,6 +18,7 @@ import (
"bufio"
"crypto/tls"
"io"
"io/ioutil"
"net/http"
"strings"
@@ -66,8 +67,7 @@ func HTTP1Fast(extMethods ...string) Matcher {
//
// By default, any TLS handshake packet is matched. An optional whitelist
// of versions can be passed in to restrict the matcher, for example:
//
// TLS(tls.VersionTLS11, tls.VersionTLS12)
// TLS(tls.VersionTLS11, tls.VersionTLS12)
func TLS(versions ...int) Matcher {
if len(versions) == 0 {
versions = []int{
@@ -148,7 +148,7 @@ func HTTP1HeaderFieldPrefix(name, valuePrefix string) Matcher {
// headers frame.
func HTTP2HeaderField(name, value string) Matcher {
return func(r io.Reader) bool {
return matchHTTP2Field(io.Discard, r, name, func(gotValue string) bool {
return matchHTTP2Field(ioutil.Discard, r, name, func(gotValue string) bool {
return gotValue == value
})
}
@@ -159,7 +159,7 @@ func HTTP2HeaderField(name, value string) Matcher {
// valuePrefix, this will match.
func HTTP2HeaderFieldPrefix(name, valuePrefix string) Matcher {
return func(r io.Reader) bool {
return matchHTTP2Field(io.Discard, r, name, func(gotValue string) bool {
return matchHTTP2Field(ioutil.Discard, r, name, func(gotValue string) bool {
return strings.HasPrefix(gotValue, valuePrefix)
})
}
@@ -240,11 +240,6 @@ func matchHTTP2Field(w io.Writer, r io.Reader, name string, matches func(string)
switch f := f.(type) {
case *http2.SettingsFrame:
// Sender acknoweldged the SETTINGS frame. No need to write
// SETTINGS again.
if f.IsAck() {
break
}
if err := framer.WriteSettings(); err != nil {
return false
}