connection.go
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) 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()
	}
}
// checkExchanges is called whenever an exchange is removed, and when Close is called.