feat: add phone frequency controls and modularize codebase
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
cmpp "github.com/bigwhite/gocmpp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A window token belongs to one physical connection and must be released only
|
||||
// after its Submit attempt completes, preserving per-connection CMPP flow control.
|
||||
|
||||
func (p *connectionPool) acquireConnection(ctx context.Context) (*connection, func(), error) {
|
||||
waitCtx, cancel := context.WithTimeout(ctx, defaultSubmitTimeout)
|
||||
defer cancel()
|
||||
ticker := time.NewTicker(10 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
if conn, release := p.tryAcquireConnection(); conn != nil {
|
||||
if connected, err := conn.ensureConnected(); err != nil {
|
||||
release()
|
||||
select {
|
||||
case <-waitCtx.Done():
|
||||
return nil, nil, waitCtx.Err()
|
||||
case <-ticker.C:
|
||||
continue
|
||||
}
|
||||
} else if connected {
|
||||
_ = p.reportState(context.Background(), "connected", nil)
|
||||
}
|
||||
return conn, release, nil
|
||||
}
|
||||
select {
|
||||
case <-waitCtx.Done():
|
||||
return nil, nil, waitCtx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *connectionPool) tryAcquireConnection() (*connection, func()) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if len(p.conns) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
for i := 0; i < len(p.conns); i++ {
|
||||
index := (p.next + i) % len(p.conns)
|
||||
conn := p.conns[index]
|
||||
if conn.tryAcquireWindow() {
|
||||
p.next = (index + 1) % len(p.conns)
|
||||
return conn, conn.releaseWindow
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *connection) tryAcquireWindow() bool {
|
||||
if c.window == nil {
|
||||
c.window = make(chan struct{}, defaultWindowSize)
|
||||
}
|
||||
select {
|
||||
case c.window <- struct{}{}:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) releaseWindow() {
|
||||
if c.window == nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-c.window:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) heartbeatLoop(ctx context.Context) {
|
||||
interval := time.Duration(c.config.HeartbeatIntervalSeconds) * time.Second
|
||||
if interval <= 0 {
|
||||
interval = defaultHeartbeatInterval
|
||||
}
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
if !c.sendHeartbeat() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) sendHeartbeat() bool {
|
||||
threshold := c.config.HeartbeatMissThreshold
|
||||
if threshold <= 0 {
|
||||
threshold = defaultHeartbeatMissThreshold
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.closed {
|
||||
c.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
if len(c.heartbeatPending) >= threshold {
|
||||
c.mu.Unlock()
|
||||
c.handleConnectionLoss(fmt.Errorf("heartbeat timeout after %d unanswered ACTIVE_TEST requests", threshold))
|
||||
return false
|
||||
}
|
||||
if c.client == nil {
|
||||
c.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
c.sendMu.Lock()
|
||||
seq, err := c.client.SendReqPkt(&cmpp.CmppActiveTestReqPkt{})
|
||||
c.sendMu.Unlock()
|
||||
if err != nil {
|
||||
c.mu.Unlock()
|
||||
c.handleConnectionLoss(fmt.Errorf("send ACTIVE_TEST: %w", err))
|
||||
return false
|
||||
}
|
||||
if !c.closed {
|
||||
c.heartbeatPending[seq] = time.Now().UTC()
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *connection) handleHeartbeatResponse(sequenceID uint32) {
|
||||
c.mu.Lock()
|
||||
delete(c.heartbeatPending, sequenceID)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
Reference in New Issue
Block a user