EdgexAgent/device-ble-go/vendor/github.com/openziti/channel/v4/multi.go
2025-07-10 20:40:32 +08:00

641 lines
16 KiB
Go

/*
Copyright NetFoundry Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package channel
import (
"bytes"
"crypto/x509"
"errors"
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/foundation/v2/concurrenz"
"github.com/openziti/foundation/v2/info"
"github.com/openziti/foundation/v2/sequence"
"io"
"net"
"sync"
"sync/atomic"
"time"
)
type MultiChannelConfig struct {
LogicalName string
Options *Options
UnderlayHandler UnderlayHandler
BindHandler BindHandler
Underlay Underlay
}
type senderContextImpl struct {
sequence *sequence.Sequence
closeNotify chan struct{}
}
func (self *senderContextImpl) NextSequence() int32 {
return int32(self.sequence.Next())
}
func (self *senderContextImpl) GetCloseNotify() chan struct{} {
return self.closeNotify
}
func NewSenderContext() SenderContext {
return &senderContextImpl{
sequence: sequence.NewSequence(),
closeNotify: make(chan struct{}),
}
}
type multiChannelImpl struct {
// Note: if altering this struct, be sure to account for 64 bit alignment on 32 bit arm arch
// https://pkg.go.dev/sync/atomic#pkg-note-BUG
// https://github.com/golang/go/issues/36606
lastRead int64
ownerId string
channelId string
logicalName string
certs concurrenz.AtomicValue[[]*x509.Certificate]
headers concurrenz.AtomicValue[map[int32][]byte]
options *Options
waiters waiterMap
flags concurrenz.AtomicBitSet
closeNotify chan struct{}
peekHandlers []PeekHandler
transformHandlers []TransformHandler
receiveHandlers map[int32]ReceiveHandler
errorHandlers []ErrorHandler
closeHandlers []CloseHandler
underlayHandler UnderlayHandler
userData interface{}
replyCounter uint32
groupSecret []byte
lock sync.Mutex
underlays concurrenz.CopyOnWriteSlice[Underlay]
}
func NewMultiChannel(config *MultiChannelConfig) (MultiChannel, error) {
if config.UnderlayHandler == nil {
return nil, fmt.Errorf("no underlay handler configured for multi channel %s", config.LogicalName)
}
if config.Underlay == nil {
return nil, errors.New("unable to initialize multi channel (initialization produced zero underlays)")
}
impl := &multiChannelImpl{
channelId: config.Underlay.ConnectionId(),
logicalName: config.LogicalName,
options: config.Options,
receiveHandlers: map[int32]ReceiveHandler{},
closeNotify: config.UnderlayHandler.GetCloseNotify(),
underlayHandler: config.UnderlayHandler,
}
impl.ownerId = config.Underlay.Id()
impl.certs.Store(config.Underlay.Certificates())
impl.headers.Store(config.Underlay.Headers())
impl.underlays.Append(config.Underlay)
if groupSecret := config.Underlay.Headers()[GroupSecretHeader]; len(groupSecret) == 0 {
return nil, errors.New("no group secret header found for multi channel")
} else {
impl.groupSecret = groupSecret
}
if err := bind(config.BindHandler, impl); err != nil {
for _, u := range impl.underlays.Value() {
if closeErr := u.Close(); closeErr != nil {
if !errors.Is(closeErr, net.ErrClosed) {
pfxlog.ContextLogger(impl.Label()).WithError(err).Warn("error closing underlay")
}
}
}
return nil, err
}
impl.startMultiplex(config.Underlay)
go impl.underlayHandler.Start(impl)
impl.underlayHandler.HandleUnderlayAccepted(impl, config.Underlay)
return impl, nil
}
func (self *multiChannelImpl) AcceptUnderlay(underlay Underlay) error {
self.lock.Lock()
defer self.lock.Unlock()
groupSecret := underlay.Headers()[GroupSecretHeader]
if !bytes.Equal(groupSecret, self.groupSecret) {
if err := underlay.Close(); err != nil {
pfxlog.ContextLogger(self.Label()).WithError(err).Error("error closing underlay")
}
return fmt.Errorf("new underlay for '%s' not accepted: incorrect group secret", self.ConnectionId())
}
if self.IsClosed() {
if err := underlay.Close(); err != nil {
pfxlog.ContextLogger(self.Label()).WithError(err).Error("error closing underlay")
}
return fmt.Errorf("new underlay for '%s' not accepted: multi-channel is closed", self.ConnectionId())
}
self.certs.Store(underlay.Certificates())
self.headers.Store(underlay.Headers())
self.underlays.Append(underlay)
self.startMultiplex(underlay)
self.underlayHandler.HandleUnderlayAccepted(self, underlay)
return nil
}
func (self *multiChannelImpl) startMultiplex(underlay Underlay) {
notifier := NewCloseNotifier()
go self.Rxer(underlay, notifier)
go self.Txer(underlay, notifier)
}
func (self *multiChannelImpl) GetUnderlayCountsByType() map[string]int {
result := map[string]int{}
for _, u := range self.underlays.Value() {
underlayType := GetUnderlayType(u)
result[underlayType]++
}
return result
}
func (self *multiChannelImpl) Send(s Sendable) error {
return self.underlayHandler.GetDefaultSender().Send(s)
}
func (self *multiChannelImpl) TrySend(s Sendable) (bool, error) {
return self.underlayHandler.GetDefaultSender().TrySend(s)
}
func (self *multiChannelImpl) Id() string {
return self.ownerId
}
func (self *multiChannelImpl) LogicalName() string {
return self.logicalName
}
func (self *multiChannelImpl) SetLogicalName(logicalName string) {
self.logicalName = logicalName
}
func (self *multiChannelImpl) ConnectionId() string {
return self.channelId
}
func (self *multiChannelImpl) Certificates() []*x509.Certificate {
return self.certs.Load()
}
func (self *multiChannelImpl) Headers() map[int32][]byte {
return self.headers.Load()
}
func (self *multiChannelImpl) Label() string {
if u := self.Underlay(); u != nil {
return fmt.Sprintf("ch{%s}->%s", self.LogicalName(), u.Label())
} else {
return fmt.Sprintf("ch{%s}->{}", self.LogicalName())
}
}
func (self *multiChannelImpl) GetOptions() *Options {
return self.options
}
func (self *multiChannelImpl) GetChannel() Channel {
return self
}
func (self *multiChannelImpl) Bind(h BindHandler) error {
return h.BindChannel(self)
}
func (self *multiChannelImpl) AddPeekHandler(h PeekHandler) {
self.peekHandlers = append(self.peekHandlers, h)
}
func (self *multiChannelImpl) AddTransformHandler(h TransformHandler) {
self.transformHandlers = append(self.transformHandlers, h)
}
func (self *multiChannelImpl) AddTypedReceiveHandler(h TypedReceiveHandler) {
self.receiveHandlers[h.ContentType()] = h
}
func (self *multiChannelImpl) AddReceiveHandler(contentType int32, h ReceiveHandler) {
self.receiveHandlers[contentType] = h
}
func (self *multiChannelImpl) AddReceiveHandlerF(contentType int32, h ReceiveHandlerF) {
self.AddReceiveHandler(contentType, h)
}
func (self *multiChannelImpl) AddErrorHandler(h ErrorHandler) {
self.errorHandlers = append(self.errorHandlers, h)
}
func (self *multiChannelImpl) AddCloseHandler(h CloseHandler) {
self.closeHandlers = append(self.closeHandlers, h)
}
func (self *multiChannelImpl) SetUserData(data interface{}) {
self.userData = data
}
func (self *multiChannelImpl) GetUserData() interface{} {
return self.userData
}
func (self *multiChannelImpl) GetUnderlayHandler() UnderlayHandler {
return self.underlayHandler
}
func (self *multiChannelImpl) Close() error {
self.lock.Lock()
defer self.lock.Unlock()
if self.flags.CompareAndSet(flagClosed, false, true) {
pfxlog.ContextLogger(self.Label()).Debug("closing channel")
close(self.closeNotify)
for _, peekHandler := range self.peekHandlers {
peekHandler.Close(self)
}
if len(self.closeHandlers) > 0 {
for _, closeHandler := range self.closeHandlers {
closeHandler.HandleClose(self)
}
} else {
pfxlog.ContextLogger(self.Label()).Debug("no close handlers")
}
var errs []error
for _, u := range self.underlays.Value() {
if err := u.Close(); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
return nil
}
func (self *multiChannelImpl) IsClosed() bool {
return self.flags.IsSet(flagClosed)
}
func (self *multiChannelImpl) Underlay() Underlay {
if underlays := self.underlays.Value(); len(underlays) > 0 {
return underlays[0]
}
return nil
}
func (self *multiChannelImpl) Rx(m *Message) {
log := pfxlog.ContextLogger(self.Label())
now := info.NowInMilliseconds()
atomic.StoreInt64(&self.lastRead, now)
for _, transformHandler := range self.transformHandlers {
transformHandler.Rx(m, self)
}
for _, peekHandler := range self.peekHandlers {
peekHandler.Rx(m, self)
}
handled := false
if m.IsReply() {
self.replyCounter++
if self.replyCounter%100 == 0 && self.waiters.Size() > 1000 {
self.waiters.reapExpired(now)
}
replyFor := m.ReplyFor()
if replyReceiver := self.waiters.RemoveWaiter(replyFor); replyReceiver != nil {
log.Tracef("waiter found for message. type [%v], sequence [%v], replyFor [%v]", m.ContentType, m.sequence, replyFor)
replyReceiver.AcceptReply(m)
handled = true
} else {
log.Debugf("no waiter for message. type [%v], sequence [%v], replyFor [%v]", m.ContentType, m.sequence, replyFor)
}
}
if !handled {
if receiveHandler, found := self.receiveHandlers[m.ContentType]; found {
receiveHandler.HandleReceive(m, self)
} else if anyHandler, found := self.receiveHandlers[AnyContentType]; found {
anyHandler.HandleReceive(m, self)
} else {
log.Warnf("dropped message. type [%d], sequence [%v], replyFor [%v]", m.ContentType, m.sequence, m.ReplyFor())
}
}
}
func (self *multiChannelImpl) Tx(underlay Underlay, sendable Sendable, writeTimeout time.Duration) error {
log := pfxlog.ContextLogger(self.Label())
sendListener := sendable.SendListener()
m := sendable.Msg()
if err := sendable.Context().Err(); err != nil {
sendListener.NotifyErr(TimeoutError{err})
return nil
}
sendListener.NotifyBeforeWrite()
if m == nil { // allow nil message in Sendable so we can send tracers to check time from send to write
return nil
}
for _, transformHandler := range self.transformHandlers {
transformHandler.Tx(m, self)
}
self.waiters.AddWaiter(sendable)
var err error
if writeTimeout > 0 {
if err = underlay.SetWriteTimeout(writeTimeout); err != nil {
log.WithError(err).Errorf("unable to set write timeout")
sendListener.NotifyErr(err)
return err
}
}
err = underlay.Tx(m)
if err != nil {
log.WithError(err).Errorf("write error")
self.waiters.RemoveWaiter(m.sequence)
for _, errorHandler := range self.errorHandlers {
errorHandler.HandleError(err, self)
}
// if we were able to requeue it, don't cancel sendable
if !self.underlayHandler.HandleTxFailed(underlay, sendable) {
sendListener.NotifyErr(err)
sendListener.NotifyAfterWrite()
}
return err
}
for _, peekHandler := range self.peekHandlers {
peekHandler.Tx(m, self)
}
sendListener.NotifyAfterWrite()
return nil
}
func (self *multiChannelImpl) closeUnderlay(underlay Underlay, notifier *CloseNotifier) {
self.lock.Lock()
if err := underlay.Close(); err != nil {
pfxlog.Logger().WithField("context", self.Label()).WithError(err).Error("error closing underlay")
}
notifier.NotifyClosed()
underlayRemoved := false
self.underlays.DeleteIf(func(element Underlay) bool {
if underlay == element {
underlayRemoved = true
return true
}
return false
})
self.lock.Unlock()
if underlayRemoved {
self.underlayHandler.HandleUnderlayClose(self, underlay)
}
}
func (self *multiChannelImpl) GetTimeSinceLastRead() time.Duration {
return time.Duration(info.NowInMilliseconds()-atomic.LoadInt64(&self.lastRead)) * time.Millisecond
}
func (self *multiChannelImpl) Txer(underlay Underlay, notifier *CloseNotifier) {
defer self.closeUnderlay(underlay, notifier)
log := pfxlog.ContextLogger(self.Label())
var writeTimeout time.Duration
if options := self.GetOptions(); options != nil {
writeTimeout = options.WriteTimeout
}
messageSource := self.underlayHandler.GetMessageSource(underlay)
for {
sendable, err := messageSource(notifier)
if err != nil {
return
}
if err = self.Tx(underlay, sendable, writeTimeout); err != nil {
if self.IsClosed() {
log.WithError(err).Debug("tx error")
} else {
log.WithError(err).Error("tx error")
}
return
}
}
}
func (self *multiChannelImpl) Rxer(underlay Underlay, notifier *CloseNotifier) {
defer self.closeUnderlay(underlay, notifier)
log := pfxlog.ContextLogger(self.Label())
log.Debug("started")
defer log.Debug("exited")
for {
m, err := underlay.Rx()
if err != nil {
if err == io.EOF {
log.WithError(err).Debug("EOF")
} else if self.IsClosed() {
log.WithError(err).Debug("rx error")
} else {
log.WithError(err).Error("rx error")
}
return
}
self.Rx(m)
}
}
func (self *multiChannelImpl) DialUnderlay(factory GroupedUnderlayFactory, underlayType string) {
log := pfxlog.ContextLogger(self.Label()).WithField("underlayType", underlayType)
attempt := 1
for {
if self.IsClosed() {
log.Info("multi-underlay channel closed, abandoning dial")
}
dialTimeout := self.GetOptions().ConnectTimeout
if dialTimeout == 0 {
dialTimeout = DefaultConnectTimeout
}
underlay, err := factory.CreateGroupedUnderlay(self.ConnectionId(), self.groupSecret, underlayType, dialTimeout)
if err == nil {
if err = self.AcceptUnderlay(underlay); err != nil {
log.WithError(err).Error("dial of new underlay failed")
factory.DialFailed(self, underlayType, attempt)
}
return
} else {
factory.DialFailed(self, underlayType, attempt)
}
attempt++
}
}
func GetUnderlayType(underlay Underlay) string {
return string(underlay.Headers()[TypeHeader])
}
type underlayConstraint struct {
numDesired int
minAllowed int
}
type UnderlayConstraints struct {
types map[string]underlayConstraint
applyInProgress atomic.Bool
}
func (self *UnderlayConstraints) AddConstraint(underlayType string, numDesired int, minAllowed int) {
if self.types == nil {
self.types = make(map[string]underlayConstraint)
}
self.types[underlayType] = underlayConstraint{numDesired, minAllowed}
}
func (self *UnderlayConstraints) CheckStateValid(ch MultiChannel, close bool) bool {
counts := ch.GetUnderlayCountsByType()
return self.countsShowValidState(ch, counts, close)
}
func (self *UnderlayConstraints) countsShowValidState(ch MultiChannel, counts map[string]int, close bool) bool {
for underlayType, constraint := range self.types {
if constraint.minAllowed > counts[underlayType] {
if close {
pfxlog.Logger().WithField("conn", ch.LogicalName()).
WithField("channelId", ch.ConnectionId()).
WithField("label", ch.Label()).
WithField("underlays", counts).
Infof("not enough open underlays of type '%s', closing multi-underlay channel", underlayType)
if err := ch.Close(); err != nil {
pfxlog.Logger().WithError(err).Error("error closing underlay")
}
}
return false
}
}
return true
}
func (self *UnderlayConstraints) Apply(ch MultiChannel, factory GroupedUnderlayFactory) {
pfxlog.Logger().WithField("conn", ch.Label()).
Info("starting constraint check")
if ch.IsClosed() {
return
}
if !self.applyInProgress.CompareAndSwap(false, true) {
return
}
defer self.applyInProgress.Store(false)
for !ch.IsClosed() {
counts := ch.GetUnderlayCountsByType()
if !self.countsShowValidState(ch, counts, true) {
return
}
allSatisfied := true
for underlayType, constraint := range self.types {
pfxlog.Logger().WithField("conn", ch.Label()).
WithField("underlayType", underlayType).
WithField("numDesired", constraint.numDesired).
WithField("current", counts[underlayType]).
Info("checking constraint")
if constraint.numDesired > counts[underlayType] {
pfxlog.Logger().WithField("conn", ch.Label()).
WithField("underlayType", underlayType).
Info("additional connections desired, dialing...")
allSatisfied = false
ch.DialUnderlay(factory, underlayType)
}
}
if allSatisfied {
pfxlog.Logger().WithField("conn", ch.Label()).Info("constraints satisfied")
return
}
}
}
func NewCloseNotifier() *CloseNotifier {
return &CloseNotifier{
c: make(chan struct{}),
}
}
type CloseNotifier struct {
c chan struct{}
notified atomic.Bool
}
func (self *CloseNotifier) NotifyClosed() {
if self.notified.CompareAndSwap(false, true) {
close(self.c)
}
}
func (self *CloseNotifier) GetCloseNotify() <-chan struct{} {
return self.c
}