@ -108,8 +108,10 @@ type MConnection struct {
pingTimer * time . Ticker // send pings periodically
pingTimer * time . Ticker // send pings periodically
// close conn if pong is not received in pongTimeout
// close conn if pong is not received in pongTimeout
pongTimer * time . Timer
pongTimeoutCh chan bool // true - timeout, false - peer sent pong
lastMsgRecv struct {
sync . Mutex
at time . Time
}
chStatsTimer * time . Ticker // update channel stats periodically
chStatsTimer * time . Ticker // update channel stats periodically
@ -161,10 +163,6 @@ func NewMConnection(
onError errorCbFunc ,
onError errorCbFunc ,
config MConnConfig ,
config MConnConfig ,
) * MConnection {
) * MConnection {
if config . PongTimeout >= config . PingInterval {
panic ( "pongTimeout must be less than pingInterval (otherwise, next ping will reset pong timer)" )
}
mconn := & MConnection {
mconn := & MConnection {
logger : logger ,
logger : logger ,
conn : conn ,
conn : conn ,
@ -205,16 +203,28 @@ func NewMConnection(
func ( c * MConnection ) OnStart ( ctx context . Context ) error {
func ( c * MConnection ) OnStart ( ctx context . Context ) error {
c . flushTimer = timer . NewThrottleTimer ( "flush" , c . config . FlushThrottle )
c . flushTimer = timer . NewThrottleTimer ( "flush" , c . config . FlushThrottle )
c . pingTimer = time . NewTicker ( c . config . PingInterval )
c . pingTimer = time . NewTicker ( c . config . PingInterval )
c . pongTimeoutCh = make ( chan bool , 1 )
c . chStatsTimer = time . NewTicker ( updateStats )
c . chStatsTimer = time . NewTicker ( updateStats )
c . quitSendRoutine = make ( chan struct { } )
c . quitSendRoutine = make ( chan struct { } )
c . doneSendRoutine = make ( chan struct { } )
c . doneSendRoutine = make ( chan struct { } )
c . quitRecvRoutine = make ( chan struct { } )
c . quitRecvRoutine = make ( chan struct { } )
c . setRecvLastMsgAt ( time . Now ( ) )
go c . sendRoutine ( ctx )
go c . sendRoutine ( ctx )
go c . recvRoutine ( ctx )
go c . recvRoutine ( ctx )
return nil
return nil
}
}
func ( c * MConnection ) setRecvLastMsgAt ( t time . Time ) {
c . lastMsgRecv . Lock ( )
defer c . lastMsgRecv . Unlock ( )
c . lastMsgRecv . at = t
}
func ( c * MConnection ) getLastMessageAt ( ) time . Time {
c . lastMsgRecv . Lock ( )
defer c . lastMsgRecv . Unlock ( )
return c . lastMsgRecv . at
}
// stopServices stops the BaseService and timers and closes the quitSendRoutine.
// stopServices stops the BaseService and timers and closes the quitSendRoutine.
// if the quitSendRoutine was already closed, it returns true, otherwise it returns false.
// if the quitSendRoutine was already closed, it returns true, otherwise it returns false.
// It uses the stopMtx to ensure only one of FlushStop and OnStop can do this at a time.
// It uses the stopMtx to ensure only one of FlushStop and OnStop can do this at a time.
@ -323,6 +333,8 @@ func (c *MConnection) sendRoutine(ctx context.Context) {
defer c . _recover ( ctx )
defer c . _recover ( ctx )
protoWriter := protoio . NewDelimitedWriter ( c . bufConnWriter )
protoWriter := protoio . NewDelimitedWriter ( c . bufConnWriter )
pongTimeout := time . NewTicker ( c . config . PongTimeout )
defer pongTimeout . Stop ( )
FOR_LOOP :
FOR_LOOP :
for {
for {
var _n int
var _n int
@ -344,20 +356,7 @@ FOR_LOOP:
break SELECTION
break SELECTION
}
}
c . sendMonitor . Update ( _n )
c . sendMonitor . Update ( _n )
c . logger . Debug ( "Starting pong timer" , "dur" , c . config . PongTimeout )
c . pongTimer = time . AfterFunc ( c . config . PongTimeout , func ( ) {
select {
case c . pongTimeoutCh <- true :
default :
}
} )
c . flush ( )
c . flush ( )
case timeout := <- c . pongTimeoutCh :
if timeout {
err = errors . New ( "pong timeout" )
} else {
c . stopPongTimer ( )
}
case <- c . pong :
case <- c . pong :
_n , err = protoWriter . WriteMsg ( mustWrapPacket ( & tmp2p . PacketPong { } ) )
_n , err = protoWriter . WriteMsg ( mustWrapPacket ( & tmp2p . PacketPong { } ) )
if err != nil {
if err != nil {
@ -370,6 +369,14 @@ FOR_LOOP:
break FOR_LOOP
break FOR_LOOP
case <- c . quitSendRoutine :
case <- c . quitSendRoutine :
break FOR_LOOP
break FOR_LOOP
case <- pongTimeout . C :
// the point of the pong timer is to check to
// see if we've seen a message recently, so we
// want to make sure that we escape this
// select statement on an interval to ensure
// that we avoid hanging on to dead
// connections for too long.
break SELECTION
case <- c . send :
case <- c . send :
// Send some PacketMsgs
// Send some PacketMsgs
eof := c . sendSomePacketMsgs ( ctx )
eof := c . sendSomePacketMsgs ( ctx )
@ -382,18 +389,21 @@ FOR_LOOP:
}
}
}
}
if ! c . IsRunning ( ) {
break FOR_LOOP
if time . Since ( c . getLastMessageAt ( ) ) > c . config . PongTimeout {
err = errors . New ( "pong timeout" )
}
}
if err != nil {
if err != nil {
c . logger . Error ( "Connection failed @ sendRoutine" , "conn" , c , "err" , err )
c . logger . Error ( "Connection failed @ sendRoutine" , "conn" , c , "err" , err )
c . stopForError ( ctx , err )
c . stopForError ( ctx , err )
break FOR_LOOP
break FOR_LOOP
}
}
if ! c . IsRunning ( ) {
break FOR_LOOP
}
}
}
// Cleanup
// Cleanup
c . stopPongTimer ( )
close ( c . doneSendRoutine )
close ( c . doneSendRoutine )
}
}
@ -462,6 +472,14 @@ func (c *MConnection) recvRoutine(ctx context.Context) {
FOR_LOOP :
FOR_LOOP :
for {
for {
select {
case <- ctx . Done ( ) :
break FOR_LOOP
case <- c . doneSendRoutine :
break FOR_LOOP
default :
}
// Block until .recvMonitor says we can read.
// Block until .recvMonitor says we can read.
c . recvMonitor . Limit ( c . _maxPacketMsgSize , atomic . LoadInt64 ( & c . config . RecvRate ) , true )
c . recvMonitor . Limit ( c . _maxPacketMsgSize , atomic . LoadInt64 ( & c . config . RecvRate ) , true )
@ -505,6 +523,9 @@ FOR_LOOP:
break FOR_LOOP
break FOR_LOOP
}
}
// record for pong/heartbeat
c . setRecvLastMsgAt ( time . Now ( ) )
// Read more depending on packet type.
// Read more depending on packet type.
switch pkt := packet . Sum . ( type ) {
switch pkt := packet . Sum . ( type ) {
case * tmp2p . Packet_PacketPing :
case * tmp2p . Packet_PacketPing :
@ -516,11 +537,9 @@ FOR_LOOP:
// never block
// never block
}
}
case * tmp2p . Packet_PacketPong :
case * tmp2p . Packet_PacketPong :
select {
case c . pongTimeoutCh <- false :
default :
// never block
}
// do nothing, we updated the "last message
// received" timestamp above, so we can ignore
// this message
case * tmp2p . Packet_PacketMsg :
case * tmp2p . Packet_PacketMsg :
channelID := ChannelID ( pkt . PacketMsg . ChannelID )
channelID := ChannelID ( pkt . PacketMsg . ChannelID )
channel , ok := c . channelsIdx [ channelID ]
channel , ok := c . channelsIdx [ channelID ]
@ -559,14 +578,6 @@ FOR_LOOP:
}
}
}
}
// not goroutine-safe
func ( c * MConnection ) stopPongTimer ( ) {
if c . pongTimer != nil {
_ = c . pongTimer . Stop ( )
c . pongTimer = nil
}
}
// maxPacketMsgSize returns a maximum size of PacketMsg
// maxPacketMsgSize returns a maximum size of PacketMsg
func ( c * MConnection ) maxPacketMsgSize ( ) int {
func ( c * MConnection ) maxPacketMsgSize ( ) int {
bz , err := proto . Marshal ( mustWrapPacket ( & tmp2p . PacketMsg {
bz , err := proto . Marshal ( mustWrapPacket ( & tmp2p . PacketMsg {