Agent startup holds the manager's global lock

This commit is contained in:
Ian Gulliver
2026-10-16 12:56:53 -07:00
parent 3da0394116
commit be4b4dda0d
5 changed files with 125 additions and 20 deletions
+78 -10
View File
@@ -40,6 +40,7 @@ type Manager struct {
mu sync.Mutex
live map[string]*live
starting map[string]*start
closing bool
rateLimits map[api.AgentSessionVendor]map[string]any
leadAnswered map[string]bool
@@ -49,9 +50,10 @@ type Manager struct {
startedTodo map[string]time.Time
finishing map[string]bool
deliverMu sync.Mutex
leadMu sync.Mutex
signals chan []api.Event
deliverMu sync.Mutex
deliverLocks map[string]*sessionLock
leadMu sync.Mutex
signals chan []api.Event
snapMu sync.Mutex
snaps map[string]*snapState
@@ -96,6 +98,14 @@ type live struct {
contextWarned bool
}
type start struct {
done chan struct{}
cancel context.CancelFunc
l *live
err error
stopping bool
}
func (l *live) implementer() bool {
return l.owner == "" && l.reviewID == ""
}
@@ -107,6 +117,8 @@ func New(st *store.Store, worktreeRoot, leadRoot, codexHome string) *Manager {
leadRoot: leadRoot,
codexHome: codexHome,
live: map[string]*live{},
starting: map[string]*start{},
deliverLocks: map[string]*sessionLock{},
rateLimits: map[api.AgentSessionVendor]map[string]any{},
leadAnswered: map[string]bool{},
paneFocus: map[string]api.PaneFocus{},
@@ -270,6 +282,10 @@ func (m *Manager) stopLive(sessionID string) {
if l != nil {
l.stopping = true
}
st := m.starting[sessionID]
if st != nil {
st.stopping = true
}
m.mu.Unlock()
if l != nil {
@@ -406,11 +422,21 @@ func (m *Manager) Close() {
for _, l := range m.live {
all = append(all, l)
}
pending := []*start{}
for _, st := range m.starting {
pending = append(pending, st)
}
m.mu.Unlock()
for _, st := range pending {
st.cancel()
}
for _, l := range all {
l.sess.kill()
}
for _, st := range pending {
<-st.done
}
err := m.parkLeads(context.Background())
if err != nil {
@@ -576,23 +602,68 @@ func (m *Manager) prepareMessage(ctx context.Context, l *live, actor, text strin
func (m *Manager) ensureLive(ctx context.Context, sess api.AgentSession, task api.Task) (*live, error) {
m.mu.Lock()
defer m.mu.Unlock()
if m.closing {
m.mu.Unlock()
return nil, errors.New("shutting down")
}
l := m.live[sess.Id]
if l != nil {
m.mu.Unlock()
return l, nil
}
st := m.starting[sess.Id]
if st != nil {
m.mu.Unlock()
select {
case <-st.done:
return st.l, st.err
case <-ctx.Done():
return nil, ctx.Err()
}
}
sctx, cancel := context.WithCancel(ctx)
st = &start{done: make(chan struct{}), cancel: cancel}
m.starting[sess.Id] = st
m.mu.Unlock()
l, err := m.startLive(sctx, sess, task)
cancel()
m.mu.Lock()
delete(m.starting, sess.Id)
closing := m.closing
stopping := st.stopping
if err == nil && !closing {
l.stopping = stopping
m.live[sess.Id] = l
}
m.mu.Unlock()
if err == nil && closing {
go l.sess.discard()
l.sess.kill()
l, err = nil, errors.New("shutting down")
}
st.l, st.err = l, err
close(st.done)
if err != nil {
return nil, err
}
go m.pump(l)
if stopping {
l.sess.kill()
}
return l, nil
}
func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api.Task) (*live, error) {
creator, err := m.store.GetUser(ctx, sess.CreatedBy)
if err != nil {
return nil, err
}
l = &live{
l := &live{
id: sess.Id,
dir: task.WorktreePath,
model: sess.Model,
@@ -661,9 +732,6 @@ func (m *Manager) ensureLive(ctx context.Context, sess api.AgentSession, task ap
if err != nil {
return nil, err
}
m.live[sess.Id] = l
go m.pump(l)
return l, nil
}