history_policy.go RawHistoryFolder
1package daemon
2
3import (
4 "context"
5 "time"
6
7 vt "github.com/bnema/vev-vt"
8 "github.com/bnema/vev/internal/domain"
9)
10
11func historyConfigFor(policy domain.ScrollbackConfig) vt.HistoryConfig {
12 if policy.Megabytes == 0 {
13 return vt.HistoryConfig{}
14 }
15 return vt.HistoryConfig{MaxBytes: policy.Megabytes * 1_000_000, MaxRows: policy.Lines}
16}
17
18func (d *Daemon) currentHistoryConfig() vt.HistoryConfig {
19 return historyConfigFor(d.currentRuntimeConfig().scrollback)
20}
21
22// historyPanes gathers references without holding a session/tab lock while
23// touching history. Include hidden floating panes; deduplicate a pane that moves
24// between tabs while the separate session snapshots are being collected.
25func (d *Daemon) historyPanes() []*pane {
26 var panes []*pane
27 seen := make(map[*pane]struct{})
28 for _, sess := range d.sessionsSnapshot() {
29 sess.mu.Lock()
30 tabs := append([]*tab(nil), sess.tabs...)
31 sess.mu.Unlock()
32 for _, tb := range tabs {
33 if tb == nil {
34 continue
35 }
36 tb.mu.Lock()
37 current := tb.panesSnapshot()
38 if tb.floating.pane != nil {
39 current = append(current, tb.floating.pane)
40 }
41 tb.mu.Unlock()
42 for _, p := range current {
43 if p == nil {
44 continue
45 }
46 if _, exists := seen[p]; exists {
47 continue
48 }
49 seen[p] = struct{}{}
50 panes = append(panes, p)
51 }
52 }
53 }
54 return panes
55}
56
57func (d *Daemon) applyHistoryLimitsToPane(p *pane) {
58 p.mu.Lock()
59 var err error
60 if p.history != nil {
61 // Load inside the pane lock so an older concurrent reload cannot install
62 // a stale policy after a newer reload has already published its value.
63 err = p.history.SetLimits(d.currentHistoryConfig())
64 }
65 p.mu.Unlock()
66 if err != nil {
67 d.log.Warn("cannot apply scrollback limits", "err", err)
68 }
69}
70
71func (d *Daemon) applyHistoryLimits() {
72 for _, p := range d.historyPanes() {
73 d.applyHistoryLimitsToPane(p)
74 }
75}
76
77// Keep background compression infrequent and bounded; it must not compete
78// with interactive output when many panes retain history.
79const historyMaintenanceInterval = 5 * time.Second
80
81// historyMaintenance is application scheduling, not a VT-owned worker. It
82// visits one history page per pane per interval and skips busy/resizing panes.
83func (d *Daemon) historyMaintenance(ctx context.Context) {
84 timer := d.clock.NewTimer(historyMaintenanceInterval)
85 defer timer.Stop()
86 for {
87 select {
88 case <-ctx.Done():
89 return
90 case <-timer.C():
91 d.compressIdleHistories()
92 timer.Reset(historyMaintenanceInterval)
93 }
94 }
95}
96
97func (d *Daemon) compressIdleHistories() {
98 for _, p := range d.historyPanes() {
99 if !p.mu.TryLock() {
100 continue
101 }
102 var err error
103 if p.history != nil && !p.resizeApplying {
104 _, err = p.history.CompressIdle(1)
105 }
106 p.mu.Unlock()
107 if err != nil {
108 d.log.Warn("cannot compress idle scrollback", "err", err)
109 }
110 }
111}