Files
ziti/controller/handler_ctrl/dial_state.go
T

111 lines
2.9 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 handler_ctrl
import (
"math/rand/v2"
"sync/atomic"
"time"
"github.com/openziti/ziti/v2/controller/config"
)
type routerDialStatus int
const (
statusNeedsDial routerDialStatus = iota
statusDialing
statusConnected
)
func (s routerDialStatus) String() string {
switch s {
case statusNeedsDial:
return "NeedsDial"
case statusDialing:
return "Dialing"
case statusConnected:
return "Connected"
default:
return "Unknown"
}
}
type routerDialState struct {
routerId string
addresses []string
addrIndex int
status routerDialStatus
retryDelay time.Duration
nextDial time.Time
dialAttempts uint32
connectedAt time.Time
dialActive atomic.Bool
}
func (self *routerDialState) currentAddress() string {
return self.addresses[self.addrIndex%len(self.addresses)]
}
func (self *routerDialState) nextAddress() {
self.addrIndex = (self.addrIndex + 1) % len(self.addresses)
}
func (self *routerDialState) dialFailed(cfg *config.CtrlDialerConfig) {
self.status = statusNeedsDial
self.dialAttempts++
factor := cfg.RetryBackoffFactor + (rand.Float64() - 0.5)
if factor < 1 {
factor = 1
}
self.retryDelay = time.Duration(float64(self.retryDelay) * factor)
if self.retryDelay < cfg.MinRetryInterval {
self.retryDelay = cfg.MinRetryInterval
}
if self.retryDelay > cfg.MaxRetryInterval {
self.retryDelay = cfg.MaxRetryInterval
}
self.nextDial = time.Now().Add(self.retryDelay)
self.nextAddress()
}
func (self *routerDialState) dialSucceeded() {
self.status = statusConnected
self.connectedAt = time.Now()
// retryDelay is intentionally NOT reset here. If the connection dies within
// FastFailureWindow, connectionLost() will continue the backoff progression.
// retryDelay gets reset in connectionLost() for normal (long-lived) disconnects,
// and in evaluateDialState() when the connection survives past FastFailureWindow.
}
func (self *routerDialState) connectionLost(cfg *config.CtrlDialerConfig) {
if self.status == statusConnected && time.Since(self.connectedAt) < cfg.FastFailureWindow {
// fast failure: connection died quickly, treat as a dial failure with backoff
self.dialFailed(cfg)
return
}
// normal disconnection: reset backoff and address index, retry immediately
self.status = statusNeedsDial
self.retryDelay = 0
self.addrIndex = 0
self.nextDial = time.Now()
}