package quic
import (
"context"
"fmt"
"sync"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/wire"
)
type incomingStream interface {
closeForShutdown(error )
}
type incomingStreamEntry[T incomingStream ] struct {
stream T
shouldDelete bool
}
type incomingStreamsMap[T incomingStream ] struct {
mutex sync .RWMutex
newStreamChan chan struct {}
streamType protocol .StreamType
streams map [protocol .StreamID ]incomingStreamEntry [T ]
nextStreamToAccept protocol .StreamID
nextStreamToOpen protocol .StreamID
maxStream protocol .StreamID
maxNumStreams uint64
newStream func (protocol .StreamID ) T
queueMaxStreamID func (*wire .MaxStreamsFrame )
closeErr error
}
func newIncomingStreamsMap[T incomingStream ](
streamType protocol .StreamType ,
newStream func (protocol .StreamID ) T ,
maxStreams uint64 ,
queueControlFrame func (wire .Frame ),
pers protocol .Perspective ,
) *incomingStreamsMap [T ] {
var nextStreamToAccept protocol .StreamID
switch {
case streamType == protocol .StreamTypeBidi && pers == protocol .PerspectiveServer :
nextStreamToAccept = protocol .FirstIncomingBidiStreamServer
case streamType == protocol .StreamTypeBidi && pers == protocol .PerspectiveClient :
nextStreamToAccept = protocol .FirstIncomingBidiStreamClient
case streamType == protocol .StreamTypeUni && pers == protocol .PerspectiveServer :
nextStreamToAccept = protocol .FirstIncomingUniStreamServer
case streamType == protocol .StreamTypeUni && pers == protocol .PerspectiveClient :
nextStreamToAccept = protocol .FirstIncomingUniStreamClient
}
return &incomingStreamsMap [T ]{
newStreamChan : make (chan struct {}, 1 ),
streamType : streamType ,
streams : make (map [protocol .StreamID ]incomingStreamEntry [T ]),
maxStream : protocol .StreamNum (maxStreams ).StreamID (streamType , pers .Opposite ()),
maxNumStreams : maxStreams ,
newStream : newStream ,
nextStreamToOpen : nextStreamToAccept ,
nextStreamToAccept : nextStreamToAccept ,
queueMaxStreamID : func (f *wire .MaxStreamsFrame ) { queueControlFrame (f ) },
}
}
func (m *incomingStreamsMap [T ]) AcceptStream (ctx context .Context ) (T , error ) {
select {
case <- m .newStreamChan :
default :
}
m .mutex .Lock ()
var id protocol .StreamID
var entry incomingStreamEntry [T ]
for {
id = m .nextStreamToAccept
if m .closeErr != nil {
m .mutex .Unlock ()
return *new (T ), m .closeErr
}
var ok bool
entry , ok = m .streams [id ]
if ok {
break
}
m .mutex .Unlock ()
select {
case <- ctx .Done ():
return *new (T ), ctx .Err ()
case <- m .newStreamChan :
}
m .mutex .Lock ()
}
m .nextStreamToAccept += 4
if entry .shouldDelete {
if err := m .deleteStream (id ); err != nil {
m .mutex .Unlock ()
return *new (T ), err
}
}
m .mutex .Unlock ()
return entry .stream , nil
}
func (m *incomingStreamsMap [T ]) GetOrOpenStream (id protocol .StreamID ) (T , error ) {
m .mutex .RLock ()
if id > m .maxStream {
m .mutex .RUnlock ()
return *new (T ), &qerr .TransportError {
ErrorCode : qerr .StreamLimitError ,
ErrorMessage : fmt .Sprintf ("peer tried to open stream %d (current limit: %d)" , id , m .maxStream ),
}
}
if id < m .nextStreamToOpen {
var s T
if entry , ok := m .streams [id ]; ok && !entry .shouldDelete {
s = entry .stream
}
m .mutex .RUnlock ()
return s , nil
}
m .mutex .RUnlock ()
m .mutex .Lock ()
for newNum := m .nextStreamToOpen ; newNum <= id ; newNum += 4 {
m .streams [newNum ] = incomingStreamEntry [T ]{stream : m .newStream (newNum )}
select {
case m .newStreamChan <- struct {}{}:
default :
}
}
m .nextStreamToOpen = id + 4
entry := m .streams [id ]
m .mutex .Unlock ()
return entry .stream , nil
}
func (m *incomingStreamsMap [T ]) DeleteStream (id protocol .StreamID ) error {
m .mutex .Lock ()
defer m .mutex .Unlock ()
if err := m .deleteStream (id ); err != nil {
return &qerr .TransportError {
ErrorCode : qerr .StreamStateError ,
ErrorMessage : err .Error(),
}
}
return nil
}
func (m *incomingStreamsMap [T ]) deleteStream (id protocol .StreamID ) error {
if _ , ok := m .streams [id ]; !ok {
return fmt .Errorf ("tried to delete unknown incoming stream %d" , id )
}
if id >= m .nextStreamToAccept {
entry , ok := m .streams [id ]
if ok && entry .shouldDelete {
return fmt .Errorf ("tried to delete incoming stream %d multiple times" , id )
}
entry .shouldDelete = true
m .streams [id ] = entry
return nil
}
delete (m .streams , id )
if m .maxNumStreams > uint64 (len (m .streams )) {
maxStream := m .nextStreamToOpen + 4 *protocol .StreamID (m .maxNumStreams -uint64 (len (m .streams ))-1 )
if maxStream <= protocol .MaxStreamID {
m .maxStream = maxStream
m .queueMaxStreamID (&wire .MaxStreamsFrame {
Type : m .streamType ,
MaxStreamNum : m .maxStream .StreamNum (),
})
}
}
return nil
}
func (m *incomingStreamsMap [T ]) CloseWithError (err error ) {
m .mutex .Lock ()
m .closeErr = err
for _ , entry := range m .streams {
entry .stream .closeForShutdown (err )
}
m .mutex .Unlock ()
close (m .newStreamChan )
}
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 .