You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

874 lines
26 KiB

// Copyright (c) 2015 Uber Technologies, Inc.
// 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.
package tchannel
import (
"errors"
"fmt"
"io"
"net"
"sync"
"time"
"github.com/uber/tchannel-go/tos"
"go.uber.org/atomic"
"golang.org/x/net/context"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const (
// CurrentProtocolVersion is the current version of the TChannel protocol
// supported by this stack
CurrentProtocolVersion = 0x02
// DefaultConnectTimeout is the default timeout used by net.Dial, if no timeout
// is specified in the context.
DefaultConnectTimeout = 5 * time.Second
// defaultConnectionBufferSize is the default size for the connection's
// read and write channels.
defaultConnectionBufferSize = 512
)
// PeerVersion contains version related information for a specific peer.
// These values are extracted from the init headers.
type PeerVersion struct {
Language string `json:"language"`
LanguageVersion string `json:"languageVersion"`
TChannelVersion string `json:"tchannelVersion"`
}
// PeerInfo contains information about a TChannel peer
type PeerInfo struct {
// The host and port that can be used to contact the peer, as encoded by net.JoinHostPort
HostPort string `json:"hostPort"`
// The logical process name for the peer, used for only for logging / debugging
ProcessName string `json:"processName"`
// IsEphemeral returns whether the remote host:port is ephemeral (e.g. not listening).
IsEphemeral bool `json:"isEphemeral"`
// Version returns the version information for the remote peer.
Version PeerVersion `json:"version"`
}
func (p PeerInfo) String() string {
return fmt.Sprintf("%s(%s)", p.HostPort, p.ProcessName)
}
// IsEphemeralHostPort returns whether the connection is from an ephemeral host:port.
func (p PeerInfo) IsEphemeralHostPort() bool {
return p.IsEphemeral
}
// LocalPeerInfo adds service name to the peer info, only required for the local peer.
type LocalPeerInfo struct {
PeerInfo
// ServiceName is the service name for the local peer.
ServiceName string `json:"serviceName"`
}
func (p LocalPeerInfo) String() string {
return fmt.Sprintf("%v: %v", p.ServiceName, p.PeerInfo)
}
var (
// ErrConnectionClosed is returned when a caller performs an method
// on a closed connection
ErrConnectionClosed = errors.New("connection is closed")
// ErrSendBufferFull is returned when a message cannot be sent to the
// peer because the frame sending buffer has become full. Typically
// this indicates that the connection is stuck and writes have become
// backed up
ErrSendBufferFull = errors.New("connection send buffer is full, cannot send frame")
// ErrConnectionNotReady is no longer used.
ErrConnectionNotReady = errors.New("connection is not yet ready")
)
// errConnectionInvalidState is returned when the connection is in an unknown state.
type errConnectionUnknownState struct {
site string
state connectionState
}
func (e errConnectionUnknownState) Error() string {
return fmt.Sprintf("connection is in unknown state: %v at %v", e.state, e.site)
}
// ConnectionOptions are options that control the behavior of a Connection
type ConnectionOptions struct {
// The frame pool, allowing better management of frame buffers. Defaults to using raw heap.
FramePool FramePool
// NOTE: This is deprecated and not used for anything.
RecvBufferSize int
// The size of send channel buffers. Defaults to 512.
SendBufferSize int
// The type of checksum to use when sending messages.
ChecksumType ChecksumType
// ToS class name marked on outbound packets.
TosPriority tos.ToS
// HealthChecks configures active connection health checking for this channel.
// By default, health checks are not enabled.
HealthChecks HealthCheckOptions
// MaxCloseTime controls how long we allow a connection to complete pending
// calls before shutting down. Only used if it is non-zero.
MaxCloseTime time.Duration
}
// connectionEvents are the events that can be triggered by a connection.
type connectionEvents struct {
// OnActive is called when a connection becomes active.
OnActive func(c *Connection)
// OnCloseStateChange is called when a connection that is closing changes state.
OnCloseStateChange func(c *Connection)
// OnExchangeUpdated is called when a message exchange added or removed.
OnExchangeUpdated func(c *Connection)
}
// Connection represents a connection to a remote peer.
type Connection struct {
channelConnectionCommon
connID uint32
connDirection connectionDirection
opts ConnectionOptions
conn net.Conn
localPeerInfo LocalPeerInfo
remotePeerInfo PeerInfo
sendCh chan *Frame
stopCh chan struct{}
state connectionState
stateMut sync.RWMutex
inbound *messageExchangeSet
outbound *messageExchangeSet
handler Handler
nextMessageID atomic.Uint32
events connectionEvents
commonStatsTags map[string]string
relay *Relayer
// outboundHP is the host:port we used to create this outbound connection.
// It may not match remotePeerInfo.HostPort, in which case the connection is
// added to peers for both host:ports. For inbound connections, this is empty.
outboundHP string
// closeNetworkCalled is used to avoid errors from being logged
// when this side closes a connection.
closeNetworkCalled atomic.Bool
// stoppedExchanges is atomically set when exchanges are stopped due to error.
stoppedExchanges atomic.Bool
// remotePeerAddress is used as a cache for remote peer address parsed into individual
// components that can be used to set peer tags on OpenTracing Span.
remotePeerAddress peerAddressComponents
// healthCheckCtx/Quit are used to stop health checks.
healthCheckCtx context.Context
healthCheckQuit context.CancelFunc
healthCheckDone chan struct{}
healthCheckHistory *healthHistory
// lastActivity is used to track how long the connection has been idle.
// (unix time, nano)
lastActivity atomic.Int64
}
type peerAddressComponents struct {
port uint16
ipv4 uint32
ipv6 string
hostname string
}
// _nextConnID is used to allocate unique IDs to every connection for debugging purposes.
var _nextConnID atomic.Uint32
type connectionState int
const (
// Connection is fully active
connectionActive connectionState = iota + 1
// Connection is starting to close; new incoming requests are rejected, outbound
// requests are allowed to proceed
connectionStartClose
// Connection has finished processing all active inbound, and is
// waiting for outbound requests to complete or timeout
connectionInboundClosed
// Connection is fully closed
connectionClosed
)
//go:generate stringer -type=connectionState
func getTimeout(ctx context.Context) time.Duration {
deadline, ok := ctx.Deadline()
if !ok {
return DefaultConnectTimeout
}
return deadline.Sub(time.Now())
}
func (co ConnectionOptions) withDefaults() ConnectionOptions {
if co.ChecksumType == ChecksumTypeNone {
co.ChecksumType = ChecksumTypeCrc32
}
if co.FramePool == nil {
co.FramePool = DefaultFramePool
}
if co.SendBufferSize <= 0 {
co.SendBufferSize = defaultConnectionBufferSize
}
co.HealthChecks = co.HealthChecks.withDefaults()
return co
}
func (ch *Channel) setConnectionTosPriority(tosPriority tos.ToS, c net.Conn) error {
tcpAddr, isTCP := c.RemoteAddr().(*net.TCPAddr)
if !isTCP {
return nil
}
// Handle dual stack listeners and set Traffic Class.
var err error
switch ip := tcpAddr.IP; {
case ip.To16() != nil && ip.To4() == nil:
err = ipv6.NewConn(c).SetTrafficClass(int(tosPriority))
case ip.To4() != nil:
err = ipv4.NewConn(c).SetTOS(int(tosPriority))
}
return err
}
func (ch *Channel) newConnection(conn net.Conn, initialID uint32, outboundHP string, remotePeer PeerInfo, remotePeerAddress peerAddressComponents, events connectionEvents) *Connection {
opts := ch.connectionOptions.withDefaults()
connID := _nextConnID.Inc()
connDirection := inbound
log := ch.log.WithFields(LogFields{
{"connID", connID},
{"localAddr", conn.LocalAddr().String()},
{"remoteAddr", conn.RemoteAddr().String()},
{"remoteHostPort", remotePeer.HostPort},
{"remoteIsEphemeral", remotePeer.IsEphemeral},
{"remoteProcess", remotePeer.ProcessName},
}...)
if outboundHP != "" {
connDirection = outbound
log = log.WithFields(LogField{"outboundHP", outboundHP})
}
log = log.WithFields(LogField{"connectionDirection", connDirection})
peerInfo := ch.PeerInfo()
c := &Connection{
channelConnectionCommon: ch.channelConnectionCommon,
connID: connID,
conn: conn,
connDirection: connDirection,
opts: opts,
state: connectionActive,
sendCh: make(chan *Frame, opts.SendBufferSize),
stopCh: make(chan struct{}),
localPeerInfo: peerInfo,
remotePeerInfo: remotePeer,
remotePeerAddress: remotePeerAddress,
outboundHP: outboundHP,
inbound: newMessageExchangeSet(log, messageExchangeSetInbound),
outbound: newMessageExchangeSet(log, messageExchangeSetOutbound),
handler: ch.handler,
events: events,
commonStatsTags: ch.commonStatsTags,
healthCheckHistory: newHealthHistory(),
lastActivity: *atomic.NewInt64(ch.timeNow().UnixNano()),
}
if tosPriority := opts.TosPriority; tosPriority > 0 {
if err := ch.setConnectionTosPriority(tosPriority, conn); err != nil {
log.WithFields(ErrField(err)).Error("Failed to set ToS priority.")
}
}
c.nextMessageID.Store(initialID)
c.log = log
c.inbound.onRemoved = c.checkExchanges
c.outbound.onRemoved = c.checkExchanges
c.inbound.onAdded = c.onExchangeAdded
c.outbound.onAdded = c.onExchangeAdded
if ch.RelayHost() != nil {
c.relay = NewRelayer(ch, c)
}
// Connections are activated as soon as they are created.
c.callOnActive()
go c.readFrames(connID)
go c.writeFrames(connID)
return c
}
func (c *Connection) onExchangeAdded() {
c.callOnExchangeChange()
}
// IsActive returns whether this connection is in an active state.
func (c *Connection) IsActive() bool {
return c.readState() == connectionActive
}
func (c *Connection) callOnActive() {
log := c.log
if remoteVersion := c.remotePeerInfo.Version; remoteVersion != (PeerVersion{}) {
log = log.WithFields(LogFields{
{"remotePeerLanguage", remoteVersion.Language},
{"remotePeerLanguageVersion", remoteVersion.LanguageVersion},
{"remotePeerTChannelVersion", remoteVersion.TChannelVersion},
}...)
}
log.Info("Created new active connection.")
if f := c.events.OnActive; f != nil {
f(c)
}
if c.opts.HealthChecks.enabled() {
c.healthCheckCtx, c.healthCheckQuit = context.WithCancel(context.Background())
c.healthCheckDone = make(chan struct{})
go c.healthCheck(c.connID)
}
}
func (c *Connection) callOnCloseStateChange() {
if f := c.events.OnCloseStateChange; f != nil {
f(c)
}
}
func (c *Connection) callOnExchangeChange() {
if f := c.events.OnExchangeUpdated; f != nil {
f(c)
}
}
// ping sends a ping message and waits for a ping response.
func (c *Connection) ping(ctx context.Context) error {
req := &pingReq{id: c.NextMessageID()}
mex, err := c.outbound.newExchange(ctx, c.opts.FramePool, req.messageType(), req.ID(), 1)
if err != nil {
return c.connectionError("create ping exchange", err)
}
defer c.outbound.removeExchange(req.ID())
if err := c.sendMessage(req); err != nil {
return c.connectionError("send ping", err)
}
return c.recvMessage(ctx, &pingRes{}, mex)
}
// handlePingRes calls registered ping handlers.
func (c *Connection) handlePingRes(frame *Frame) bool {
if err := c.outbound.forwardPeerFrame(frame); err != nil {
c.log.WithFields(LogField{"response", frame.Header}).Warn("Unexpected ping response.")
return true
}
// ping req is waiting for this frame, and will release it.
return false
}
// handlePingReq responds to the pingReq message with a pingRes.
func (c *Connection) handlePingReq(frame *Frame) {
if state := c.readState(); state != connectionActive {
c.protocolError(frame.Header.ID, errConnNotActive{"ping on incoming", state})
return
}
pingRes := &pingRes{id: frame.Header.ID}
if err := c.sendMessage(pingRes); err != nil {
c.connectionError("send pong", err)
}
}
// sendMessage sends a standalone message (typically a control message)
func (c *Connection) sendMessage(msg message) error {
frame := c.opts.FramePool.Get()
if err := frame.write(msg); err != nil {
c.opts.FramePool.Release(frame)
return err
}
select {
case c.sendCh <- frame:
return nil
default:
return ErrSendBufferFull
}
}
// recvMessage blocks waiting for a standalone response message (typically a
// control message)
func (c *Connection) recvMessage(ctx context.Context, msg message, mex *messageExchange) error {
frame, err := mex.recvPeerFrameOfType(msg.messageType())
if err != nil {
if err, ok := err.(errorMessage); ok {
return err.AsSystemError()
}
return err
}
err = frame.read(msg)
c.opts.FramePool.Release(frame)
return err
}
// RemotePeerInfo returns the peer info for the remote peer.
func (c *Connection) RemotePeerInfo() PeerInfo {
return c.remotePeerInfo
}
// NextMessageID reserves the next available message id for this connection
func (c *Connection) NextMessageID() uint32 {
return c.nextMessageID.Inc()
}
// SendSystemError sends an error frame for the given system error.
func (c *Connection) SendSystemError(id uint32, span Span, err error) error {
frame := c.opts.FramePool.Get()
if err := frame.write(&errorMessage{
id: id,
errCode: GetSystemErrorCode(err),
tracing: span,
message: GetSystemErrorMessage(err),
}); err != nil {
// This shouldn't happen - it means writing the errorMessage is broken.
c.log.WithFields(
LogField{"remotePeer", c.remotePeerInfo},
LogField{"id", id},
ErrField(err),
).Warn("Couldn't create outbound frame.")
return fmt.Errorf("failed to create outbound error frame: %v", err)
}
// When sending errors, we hold the state rlock to ensure that sendCh is not closed
// as we are sending the frame.
return c.withStateRLock(func() error {
// Errors cannot be sent if the connection has been closed.
if c.state == connectionClosed {
c.log.WithFields(
LogField{"remotePeer", c.remotePeerInfo},
LogField{"id", id},
).Info("Could not send error frame on closed connection.")
return fmt.Errorf("failed to send error frame, connection state %v", c.state)
}
select {
case c.sendCh <- frame: // Good to go
return nil
default: // If the send buffer is full, log and return an error.
}
c.log.WithFields(
LogField{"remotePeer", c.remotePeerInfo},
LogField{"id", id},
ErrField(err),
).Warn("Couldn't send outbound frame.")
return fmt.Errorf("failed to send error frame, buffer full")
})
}
func (c *Connection) logConnectionError(site string, err error) error {
errCode := ErrCodeNetwork
if err == io.EOF {
c.log.Debugf("Connection got EOF")
} else {
logger := c.log.WithFields(
LogField{"site", site},
ErrField(err),
)
if se, ok := err.(SystemError); ok && se.Code() != ErrCodeNetwork {
errCode = se.Code()
logger.Error("Connection error.")
} else if ne, ok := err.(net.Error); ok && ne.Timeout() {
logger.Warn("Connection error due to timeout.")
} else {
logger.Info("Connection error.")
}
}
return NewWrappedSystemError(errCode, err)
}
// connectionError handles a connection level error
func (c *Connection) connectionError(site string, err error) error {
var closeLogFields LogFields
if err == io.EOF {
closeLogFields = LogFields{{"reason", "network connection EOF"}}
} else {
closeLogFields = LogFields{
{"reason", "connection error"},
ErrField(err),
}
}
c.stopHealthCheck()
err = c.logConnectionError(site, err)
c.close(closeLogFields...)
// On any connection error, notify the exchanges of this error.
if c.stoppedExchanges.CAS(false, true) {
c.outbound.stopExchanges(err)
c.inbound.stopExchanges(err)
}
// checkExchanges will close the connection due to stoppedExchanges.
c.checkExchanges()
return err
}
func (c *Connection) protocolError(id uint32, err error) error {
c.log.WithFields(ErrField(err)).Warn("Protocol error.")
sysErr := NewWrappedSystemError(ErrCodeProtocol, err)
c.SendSystemError(id, Span{}, sysErr)
// Don't close the connection until the error has been sent.
c.close(
LogField{"reason", "protocol error"},
ErrField(err),
)
// On any connection error, notify the exchanges of this error.
if c.stoppedExchanges.CAS(false, true) {
c.outbound.stopExchanges(sysErr)
c.inbound.stopExchanges(sysErr)
}
return sysErr
}
// withStateLock performs an action with the connection state mutex locked
func (c *Connection) withStateLock(f func() error) error {
c.stateMut.Lock()
err := f()
c.stateMut.Unlock()
return err
}
// withStateRLock performs an action with the connection state mutex rlocked.
func (c *Connection) withStateRLock(f func() error) error {
c.stateMut.RLock()
err := f()
c.stateMut.RUnlock()
return err
}
func (c *Connection) readState() connectionState {
c.stateMut.RLock()
state := c.state
c.stateMut.RUnlock()
return state
}
// readFrames is the loop that reads frames from the network connection and
// dispatches to the appropriate handler. Run within its own goroutine to
// prevent overlapping reads on the socket. Most handlers simply send the
// incoming frame to a channel; the init handlers are a notable exception,
// since we cannot process new frames until the initialization is complete.
func (c *Connection) readFrames(_ uint32) {
headerBuf := make([]byte, FrameHeaderSize)
handleErr := func(err error) {
if !c.closeNetworkCalled.Load() {
c.connectionError("read frames", err)
} else {
c.log.Debugf("Ignoring error after connection was closed: %v", err)
}
}
for {
// Read the header, avoid allocating the frame till we know the size
// we need to allocate.
if _, err := io.ReadFull(c.conn, headerBuf); err != nil {
handleErr(err)
return
}
frame := c.opts.FramePool.Get()
if err := frame.ReadBody(headerBuf, c.conn); err != nil {
handleErr(err)
c.opts.FramePool.Release(frame)
return
}
c.updateLastActivity(frame)
var releaseFrame bool
if c.relay == nil {
releaseFrame = c.handleFrameNoRelay(frame)
} else {
releaseFrame = c.handleFrameRelay(frame)
}
if releaseFrame {
c.opts.FramePool.Release(frame)
}
}
}
func (c *Connection) handleFrameRelay(frame *Frame) bool {
switch frame.Header.messageType {
case messageTypeCallReq, messageTypeCallReqContinue, messageTypeCallRes, messageTypeCallResContinue, messageTypeError:
if err := c.relay.Relay(frame); err != nil {
c.log.WithFields(
ErrField(err),
LogField{"header", frame.Header},
LogField{"remotePeer", c.remotePeerInfo},
).Error("Failed to relay frame.")
}
return false
default:
return c.handleFrameNoRelay(frame)
}
}
func (c *Connection) handleFrameNoRelay(frame *Frame) bool {
releaseFrame := true
// call req and call res messages may not want the frame released immediately.
switch frame.Header.messageType {
case messageTypeCallReq:
releaseFrame = c.handleCallReq(frame)
case messageTypeCallReqContinue:
releaseFrame = c.handleCallReqContinue(frame)
case messageTypeCallRes:
releaseFrame = c.handleCallRes(frame)
case messageTypeCallResContinue:
releaseFrame = c.handleCallResContinue(frame)
case messageTypePingReq:
c.handlePingReq(frame)
case messageTypePingRes:
releaseFrame = c.handlePingRes(frame)
case messageTypeError:
releaseFrame = c.handleError(frame)
default:
// TODO(mmihic): Log and close connection with protocol error
c.log.WithFields(
LogField{"header", frame.Header},
LogField{"remotePeer", c.remotePeerInfo},
).Error("Received unexpected frame.")
}
return releaseFrame
}
// writeFrames is the main loop that pulls frames from the send channel and
// writes them to the connection.
func (c *Connection) writeFrames(_ uint32) {
for {
select {
case f := <-c.sendCh:
if c.log.Enabled(LogLevelDebug) {
c.log.Debugf("Writing frame %s", f.Header)
}
c.updateLastActivity(f)
err := f.WriteOut(c.conn)
c.opts.FramePool.Release(f)
if err != nil {
c.connectionError("write frames", err)
return
}
case <-c.stopCh:
// If there are frames in sendCh, we want to drain them.
if len(c.sendCh) > 0 {
continue
}
// Close the network once we're no longer writing frames.
c.closeNetwork()
return
}
}
}
// updateLastActivity marks when the last message was received/sent on the channel.
// This is used for monitoring idle connections and timing them out.
func (c *Connection) updateLastActivity(frame *Frame) {
// Pings are ignored for last activity.
switch frame.Header.messageType {
case messageTypeCallReq, messageTypeCallReqContinue, messageTypeCallRes, messageTypeCallResContinue, messageTypeError:
c.lastActivity.Store(c.timeNow().UnixNano())
}
}
// hasPendingCalls returns whether there's any pending inbound or outbound calls on this connection.
func (c *Connection) hasPendingCalls() bool {
if c.inbound.count() > 0 || c.outbound.count() > 0 {
return true
}
if !c.relay.canClose() {
return true
}
return false
}
// checkExchanges is called whenever an exchange is removed, and when Close is called.
func (c *Connection) checkExchanges() {
c.callOnExchangeChange()
moveState := func(fromState, toState connectionState) bool {
err := c.withStateLock(func() error {
if c.state != fromState {
return errors.New("")
}
c.state = toState
return nil
})
return err == nil
}
curState := c.readState()
origState := curState
if curState != connectionClosed && c.stoppedExchanges.Load() {
if moveState(curState, connectionClosed) {
curState = connectionClosed
}
}
if curState == connectionStartClose {
if !c.relay.canClose() {
return
}
if c.inbound.count() == 0 && moveState(connectionStartClose, connectionInboundClosed) {
curState = connectionInboundClosed
}
}
if curState == connectionInboundClosed {
// Safety check -- this should never happen since we already did the check
// when transitioning to connectionInboundClosed.
if !c.relay.canClose() {
c.relay.logger.Error("Relay can't close even though state is InboundClosed.")
return
}
if c.outbound.count() == 0 && moveState(connectionInboundClosed, connectionClosed) {
curState = connectionClosed
}
}
if curState != origState {
// If the connection is closed, we can notify writeFrames to stop which
// closes the underlying network connection. We never close sendCh to avoid
// races causing panics, see 93ef5c112c8b321367ae52d2bd79396e2e874f31
if curState == connectionClosed {
close(c.stopCh)
}
c.log.WithFields(
LogField{"newState", curState},
).Debug("Connection state updated during shutdown.")
c.callOnCloseStateChange()
}
}
func (c *Connection) close(fields ...LogField) error {
c.log.WithFields(fields...).Info("Connection closing.")
// Update the state which will start blocking incoming calls.
if err := c.withStateLock(func() error {
switch s := c.state; s {
case connectionActive:
c.state = connectionStartClose
default:
return fmt.Errorf("connection must be Active to Close, but it is %v", s)
}
return nil
}); err != nil {
return err
}
// Set a read deadline with any close timeout. This will cause a i/o timeout
// if the connection isn't closed by then.
if c.opts.MaxCloseTime > 0 {
c.conn.SetReadDeadline(c.timeNow().Add(c.opts.MaxCloseTime))
}
c.log.WithFields(
LogField{"newState", c.readState()},
).Debug("Connection state updated in Close.")
c.callOnCloseStateChange()
// Check all in-flight requests to see whether we can transition the Close state.
c.checkExchanges()
return nil
}
// Close starts a graceful Close which will first reject incoming calls, reject outgoing calls
// before finally marking the connection state as closed.
func (c *Connection) Close() error {
return c.close(LogField{"reason", "user initiated"})
}
// closeNetwork closes the network connection and all network-related channels.
// This should only be done in response to a fatal connection or protocol
// error, or after all pending frames have been sent.
func (c *Connection) closeNetwork() {
// NB(mmihic): The sender goroutine will exit once the connection is
// closed; no need to close the send channel (and closing the send
// channel would be dangerous since other goroutine might be sending)
c.log.Debugf("Closing underlying network connection")
c.stopHealthCheck()
c.closeNetworkCalled.Store(true)
if err := c.conn.Close(); err != nil {
c.log.WithFields(
LogField{"remotePeer", c.remotePeerInfo},
ErrField(err),
).Warn("Couldn't close connection to peer.")
}
}
// getLastActivityTime returns the timestamp of the last frame read or written,
// excluding pings. If no frames were transmitted yet, it will return the time
// this connection was created.
func (c *Connection) getLastActivityTime() time.Time {
return time.Unix(0, c.lastActivity.Load())
}