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
73func (p *graphicsNamespacePool) releaseLease(state *graphicsOutputState) {
74 if state == nil || state.namespaceBase == 0 || state.namespaceBase%graphicsIDNamespaceSize != 1 {
75 return
76 }
77 block := (state.namespaceBase - 1) / graphicsIDNamespaceSize
78 if _, quarantined := p.quarantines[block]; quarantined {
79 return
80 }
81 if current := p.fences[block]; current != 0 && current != state.namespaceFence {
82 return
83 }
84 delete(p.reserved, block)
85 delete(p.fences, block)
86}
87
88func (p *graphicsNamespacePool) quarantine(state *graphicsOutputState) *graphicsNamespaceQuarantine {
89 if state == nil || state.namespaceBase == 0 || state.namespaceBase%graphicsIDNamespaceSize != 1 {
90 return nil
91 }
92 block := (state.namespaceBase - 1) / graphicsIDNamespaceSize
93 p.init()
94 if _, exists := p.quarantines[block]; exists {
95 return nil
96 }
97 p.reserved[block] = struct{}{}
98 p.fences[block] = state.namespaceFence
99 q := &graphicsNamespaceQuarantine{base: state.namespaceBase, fence: state.namespaceFence}
100 p.quarantines[block] = q
101 return q
102}