perf: expand gateway capacity and prevent receipt replay
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"cmpp-platform/gateway/internal/protocollog"
|
||||
"cmpp-platform/gateway/internal/queue"
|
||||
"context"
|
||||
"net/http"
|
||||
@@ -9,12 +10,20 @@ import (
|
||||
)
|
||||
|
||||
type connectionPool struct {
|
||||
channelID string
|
||||
connectionID string
|
||||
config queue.UpstreamConfig
|
||||
apiBaseURL string
|
||||
httpClient *http.Client
|
||||
reporter func(context.Context, ConnectionState) error
|
||||
channelID string
|
||||
connectionID string
|
||||
config queue.UpstreamConfig
|
||||
apiBaseURL string
|
||||
httpClient *http.Client
|
||||
reporter func(context.Context, ConnectionState) error
|
||||
protocolLogPublisher interface {
|
||||
Publish(context.Context, protocollog.Event) error
|
||||
}
|
||||
gatewayInstanceID string
|
||||
eventPublisher interface {
|
||||
PublishReceipt(context.Context, queue.ReceiptEvent) error
|
||||
PublishUplink(context.Context, queue.UplinkEvent) error
|
||||
}
|
||||
|
||||
mu sync.Mutex
|
||||
connectMu sync.Mutex
|
||||
@@ -34,6 +43,103 @@ func (p *connectionPool) matches(config queue.UpstreamConfig) bool {
|
||||
return p.config == normalizeUpstreamConfig(config)
|
||||
}
|
||||
|
||||
func (p *connectionPool) sameEndpoint(config queue.UpstreamConfig) bool {
|
||||
next := normalizeUpstreamConfig(config)
|
||||
current := p.config
|
||||
return current.GatewayHost == next.GatewayHost && current.GatewayPort == next.GatewayPort &&
|
||||
current.Account == next.Account && current.PasswordCipher == next.PasswordCipher && current.CMPPVersion == next.CMPPVersion
|
||||
}
|
||||
|
||||
// reconfigure changes only runtime capacity on the existing pool. Existing
|
||||
// sequence mappings remain attached to their physical connection. Scale down
|
||||
// marks surplus connections draining before closing them; scale up is serialized.
|
||||
func (p *connectionPool) reconfigure(config queue.UpstreamConfig) {
|
||||
next := normalizeUpstreamConfig(config)
|
||||
p.mu.Lock()
|
||||
p.config = next
|
||||
for _, conn := range p.conns {
|
||||
conn.mu.Lock()
|
||||
conn.config = next
|
||||
conn.windowLimit = next.WindowSize
|
||||
conn.mu.Unlock()
|
||||
}
|
||||
p.mu.Unlock()
|
||||
go p.reconcileCapacity()
|
||||
}
|
||||
|
||||
func (p *connectionPool) reconcileCapacity() {
|
||||
p.connectMu.Lock()
|
||||
defer p.connectMu.Unlock()
|
||||
if p.stopped() {
|
||||
return
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
desired := max(1, min(maximumConnections, p.config.DesiredConnections))
|
||||
if len(p.conns) > desired {
|
||||
retiring := append([]*connection(nil), p.conns[desired:]...)
|
||||
p.conns = p.conns[:desired]
|
||||
for _, conn := range retiring {
|
||||
conn.mu.Lock()
|
||||
conn.draining = true
|
||||
conn.mu.Unlock()
|
||||
}
|
||||
p.mu.Unlock()
|
||||
deadline := time.Now().Add(time.Duration(p.config.ConnectionDrainSeconds) * time.Second)
|
||||
for _, conn := range retiring {
|
||||
for len(conn.window) > 0 && time.Now().Before(deadline) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
conn.retire()
|
||||
}
|
||||
_ = p.reportState(context.Background(), "connected", nil)
|
||||
return
|
||||
}
|
||||
p.mu.Unlock()
|
||||
|
||||
for {
|
||||
p.mu.Lock()
|
||||
if len(p.conns) >= desired || p.stopped() {
|
||||
p.mu.Unlock()
|
||||
break
|
||||
}
|
||||
index := len(p.conns)
|
||||
config := p.config
|
||||
p.mu.Unlock()
|
||||
conn := p.newConnection(index, config)
|
||||
if _, err := conn.ensureConnected(); err != nil {
|
||||
p.scheduleReconnect(err)
|
||||
_ = p.reportState(context.Background(), "failed", err)
|
||||
return
|
||||
}
|
||||
p.mu.Lock()
|
||||
p.conns = append(p.conns, conn)
|
||||
p.mu.Unlock()
|
||||
_ = p.reportState(context.Background(), "connected", nil)
|
||||
if len(p.conns) < desired && config.ConnectionWarmupSeconds > 0 {
|
||||
timer := time.NewTimer(time.Duration(config.ConnectionWarmupSeconds) * time.Second)
|
||||
select {
|
||||
case <-p.stopCh:
|
||||
timer.Stop()
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *connectionPool) newConnection(index int, config queue.UpstreamConfig) *connection {
|
||||
return &connection{
|
||||
channelID: p.channelID, config: config, index: index, pool: p,
|
||||
apiBaseURL: p.apiBaseURL, httpClient: p.httpClient,
|
||||
protocolLogPublisher: p.protocolLogPublisher, gatewayInstanceID: p.gatewayInstanceID,
|
||||
eventPublisher: p.eventPublisher,
|
||||
window: make(chan struct{}, maximumWindowSize), windowLimit: config.WindowSize,
|
||||
pending: make(map[uint32]chan submitPartResponse), tracker: make(map[uint64]queue.SubmitCommand),
|
||||
longUplink: make(map[string]*longUplinkAssembly), heartbeatPending: make(map[uint32]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *connectionPool) ensureConnected() error {
|
||||
p.connectMu.Lock()
|
||||
defer p.connectMu.Unlock()
|
||||
@@ -58,20 +164,12 @@ func (p *connectionPool) ensureConnected() error {
|
||||
p.mu.Unlock()
|
||||
break
|
||||
}
|
||||
index := len(p.conns)
|
||||
conn := &connection{
|
||||
channelID: p.channelID,
|
||||
config: p.config,
|
||||
index: index,
|
||||
pool: p,
|
||||
apiBaseURL: p.apiBaseURL,
|
||||
httpClient: p.httpClient,
|
||||
window: make(chan struct{}, p.config.WindowSize),
|
||||
pending: make(map[uint32]chan submitPartResponse),
|
||||
tracker: make(map[uint64]queue.SubmitCommand),
|
||||
longUplink: make(map[string]*longUplinkAssembly),
|
||||
heartbeatPending: make(map[uint32]time.Time),
|
||||
if connectedAny {
|
||||
p.mu.Unlock()
|
||||
break
|
||||
}
|
||||
index := len(p.conns)
|
||||
conn := p.newConnection(index, p.config)
|
||||
p.mu.Unlock()
|
||||
|
||||
connected, err := conn.ensureConnected()
|
||||
@@ -87,6 +185,7 @@ func (p *connectionPool) ensureConnected() error {
|
||||
if connectedAny {
|
||||
_ = p.reportState(context.Background(), "connected", nil)
|
||||
}
|
||||
go p.reconcileCapacity()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user