v0.33.1 9577fee

cleanup_sessions.go RawHistoryFolder

1package usecase
2
3import (
4	"context"
5	"time"
6
7	"github.com/bnema/dumber/internal/application/port"
8	"github.com/bnema/dumber/internal/domain/entity"
9	"github.com/bnema/dumber/internal/domain/repository"
10	"github.com/bnema/dumber/internal/logging"
11)
12
13// CleanupSessionsUseCase handles automatic cleanup of old exited sessions.
14type CleanupSessionsUseCase struct {
15	sessionRepo  repository.SessionRepository
16	processProbe port.SessionProcessProbe
17}
18
19// NewCleanupSessionsUseCase creates a new CleanupSessionsUseCase.
20func NewCleanupSessionsUseCase(
21	sessionRepo repository.SessionRepository,
22	processProbe port.SessionProcessProbe,
23) *CleanupSessionsUseCase {
24	return &CleanupSessionsUseCase{
25		sessionRepo:  sessionRepo,
26		processProbe: processProbe,
27	}
28}
29
30// CleanupSessionsInput contains the cleanup configuration.
31type CleanupSessionsInput struct {
32	// MaxExitedSessions is the maximum number of exited sessions to keep.
33	// Sessions beyond this count will be deleted (oldest first).
34	MaxExitedSessions int
35
36	// MaxExitedSessionAgeDays is the maximum age in days for exited sessions.
37	// Sessions older than this will be deleted.
38	// A value of 0 disables age-based cleanup.
39	MaxExitedSessionAgeDays int
40}
41
42// CleanupSessionsOutput contains the cleanup results.
43type CleanupSessionsOutput struct {
44	// DeletedByAge is the number of sessions deleted due to age.
45	DeletedByAge int64
46
47	// DeletedByCount is the number of sessions deleted due to count limit.
48	DeletedByCount int64
49
50	// TotalDeleted is the total number of sessions deleted.
51	TotalDeleted int64
52}
53
54type ReconcileActiveBrowserSessionsInput struct {
55	CurrentSessionID entity.SessionID
56	RecentLimit      int
57	EndedAt          time.Time
58}
59
60type ReconcileActiveBrowserSessionsOutput struct {
61	EndedDeadSessions int64
62}
63
64// ReconcileActiveBrowserSessions ends only active browser sessions whose recorded process is proven dead.
65func (uc *CleanupSessionsUseCase) ReconcileActiveBrowserSessions(
66	ctx context.Context,
67	input ReconcileActiveBrowserSessionsInput,
68) (ReconcileActiveBrowserSessionsOutput, error) {
69	log := logging.FromContext(ctx)
70	output := ReconcileActiveBrowserSessionsOutput{}
71	if uc.processProbe == nil {
72		return output, nil
73	}
74	if input.RecentLimit <= 0 {
75		input.RecentLimit = 20
76	}
77	endedAt := input.EndedAt
78	if endedAt.IsZero() {
79		endedAt = time.Now()
80	}
81
82	recent, err := uc.sessionRepo.GetRecent(ctx, input.RecentLimit)
83	if err != nil {
84		return output, err
85	}
86	for _, s := range recent {
87		if s == nil || s.ID == input.CurrentSessionID || s.Type != entity.SessionTypeBrowser || !s.IsActive() {
88			continue
89		}
90		if s.ProcessID == nil {
91			log.Debug().Str("session_id", string(s.ID)).Msg("skipping active session without process id")
92			continue
93		}
94		alive, probeErr := uc.processProbe.IsProcessAlive(ctx, *s.ProcessID)
95		if probeErr != nil {
96			log.Warn().Err(probeErr).Str("session_id", string(s.ID)).Int("pid", *s.ProcessID).Msg("failed to probe session process")
97			continue
98		}
99		if alive {
100			continue
101		}
102		if err := uc.sessionRepo.MarkEnded(ctx, s.ID, endedAt); err != nil {
103			return output, err
104		}
105		log.Info().Str("session_id", string(s.ID)).Int("pid", *s.ProcessID).Msg("ended dead browser session")
106		output.EndedDeadSessions++
107	}
108	return output, nil
109}
110
111// Execute cleans up old exited sessions based on the provided configuration.
112// It first deletes sessions older than MaxExitedSessionAgeDays,
113// then enforces the MaxExitedSessions count limit.
114func (uc *CleanupSessionsUseCase) Execute(ctx context.Context, input CleanupSessionsInput) (CleanupSessionsOutput, error) {
115	log := logging.FromContext(ctx)
116	output := CleanupSessionsOutput{}
117
118	// 1. Delete by age first (if enabled)
119	if input.MaxExitedSessionAgeDays > 0 {
120		cutoff := time.Now().AddDate(0, 0, -input.MaxExitedSessionAgeDays)
121		deleted, err := uc.sessionRepo.DeleteExitedBefore(ctx, cutoff)
122		if err != nil {
123			log.Warn().Err(err).Msg("failed to delete sessions by age")
124		} else {
125			output.DeletedByAge = deleted
126			if deleted > 0 {
127				log.Info().
128					Int64("deleted", deleted).
129					Int("max_age_days", input.MaxExitedSessionAgeDays).
130					Msg("cleaned up old sessions by age")
131			}
132		}
133	}
134
135	// 2. Enforce count limit on remaining sessions
136	if input.MaxExitedSessions >= 0 {
137		deleted, err := uc.sessionRepo.DeleteOldestExited(ctx, input.MaxExitedSessions)
138		if err != nil {
139			log.Warn().Err(err).Msg("failed to delete sessions by count")
140		} else {
141			output.DeletedByCount = deleted
142			if deleted > 0 {
143				log.Info().
144					Int64("deleted", deleted).
145					Int("max_count", input.MaxExitedSessions).
146					Msg("cleaned up old sessions by count")
147			}
148		}
149	}
150
151	output.TotalDeleted = output.DeletedByAge + output.DeletedByCount
152
153	if output.TotalDeleted > 0 {
154		log.Info().
155			Int64("total_deleted", output.TotalDeleted).
156			Int64("by_age", output.DeletedByAge).
157			Int64("by_count", output.DeletedByCount).
158			Msg("session cleanup completed")
159	}
160
161	return output, nil
162}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close