Relay current process output without spool
This commit is contained in:
+26
-46
@@ -132,7 +132,7 @@ func TestTypedProcessOutputCaptureResumesAfterRunRestart(t *testing.T) {
|
||||
waitForManagedTailers(t, restarted.managed.(*OSManagedProcessSupervisor))
|
||||
}
|
||||
|
||||
func TestManagedProcessOutputAfterCanceledRunContextIsSpooledOnRestart(t *testing.T) {
|
||||
func TestManagedProcessOutputAfterCanceledRunContextIsNotSpooledOnRestart(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
assignment := executionAssignment(protocol.RunCapabilityProcessStart)
|
||||
assignment.ExecutionInput.LogSources = []protocol.RuntimeLogSourcePlan{
|
||||
@@ -199,24 +199,14 @@ func TestManagedProcessOutputAfterCanceledRunContextIsSpooledOnRestart(t *testin
|
||||
WithProcessLogSink(&SpoolLogSink{RunEndpointID: assignment.RunEndpointID, SessionToken: "new-session", Spool: restartedSpool}),
|
||||
)
|
||||
restarted.ResumeManagedProcessLogs(context.Background())
|
||||
waitForSpooledText(t, restartedSpool, "managed stdout triggered after-cancel", "managed stderr triggered after-cancel")
|
||||
restartedManaged := restarted.managed.(*OSManagedProcessSupervisor)
|
||||
resumed := waitForManagedOffsets(t, restartedManaged, scope, identity.StdoutOffset, identity.StderrOffset)
|
||||
if resumed.LogSessionID != identity.LogSessionID || resumed.StdoutOffset <= identity.StdoutOffset || resumed.StderrOffset <= identity.StderrOffset {
|
||||
t.Fatalf("restart did not retain session and commit offsets: before=%+v after=%+v", identity, resumed)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
if pending := mustPendingLogs(t, restartedSpool); len(pending) != 0 {
|
||||
t.Fatalf("restart replayed output observed before Run restart: %+v", pending)
|
||||
}
|
||||
for _, batch := range mustPendingLogs(t, restartedSpool) {
|
||||
if batch.LogSessionID != identity.LogSessionID || !batch.SessionStartedAt.Equal(identity.StartedAt) {
|
||||
t.Fatalf("spooled batch lost process session metadata: %+v", batch)
|
||||
}
|
||||
if batch.FirstSeq != 1 || len(batch.Entries) == 0 || batch.Entries[0].Seq != 1 {
|
||||
t.Fatalf("fresh generation stream did not start durably at sequence 1: %+v", batch)
|
||||
}
|
||||
for index, entry := range batch.Entries {
|
||||
if entry.Seq != uint64(index+1) {
|
||||
t.Fatalf("fresh generation stream sequence is not contiguous: %+v", batch)
|
||||
}
|
||||
}
|
||||
restartedManaged := restarted.managed.(*OSManagedProcessSupervisor)
|
||||
resumed := restartedManaged.Status(ProcessIdentity{Scope: scope})
|
||||
if resumed.LogSessionID != identity.LogSessionID || resumed.StdoutOffset != identity.StdoutOffset || resumed.StderrOffset != identity.StderrOffset {
|
||||
t.Fatalf("restart changed output offsets without observing new output: before=%+v after=%+v", identity, resumed)
|
||||
}
|
||||
stop := executionAssignment(protocol.RunCapabilityProcessStop)
|
||||
stop.TargetKey = "actions/stop.json"
|
||||
@@ -293,7 +283,7 @@ func TestImmediateManagedProcessRestartTailsNewGeneration(t *testing.T) {
|
||||
waitForManagedTailers(t, managed)
|
||||
}
|
||||
|
||||
func TestManagedProcessRestartRetainsUndrainedRetiredGeneration(t *testing.T) {
|
||||
func TestManagedProcessRestartDoesNotRetainUndrainedRetiredGeneration(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
assignment := executionAssignment(protocol.RunCapabilityProcessStart)
|
||||
assignment.ExecutionInput.LogSources = []protocol.RuntimeLogSourcePlan{{Kind: "process.stdout", StreamKey: "game.console.stdout"}, {Kind: "process.stderr", StreamKey: "game.console.stderr"}}
|
||||
@@ -328,9 +318,9 @@ func TestManagedProcessRestartRetainsUndrainedRetiredGeneration(t *testing.T) {
|
||||
t.Fatalf("process restart reused log session: first=%+v second=%+v", first, second)
|
||||
}
|
||||
oldManaged.mu.Lock()
|
||||
if len(oldManaged.retired) != 1 {
|
||||
if len(oldManaged.retired) != 0 {
|
||||
oldManaged.mu.Unlock()
|
||||
t.Fatalf("expected one durable retired generation, got %+v", oldManaged.retired)
|
||||
t.Fatalf("expected no durable retired generation, got %+v", oldManaged.retired)
|
||||
}
|
||||
oldManaged.stopTailersLocked(first)
|
||||
oldManaged.stopTailersLocked(second)
|
||||
@@ -342,7 +332,10 @@ func TestManagedProcessRestartRetainsUndrainedRetiredGeneration(t *testing.T) {
|
||||
}
|
||||
restarted := NewLifecycleExecutor(WithLifecycleWorkspaceRoot(root), WithProcessLogSink(&SpoolLogSink{RunEndpointID: assignment.RunEndpointID, SessionToken: "restart-session", Spool: logSpool}))
|
||||
restarted.ResumeManagedProcessLogs(context.Background())
|
||||
waitForSpooledText(t, logSpool, "retired-a", "current-b")
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
if pending := mustPendingLogs(t, logSpool); len(pending) != 0 {
|
||||
t.Fatalf("restart replayed retired output: %+v", pending)
|
||||
}
|
||||
restartedManaged := restarted.managed.(*OSManagedProcessSupervisor)
|
||||
waitForManagedTailers(t, restartedManaged)
|
||||
restartedManaged.mu.Lock()
|
||||
@@ -353,7 +346,7 @@ func TestManagedProcessRestartRetainsUndrainedRetiredGeneration(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagedProcessSourceCursorDeduplicatesCommittedLineWithStaleJournalOffset(t *testing.T) {
|
||||
func TestManagedProcessRestartDoesNotReplayHistoricalOutput(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
stateDir := filepath.Join(root, "state")
|
||||
outputDir := filepath.Join(stateDir, "process-output")
|
||||
@@ -361,7 +354,7 @@ func TestManagedProcessSourceCursorDeduplicatesCommittedLineWithStaleJournalOffs
|
||||
t.Fatalf("create process output dir: %v", err)
|
||||
}
|
||||
line := "committed before offset\n"
|
||||
identity := ProcessIdentity{Scope: filepath.Join(root, "instances", "server-execution", "local"), ServerInstanceID: "server-execution", RunEndpointID: "run-execution", JobID: "execution-job", Capability: protocol.RunCapabilityProcessStart, ProfileKey: "local", LogSessionID: "session-stale-offset", PID: 12345, StartedAt: time.Now().UTC(), State: "exited", StdoutLogRef: "stale.stdout.log", StderrLogRef: "stale.stderr.log", StdoutStreamKey: "game.console.stdout", StderrStreamKey: "game.console.stderr"}
|
||||
identity := ProcessIdentity{Scope: filepath.Join(root, "instances", "server-execution", "local"), ServerInstanceID: "server-execution", RunEndpointID: "run-execution", JobID: "execution-job", Capability: protocol.RunCapabilityProcessStart, ProfileKey: "local", LogSessionID: "session-stale-offset", PID: os.Getpid(), StartedAt: time.Now().UTC(), State: "running", StdoutLogRef: "stale.stdout.log", StderrLogRef: "stale.stderr.log", StdoutStreamKey: "game.console.stdout", StderrStreamKey: "game.console.stderr"}
|
||||
if err := os.WriteFile(filepath.Join(outputDir, identity.StdoutLogRef), []byte(line), 0o600); err != nil {
|
||||
t.Fatalf("write stale stdout: %v", err)
|
||||
}
|
||||
@@ -375,34 +368,21 @@ func TestManagedProcessSourceCursorDeduplicatesCommittedLineWithStaleJournalOffs
|
||||
if err := os.WriteFile(filepath.Join(stateDir, "processes.json"), body, 0o600); err != nil {
|
||||
t.Fatalf("write process journal: %v", err)
|
||||
}
|
||||
logSpool, err := spool.NewLogSpool(filepath.Join(root, "spool"))
|
||||
if err != nil {
|
||||
t.Fatalf("new log spool: %v", err)
|
||||
}
|
||||
assignment := assignmentFromProcessIdentity(identity)
|
||||
sink := &SpoolLogSink{RunEndpointID: identity.RunEndpointID, SessionToken: "old-run-session", Spool: logSpool}
|
||||
if err := sink.AppendWithCursor(context.Background(), assignment, "stdout", strings.TrimSpace(line), ProcessLogCursor{StartOffset: 0, EndOffset: int64(len(line))}); err != nil {
|
||||
t.Fatalf("commit line before offset: %v", err)
|
||||
}
|
||||
streamID := logStreamIDForAssignment(assignment, identity.StdoutStreamKey)
|
||||
if err := logSpool.Ack(protocol.LogBatchIngestResponse{LogStreamID: streamID, AcceptedFrom: 1, AcceptedTo: 1}); err != nil {
|
||||
t.Fatalf("ack committed line: %v", err)
|
||||
}
|
||||
restartedSpool, err := spool.NewLogSpool(filepath.Join(root, "spool"))
|
||||
if err != nil {
|
||||
t.Fatalf("restart log spool: %v", err)
|
||||
}
|
||||
restarted := NewLifecycleExecutor(WithLifecycleWorkspaceRoot(root), WithProcessLogSink(&SpoolLogSink{RunEndpointID: identity.RunEndpointID, SessionToken: "new-run-session", Spool: restartedSpool}))
|
||||
sink := &recordingLogSink{}
|
||||
restarted := NewLifecycleExecutor(WithLifecycleWorkspaceRoot(root), WithProcessLogSink(sink))
|
||||
restarted.ResumeManagedProcessLogs(context.Background())
|
||||
managed := restarted.managed.(*OSManagedProcessSupervisor)
|
||||
resumed := waitForManagedOffsets(t, managed, identity.Scope, 0, -1)
|
||||
waitForManagedTailers(t, managed)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if resumed.StdoutOffset != int64(len(line)) {
|
||||
t.Fatalf("stale journal offset was not advanced: %+v", resumed)
|
||||
t.Fatalf("restart did not move live tail to the current output boundary: %+v", resumed)
|
||||
}
|
||||
if pending := mustPendingLogs(t, restartedSpool); len(pending) != 0 {
|
||||
t.Fatalf("committed source cursor was enqueued twice: %+v", pending)
|
||||
if lines := sink.snapshot(); len(lines) != 0 {
|
||||
t.Fatalf("restart replayed historical output: %+v", lines)
|
||||
}
|
||||
managed.mu.Lock()
|
||||
managed.stopTailersLocked(identity)
|
||||
managed.mu.Unlock()
|
||||
}
|
||||
|
||||
func TestManagedProcessSupervisorMigratesLiveLegacySession(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user