Commit 8acc21e8 authored by Jeromy's avatar Jeromy
Browse files

Vendor in go-peerstream

parent a9de494f
{
"ImportPath": "github.com/jbenet/go-peerstream",
"GoVersion": "go1.4.2",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/docker/spdystream",
"Rev": "b2c3287865f3ad6aa22821ddb7b4692b896ac207"
},
{
"ImportPath": "github.com/hashicorp/yamux",
"Rev": "b2e55852ddaf823a85c67f798080eb7d08acd71d"
},
{
"ImportPath": "github.com/inconshreveable/muxado",
"Rev": "f693c7e88ba316d1a0ae3e205e22a01aa3ec2848"
},
{
"ImportPath": "github.com/jbenet/go-stream-muxer",
"Rev": "e2e261765847234749629e0190fef193a4548303"
},
{
"ImportPath": "github.com/jbenet/go-temp-err-catcher",
"Rev": "aac704a3f4f27190b4ccc05f303a4931fd1241ff"
},
{
"ImportPath": "github.com/whyrusleeping/go-multiplex",
"Rev": "474b9aebeb391746f304ddf7c764a5da12319857"
},
{
"ImportPath": "github.com/whyrusleeping/go-multistream",
"Rev": "08e8f9c9f5665ed0c63ffde4fa5ef1d5fb3d516d"
}
]
}
The MIT License (MIT)
Copyright (c) 2014 Juan Batiz-Benet
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
godep:
go get github.com/tools/godep
vendor: godep
godep save -r ./...
build:
go build ./...
test:
go test ./...
test_race:
go test -race -cpu 5 ./...
# go-peerstream p2p multi-multixplexing
Package peerstream is a peer-to-peer networking library that multiplexes
connections to many hosts. It tried to simplify the complexity of:
* accepting incoming connections over **multiple** listeners
* dialing outgoing connections over **multiple** transports
* multiplexing **multiple** connections per-peer
* multiplexing **multiple** different servers or protocols
* handling backpressure correctly
* handling stream multiplexing (we use SPDY, but maybe QUIC some day)
* providing a **simple** interface to the user
### Godoc: https://godoc.org/github.com/jbenet/go-peerstream
---
See this working [example/example.go](example/example):
```Go
package main
import (
"fmt"
"io"
"net"
"os"
ps "github.com/jbenet/go-peerstream"
)
func main() {
// create a new Swarm
swarm := ps.NewSwarm()
defer swarm.Close()
// tell swarm what to do with a new incoming streams.
// EchoHandler just echos back anything they write.
swarm.SetStreamHandler(ps.EchoHandler)
// Okay, let's try listening on some transports
l1, err := net.Listen("tcp", "localhost:8001")
if err != nil {
panic(err)
}
l2, err := net.Listen("tcp", "localhost:8002")
if err != nil {
panic(err)
}
// tell swarm to accept incoming connections on these
// listeners. Swarm will start accepting new connections.
if err := swarm.AddListener(l1); err != nil {
panic(err)
}
if err := swarm.AddListener(l2); err != nil {
panic(err)
}
// ok, let's try some outgoing connections
nc1, err := net.Dial("tcp", "localhost:8001")
if err != nil {
panic(err)
}
nc2, err := net.Dial("tcp", "localhost:8002")
if err != nil {
panic(err)
}
// add them to the swarm
c1, err := swarm.AddConn(nc1)
if err != nil {
panic(err)
}
c2, err := swarm.AddConn(nc2)
if err != nil {
panic(err)
}
// Swarm treats listeners as sources of new connections and does
// not distinguish between outgoing or incoming connections.
// It provides the net.Conn to the StreamHandler so you can
// distinguish between them however you wish.
// now let's try opening some streams!
// You can specify what connection you want to use
s1, err := swarm.NewStreamWithConn(c1)
if err != nil {
panic(err)
}
// Or, you can specify a SelectConn function that picks between all
// (it calls NewStreamWithConn underneath the hood)
s2, err := swarm.NewStreamSelectConn(func(conns []*ps.Conn) *ps.Conn {
if len(conns) > 0 {
return conns[0]
}
return nil
})
if err != nil {
panic(err)
}
// Or, you can bind connections to ConnGroup ids. You can bind a conn to
// multiple groups. And, if conn wasn't in swarm, it calls swarm.AddConn.
// You can use any Go `KeyType` as a group A `KeyType` as in maps...)
swarm.AddConnToGroup(c2, 1)
// And then use that group to select a connection. Swarm will use any
// connection it finds in that group, using a SelectConn you can rebind:
// swarm.SetGroupSelectConn(1, SelectConn)
// swarm.SetDegaultGroupSelectConn(SelectConn)
s3, err := swarm.NewStreamWithGroup(1)
if err != nil {
panic(err)
}
// Why groups? It's because with many connections, and many transports,
// and many Servers (or Protocols), we can use the Swarm to associate
// a different StreamHandlers per group, and to let us create NewStreams
// on a given group.
// Ok, we have streams. now what. Use them! Our Streams are basically
// streams from github.com/docker/spdystream, so they work the same
// way:
for i, stream := range []ps.Stream{s1, s2, s3} {
stream.Wait()
str := "stream %d ready:"
fmt.Fprintf(stream, str, i)
buf := make([]byte, len(str))
stream.Read(buf)
fmt.Println(string(buf))
}
go io.Copy(os.Stdout, s1)
go io.Copy(os.Stdout, s2)
go io.Copy(os.Stdout, s3)
io.Copy(io.MultiWriter(s1, s2, s3), os.Stdin)
}
func log(s string) {
fmt.Fprintf(os.Stderr, s+"\n")
}
```
package peerstream
import (
"errors"
"fmt"
"net"
"sync"
smux "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer"
)
// ConnHandler is a function which receives a Conn. It allows
// clients to set a function to receive newly accepted
// connections. It works like StreamHandler, but is usually
// less useful than usual as most services will only use
// Streams. It is safe to pass or store the *Conn elsewhere.
// Note: the ConnHandler is called sequentially, so spawn
// goroutines or pass the Conn. See EchoHandler.
type ConnHandler func(s *Conn)
// SelectConn selects a connection out of list. It allows
// delegation of decision making to clients. Clients can
// make SelectConn functons that check things connection
// qualities -- like latency andbandwidth -- or pick from
// a logical set of connections.
type SelectConn func([]*Conn) *Conn
// ErrInvalidConnSelected signals that a connection selected
// with a SelectConn function is invalid. This may be due to
// the Conn not being part of the original set given to the
// function, or the value being nil.
var ErrInvalidConnSelected = errors.New("invalid selected connection")
// ErrNoConnections signals that no connections are available
var ErrNoConnections = errors.New("no connections")
// Conn is a Swarm-associated connection.
type Conn struct {
smuxConn smux.Conn
netConn net.Conn // underlying connection
swarm *Swarm
groups groupSet
streams map[*Stream]struct{}
streamLock sync.RWMutex
closed bool
closeLock sync.Mutex
}
func newConn(nconn net.Conn, tconn smux.Conn, s *Swarm) *Conn {
return &Conn{
netConn: nconn,
smuxConn: tconn,
swarm: s,
groups: groupSet{m: make(map[Group]struct{})},
streams: make(map[*Stream]struct{}),
}
}
// String returns a string representation of the Conn
func (c *Conn) String() string {
c.streamLock.RLock()
ls := len(c.streams)
c.streamLock.RUnlock()
f := "<peerstream.Conn %d streams %s <--> %s>"
return fmt.Sprintf(f, ls, c.netConn.LocalAddr(), c.netConn.RemoteAddr())
}
// Swarm returns the Swarm associated with this Conn
func (c *Conn) Swarm() *Swarm {
return c.swarm
}
// NetConn returns the underlying net.Conn
func (c *Conn) NetConn() net.Conn {
return c.netConn
}
// Conn returns the underlying transport Connection we use
// Warning: modifying this object is undefined.
func (c *Conn) Conn() smux.Conn {
return c.smuxConn
}
// Groups returns the Groups this Conn belongs to
func (c *Conn) Groups() []Group {
return c.groups.Groups()
}
// InGroup returns whether this Conn belongs to a Group
func (c *Conn) InGroup(g Group) bool {
return c.groups.Has(g)
}
// AddGroup assigns given Group to Conn
func (c *Conn) AddGroup(g Group) {
c.groups.Add(g)
}
// Stream returns a stream associated with this Conn
func (c *Conn) NewStream() (*Stream, error) {
return c.swarm.NewStreamWithConn(c)
}
func (c *Conn) Streams() []*Stream {
c.streamLock.RLock()
defer c.streamLock.RUnlock()
streams := make([]*Stream, 0, len(c.streams))
for s := range c.streams {
streams = append(streams, s)
}
return streams
}
// Close closes this connection
func (c *Conn) Close() error {
c.closeLock.Lock()
defer c.closeLock.Unlock()
if c.closed {
return nil
}
c.closed = true
// close streams
streams := c.Streams()
for _, s := range streams {
s.Close()
}
// close underlying connection
c.swarm.removeConn(c)
err := c.smuxConn.Close()
c.swarm.notifyAll(func(n Notifiee) {
n.Disconnected(c)
})
return err
}
// ConnsWithGroup narrows down a set of connections to those in a given group.
func ConnsWithGroup(g Group, conns []*Conn) []*Conn {
var out []*Conn
for _, c := range conns {
if c.InGroup(g) {
out = append(out, c)
}
}
return out
}
func ConnInConns(c1 *Conn, conns []*Conn) bool {
for _, c2 := range conns {
if c2 == c1 {
return true
}
}
return false
}
// ------------------------------------------------------------------
// All the connection setup logic here, in one place.
// these are mostly *Swarm methods, but i wanted a less-crowded place
// for them.
// ------------------------------------------------------------------
// addConn is the internal version of AddConn. we need the server bool
// as spdystream requires it.
func (s *Swarm) addConn(netConn net.Conn, isServer bool) (*Conn, error) {
c, err := s.setupConn(netConn, isServer)
if err != nil {
return nil, err
}
s.ConnHandler()(c)
// go listen for incoming streams on this connection
go c.smuxConn.Serve(func(ss smux.Stream) {
// log.Printf("accepted stream %d from %s\n", ssS.Identifier(), netConn.RemoteAddr())
stream := s.setupStream(ss, c)
s.StreamHandler()(stream) // call our handler
})
s.notifyAll(func(n Notifiee) {
n.Connected(c)
})
return c, nil
}
// setupConn adds the relevant connection to the map, first checking if it
// was already there.
func (s *Swarm) setupConn(netConn net.Conn, isServer bool) (*Conn, error) {
if netConn == nil {
return nil, errors.New("nil conn")
}
// first, check if we already have it, to avoid constructing it
// if it is already there
s.connLock.Lock()
for c := range s.conns {
if c.netConn == netConn {
s.connLock.Unlock()
return c, nil
}
}
s.connLock.Unlock()
// construct the connection without hanging onto the lock
// (as there could be deadlock if so.)
// create a new spdystream connection
ssConn, err := s.transport.NewConn(netConn, isServer)
if err != nil {
return nil, err
}
// take the lock to add it to the map.
s.connLock.Lock()
defer s.connLock.Unlock()
// check for it again as it may have been added already. (TOCTTOU)
for c := range s.conns {
if c.netConn == netConn {
return c, nil
}
}
// add the connection
c := newConn(netConn, ssConn, s)
s.conns[c] = struct{}{}
return c, nil
}
// createStream is the internal function that creates a new stream. assumes
// all validation has happened.
func (s *Swarm) createStream(c *Conn) (*Stream, error) {
// Create a new smux.Stream
smuxStream, err := c.smuxConn.OpenStream()
if err != nil {
return nil, err
}
return s.setupStream(smuxStream, c), nil
}
// newStream is the internal function that creates a new stream. assumes
// all validation has happened.
func (s *Swarm) setupStream(smuxStream smux.Stream, c *Conn) *Stream {
// create a new stream
stream := newStream(smuxStream, c)
// add it to our streams maps
s.streamLock.Lock()
c.streamLock.Lock()
s.streams[stream] = struct{}{}
c.streams[stream] = struct{}{}
s.streamLock.Unlock()
c.streamLock.Unlock()
s.notifyAll(func(n Notifiee) {
n.OpenedStream(stream)
})
return stream
}
func (s *Swarm) removeStream(stream *Stream) error {
// remove from our maps
s.streamLock.Lock()
stream.conn.streamLock.Lock()
delete(s.streams, stream)
delete(stream.conn.streams, stream)
s.streamLock.Unlock()
stream.conn.streamLock.Unlock()
err := stream.smuxStream.Close()
s.notifyAll(func(n Notifiee) {
n.ClosedStream(stream)
})
return err
}
func (s *Swarm) removeConn(conn *Conn) {
// remove from our maps
s.connLock.Lock()
delete(s.conns, conn)
s.connLock.Unlock()
}
// Package peerstream is a peer-to-peer networking library that multiplexes
// connections to many hosts. It tried to simplify the complexity of:
//
// * accepting incoming connections over **multiple** listeners
// * dialing outgoing connections over **multiple** transports
// * multiplexing **multiple** connections per-peer
// * multiplexing **multiple** different servers or protocols
// * handling backpressure correctly
// * handling stream multiplexing (we use SPDY, but maybe QUIC some day)
// * providing a **simple** interface to the user
//
package peerstream
package main
import (
"bufio"
"fmt"
"net"
"os"
"time"
spdy "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/spdystream"
ps "QmTgxFwS1nDK126fH5XPnLFcxcDFsxKbPPnCBwyRWNAjDX/go-peerstream"
)
func die(err error) {
fmt.Fprintf(os.Stderr, "error: %s\n")
os.Exit(1)
}
func main() {
// create a new Swarm
swarm := ps.NewSwarm(spdy.Transport)
defer swarm.Close()
// tell swarm what to do with a new incoming streams.
// EchoHandler just echos back anything they write.
swarm.SetStreamHandler(ps.EchoHandler)
l, err := net.Listen("tcp", "localhost:8001")
if err != nil {
die(err)
}
if _, err := swarm.AddListener(l); err != nil {
die(err)
}
nc, err := net.Dial("tcp", "localhost:8001")
if err != nil {
die(err)
}
c, err := swarm.AddConn(nc)
if err != nil {
die(err)
}
nRcvStream := 0
bio := bufio.NewReader(os.Stdin)
swarm.SetStreamHandler(func(s *ps.Stream) {
log("handling new stream %d", nRcvStream)
nRcvStream++
line, err := bio.ReadString('\n')
if err != nil {
die(err)
}
_ = line
// line = "read: " + line
// s.Write([]byte(line))
s.Close()
})
nSndStream := 0
for {
<-time.After(200 * time.Millisecond)
_, err := swarm.NewStreamWithConn(c)
if err != nil {
die(err)
}
log("sender got new stream %d", nSndStream)
nSndStream++
}
}
func log(s string, ifs ...interface{}) {
fmt.Fprintf(os.Stderr, s+"\n", ifs...)
}
package main
import (
"fmt"
"io"
"net"
"os"
spdy "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/spdystream"
ps "QmTgxFwS1nDK126fH5XPnLFcxcDFsxKbPPnCBwyRWNAjDX/go-peerstream"
)
func main() {
log("creating a new swarm with spdystream transport") // create a new Swarm
swarm := ps.NewSwarm(spdy.Transport)
defer swarm.Close()
// tell swarm what to do with a new incoming streams.
// EchoHandler just echos back anything they write.
log("setup EchoHandler")
swarm.SetStreamHandler(ps.EchoHandler)
// Okay, let's try listening on some transports
log("listening at localhost:8001")
l1, err := net.Listen("tcp", "localhost:8001")
if err != nil {
panic(err)
}
log("listening at localhost:8002")
l2, err := net.Listen("tcp", "localhost:8002")
if err != nil {
panic(err)
}
// tell swarm to accept incoming connections on these
// listeners. Swarm will start accepting new connections.
if _, err := swarm.AddListener(l1); err != nil {
panic(err)
}
if _, err := swarm.AddListener(l2); err != nil {
panic(err)
}
// ok, let's try some outgoing connections
log("dialing localhost:8001")
nc1, err := net.Dial("tcp", "localhost:8001")
if err != nil {
panic(err)
}
log("dialing localhost:8002")
nc2, err := net.Dial("tcp", "localhost:8002")
if err != nil {
panic(err)
}
// add them to the swarm
c1, err := swarm.AddConn(nc1)
if err != nil {
panic(err)
}
c2, err := swarm.AddConn(nc2)
if err != nil {
panic(err)
}
// Swarm treats listeners as sources of new connections and does
// not distinguish between outgoing or incoming connections.
// It provides the net.Conn to the StreamHandler so you can
// distinguish between them however you wish.
// now let's try opening some streams!
// You can specify what connection you want to use
log("opening stream with NewStreamWithConn(c1)")
s1, err := swarm.NewStreamWithConn(c1)
if err != nil {
panic(err)
}
// Or, you can specify a SelectConn function that picks between all
// (it calls NewStreamWithConn underneath the hood)
log("opening stream with NewStreamSelectConn(.)")
s2, err := swarm.NewStreamSelectConn(func(conns []*ps.Conn) *ps.Conn {
if len(conns) > 0 {
return conns[0]
}
return nil
})
if err != nil {
panic(err)
}
// Or, you can bind connections to ConnGroup ids. You can bind a conn to
// multiple groups. And, if conn wasn't in swarm, it calls swarm.AddConn.
// You can use any Go `KeyType` as a group A `KeyType` as in maps...)
swarm.AddConnToGroup(c2, 1)
// And then use that group to select a connection. Swarm will use any
// connection it finds in that group, using a SelectConn you can rebind:
// swarm.SetGroupSelectConn(1, SelectConn)
// swarm.SetDegaultGroupSelectConn(SelectConn)
log("opening stream with NewStreamWithGroup(1)")
s3, err := swarm.NewStreamWithGroup(1)
if err != nil {
panic(err)
}
// Why groups? It's because with many connections, and many transports,
// and many Servers (or Protocols), we can use the Swarm to associate
// a different StreamHandlers per group, and to let us create NewStreams
// on a given group.
// Ok, we have streams. now what. Use them! Our Streams are basically
// streams from github.com/docker/spdystream, so they work the same
// way:
log("preparing the streams")
for i, stream := range []*ps.Stream{s1, s2, s3} {
str := "stream %d ready:"
fmt.Fprintf(stream, str, i)
buf := make([]byte, len(str))
log(fmt.Sprintf("reading from stream %d", i))
stream.Read(buf)
fmt.Println(string(buf))
}
log("let's test the streams")
log("enter some text below:\n")
go io.Copy(os.Stdout, s1)
go io.Copy(os.Stdout, s2)
go io.Copy(os.Stdout, s3)
io.Copy(io.MultiWriter(s1, s2, s3), os.Stdin)
}
func log(s string) {
fmt.Fprintf(os.Stderr, s+"\n")
}
package peerstream
import (
"errors"
"sync"
"unsafe"
)
// ErrGroupNotFound signals no such group exists
var ErrGroupNotFound = errors.New("group not found")
// Group is an object used to associate a group of
// Streams, Connections, and Listeners. It can be anything,
// it is meant to work like a KeyType in maps
type Group interface{}
// Groupable is an interface for a set of objects that can
// be assigned groups: Streams, Connections, and Listeners.
// Objects inherit groups (e.g. a Stream inherits the groups
// of its parent Connection, and in turn that of its Listener).
type Groupable interface {
// Groups returns the groups this object belongs to
Groups() []Group
// InGroup returns whether this object belongs to a Group
InGroup(g Group) bool
// AddGroup adds this object to a group
AddGroup(g Group)
}
// groupSet is a struct designed to be embedded and
// give things group memebership
type groupSet struct {
m map[Group]struct{}
sync.RWMutex
}
func (gs *groupSet) Add(g Group) {
gs.Lock()
defer gs.Unlock()
gs.m[g] = struct{}{}
}
func (gs *groupSet) Remove(g Group) {
gs.Lock()
defer gs.Unlock()
delete(gs.m, g)
}
func (gs *groupSet) Has(g Group) bool {
gs.RLock()
defer gs.RUnlock()
_, ok := gs.m[g]
return ok
}
func (gs *groupSet) Groups() []Group {
gs.RLock()
defer gs.RUnlock()
out := make([]Group, 0, len(gs.m))
for k := range gs.m {
out = append(out, k)
}
return out
}
// AddSet adds all elements in another set.
func (gs *groupSet) AddSet(gs2 *groupSet) {
// acquire locks in order
p1 := uintptr(unsafe.Pointer(gs))
p2 := uintptr(unsafe.Pointer(gs2))
switch {
case p1 < p2:
gs.Lock()
gs2.RLock()
defer gs.Unlock()
defer gs2.RUnlock()
case p1 > p2:
gs2.Lock()
gs.Lock()
defer gs2.Unlock()
defer gs.Unlock()
default:
return // they're the same!
}
for g := range gs2.m {
gs.m[g] = struct{}{}
}
}
package peerstream
import (
"io"
"math/rand"
)
var SelectRandomConn = func(conns []*Conn) *Conn {
if len(conns) == 0 {
return nil
}
return conns[rand.Intn(len(conns))]
}
func EchoHandler(s *Stream) {
go func() {
io.Copy(s, s)
s.Close()
}()
}
func CloseHandler(s *Stream) {
s.Close()
}
func NoOpStreamHandler(s *Stream) {}
func NoOpConnHandler(c *Conn) {}
package peerstream
import (
"errors"
"fmt"
"net"
"sync"
tec "QmWtLNgjHvFnRHcHUheAMGx4sLYYYGSacNA3eG52ywy2UQ/go-temp-err-catcher"
)
// AcceptConcurrency is how many connections can simultaneously be
// in process of being accepted. Handshakes can sometimes occur as
// part of this process, so it may take some time. It is imporant to
// rate limit lest a malicious influx of connections would cause our
// node to consume all its resources accepting new connections.
var AcceptConcurrency = 200
type Listener struct {
netList net.Listener
groups groupSet
swarm *Swarm
acceptErr chan error
}
func newListener(nl net.Listener, s *Swarm) *Listener {
return &Listener{
netList: nl,
swarm: s,
acceptErr: make(chan error, 10),
}
}
// String returns a string representation of the Listener
func (l *Listener) String() string {
f := "<peerstream.Listener %s>"
return fmt.Sprintf(f, l.netList.Addr())
}
// NetListener is the underlying net.Listener
func (l *Listener) NetListener() net.Listener {
return l.netList
}
// Groups returns the groups this Listener belongs to
func (l *Listener) Groups() []Group {
return l.groups.Groups()
}
// InGroup returns whether this Listener belongs to a Group
func (l *Listener) InGroup(g Group) bool {
return l.groups.Has(g)
}
// AddGroup assigns given Group to Listener
func (l *Listener) AddGroup(g Group) {
l.groups.Add(g)
}
// ListenersWithGroup narrows down a set of listeners to those in given group.
func ListenersWithGroup(g Group, ls []*Listener) []*Listener {
var out []*Listener
for _, l := range ls {
if l.InGroup(g) {
out = append(out, l)
}
}
return out
}
// accept continously accepts incoming connections and
// adds them to the listener's Swarm. is is meant to be
// run in a goroutine.
// TODO: add rate limiting
func (l *Listener) accept() {
var wg sync.WaitGroup
defer func() {
wg.Wait() // must happen before teardown
l.teardown()
}()
// catching the error here is odd. doing what net/http does:
// http://golang.org/src/net/http/server.go?s=51504:51550#L1728
// Using the lib: https://godoc.org/github.com/jbenet/go-temp-err-catcher
var catcher tec.TempErrCatcher
// rate limit concurrency
limit := make(chan struct{}, AcceptConcurrency)
// loop forever accepting connections
for {
conn, err := l.netList.Accept()
if err != nil {
if catcher.IsTemporary(err) {
continue
}
l.acceptErr <- fmt.Errorf("peerstream listener failed: %s", err)
return // ok, problems. bail.
}
// add conn to swarm and listen for incoming streams
// do this in a goroutine to avoid blocking the Accept loop.
// note that this does not rate limit accepts.
limit <- struct{}{} // sema down
wg.Add(1)
go func(conn net.Conn) {
defer func() { <-limit }() // sema up
defer wg.Done()
conn2, err := l.swarm.addConn(conn, true)
if err != nil {
l.acceptErr <- err
return
}
conn2.groups.AddSet(&l.groups) // add out groups
}(conn)
}
}
// AcceptError returns the error that we **might** on listener close
func (l *Listener) AcceptErrors() <-chan error {
return l.acceptErr
}
func (l *Listener) teardown() {
// in case we exit from network errors (accept fails) but
// (a) client doesn't call Close, and (b) listener remains open)
l.netList.Close()
close(l.acceptErr)
// remove self from swarm
l.swarm.listenerLock.Lock()
delete(l.swarm.listeners, l)
l.swarm.listenerLock.Unlock()
}
func (l *Listener) Close() error {
return l.netList.Close()
}
// addListener is the internal version of AddListener.
func (s *Swarm) addListener(nl net.Listener) (*Listener, error) {
if nl == nil {
return nil, errors.New("nil listener")
}
s.listenerLock.Lock()
defer s.listenerLock.Unlock()
// first, check if we already have it...
for l := range s.listeners {
if l.netList == nl {
return l, nil
}
}
l := newListener(nl, s)
s.listeners[l] = struct{}{}
go l.accept()
return l, nil
}
package muxtest
import (
multiplex "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/multiplex"
multistream "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/multistream"
muxado "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/muxado"
spdy "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/spdystream"
yamux "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/yamux"
)
var _ = multiplex.DefaultTransport
var _ = multistream.NewTransport
var _ = muxado.Transport
var _ = spdy.Transport
var _ = yamux.DefaultTransport
package muxtest
import (
"testing"
multiplex "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/multiplex"
multistream "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/multistream"
muxado "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/muxado"
spdy "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/spdystream"
yamux "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer/yamux"
)
func TestYamuxTransport(t *testing.T) {
SubtestAll(t, yamux.DefaultTransport)
}
func TestSpdyStreamTransport(t *testing.T) {
t.SkipNow()
SubtestAll(t, spdy.Transport)
}
func TestMultiplexTransport(t *testing.T) {
SubtestAll(t, multiplex.DefaultTransport)
}
func TestMuxadoTransport(t *testing.T) {
SubtestAll(t, muxado.Transport)
}
func TestMultistreamTransport(t *testing.T) {
SubtestAll(t, multistream.NewTransport())
}
package muxtest
import (
"bytes"
crand "crypto/rand"
"fmt"
"io"
mrand "math/rand"
"net"
"os"
"reflect"
"runtime"
"sync"
"testing"
ps "QmTgxFwS1nDK126fH5XPnLFcxcDFsxKbPPnCBwyRWNAjDX/go-peerstream"
smux "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer"
)
var randomness []byte
var nextPort = 20000
var verbose = false
func init() {
// read 1MB of randomness
randomness = make([]byte, 1<<20)
if _, err := crand.Read(randomness); err != nil {
panic(err)
}
}
func randBuf(size int) []byte {
n := len(randomness) - size
if size < 1 {
panic(fmt.Errorf("requested too large buffer (%d). max is %d", size, len(randomness)))
}
start := mrand.Intn(n)
return randomness[start : start+size]
}
func checkErr(t *testing.T, err error) {
if err != nil {
t.Fatal(err)
}
}
func log(s string, v ...interface{}) {
if verbose {
fmt.Fprintf(os.Stderr, "> "+s+"\n", v...)
}
}
type echoSetup struct {
swarm *ps.Swarm
conns []*ps.Conn
}
func singleConn(t *testing.T, tr smux.Transport) echoSetup {
swarm := ps.NewSwarm(tr)
swarm.SetStreamHandler(func(s *ps.Stream) {
defer s.Close()
log("accepted stream")
io.Copy(s, s) // echo everything
log("closing stream")
})
log("listening at %s", "localhost:0")
l, err := net.Listen("tcp", "localhost:0")
checkErr(t, err)
_, err = swarm.AddListener(l)
checkErr(t, err)
log("dialing to %s", l.Addr())
nc1, err := net.Dial("tcp", l.Addr().String())
checkErr(t, err)
c1, err := swarm.AddConn(nc1)
checkErr(t, err)
return echoSetup{
swarm: swarm,
conns: []*ps.Conn{c1},
}
}
func makeSwarm(t *testing.T, tr smux.Transport, nListeners int) *ps.Swarm {
swarm := ps.NewSwarm(tr)
swarm.SetStreamHandler(func(s *ps.Stream) {
defer s.Close()
log("accepted stream")
io.Copy(s, s) // echo everything
log("closing stream")
})
for i := 0; i < nListeners; i++ {
log("%p listening at %s", swarm, "localhost:0")
l, err := net.Listen("tcp", "localhost:0")
checkErr(t, err)
_, err = swarm.AddListener(l)
checkErr(t, err)
}
return swarm
}
func makeSwarms(t *testing.T, tr smux.Transport, nSwarms, nListeners int) []*ps.Swarm {
swarms := make([]*ps.Swarm, nSwarms)
for i := 0; i < nSwarms; i++ {
swarms[i] = makeSwarm(t, tr, nListeners)
}
return swarms
}
func SubtestConstructSwarm(t *testing.T, tr smux.Transport) {
ps.NewSwarm(tr)
}
func SubtestSimpleWrite(t *testing.T, tr smux.Transport) {
swarm := ps.NewSwarm(tr)
defer swarm.Close()
piper, pipew := io.Pipe()
swarm.SetStreamHandler(func(s *ps.Stream) {
defer s.Close()
log("accepted stream")
w := io.MultiWriter(s, pipew)
io.Copy(w, s) // echo everything and write it to pipew
log("closing stream")
})
log("listening at %s", "localhost:0")
l, err := net.Listen("tcp", "localhost:0")
checkErr(t, err)
_, err = swarm.AddListener(l)
checkErr(t, err)
log("dialing to %s", l.Addr().String())
nc1, err := net.Dial("tcp", l.Addr().String())
checkErr(t, err)
c1, err := swarm.AddConn(nc1)
checkErr(t, err)
defer c1.Close()
log("creating stream")
s1, err := c1.NewStream()
checkErr(t, err)
defer s1.Close()
buf1 := randBuf(4096)
log("writing %d bytes to stream", len(buf1))
_, err = s1.Write(buf1)
checkErr(t, err)
buf2 := make([]byte, len(buf1))
log("reading %d bytes from stream (echoed)", len(buf2))
_, err = s1.Read(buf2)
checkErr(t, err)
if string(buf2) != string(buf1) {
t.Error("buf1 and buf2 not equal: %s != %s", string(buf1), string(buf2))
}
buf3 := make([]byte, len(buf1))
log("reading %d bytes from pipe (tee)", len(buf3))
_, err = piper.Read(buf3)
checkErr(t, err)
if string(buf3) != string(buf1) {
t.Error("buf1 and buf3 not equal: %s != %s", string(buf1), string(buf3))
}
}
func SubtestSimpleWrite100msgs(t *testing.T, tr smux.Transport) {
msgs := 100
msgsize := 1 << 19
es := singleConn(t, tr)
defer es.swarm.Close()
log("creating stream")
stream, err := es.conns[0].NewStream()
checkErr(t, err)
bufs := make(chan []byte, msgs)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < msgs; i++ {
buf := randBuf(msgsize)
bufs <- buf
log("writing %d bytes (message %d/%d #%x)", len(buf), i, msgs, buf[:3])
if _, err := stream.Write(buf); err != nil {
t.Error(fmt.Errorf("stream.Write(buf): %s", err))
continue
}
}
close(bufs)
}()
wg.Add(1)
go func() {
defer wg.Done()
buf2 := make([]byte, msgsize)
i := 0
for buf1 := range bufs {
log("reading %d bytes (message %d/%d #%x)", len(buf1), i, msgs, buf1[:3])
i++
if _, err := io.ReadFull(stream, buf2); err != nil {
t.Error(fmt.Errorf("readFull(stream, buf2): %s", err))
continue
}
if !bytes.Equal(buf1, buf2) {
t.Error(fmt.Errorf("buffers not equal (%x != %x)", buf1[:3], buf2[:3]))
}
}
}()
wg.Wait()
}
func SubtestStressNSwarmNConnNStreamNMsg(t *testing.T, tr smux.Transport, nSwarm, nConn, nStream, nMsg int) {
msgsize := 1 << 11
rateLimitN := 5000
rateLimitChan := make(chan struct{}, rateLimitN) // max of 5k funcs.
for i := 0; i < rateLimitN; i++ {
rateLimitChan <- struct{}{}
}
rateLimit := func(f func()) {
<-rateLimitChan
f()
rateLimitChan <- struct{}{}
}
writeStream := func(s *ps.Stream, bufs chan<- []byte) {
log("writeStream %p, %d nMsg", s, nMsg)
for i := 0; i < nMsg; i++ {
buf := randBuf(msgsize)
bufs <- buf
log("%p writing %d bytes (message %d/%d #%x)", s, len(buf), i, nMsg, buf[:3])
if _, err := s.Write(buf); err != nil {
t.Error(fmt.Errorf("s.Write(buf): %s", err))
continue
}
}
}
readStream := func(s *ps.Stream, bufs <-chan []byte) {
log("readStream %p, %d nMsg", s, nMsg)
buf2 := make([]byte, msgsize)
i := 0
for buf1 := range bufs {
i++
log("%p reading %d bytes (message %d/%d #%x)", s, len(buf1), i-1, nMsg, buf1[:3])
if _, err := io.ReadFull(s, buf2); err != nil {
log("%p failed to read %d bytes (message %d/%d #%x)", s, len(buf1), i-1, nMsg, buf1[:3])
t.Error(fmt.Errorf("io.ReadFull(s, buf2): %s", err))
continue
}
if !bytes.Equal(buf1, buf2) {
t.Error(fmt.Errorf("buffers not equal (%x != %x)", buf1[:3], buf2[:3]))
}
}
}
openStreamAndRW := func(c *ps.Conn) {
log("openStreamAndRW %p, %d nMsg", c, nMsg)
s, err := c.NewStream()
if err != nil {
t.Error(fmt.Errorf("Failed to create NewStream: %s", err))
return
}
bufs := make(chan []byte, nMsg)
go func() {
writeStream(s, bufs)
close(bufs)
}()
readStream(s, bufs)
s.Close()
}
openConnAndRW := func(a, b *ps.Swarm) {
log("openConnAndRW %p -> %p, %d nStream", a, b, nConn)
ls := b.Listeners()
l := ls[mrand.Intn(len(ls))]
nl := l.NetListener()
nla := nl.Addr()
nc, err := net.Dial(nla.Network(), nla.String())
if err != nil {
t.Fatal(fmt.Errorf("net.Dial(%s, %s): %s", nla.Network(), nla.String(), err))
return
}
c, err := a.AddConn(nc)
if err != nil {
t.Fatal(fmt.Errorf("a.AddConn(%s <--> %s): %s", nc.LocalAddr(), nc.RemoteAddr(), err))
return
}
var wg sync.WaitGroup
for i := 0; i < nStream; i++ {
wg.Add(1)
go rateLimit(func() {
defer wg.Done()
openStreamAndRW(c)
})
}
wg.Wait()
c.Close()
}
openConnsAndRW := func(a, b *ps.Swarm) {
log("openConnsAndRW %p -> %p, %d conns", a, b, nConn)
var wg sync.WaitGroup
for i := 0; i < nConn; i++ {
wg.Add(1)
go rateLimit(func() {
defer wg.Done()
openConnAndRW(a, b)
})
}
wg.Wait()
}
connectSwarmsAndRW := func(swarms []*ps.Swarm) {
log("connectSwarmsAndRW %d swarms", len(swarms))
var wg sync.WaitGroup
for _, a := range swarms {
for _, b := range swarms {
wg.Add(1)
a := a // race
b := b // race
go rateLimit(func() {
defer wg.Done()
openConnsAndRW(a, b)
})
}
}
wg.Wait()
}
swarms := makeSwarms(t, tr, nSwarm, 3) // 3 listeners per swarm.
connectSwarmsAndRW(swarms)
for _, s := range swarms {
s.Close()
}
}
func SubtestStress1Swarm1Conn1Stream1Msg(t *testing.T, tr smux.Transport) {
SubtestStressNSwarmNConnNStreamNMsg(t, tr, 1, 1, 1, 1)
}
func SubtestStress1Swarm1Conn1Stream100Msg(t *testing.T, tr smux.Transport) {
SubtestStressNSwarmNConnNStreamNMsg(t, tr, 1, 1, 1, 100)
}
func SubtestStress1Swarm1Conn100Stream100Msg(t *testing.T, tr smux.Transport) {
SubtestStressNSwarmNConnNStreamNMsg(t, tr, 1, 1, 100, 100)
}
func SubtestStress1Swarm10Conn50Stream50Msg(t *testing.T, tr smux.Transport) {
SubtestStressNSwarmNConnNStreamNMsg(t, tr, 1, 10, 50, 50)
}
func SubtestStress5Swarm2Conn20Stream20Msg(t *testing.T, tr smux.Transport) {
SubtestStressNSwarmNConnNStreamNMsg(t, tr, 5, 2, 20, 20)
}
func SubtestStress10Swarm2Conn100Stream100Msg(t *testing.T, tr smux.Transport) {
SubtestStressNSwarmNConnNStreamNMsg(t, tr, 10, 2, 100, 100)
}
func SubtestAll(t *testing.T, tr smux.Transport) {
tests := []TransportTest{
SubtestConstructSwarm,
SubtestSimpleWrite,
SubtestSimpleWrite100msgs,
SubtestStress1Swarm1Conn1Stream1Msg,
SubtestStress1Swarm1Conn1Stream100Msg,
SubtestStress1Swarm1Conn100Stream100Msg,
SubtestStress1Swarm10Conn50Stream50Msg,
SubtestStress5Swarm2Conn20Stream20Msg,
// SubtestStress10Swarm2Conn100Stream100Msg, <-- this hoses the osx network stack...
}
for _, f := range tests {
if testing.Verbose() {
fmt.Fprintf(os.Stderr, "==== RUN %s\n", GetFunctionName(f))
}
f(t, tr)
}
}
type TransportTest func(t *testing.T, tr smux.Transport)
func TestNoOp(t *testing.T) {}
func GetFunctionName(i interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}
{
"name": "go-peerstream",
"author": "whyrusleeping",
"version": "1.0.0",
"gxDependencies": [
{
"name": "go-temp-err-catcher",
"hash": "QmWtLNgjHvFnRHcHUheAMGx4sLYYYGSacNA3eG52ywy2UQ",
"version": "1.0.0"
},
{
"name": "go-stream-muxer",
"hash": "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q",
"version": "1.0.0"
}
],
"language": "go",
"gx": {
"dvcsimport": "github.com/jbenet/go-peerstream"
}
}
\ No newline at end of file
package peerstream
import (
"fmt"
smux "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer"
)
// StreamHandler is a function which receives a Stream. It
// allows clients to set a function to receive newly created
// streams, and decide whether to continue adding them.
// It works sort of like a http.HandleFunc.
// Note: the StreamHandler is called sequentially, so spawn
// goroutines or pass the Stream. See EchoHandler.
type StreamHandler func(s *Stream)
// Stream is an io.{Read,Write,Close}r to a remote counterpart.
// It wraps a spdystream.Stream, and links it to a Conn and groups
type Stream struct {
smuxStream smux.Stream
conn *Conn
groups groupSet
}
func newStream(ss smux.Stream, c *Conn) *Stream {
s := &Stream{
conn: c,
smuxStream: ss,
groups: groupSet{m: make(map[Group]struct{})},
}
s.groups.AddSet(&c.groups) // inherit groups
return s
}
// String returns a string representation of the Stream
func (s *Stream) String() string {
f := "<peerstream.Stream %s <--> %s>"
return fmt.Sprintf(f, s.conn.NetConn().LocalAddr(), s.conn.NetConn().RemoteAddr())
}
// SPDYStream returns the underlying *spdystream.Stream
func (s *Stream) Stream() smux.Stream {
return s.smuxStream
}
// Conn returns the Conn associated with this Stream
func (s *Stream) Conn() *Conn {
return s.conn
}
// Swarm returns the Swarm asociated with this Stream
func (s *Stream) Swarm() *Swarm {
return s.conn.swarm
}
// Groups returns the Groups this Stream belongs to
func (s *Stream) Groups() []Group {
return s.groups.Groups()
}
// InGroup returns whether this stream belongs to a Group
func (s *Stream) InGroup(g Group) bool {
return s.groups.Has(g)
}
// AddGroup assigns given Group to Stream
func (s *Stream) AddGroup(g Group) {
s.groups.Add(g)
}
func (s *Stream) Read(p []byte) (n int, err error) {
return s.smuxStream.Read(p)
}
func (s *Stream) Write(p []byte) (n int, err error) {
return s.smuxStream.Write(p)
}
func (s *Stream) Close() error {
return s.conn.swarm.removeStream(s)
}
// StreamsWithGroup narrows down a set of streams to those in given group.
func StreamsWithGroup(g Group, streams []*Stream) []*Stream {
var out []*Stream
for _, s := range streams {
if s.InGroup(g) {
out = append(out, s)
}
}
return out
}
package peerstream
import (
"errors"
"fmt"
"net"
"sync"
"time"
smux "QmPxuHs2NQjz16gnvndgkzHkm5PjtqbB5rwoSpLusBkQ7Q/go-stream-muxer"
)
// fd is a (file) descriptor, unix style
type fd uint32
// GarbageCollectTimeout governs the periodic connection closer.
var GarbageCollectTimeout = 5 * time.Second
type Swarm struct {
// the transport we'll use.
transport smux.Transport
// active streams.
streams map[*Stream]struct{}
streamLock sync.RWMutex
// active connections. generate new Streams
conns map[*Conn]struct{}
connLock sync.RWMutex
// active listeners. generate new Listeners
listeners map[*Listener]struct{}
listenerLock sync.RWMutex
// these handlers should be accessed with their getter/setter
// as this pointer may be changed at any time.
handlerLock sync.RWMutex // protects the functions below
connHandler ConnHandler // receives Conns intiated remotely
streamHandler StreamHandler // receives Streams initiated remotely
selectConn SelectConn // default SelectConn function
// notification listeners
notifiees map[Notifiee]struct{}
notifieeLock sync.RWMutex
closed chan struct{}
}
func NewSwarm(t smux.Transport) *Swarm {
s := &Swarm{
transport: t,
streams: make(map[*Stream]struct{}),
conns: make(map[*Conn]struct{}),
listeners: make(map[*Listener]struct{}),
notifiees: make(map[Notifiee]struct{}),
selectConn: SelectRandomConn,
streamHandler: NoOpStreamHandler,
connHandler: NoOpConnHandler,
closed: make(chan struct{}),
}
go s.connGarbageCollect()
return s
}
// String returns a string with various internal stats
func (s *Swarm) String() string {
s.listenerLock.Lock()
ls := len(s.listeners)
s.listenerLock.Unlock()
s.connLock.Lock()
cs := len(s.conns)
s.connLock.Unlock()
s.streamLock.Lock()
ss := len(s.streams)
s.streamLock.Unlock()
str := "<peerstream.Swarm %d listeners %d conns %d streams>"
return fmt.Sprintf(str, ls, cs, ss)
}
// Dump returns a string with all the internal state
func (s *Swarm) Dump() string {
str := s.String() + "\n"
s.listenerLock.Lock()
for l, _ := range s.listeners {
str += fmt.Sprintf("\t%s %v\n", l, l.Groups())
}
s.listenerLock.Unlock()
s.connLock.Lock()
for c, _ := range s.conns {
str += fmt.Sprintf("\t%s %v\n", c, c.Groups())
}
s.connLock.Unlock()
s.streamLock.Lock()
for ss, _ := range s.streams {
str += fmt.Sprintf("\t%s %v\n", ss, ss.Groups())
}
s.streamLock.Unlock()
return str
}
// SetStreamHandler assigns the stream handler in the swarm.
// The handler assumes responsibility for closing the stream.
// This need not happen at the end of the handler, leaving the
// stream open (to be used and closed later) is fine.
// It is also fine to keep a pointer to the Stream.
// This is a threadsafe (atomic) operation
func (s *Swarm) SetStreamHandler(sh StreamHandler) {
s.handlerLock.Lock()
defer s.handlerLock.Unlock()
s.streamHandler = sh
}
// StreamHandler returns the Swarm's current StreamHandler.
// This is a threadsafe (atomic) operation
func (s *Swarm) StreamHandler() StreamHandler {
s.handlerLock.RLock()
defer s.handlerLock.RUnlock()
if s.streamHandler == nil {
return NoOpStreamHandler
}
return s.streamHandler
}
// SetConnHandler assigns the conn handler in the swarm.
// Unlike the StreamHandler, the ConnHandler has less respon-
// ibility for the Connection. The Swarm is still its client.
// This handler is only a notification.
// This is a threadsafe (atomic) operation
func (s *Swarm) SetConnHandler(ch ConnHandler) {
s.handlerLock.Lock()
defer s.handlerLock.Unlock()
s.connHandler = ch
}
// ConnHandler returns the Swarm's current ConnHandler.
// This is a threadsafe (atomic) operation
func (s *Swarm) ConnHandler() ConnHandler {
s.handlerLock.RLock()
defer s.handlerLock.RUnlock()
if s.connHandler == nil {
return NoOpConnHandler
}
return s.connHandler
}
// SetConnSelect assigns the connection selector in the swarm.
// If cs is nil, will use SelectRandomConn
// This is a threadsafe (atomic) operation
func (s *Swarm) SetSelectConn(cs SelectConn) {
s.handlerLock.Lock()
defer s.handlerLock.Unlock()
s.selectConn = cs
}
// ConnSelect returns the Swarm's current connection selector.
// ConnSelect is used in order to select the best of a set of
// possible connections. The default chooses one at random.
// This is a threadsafe (atomic) operation
func (s *Swarm) SelectConn() SelectConn {
s.handlerLock.RLock()
defer s.handlerLock.RUnlock()
if s.selectConn == nil {
return SelectRandomConn
}
return s.selectConn
}
// Conns returns all the connections associated with this Swarm.
func (s *Swarm) Conns() []*Conn {
s.connLock.RLock()
conns := make([]*Conn, 0, len(s.conns))
for c := range s.conns {
conns = append(conns, c)
}
s.connLock.RUnlock()
open := make([]*Conn, 0, len(conns))
for _, c := range conns {
if c.smuxConn.IsClosed() {
c.Close()
} else {
open = append(open, c)
}
}
return open
}
// Listeners returns all the listeners associated with this Swarm.
func (s *Swarm) Listeners() []*Listener {
s.listenerLock.RLock()
out := make([]*Listener, 0, len(s.listeners))
for c := range s.listeners {
out = append(out, c)
}
s.listenerLock.RUnlock()
return out
}
// Streams returns all the streams associated with this Swarm.
func (s *Swarm) Streams() []*Stream {
s.streamLock.RLock()
out := make([]*Stream, 0, len(s.streams))
for c := range s.streams {
out = append(out, c)
}
s.streamLock.RUnlock()
return out
}
// AddListener adds net.Listener to the Swarm, and immediately begins
// accepting incoming connections.
func (s *Swarm) AddListener(l net.Listener) (*Listener, error) {
return s.addListener(l)
}
// AddListenerWithRateLimit adds Listener to the Swarm, and immediately
// begins accepting incoming connections. The rate of connection acceptance
// depends on the RateLimit option
// func (s *Swarm) AddListenerWithRateLimit(net.Listner, RateLimit) // TODO
// AddConn gives the Swarm ownership of net.Conn. The Swarm will open a
// SPDY session and begin listening for Streams.
// Returns the resulting Swarm-associated peerstream.Conn.
// Idempotent: if the Connection has already been added, this is a no-op.
func (s *Swarm) AddConn(netConn net.Conn) (*Conn, error) {
return s.addConn(netConn, false)
}
// NewStream opens a new Stream on the best available connection,
// as selected by current swarm.SelectConn.
func (s *Swarm) NewStream() (*Stream, error) {
return s.NewStreamSelectConn(s.SelectConn())
}
func (s *Swarm) newStreamSelectConn(selConn SelectConn, conns []*Conn) (*Stream, error) {
if selConn == nil {
return nil, errors.New("nil SelectConn")
}
best := selConn(conns)
if best == nil || !ConnInConns(best, conns) {
return nil, ErrInvalidConnSelected
}
return s.NewStreamWithConn(best)
}
// NewStreamWithSelectConn opens a new Stream on a connection selected
// by selConn.
func (s *Swarm) NewStreamSelectConn(selConn SelectConn) (*Stream, error) {
if selConn == nil {
return nil, errors.New("nil SelectConn")
}
conns := s.Conns()
if len(conns) == 0 {
return nil, ErrNoConnections
}
return s.newStreamSelectConn(selConn, conns)
}
// NewStreamWithGroup opens a new Stream on an available connection in
// the given group. Uses the current swarm.SelectConn to pick between
// multiple connections.
func (s *Swarm) NewStreamWithGroup(group Group) (*Stream, error) {
conns := s.ConnsWithGroup(group)
return s.newStreamSelectConn(s.SelectConn(), conns)
}
// NewStreamWithNetConn opens a new Stream on given net.Conn.
// Calls s.AddConn(netConn).
func (s *Swarm) NewStreamWithNetConn(netConn net.Conn) (*Stream, error) {
c, err := s.AddConn(netConn)
if err != nil {
return nil, err
}
return s.NewStreamWithConn(c)
}
// NewStreamWithConnection opens a new Stream on given connection.
func (s *Swarm) NewStreamWithConn(conn *Conn) (*Stream, error) {
if conn == nil {
return nil, errors.New("nil Conn")
}
if conn.Swarm() != s {
return nil, errors.New("connection not associated with swarm")
}
if conn.smuxConn.IsClosed() {
go conn.Close()
return nil, errors.New("conn is closed")
}
s.connLock.RLock()
if _, found := s.conns[conn]; !found {
s.connLock.RUnlock()
return nil, errors.New("connection not associated with swarm")
}
s.connLock.RUnlock()
return s.createStream(conn)
}
// AddConnToGroup assigns given Group to conn
func (s *Swarm) AddConnToGroup(conn *Conn, g Group) {
conn.groups.Add(g)
}
// ConnsWithGroup returns all the connections with a given Group
func (s *Swarm) ConnsWithGroup(g Group) []*Conn {
return ConnsWithGroup(g, s.Conns())
}
// StreamsWithGroup returns all the streams with a given Group
func (s *Swarm) StreamsWithGroup(g Group) []*Stream {
return StreamsWithGroup(g, s.Streams())
}
// Close shuts down the Swarm, and it's listeners.
func (s *Swarm) Close() error {
defer close(s.closed)
// automatically close everything new we get.
s.SetConnHandler(func(c *Conn) { c.Close() })
s.SetStreamHandler(func(s *Stream) { s.Close() })
var wgl sync.WaitGroup
for _, l := range s.Listeners() {
wgl.Add(1)
go func(list *Listener) {
list.Close()
wgl.Done()
}(l)
}
wgl.Wait()
var wgc sync.WaitGroup
for _, c := range s.Conns() {
wgc.Add(1)
go func(conn *Conn) {
conn.Close()
wgc.Done()
}(c)
}
wgc.Wait()
return nil
}
// connGarbageCollect periodically sweeps conns to make sure
// they're still alive. if any are closed, remvoes them.
func (s *Swarm) connGarbageCollect() {
for {
select {
case <-s.closed:
return
case <-time.After(GarbageCollectTimeout):
}
for _, c := range s.Conns() {
if c.smuxConn.IsClosed() {
go c.Close()
}
}
}
}
// Notify signs up Notifiee to receive signals when events happen
func (s *Swarm) Notify(n Notifiee) {
s.notifieeLock.Lock()
s.notifiees[n] = struct{}{}
s.notifieeLock.Unlock()
}
// StopNotify unregisters Notifiee fromr receiving signals
func (s *Swarm) StopNotify(n Notifiee) {
s.notifieeLock.Lock()
delete(s.notifiees, n)
s.notifieeLock.Unlock()
}
// notifyAll runs the notification function on all Notifiees
func (s *Swarm) notifyAll(notification func(n Notifiee)) {
s.notifieeLock.RLock()
for n := range s.notifiees {
// make sure we dont block
// and they dont block each other.
go notification(n)
}
s.notifieeLock.RUnlock()
}
// Notifiee is an interface for an object wishing to receive
// notifications from a Swarm
type Notifiee interface {
Connected(*Conn) // called when a connection opened
Disconnected(*Conn) // called when a connection closed
OpenedStream(*Stream) // called when a stream opened
ClosedStream(*Stream) // called when a stream closed
}
language: go
go:
- 1.3
- release
- tip
script:
- go test -race -cpu=5 -v ./...
The MIT License (MIT)
Copyright (c) 2014 Juan Batiz-Benet
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment