chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
120
internal/app/phone/registry.go
Normal file
120
internal/app/phone/registry.go
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
package phone
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
// registry 是 active call 的进程内权威存储:单实现、不进 store 双实现体系。
|
||||
//
|
||||
// 论证(设计已确认,勿改回双 store):active call 是秒级短命状态,服务端重启后
|
||||
// 客户端侧媒体连接早已断开,「重启恢复半截通话」不是有效需求;单一实现让测试与
|
||||
// 生产共用同一份代码,从构造上消灭 memory/postgres 行为漂移。多实例化时以同样的
|
||||
// 窄接口换 Redis 实现,信令层零改动。
|
||||
type registry struct {
|
||||
mu sync.Mutex
|
||||
byID map[int64]*entry
|
||||
byRandom map[randomKey]int64 // (callerID, randomID) → callID,吸收客户端 RPC 重试
|
||||
active map[int64]int // userID → 非终态通话数(并发上限依据)
|
||||
}
|
||||
|
||||
type randomKey struct {
|
||||
callerID int64
|
||||
randomID int64
|
||||
}
|
||||
|
||||
// entry 持有一通通话;call 字段由 registry.mu 保护。
|
||||
// sigMu 单独串行化该通话的信令转发(锁内做推送入队),与状态锁分离,
|
||||
// 保证 discard 等状态迁移不被对端出站队列堵塞拖住。
|
||||
type entry struct {
|
||||
call domain.PhoneCall
|
||||
|
||||
sigMu sync.Mutex
|
||||
sigWindowSec int64
|
||||
sigCount int
|
||||
}
|
||||
|
||||
func newRegistry() *registry {
|
||||
return ®istry{
|
||||
byID: make(map[int64]*entry),
|
||||
byRandom: make(map[randomKey]int64),
|
||||
active: make(map[int64]int),
|
||||
}
|
||||
}
|
||||
|
||||
// sweepLocked 是 P1 的纯年龄 GC(调用方持有 r.mu):
|
||||
// - 终态 tombstone 超过 tombstoneTTL → 回收(密钥材料随之销毁);
|
||||
// - 非终态超过 2×ringTimeout → 直接回收(双端同时崩溃的兜底,防僵尸通话
|
||||
// 吃满并发上限;不推送、不落历史,正常超时由客户端定时器与 P2 dispatcher 处理)。
|
||||
func (r *registry) sweepLocked(nowUnix int64, ringTimeoutSec, tombstoneTTLSec int64) {
|
||||
for id, e := range r.byID {
|
||||
switch {
|
||||
case e.call.Terminal():
|
||||
if nowUnix-int64(e.call.DiscardedAt) > tombstoneTTLSec {
|
||||
r.removeLocked(id, e, false)
|
||||
}
|
||||
default:
|
||||
if nowUnix-int64(e.call.Date) > 2*ringTimeoutSec {
|
||||
r.removeLocked(id, e, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) removeLocked(id int64, e *entry, wasActive bool) {
|
||||
delete(r.byID, id)
|
||||
delete(r.byRandom, randomKey{callerID: e.call.AdminID, randomID: e.call.RandomID})
|
||||
if wasActive {
|
||||
r.decActiveLocked(e.call.AdminID)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) decActiveLocked(userID int64) {
|
||||
if n := r.active[userID]; n <= 1 {
|
||||
delete(r.active, userID)
|
||||
} else {
|
||||
r.active[userID] = n - 1
|
||||
}
|
||||
}
|
||||
|
||||
// markDiscardedLocked 把非终态 entry 迁入终态并更新并发计数。
|
||||
func (r *registry) markDiscardedLocked(e *entry, reason domain.PhoneCallDiscardReason, duration, nowUnix int) {
|
||||
if e.call.Terminal() {
|
||||
return
|
||||
}
|
||||
e.call.State = domain.PhoneCallStateDiscarded
|
||||
e.call.DiscardReason = reason
|
||||
e.call.Duration = duration
|
||||
e.call.DiscardedAt = nowUnix
|
||||
r.decActiveLocked(e.call.AdminID)
|
||||
}
|
||||
|
||||
// newID 生成 registry 内唯一的正 int64(调用方持有 r.mu)。
|
||||
func (r *registry) newIDLocked() (int64, error) {
|
||||
for i := 0; i < 32; i++ {
|
||||
id, err := randomPositiveInt64()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, exists := r.byID[id]; !exists {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("phone: exhausted call id attempts")
|
||||
}
|
||||
|
||||
func randomPositiveInt64() (int64, error) {
|
||||
var buf [8]byte
|
||||
if _, err := rand.Read(buf[:]); err != nil {
|
||||
return 0, fmt.Errorf("phone: random id: %w", err)
|
||||
}
|
||||
v := int64(binary.BigEndian.Uint64(buf[:]) >> 1)
|
||||
if v == 0 {
|
||||
v = 1
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue