feat: deliver durable live signal intents
This commit is contained in:
@@ -142,13 +142,14 @@ func (server *AgentServer) serveConnection(parent context.Context, connection *w
|
||||
var capacityMu sync.Mutex
|
||||
reservations := make(map[string]DispatchLane)
|
||||
stdinSent := make(map[string]struct{})
|
||||
signalSent := make(map[string]struct{})
|
||||
reconciled := make(chan struct{})
|
||||
var reconcileOnce sync.Once
|
||||
if !capacity.UpdateAdvertised(0, 0, hello.GetMaxRunningCommands(), hello.GetMaxQueuedCommands()) {
|
||||
server.close(connection, websocket.StatusPolicyViolation, "invalid initial client capacity")
|
||||
return
|
||||
}
|
||||
go server.dispatchLoop(sessionContext, queue, handle.DispatchWake(), reconciled, &capacityMu, capacity, reservations, stdinSent, hello.GetClientId(), hello.GetPlatform(), encodeSessionID(sessionID), registration.Generation, cancel)
|
||||
go server.dispatchLoop(sessionContext, queue, handle.DispatchWake(), reconciled, &capacityMu, capacity, reservations, stdinSent, signalSent, hello.GetClientId(), hello.GetPlatform(), encodeSessionID(sessionID), registration.Generation, cancel)
|
||||
encodedSessionID := encodeSessionID(sessionID)
|
||||
welcome, err := proto.Marshal(&rvboxv1.AgentEnvelope{
|
||||
SessionId: encodedSessionID, SessionGeneration: registration.Generation,
|
||||
@@ -273,6 +274,13 @@ func (server *AgentServer) serveConnection(parent context.Context, connection *w
|
||||
capacityMu.Unlock()
|
||||
handle.SignalDispatch()
|
||||
}
|
||||
if signalResult := event.GetSignalResult(); signalResult != nil {
|
||||
issue, _ := domain.ParseUUIDv7(event.GetIssueUuid())
|
||||
capacityMu.Lock()
|
||||
delete(signalSent, signalIntentKey(issue, signalResult.GetCommandRevision(), signalResult.GetSignal()))
|
||||
capacityMu.Unlock()
|
||||
handle.SignalDispatch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,10 +430,49 @@ func stdinIntentKey(issue domain.UUID, writeSeq uint64) string {
|
||||
return issue.String() + ":" + fmt.Sprint(writeSeq)
|
||||
}
|
||||
|
||||
func signalIntentKey(issue domain.UUID, revision uint64, signal rvboxv1.SignalKind) string {
|
||||
return issue.String() + ":" + fmt.Sprint(revision) + ":" + fmt.Sprint(signal)
|
||||
}
|
||||
|
||||
func (server *AgentServer) enqueueNextSignal(ctx context.Context, queue *WriterQueue, clientID, sessionID string, generation uint64, sent map[string]struct{}, sentMu *sync.Mutex) (bool, error) {
|
||||
intents, err := server.Store.PendingSignals(ctx, clientID, generation)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, intent := range intents {
|
||||
key := signalIntentKey(intent.IssueUUID, intent.CommandRevision, intent.Signal)
|
||||
sentMu.Lock()
|
||||
_, alreadySent := sent[key]
|
||||
if !alreadySent {
|
||||
sent[key] = struct{}{}
|
||||
}
|
||||
sentMu.Unlock()
|
||||
if alreadySent {
|
||||
continue
|
||||
}
|
||||
envelope := &rvboxv1.AgentEnvelope{SessionId: sessionID, SessionGeneration: generation, Payload: &rvboxv1.AgentEnvelope_SignalCommand{SignalCommand: &rvboxv1.SignalCommand{IssueUuid: intent.IssueUUID.String(), CommandRevision: intent.CommandRevision, Signal: intent.Signal}}}
|
||||
encoded, marshalErr := proto.Marshal(envelope)
|
||||
if marshalErr != nil {
|
||||
sentMu.Lock()
|
||||
delete(sent, key)
|
||||
sentMu.Unlock()
|
||||
return false, marshalErr
|
||||
}
|
||||
if enqueueErr := queue.EnqueueControl(Frame{Kind: FrameControl, Payload: encoded}); enqueueErr != nil {
|
||||
sentMu.Lock()
|
||||
delete(sent, key)
|
||||
sentMu.Unlock()
|
||||
return false, enqueueErr
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// dispatchLoop is the per-session serialized dispatcher. It waits for a
|
||||
// complete reconciliation result before consuming queued work, then coalesces
|
||||
// wakeups from local control RPCs, capacity advertisements, and acceptances.
|
||||
func (server *AgentServer) dispatchLoop(ctx context.Context, queue *WriterQueue, wake <-chan struct{}, reconciled <-chan struct{}, capacityMu *sync.Mutex, capacity *CapacityShadow, reservations map[string]DispatchLane, stdinSent map[string]struct{}, clientID string, platform rvboxv1.Platform, sessionID string, generation uint64, cancel context.CancelFunc) {
|
||||
func (server *AgentServer) dispatchLoop(ctx context.Context, queue *WriterQueue, wake <-chan struct{}, reconciled <-chan struct{}, capacityMu *sync.Mutex, capacity *CapacityShadow, reservations map[string]DispatchLane, stdinSent, signalSent map[string]struct{}, clientID string, platform rvboxv1.Platform, sessionID string, generation uint64, cancel context.CancelFunc) {
|
||||
select {
|
||||
case <-reconciled:
|
||||
case <-ctx.Done():
|
||||
@@ -441,6 +488,14 @@ func (server *AgentServer) dispatchLoop(ctx context.Context, queue *WriterQueue,
|
||||
if stdinQueued {
|
||||
continue
|
||||
}
|
||||
signalQueued, signalErr := server.enqueueNextSignal(ctx, queue, clientID, sessionID, generation, signalSent, capacityMu)
|
||||
if signalErr != nil {
|
||||
server.closeForDispatchFailure(cancel)
|
||||
return
|
||||
}
|
||||
if signalQueued {
|
||||
continue
|
||||
}
|
||||
capacityMu.Lock()
|
||||
lane := capacity.Reserve()
|
||||
capacityMu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user