fix(session): persist new session before index mutation

This commit is contained in:
mingmxren 2026-03-01 12:12:51 +08:00
parent bcd9efad98
commit ab438d453c
2 changed files with 71 additions and 8 deletions

View file

@ -89,10 +89,27 @@ func (sm *SessionManager) StartNew(scopeKey string) (string, error) {
defer sm.mu.Unlock()
now := time.Now()
scope, _ := sm.ensureScopeLocked(scopeKey, now)
prevScopeEntryExists := false
prevScopeEntryWasNil := false
var prevScopeSnapshot *scopeIndex
if sm.index.Scopes != nil {
if existingScope, ok := sm.index.Scopes[scopeKey]; ok {
prevScopeEntryExists = true
if existingScope == nil {
prevScopeEntryWasNil = true
} else {
prevScopeSnapshot = cloneScopeIndex(existingScope)
}
}
}
orderedForOrdinal := []string{scopeKey}
if prevScopeSnapshot != nil && len(prevScopeSnapshot.OrderedSessions) > 0 {
orderedForOrdinal = prevScopeSnapshot.OrderedSessions
}
newOrdinal := 2
for _, existing := range scope.OrderedSessions {
for _, existing := range orderedForOrdinal {
ordinal, ok := sessionOrdinal(scopeKey, existing)
if !ok {
continue
@ -121,16 +138,23 @@ func (sm *SessionManager) StartNew(scopeKey string) (string, error) {
return "", err
}
prevActive := scope.ActiveSessionKey
prevOrdered := append([]string(nil), scope.OrderedSessions...)
prevUpdated := scope.UpdatedAt
scope, _ := sm.ensureScopeLocked(scopeKey, now)
scope.ActiveSessionKey = newSessionKey
scope.OrderedSessions = prependSessionUnique(scope.OrderedSessions, newSessionKey)
scope.UpdatedAt = now
if err := sm.saveIndexLocked(); err != nil {
scope.ActiveSessionKey = prevActive
scope.OrderedSessions = prevOrdered
scope.UpdatedAt = prevUpdated
if prevScopeEntryExists {
if sm.index.Scopes == nil {
sm.index.Scopes = make(map[string]*scopeIndex)
}
if prevScopeEntryWasNil {
sm.index.Scopes[scopeKey] = nil
} else {
sm.index.Scopes[scopeKey] = cloneScopeIndex(prevScopeSnapshot)
}
} else if sm.index.Scopes != nil {
delete(sm.index.Scopes, scopeKey)
}
if created {
delete(sm.sessions, newSessionKey)
_ = sm.deleteSessionFile(newSessionKey)
@ -625,6 +649,18 @@ func prependSessionUnique(ordered []string, sessionKey string) []string {
return next
}
func cloneScopeIndex(scope *scopeIndex) *scopeIndex {
if scope == nil {
return nil
}
cloned := &scopeIndex{
ActiveSessionKey: scope.ActiveSessionKey,
UpdatedAt: scope.UpdatedAt,
}
cloned.OrderedSessions = append([]string(nil), scope.OrderedSessions...)
return cloned
}
func cloneSession(stored *Session) Session {
snapshot := Session{
Key: stored.Key,

View file

@ -175,6 +175,33 @@ func TestStartNew_PersistsSessionFileWithoutManualSave(t *testing.T) {
}
}
func TestStartNew_DoesNotMutateIndexWhenSessionPersistFails(t *testing.T) {
dir := t.TempDir()
sm := NewSessionManager(dir)
scope := "../invalid/scope"
_, err := sm.StartNew(scope)
if err == nil {
t.Fatalf("expected StartNew to fail for invalid persisted session key")
}
if _, exists := sm.index.Scopes[scope]; exists {
t.Fatalf("scope %q should not be added to index on session persist failure", scope)
}
if _, exists := sm.sessions[scope+"#2"]; exists {
t.Fatalf("session %q should not remain in memory on session persist failure", scope+"#2")
}
files, err := os.ReadDir(dir)
if err != nil {
t.Fatalf("read dir %q: %v", dir, err)
}
if len(files) != 0 {
t.Fatalf("storage should stay untouched, found files: %v", files)
}
}
func TestListAndResume_ByScopeOrdinal(t *testing.T) {
sm := NewSessionManager(t.TempDir())
scope := "agent:main:telegram:direct:user1"