- 引入 LogicalConn/TransportConn 分层,ClientConn 保留兼容适配层 - 新增 Stream、Bulk、RecordStream 三条数据面能力及对应控制路径 - 完成 transfer/file 传输内核与状态快照、诊断能力 - 补齐 reconnect、inbound dispatcher、modern psk 等基础模块 - 增加大规模回归、并发与基准测试覆盖 - 更新依赖库
178 lines
3.4 KiB
Go
178 lines
3.4 KiB
Go
package notify
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
errFileAckCanceled = errors.New("file ack canceled")
|
|
errFileAckTimeout = errors.New("file ack timeout")
|
|
)
|
|
|
|
type fileAckWait struct {
|
|
key string
|
|
scope string
|
|
pool *fileAckPool
|
|
reply chan FileEvent
|
|
closeOnce sync.Once
|
|
}
|
|
|
|
type fileAckPool struct {
|
|
pool sync.Map
|
|
}
|
|
|
|
func newFileAckPool() *fileAckPool {
|
|
return &fileAckPool{}
|
|
}
|
|
|
|
func fileAckKey(scope string, fileID string, stage string, offset int64) string {
|
|
return normalizeFileScope(scope) + "|" + fileID + "|" + stage + "|" + formatInt(offset)
|
|
}
|
|
|
|
func (p *fileAckPool) prepare(scope string, fileID string, stage string, offset int64) *fileAckWait {
|
|
scope = normalizeFileScope(scope)
|
|
wait := &fileAckWait{
|
|
key: fileAckKey(scope, fileID, stage, offset),
|
|
scope: scope,
|
|
pool: p,
|
|
reply: make(chan FileEvent, 1),
|
|
}
|
|
p.pool.Store(wait.key, wait)
|
|
return wait
|
|
}
|
|
|
|
func (p *fileAckPool) deliver(scope string, event FileEvent) bool {
|
|
return p.deliverAny([]string{scope}, event)
|
|
}
|
|
|
|
func (p *fileAckPool) deliverAny(scopes []string, event FileEvent) bool {
|
|
if p == nil {
|
|
return false
|
|
}
|
|
for _, scope := range scopes {
|
|
key := fileAckKey(scope, event.Packet.FileID, event.Packet.Stage, event.Packet.Offset)
|
|
data, ok := p.pool.LoadAndDelete(key)
|
|
if !ok {
|
|
continue
|
|
}
|
|
wait := data.(*fileAckWait)
|
|
wait.deliver(event)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (w *fileAckWait) cancel() {
|
|
if w == nil {
|
|
return
|
|
}
|
|
if w.pool != nil {
|
|
w.pool.pool.Delete(w.key)
|
|
}
|
|
w.closeReply()
|
|
}
|
|
|
|
func (w *fileAckWait) deliver(event FileEvent) {
|
|
if w == nil {
|
|
return
|
|
}
|
|
w.closeOnce.Do(func() {
|
|
select {
|
|
case w.reply <- event:
|
|
default:
|
|
}
|
|
close(w.reply)
|
|
})
|
|
}
|
|
|
|
func (w *fileAckWait) closeReply() {
|
|
if w == nil {
|
|
return
|
|
}
|
|
w.closeOnce.Do(func() {
|
|
close(w.reply)
|
|
})
|
|
}
|
|
|
|
func (p *fileAckPool) waitPrepared(wait *fileAckWait, timeout time.Duration) error {
|
|
if timeout <= 0 {
|
|
timeout = defaultFileAckTimeout
|
|
}
|
|
timer := time.NewTimer(timeout)
|
|
defer timer.Stop()
|
|
select {
|
|
case event, ok := <-wait.reply:
|
|
if !ok {
|
|
return errFileAckCanceled
|
|
}
|
|
if event.Err != nil {
|
|
return event.Err
|
|
}
|
|
if event.Packet.Error != "" {
|
|
return errors.New(event.Packet.Error)
|
|
}
|
|
return nil
|
|
case <-timer.C:
|
|
wait.cancel()
|
|
return errFileAckTimeout
|
|
}
|
|
}
|
|
|
|
func (p *fileAckPool) wait(scope string, fileID string, stage string, offset int64, timeout time.Duration) error {
|
|
wait := p.prepare(scope, fileID, stage, offset)
|
|
return p.waitPrepared(wait, timeout)
|
|
}
|
|
|
|
func (p *fileAckPool) closeAll() {
|
|
if p == nil {
|
|
return
|
|
}
|
|
p.pool.Range(func(_, value interface{}) bool {
|
|
value.(*fileAckWait).cancel()
|
|
return true
|
|
})
|
|
}
|
|
|
|
func (p *fileAckPool) closeScope(scope string) {
|
|
if p == nil {
|
|
return
|
|
}
|
|
scope = normalizeFileScope(scope)
|
|
p.pool.Range(func(_, value interface{}) bool {
|
|
wait := value.(*fileAckWait)
|
|
if wait.scope == scope {
|
|
wait.cancel()
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
func (p *fileAckPool) closeScopeFamily(scope string) {
|
|
if p == nil {
|
|
return
|
|
}
|
|
base := normalizeFileScope(scope)
|
|
p.pool.Range(func(_, value interface{}) bool {
|
|
wait := value.(*fileAckWait)
|
|
if scopeBelongsToServerFileScope(wait.scope, base) {
|
|
wait.cancel()
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
func formatInt(v int64) string {
|
|
return strconv.FormatInt(v, 10)
|
|
}
|
|
|
|
func (c *ClientCommon) getFileAckPool() *fileAckPool {
|
|
return c.getLogicalSessionState().fileAckWaits
|
|
}
|
|
|
|
func (s *ServerCommon) getFileAckPool() *fileAckPool {
|
|
return s.getLogicalSessionState().fileAckWaits
|
|
}
|