refactor(mtproto): sync redesign RPC replay ownership
This commit is contained in:
parent
7e0f9d1e62
commit
ac0566f779
28 changed files with 1304 additions and 343 deletions
186
internal/mtprotoedge/logical_session.go
Normal file
186
internal/mtprotoedge/logical_session.go
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
package mtprotoedge
|
||||
|
||||
import "time"
|
||||
|
||||
// logicalSession is the process-local MTProto session that survives physical
|
||||
// transport replacement. The outbound state is the sole owner of every
|
||||
// content-related server message until msgs_ack, explicit session destruction,
|
||||
// or the bounded offline-retention window expires.
|
||||
type logicalSession struct {
|
||||
key sessionKey
|
||||
outbound *outboundState
|
||||
offlineAt time.Time
|
||||
businessAuthKeyID [8]byte
|
||||
businessAuthResolved bool
|
||||
}
|
||||
|
||||
const logicalSessionOfflineTTL = 6 * time.Minute
|
||||
|
||||
func (m *SessionManager) attachLogicalSession(c *Conn, budget *outboundTrackedBudget) {
|
||||
if m == nil || c == nil {
|
||||
return
|
||||
}
|
||||
key := connSessionKey(c)
|
||||
m.mu.Lock()
|
||||
logical := m.logicalSessions[key]
|
||||
if logical == nil {
|
||||
logical = &logicalSession{
|
||||
key: key,
|
||||
outbound: newOutboundState(budget),
|
||||
}
|
||||
m.logicalSessions[key] = logical
|
||||
}
|
||||
logical.outbound.persistent.Store(true)
|
||||
if businessAuthKeyID, resolved := c.BusinessAuthKeyID(); resolved {
|
||||
logical.businessAuthKeyID = businessAuthKeyID
|
||||
logical.businessAuthResolved = true
|
||||
}
|
||||
logical.offlineAt = time.Time{}
|
||||
c.outboundState = logical.outbound
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// adoptLogicalSession is a construction-test/embedded-Conn bridge. Production
|
||||
// Conns are attached before their actor starts; direct Conn builders may already
|
||||
// own an actor-local state when a Server terminal callback publishes the receipt.
|
||||
func (m *SessionManager) adoptLogicalSession(c *Conn) {
|
||||
if m == nil || c == nil || c.outboundState == nil {
|
||||
return
|
||||
}
|
||||
key := connSessionKey(c)
|
||||
m.mu.Lock()
|
||||
logical := m.logicalSessions[key]
|
||||
if logical == nil {
|
||||
logical = &logicalSession{key: key, outbound: c.outboundState}
|
||||
m.logicalSessions[key] = logical
|
||||
}
|
||||
logical.outbound.persistent.Store(true)
|
||||
if businessAuthKeyID, resolved := c.BusinessAuthKeyID(); resolved {
|
||||
logical.businessAuthKeyID = businessAuthKeyID
|
||||
logical.businessAuthResolved = true
|
||||
}
|
||||
logical.offlineAt = time.Time{}
|
||||
// The actor's state pointer is immutable after startOutbound. Production
|
||||
// attaches before start; this adoption bridge only marks that already-owned
|
||||
// state persistent and must never write the Conn field concurrently.
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *SessionManager) markLogicalSessionOfflineLocked(key sessionKey, now time.Time) {
|
||||
logical := m.logicalSessions[key]
|
||||
if logical == nil || m.bySession[key] != nil || m.claims[key] != nil {
|
||||
return
|
||||
}
|
||||
if logical.offlineAt.IsZero() {
|
||||
logical.offlineAt = now
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SessionManager) destroyLogicalSessionLocked(key sessionKey) *outboundState {
|
||||
logical := m.logicalSessions[key]
|
||||
if logical == nil {
|
||||
return nil
|
||||
}
|
||||
delete(m.logicalSessions, key)
|
||||
return logical.outbound
|
||||
}
|
||||
|
||||
func (m *SessionManager) bindLogicalSessionAuthKeyLocked(key sessionKey, businessAuthKeyID [8]byte) {
|
||||
if logical := m.logicalSessions[key]; logical != nil {
|
||||
logical.businessAuthKeyID = businessAuthKeyID
|
||||
logical.businessAuthResolved = true
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SessionManager) releaseLogicalSession(key sessionKey, state *outboundState) {
|
||||
if state != nil {
|
||||
state.releaseAll()
|
||||
}
|
||||
m.mu.RLock()
|
||||
hook := m.logicalSessionReleased
|
||||
m.mu.RUnlock()
|
||||
if hook != nil {
|
||||
hook(key)
|
||||
}
|
||||
}
|
||||
|
||||
// ForgetLogicalSessionsForRawAuthKey is the terminal auth-key destruction path.
|
||||
// Once destroy_auth_key_ok is on the wire the key can never reconnect to ACK or
|
||||
// replay an old answer, so retaining any session payload or receipt is useless.
|
||||
func (m *SessionManager) ForgetLogicalSessionsForRawAuthKey(authKeyID [8]byte) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
var release []*logicalSession
|
||||
m.mu.Lock()
|
||||
for key, logical := range m.logicalSessions {
|
||||
if key.authKeyID != authKeyID {
|
||||
continue
|
||||
}
|
||||
delete(m.logicalSessions, key)
|
||||
if logical != nil {
|
||||
release = append(release, logical)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
for _, logical := range release {
|
||||
m.releaseLogicalSession(logical.key, logical.outbound)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SessionManager) sweepLogicalSessions(now time.Time) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
var release []*logicalSession
|
||||
m.mu.Lock()
|
||||
for key, logical := range m.logicalSessions {
|
||||
if logical == nil || logical.offlineAt.IsZero() || now.Sub(logical.offlineAt) < logicalSessionOfflineTTL {
|
||||
continue
|
||||
}
|
||||
if m.bySession[key] != nil || m.claims[key] != nil {
|
||||
logical.offlineAt = time.Time{}
|
||||
continue
|
||||
}
|
||||
delete(m.logicalSessions, key)
|
||||
release = append(release, logical)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
for _, logical := range release {
|
||||
m.releaseLogicalSession(logical.key, logical.outbound)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SessionManager) releaseAllLogicalSessions() {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
var release []*logicalSession
|
||||
m.mu.Lock()
|
||||
for key, logical := range m.logicalSessions {
|
||||
delete(m.logicalSessions, key)
|
||||
if logical != nil {
|
||||
release = append(release, logical)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
for _, logical := range release {
|
||||
m.releaseLogicalSession(logical.key, logical.outbound)
|
||||
}
|
||||
}
|
||||
|
||||
// rpcResult returns the exact unacknowledged wire result owned by the logical
|
||||
// session. The receipt ledger calls this only after validating request identity.
|
||||
func (m *SessionManager) rpcResult(authKeyID [8]byte, sessionID, reqMsgID int64) (*encodedOutboundMessage, bool) {
|
||||
if m == nil || reqMsgID == 0 {
|
||||
return nil, false
|
||||
}
|
||||
key := sessionKey{authKeyID: authKeyID, sessionID: sessionID}
|
||||
m.mu.RLock()
|
||||
logical := m.logicalSessions[key]
|
||||
m.mu.RUnlock()
|
||||
if logical == nil || logical.outbound == nil {
|
||||
return nil, false
|
||||
}
|
||||
return logical.outbound.rpcResult(reqMsgID)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue