package iroh
import (
"context"
"errors"
"net"
"sync"
"github.com/tmc/go-iroh/key"
)
func (e *Endpoint ) ListenStreams () (*StreamListener , error ) {
if err := e .acquireAcceptOwner (acceptOwnerListenStreams ); err != nil {
return nil , err
}
l := NewStreamListener ()
l .ep = e
l .addr = net .UDPAddrFromAddrPort (e .LocalAddr ())
l .onClose = func () {
e .releaseAcceptOwner (acceptOwnerListenStreams )
}
go l .run ()
return l , nil
}
func NewStreamListener () *StreamListener {
ctx , cancel := context .WithCancel (context .Background ())
return &StreamListener {
ctx : ctx ,
cancel : cancel ,
streams : make (chan net .Conn ),
done : make (chan struct {}),
}
}
type StreamListener struct {
ep *Endpoint
ctx context .Context
cancel context .CancelFunc
addr net .Addr
streams chan net .Conn
done chan struct {}
onClose func ()
closeOnce sync .Once
errMu sync .Mutex
err error
}
func (l *StreamListener ) Accept () (net .Conn , error ) {
select {
case c := <- l .streams :
return c , nil
case <- l .done :
return nil , l .acceptErr ()
}
}
func (l *StreamListener ) Close () error {
l .closeOnce .Do (func () {
l .setErr (net .ErrClosed )
l .cancel ()
close (l .done )
if l .onClose != nil {
l .onClose ()
}
})
return nil
}
func (l *StreamListener ) Addr () net .Addr {
return l .addr
}
func (l *StreamListener ) Handler () ProtocolHandler {
return streamListenerHandler {l }
}
func (l *StreamListener ) handleConn (ctx context .Context , conn *Conn ) error {
done := make (chan struct {})
go func () {
l .acceptStreams (conn )
close (done )
}()
select {
case <- done :
return nil
case <- ctx .Done ():
conn .Close ()
<-done
return ctx .Err ()
case <- l .done :
conn .Close ()
<-done
return net .ErrClosed
}
}
func (l *StreamListener ) run () {
var wg sync .WaitGroup
defer func () {
l .cancel ()
wg .Wait ()
l .closeOnce .Do (func () {
close (l .done )
})
}()
for {
conn , err := l .ep .accept (l .ctx )
if err != nil {
l .setErr (err )
return
}
wg .Add (1 )
go func (conn *Conn ) {
defer wg .Done ()
l .acceptStreams (conn )
}(conn )
}
}
type streamListenerHandler struct {
l *StreamListener
}
func (h streamListenerHandler ) Accept (ctx context .Context , conn *Conn ) error {
return h .l .handleConn (ctx , conn )
}
func (h streamListenerHandler ) Shutdown (ctx context .Context ) {
h .l .Close ()
}
func (l *StreamListener ) acceptStreams (conn *Conn ) {
owner := newListenerConn (conn )
defer owner .doneAccepting ()
for {
c , err := conn .AcceptStreamConn (l .ctx )
if err != nil {
return
}
owner .addStream ()
c = &listenerStreamConn {Conn : c , owner : owner }
select {
case l .streams <- c :
case <- l .done :
c .Close ()
return
case <- l .ctx .Done ():
c .Close ()
return
}
}
}
type listenerConn struct {
conn *Conn
mu sync .Mutex
active int
accepting bool
closed bool
}
func newListenerConn(conn *Conn ) *listenerConn {
return &listenerConn {conn : conn , accepting : true }
}
func (c *listenerConn ) addStream () {
c .mu .Lock ()
c .active ++
c .mu .Unlock ()
}
func (c *listenerConn ) releaseStream () {
c .mu .Lock ()
if c .active > 0 {
c .active --
}
c .closeIfIdleLocked ()
c .mu .Unlock ()
}
func (c *listenerConn ) doneAccepting () {
c .mu .Lock ()
c .accepting = false
c .closeIfIdleLocked ()
c .mu .Unlock ()
}
func (c *listenerConn ) closeIfIdleLocked () {
if c .closed || c .accepting || c .active != 0 {
return
}
c .closed = true
c .conn .Close ()
}
type listenerStreamConn struct {
net .Conn
owner *listenerConn
once sync .Once
}
func (c *listenerStreamConn ) Close () error {
err := c .Conn .Close ()
c .once .Do (c .owner .releaseStream )
return err
}
func (c *listenerStreamConn ) RemoteID () key .EndpointID {
return c .Conn .(interface { RemoteID () key .EndpointID }).RemoteID ()
}
func (c *listenerStreamConn ) Used0RTT () bool {
return c .Conn .(interface { Used0RTT () bool }).Used0RTT ()
}
func (l *StreamListener ) setErr (err error ) {
l .errMu .Lock ()
defer l .errMu .Unlock ()
if l .err != nil {
return
}
if l .ctx .Err () != nil || errors .Is (err , context .Canceled ) {
err = net .ErrClosed
}
l .err = err
}
func (l *StreamListener ) acceptErr () error {
l .errMu .Lock ()
defer l .errMu .Unlock ()
if l .err != nil {
return l .err
}
return net .ErrClosed
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .