github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/realtime/mem_realtime.go (about) 1 package realtime 2 3 import ( 4 "sync" 5 6 "github.com/cozy/cozy-stack/pkg/logger" 7 "github.com/cozy/cozy-stack/pkg/prefixer" 8 ) 9 10 var globalPrefixer = prefixer.NewPrefixer(prefixer.GlobalCouchCluster, "", "*") 11 12 type memHub struct { 13 sync.RWMutex 14 topics map[string]*topic 15 bySubscribers map[*Subscriber][]string // the list of topic keys by subscriber 16 } 17 18 func newMemHub() *memHub { 19 return &memHub{ 20 topics: make(map[string]*topic), 21 bySubscribers: make(map[*Subscriber][]string), 22 } 23 } 24 25 func (h *memHub) Publish(db prefixer.Prefixer, verb string, doc, oldDoc Doc) { 26 h.RLock() 27 defer h.RUnlock() 28 29 e := newEvent(db, verb, doc, oldDoc) 30 key := topicKey(db, doc.DocType()) 31 it := h.topics[key] 32 if it != nil { 33 select { 34 case it.broadcast <- e: 35 case running := <-it.running: 36 logger.WithNamespace("realtime"). 37 Warnf("unexpected state: publish with running=%v", running) 38 if !running { 39 delete(h.topics, key) 40 } 41 } 42 } 43 it = h.topics[topicKey(globalPrefixer, "*")] 44 if it != nil { 45 it.broadcast <- e 46 } 47 } 48 49 func (h *memHub) Subscriber(db prefixer.Prefixer) *Subscriber { 50 return newSubscriber(h, db) 51 } 52 53 func (h *memHub) SubscribeFirehose() *Subscriber { 54 sub := newSubscriber(h, globalPrefixer) 55 key := topicKey(sub, "*") 56 h.subscribe(sub, key) 57 return sub 58 } 59 60 func (h *memHub) subscribe(sub *Subscriber, key string) { 61 h.Lock() 62 go func() { 63 defer h.Unlock() 64 65 h.addTopic(sub, key) 66 67 w := &toWatch{sub, ""} 68 for { 69 it, exists := h.topics[key] 70 if !exists { 71 it = newTopic() 72 h.topics[key] = it 73 } 74 75 select { 76 case it.subscribe <- w: 77 return 78 case running := <-it.running: 79 logger.WithNamespace("realtime"). 80 Warnf("unexpected state: subscribe with running=%v", running) 81 if !running { 82 delete(h.topics, key) 83 } 84 } 85 } 86 }() 87 } 88 89 func (h *memHub) unsubscribe(sub *Subscriber, key string) { 90 h.Lock() 91 go func() { 92 defer h.Unlock() 93 94 it, exists := h.topics[key] 95 if !exists { 96 return 97 } 98 99 h.removeTopic(sub, key) 100 101 w := &toWatch{sub, ""} 102 select { 103 case it.unsubscribe <- w: 104 if running := <-it.running; !running { 105 delete(h.topics, key) 106 } 107 case running := <-it.running: 108 logger.WithNamespace("realtime"). 109 Warnf("unexpected state: unsubscribe with running=%v", running) 110 if !running { 111 delete(h.topics, key) 112 } 113 } 114 }() 115 } 116 117 func (h *memHub) watch(sub *Subscriber, key, id string) { 118 h.Lock() 119 go func() { 120 defer h.Unlock() 121 122 h.addTopic(sub, key) 123 124 w := &toWatch{sub, id} 125 for { 126 it, exists := h.topics[key] 127 if !exists { 128 it = newTopic() 129 h.topics[key] = it 130 } 131 132 select { 133 case it.subscribe <- w: 134 return 135 case running := <-it.running: 136 logger.WithNamespace("realtime"). 137 Warnf("unexpected state: watch with running=%v", running) 138 if !running { 139 delete(h.topics, key) 140 } 141 } 142 } 143 }() 144 } 145 146 func (h *memHub) unwatch(sub *Subscriber, key, id string) { 147 h.Lock() 148 go func() { 149 defer h.Unlock() 150 151 it, exists := h.topics[key] 152 if !exists { 153 return 154 } 155 156 w := &toWatch{sub, id} 157 select { 158 case it.unsubscribe <- w: 159 if running := <-it.running; !running { 160 delete(h.topics, key) 161 } 162 case running := <-it.running: 163 logger.WithNamespace("realtime"). 164 Warnf("unexpected state: unwatch with running=%v", running) 165 if !running { 166 delete(h.topics, key) 167 } 168 } 169 }() 170 } 171 172 func (h *memHub) close(sub *Subscriber) { 173 h.RLock() 174 list := h.bySubscribers[sub] 175 h.RUnlock() 176 keys := make([]string, len(list)) 177 copy(keys, list) 178 179 for _, key := range keys { 180 h.unsubscribe(sub, key) 181 } 182 } 183 184 func (h *memHub) addTopic(sub *Subscriber, key string) { 185 list := h.bySubscribers[sub] 186 for _, k := range list { 187 if k == key { 188 return 189 } 190 } 191 list = append(list, key) 192 h.bySubscribers[sub] = list 193 } 194 195 func (h *memHub) removeTopic(sub *Subscriber, key string) { 196 list := h.bySubscribers[sub] 197 kept := list[:0] 198 for _, k := range list { 199 if k != key { 200 kept = append(kept, k) 201 } 202 } 203 if len(kept) == 0 { 204 delete(h.bySubscribers, sub) 205 } else { 206 h.bySubscribers[sub] = kept 207 } 208 } 209 210 func topicKey(db prefixer.Prefixer, doctype string) string { 211 return db.DBPrefix() + ":" + doctype 212 }