| 1 | package session |
| 2 | |
| 3 | import "sync" |
| 4 | |
| 5 | // IdlePool bounds unbound runtime retention across historical storage roots. |
| 6 | // It never calls a service while holding its mutex. Victims are only candidates: |
| 7 | // the owning service rechecks bindings and execution before retiring them. |
| 8 | type IdlePool struct { |
| 9 | mu sync.Mutex |
| 10 | entries map[*Runtime]idlePoolEntry |
| 11 | used int64 |
| 12 | clock uint64 |
| 13 | } |
| 14 | |
| 15 | type idlePoolEntry struct { |
| 16 | service *Service |
| 17 | runtime *Runtime |
| 18 | bytes int64 |
| 19 | order uint64 |
| 20 | epoch uint64 |
| 21 | } |
| 22 | |
| 23 | const DesktopIdleBudgetBytes int64 = 256 << 20 |
| 24 | |
| 25 | // UseIdlePool must be called before publishing the service to clients. |
| 26 | func (s *Service) UseIdlePool(pool *IdlePool) { s.idlePool = pool } |
| 27 | |
| 28 | // MetadataOnlyListings keeps listing observations free of content replay. |
| 29 | // Configure it before the service is published; history/search remain explicit. |
| 30 | func (s *Service) MetadataOnlyListings() { s.query.metadataOnlyListings = true } |
| 31 | |
| 32 | func (p *IdlePool) add(s *Service, r *Runtime, bytes int64) []idlePoolEntry { |
| 33 | p.mu.Lock() |
| 34 | defer p.mu.Unlock() |
| 35 | if p.entries == nil { |
| 36 | p.entries = make(map[*Runtime]idlePoolEntry) |
| 37 | } |
| 38 | if old, ok := p.entries[r]; ok { |
| 39 | p.used -= old.bytes |
| 40 | } |
| 41 | p.clock++ |
| 42 | p.entries[r] = idlePoolEntry{service: s, runtime: r, bytes: bytes, order: p.clock, epoch: s.idleOrder[r]} |
| 43 | p.used += bytes |
| 44 | var victims []idlePoolEntry |
| 45 | for p.used > DesktopIdleBudgetBytes && len(p.entries) > 0 { |
| 46 | var oldest idlePoolEntry |
| 47 | for _, entry := range p.entries { |
| 48 | if oldest.runtime == nil || entry.order < oldest.order { |
| 49 | oldest = entry |
| 50 | } |
| 51 | } |
| 52 | delete(p.entries, oldest.runtime) |
| 53 | p.used -= oldest.bytes |
| 54 | victims = append(victims, oldest) |
| 55 | } |
| 56 | return victims |
| 57 | } |
| 58 | |
| 59 | func (p *IdlePool) remove(r *Runtime) { |
| 60 | if p == nil { |
| 61 | return |
| 62 | } |
| 63 | p.mu.Lock() |
| 64 | defer p.mu.Unlock() |
| 65 | if old, ok := p.entries[r]; ok { |
| 66 | p.used -= old.bytes |
| 67 | delete(p.entries, r) |
| 68 | } |
| 69 | } |
| 70 |