Skip to content

Commit e28d719

Browse files
committed
⏱️ session: add timeout and exit-reason handling
1 parent f3fb15a commit e28d719

3 files changed

Lines changed: 59 additions & 14 deletions

File tree

internal/cmd/ps.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,25 @@ func runPs(cmd *cobra.Command, args []string) error {
4747

4848
// Create tabwriter for aligned output
4949
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
50-
_, _ = fmt.Fprintln(w, "ID\tPROJECT\tSTATUS\tSTARTED")
51-
_, _ = fmt.Fprintln(w, "--\t-------\t------\t-------")
50+
_, _ = fmt.Fprintln(w, "ID\tPROJECT\tSTATUS\tTIMEOUT\tEXIT REASON\tSTARTED")
51+
_, _ = fmt.Fprintln(w, "--\t-------\t------\t-------\t-----------\t-------")
5252

5353
for _, session := range sessions {
5454
started := session.StartedAt.Format("2006-01-02 15:04:05")
55-
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
55+
timeout := session.Timeout
56+
if timeout == "" {
57+
timeout = "-"
58+
}
59+
exitReason := session.ExitReason
60+
if exitReason == "" {
61+
exitReason = "-"
62+
}
63+
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
5664
session.ID,
5765
session.ProjectDir,
5866
session.Status,
67+
timeout,
68+
exitReason,
5969
started,
6070
)
6171
}

internal/cmd/start.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8+
"sync/atomic"
89
"time"
910

1011
"github.com/faize-ai/faize/internal/changeset"
@@ -269,6 +270,17 @@ func runStart(cmd *cobra.Command, args []string) error {
269270
}
270271
Debug("VM started successfully")
271272

273+
// Timeout enforcement: stop the VM when the timeout expires
274+
var timedOut atomic.Bool
275+
if timeoutDuration > 0 {
276+
timer := time.AfterFunc(timeoutDuration, func() {
277+
timedOut.Store(true)
278+
fmt.Printf("\nSession timeout (%s) reached. Stopping...\n", timeoutDuration)
279+
_ = manager.Stop(sess.ID)
280+
})
281+
defer timer.Stop()
282+
}
283+
272284
// Take pre-snapshots of rw mounts for change tracking
273285
type mountSnapshot struct {
274286
source string
@@ -312,8 +324,28 @@ func runStart(cmd *cobra.Command, args []string) error {
312324

313325
// Attach to console — session stops when we return
314326
fmt.Println("Attaching to console... (~. to detach)")
315-
if err := manager.Attach(sess.ID); err != nil && !errors.Is(err, vm.ErrUserDetach) {
316-
return fmt.Errorf("console error: %w", err)
327+
attachErr := manager.Attach(sess.ID)
328+
if attachErr != nil && !errors.Is(attachErr, vm.ErrUserDetach) {
329+
return fmt.Errorf("console error: %w", attachErr)
330+
}
331+
332+
// Determine exit reason and persist session metadata
333+
exitReason := "normal"
334+
if timedOut.Load() {
335+
exitReason = "timeout"
336+
} else if errors.Is(attachErr, vm.ErrUserDetach) {
337+
exitReason = "detach"
338+
}
339+
now := time.Now()
340+
sess.Timeout = startTimeout
341+
sess.StoppedAt = &now
342+
sess.ExitReason = exitReason
343+
sess.Status = "stopped"
344+
store, storeErr := session.NewStore()
345+
if storeErr == nil {
346+
if saveErr := store.Save(sess); saveErr != nil {
347+
Debug("Failed to save session: %v", saveErr)
348+
}
317349
}
318350

319351
// Post-session change tracking

internal/session/types.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ type VMMount struct {
1212

1313
// Session represents a VM session with its configuration
1414
type Session struct {
15-
ID string `json:"id"`
16-
ProjectDir string `json:"project_dir"`
17-
Mounts []VMMount `json:"mounts"`
18-
Network []string `json:"network"`
19-
CPUs int `json:"cpus"`
20-
Memory string `json:"memory"`
21-
Status string `json:"status"` // "created", "running", "stopped"
22-
StartedAt time.Time `json:"started_at"`
23-
ClaudeMode bool `json:"claude_mode"` // Whether using Claude rootfs
15+
ID string `json:"id"`
16+
ProjectDir string `json:"project_dir"`
17+
Mounts []VMMount `json:"mounts"`
18+
Network []string `json:"network"`
19+
CPUs int `json:"cpus"`
20+
Memory string `json:"memory"`
21+
Status string `json:"status"` // "created", "running", "stopped"
22+
StartedAt time.Time `json:"started_at"`
23+
ClaudeMode bool `json:"claude_mode"` // Whether using Claude rootfs
24+
Timeout string `json:"timeout,omitempty"` // e.g., "2h" - human-readable timeout
25+
StoppedAt *time.Time `json:"stopped_at,omitempty"`
26+
ExitReason string `json:"exit_reason,omitempty"` // "normal" | "timeout" | "detach" | "killed"
2427
}

0 commit comments

Comments
 (0)