graphics_namespace_pool.go RawHistoryFolder
1package daemon
2
3import (
4 "crypto/sha256"
5 "encoding/binary"
6)
7
8type graphicsNamespaceQuarantine struct {
9 base uint64
10 fence uint64
11}
12
13// graphicsNamespacePool reserves deterministic, attachment/session-scoped Kitty
14// ID blocks. Once a block may have reached an outer terminal it remains in
15// this bounded table for the daemon lifetime: side-effect Output frames have
16// no terminal ACK. Pool exhaustion disables graphics for new attachments and
17// leaves their ordinary text output intact.
18//
19// The pool is guarded by Daemon.mu and has no lock of its own: every method
20// requires the caller to hold it. The zero value is usable; the maps are
21// created lazily and a zero salt simply skips salting.
22type graphicsNamespacePool struct {
23 reserved map[uint64]struct{}
24 fences map[uint64]uint64
25 quarantines map[uint64]*graphicsNamespaceQuarantine
26 // salt separates daemon lifetimes before attachment keys are hashed. Kitty
27 // IDs are terminal-global, so a restarted or neighboring daemon must not
28 // deterministically reopen the previous daemon's block.
29 salt uint64
30}
31
32func newGraphicsNamespacePool() graphicsNamespacePool {
33 p := graphicsNamespacePool{salt: newGraphicsNamespaceSalt()}
34 p.init()
35 return p
36}
37
38func (p *graphicsNamespacePool) init() {
39 if p.reserved == nil {
40 p.reserved = make(map[uint64]struct{})
41 }
42 if p.fences == nil {
43 p.fences = make(map[uint64]uint64)
44 }
45 if p.quarantines == nil {
46 p.quarantines = make(map[uint64]*graphicsNamespaceQuarantine)
47 }
48}
49
50func (p *graphicsNamespacePool) reserveLease(key string) (uint64, uint64) {
51 p.init()
52 hashInput := []byte(key)
53 if p.salt != 0 {
54 salt := make([]byte, 8, 8+len(key))
55 binary.BigEndian.PutUint64(salt, p.salt)
56 hashInput = append(salt, hashInput...)
57 }
58 digest := sha256.Sum256(hashInput)
59 preferred := binary.BigEndian.Uint64(digest[:8]) % graphicsIDNamespaceCount
60 for offset := uint64(0); offset < graphicsIDNamespaceCount; offset++ {
61 block := (preferred + offset) % graphicsIDNamespaceCount
62 if _, exists := p.reserved[block]; exists {
63 continue
64 }
65 fence := nextGraphicsNamespaceFence()
66 p.reserved[block] = struct{}{}
67 p.fences[block] = fence
68 return block*graphicsIDNamespaceSize + 1, fence
69 }
70 return 0, 0
71}
72
73// releaseBase is the legacy base-only release. A quarantined block is never
74// released by it.
75func (p *graphicsNamespacePool) releaseBase(base uint64) {
76 if base == 0 || base%graphicsIDNamespaceSize != 1 {
77 return
78 }
79 block := (base - 1) / graphicsIDNamespaceSize
80 if _, quarantined := p.quarantines[block]; quarantined {
81 return
82 }
83 delete(p.reserved, block)
84 delete(p.fences, block)
85}
86
87func (p *graphicsNamespacePool) releaseLease(state *graphicsOutputState) {
88 if state == nil || state.namespaceBase == 0 || state.namespaceBase%graphicsIDNamespaceSize != 1 {
89 return
90 }
91 block := (state.namespaceBase - 1) / graphicsIDNamespaceSize
92 if _, quarantined := p.quarantines[block]; quarantined {
93 return
94 }
95 if current := p.fences[block]; current != 0 && current != state.namespaceFence {
96 return
97 }
98 delete(p.reserved, block)
99 delete(p.fences, block)
100}
101
102func (p *graphicsNamespacePool) quarantine(state *graphicsOutputState) *graphicsNamespaceQuarantine {
103 if state == nil || state.namespaceBase == 0 || state.namespaceBase%graphicsIDNamespaceSize != 1 {
104 return nil
105 }
106 block := (state.namespaceBase - 1) / graphicsIDNamespaceSize
107 p.init()
108 if _, exists := p.quarantines[block]; exists {
109 return nil
110 }
111 p.reserved[block] = struct{}{}
112 p.fences[block] = state.namespaceFence
113 q := &graphicsNamespaceQuarantine{base: state.namespaceBase, fence: state.namespaceFence}
114 p.quarantines[block] = q
115 return q
116}