uquic/closed_session.go
Marten Seemann 03483d5e71 refactor how sessions are deleted
Replacing sessions with different structs representing a closed session
doesn't work if a session is using multiple connection IDs.
2019-09-25 15:34:38 +07:00

114 lines
2.6 KiB
Go

package quic
import (
"sync"
"github.com/lucas-clemente/quic-go/internal/utils"
)
type closedSession interface {
destroy()
}
// A closedLocalSession is a session that we closed locally.
// When receiving packets for such a session, we need to retransmit the packet containing the CONNECTION_CLOSE frame,
// with an exponential backoff.
type closedBaseSession struct {
closeOnce sync.Once
closeChan chan struct{} // is closed when the session is closed or destroyed
receivedPackets <-chan *receivedPacket
}
func (s *closedBaseSession) destroy() {
s.closeOnce.Do(func() {
close(s.closeChan)
})
}
func newClosedBaseSession(receivedPackets <-chan *receivedPacket) closedBaseSession {
return closedBaseSession{
receivedPackets: receivedPackets,
closeChan: make(chan struct{}),
}
}
type closedLocalSession struct {
closedBaseSession
conn connection
connClosePacket []byte
counter uint64 // number of packets received
logger utils.Logger
}
// newClosedLocalSession creates a new closedLocalSession and runs it.
func newClosedLocalSession(
conn connection,
receivedPackets <-chan *receivedPacket,
connClosePacket []byte,
logger utils.Logger,
) closedSession {
s := &closedLocalSession{
closedBaseSession: newClosedBaseSession(receivedPackets),
conn: conn,
connClosePacket: connClosePacket,
logger: logger,
}
go s.run()
return s
}
func (s *closedLocalSession) run() {
for {
select {
case p := <-s.receivedPackets:
s.handlePacket(p)
case <-s.closeChan:
return
}
}
}
func (s *closedLocalSession) handlePacket(_ *receivedPacket) {
s.counter++
// exponential backoff
// only send a CONNECTION_CLOSE for the 1st, 2nd, 4th, 8th, 16th, ... packet arriving
for n := s.counter; n > 1; n = n / 2 {
if n%2 != 0 {
return
}
}
s.logger.Debugf("Received %d packets after sending CONNECTION_CLOSE. Retransmitting.", s.counter)
if err := s.conn.Write(s.connClosePacket); err != nil {
s.logger.Debugf("Error retransmitting CONNECTION_CLOSE: %s", err)
}
}
// A closedRemoteSession is a session that was closed remotely.
// For such a session, we might receive reordered packets that were sent before the CONNECTION_CLOSE.
// We can just ignore those packets.
type closedRemoteSession struct {
closedBaseSession
}
var _ closedSession = &closedRemoteSession{}
func newClosedRemoteSession(receivedPackets <-chan *receivedPacket) closedSession {
s := &closedRemoteSession{
closedBaseSession: newClosedBaseSession(receivedPackets),
}
go s.run()
return s
}
func (s *closedRemoteSession) run() {
for {
select {
case <-s.receivedPackets: // discard packets
case <-s.closeChan:
return
}
}
}