fix(session): persist new session before index mutation
This commit is contained in:
parent
bcd9efad98
commit
ab438d453c
2 changed files with 71 additions and 8 deletions
|
|
@ -89,10 +89,27 @@ func (sm *SessionManager) StartNew(scopeKey string) (string, error) {
|
||||||
defer sm.mu.Unlock()
|
defer sm.mu.Unlock()
|
||||||
|
|
||||||
now := time.Now()
|
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
|
newOrdinal := 2
|
||||||
for _, existing := range scope.OrderedSessions {
|
for _, existing := range orderedForOrdinal {
|
||||||
ordinal, ok := sessionOrdinal(scopeKey, existing)
|
ordinal, ok := sessionOrdinal(scopeKey, existing)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
|
|
@ -121,16 +138,23 @@ func (sm *SessionManager) StartNew(scopeKey string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
prevActive := scope.ActiveSessionKey
|
scope, _ := sm.ensureScopeLocked(scopeKey, now)
|
||||||
prevOrdered := append([]string(nil), scope.OrderedSessions...)
|
|
||||||
prevUpdated := scope.UpdatedAt
|
|
||||||
scope.ActiveSessionKey = newSessionKey
|
scope.ActiveSessionKey = newSessionKey
|
||||||
scope.OrderedSessions = prependSessionUnique(scope.OrderedSessions, newSessionKey)
|
scope.OrderedSessions = prependSessionUnique(scope.OrderedSessions, newSessionKey)
|
||||||
scope.UpdatedAt = now
|
scope.UpdatedAt = now
|
||||||
if err := sm.saveIndexLocked(); err != nil {
|
if err := sm.saveIndexLocked(); err != nil {
|
||||||
scope.ActiveSessionKey = prevActive
|
if prevScopeEntryExists {
|
||||||
scope.OrderedSessions = prevOrdered
|
if sm.index.Scopes == nil {
|
||||||
scope.UpdatedAt = prevUpdated
|
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 {
|
if created {
|
||||||
delete(sm.sessions, newSessionKey)
|
delete(sm.sessions, newSessionKey)
|
||||||
_ = sm.deleteSessionFile(newSessionKey)
|
_ = sm.deleteSessionFile(newSessionKey)
|
||||||
|
|
@ -625,6 +649,18 @@ func prependSessionUnique(ordered []string, sessionKey string) []string {
|
||||||
return next
|
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 {
|
func cloneSession(stored *Session) Session {
|
||||||
snapshot := Session{
|
snapshot := Session{
|
||||||
Key: stored.Key,
|
Key: stored.Key,
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
func TestListAndResume_ByScopeOrdinal(t *testing.T) {
|
||||||
sm := NewSessionManager(t.TempDir())
|
sm := NewSessionManager(t.TempDir())
|
||||||
scope := "agent:main:telegram:direct:user1"
|
scope := "agent:main:telegram:direct:user1"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue