github.com/turingchain2020/turingchain@v1.1.21/common/pubsub/pubsub.go (about) 1 // Copyright Turing Corp. 2018 All Rights Reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Copyright 2013, Chandra Sekar S. All rights reserved. 6 // Use of this source code is governed by a BSD-style 7 // license that can be found in the README.md file. 8 9 // Package pubsub implements a simple multi-topic pub-sub 10 // library. 11 // 12 // Topics must be strings and messages of any type can be 13 // published. A topic can have any number of subcribers and 14 // all of them receive messages published on the topic. 15 package pubsub 16 17 //"log" 18 19 type operation int 20 21 const ( 22 sub operation = iota 23 subOnce 24 pub 25 tryPub 26 fifoPub 27 unsub 28 unsubAll 29 closeTopic 30 shutdown 31 ) 32 33 // PubSub is a collection of topics. 34 type PubSub struct { 35 cmdChan chan cmd 36 capacity int 37 } 38 39 type cmd struct { 40 op operation 41 topics []string 42 ch chan interface{} 43 msg interface{} 44 } 45 46 //NewPubSub New creates a new PubSub and starts a goroutine for handling operations. 47 // The capacity of the channels created by Sub and SubOnce will be as specified. 48 func NewPubSub(capacity int) *PubSub { 49 ps := &PubSub{make(chan cmd), capacity} 50 go ps.start() 51 return ps 52 } 53 54 // Sub returns a channel on which messages published on any of 55 // the specified topics can be received. 56 func (ps *PubSub) Sub(topics ...string) chan interface{} { 57 return ps.sub(sub, topics...) 58 } 59 60 // SubOnce is similar to Sub, but only the first message published, after subscription, 61 // on any of the specified topics can be received. 62 func (ps *PubSub) SubOnce(topics ...string) chan interface{} { 63 return ps.sub(subOnce, topics...) 64 } 65 66 func (ps *PubSub) sub(op operation, topics ...string) chan interface{} { 67 ch := make(chan interface{}, ps.capacity) 68 ps.cmdChan <- cmd{op: op, topics: topics, ch: ch} 69 return ch 70 } 71 72 // AddSub adds subscriptions to an existing channel. 73 func (ps *PubSub) AddSub(ch chan interface{}, topics ...string) { 74 ps.cmdChan <- cmd{op: sub, topics: topics, ch: ch} 75 } 76 77 // Pub publishes the given message to all subscribers of 78 // the specified topics. 79 func (ps *PubSub) Pub(msg interface{}, topics ...string) { 80 ps.cmdChan <- cmd{op: pub, topics: topics, msg: msg} 81 } 82 83 // TryPub publishes the given message to all subscribers of 84 // the specified topics if the topic has buffer space. 85 func (ps *PubSub) TryPub(msg interface{}, topics ...string) { 86 ps.cmdChan <- cmd{op: tryPub, topics: topics, msg: msg} 87 } 88 89 //FIFOPub 发布交易:如果channel满了,那么把最旧的数据删除,并写入msg 90 func (ps *PubSub) FIFOPub(msg interface{}, topics ...string) { 91 ps.cmdChan <- cmd{op: fifoPub, topics: topics, msg: msg} 92 } 93 94 // Unsub unsubscribes the given channel from the specified 95 // topics. If no topic is specified, it is unsubscribed 96 // from all topics. 97 func (ps *PubSub) Unsub(ch chan interface{}, topics ...string) { 98 if len(topics) == 0 { 99 ps.cmdChan <- cmd{op: unsubAll, ch: ch} 100 return 101 } 102 103 ps.cmdChan <- cmd{op: unsub, topics: topics, ch: ch} 104 } 105 106 // Close closes all channels currently subscribed to the specified topics. 107 // If a channel is subscribed to multiple topics, some of which is 108 // not specified, it is not closed. 109 func (ps *PubSub) Close(topics ...string) { 110 ps.cmdChan <- cmd{op: closeTopic, topics: topics} 111 } 112 113 // Shutdown closes all subscribed channels and terminates the goroutine. 114 func (ps *PubSub) Shutdown() { 115 ps.cmdChan <- cmd{op: shutdown} 116 } 117 118 func (ps *PubSub) start() { 119 reg := registry{ 120 topics: make(map[string]map[chan interface{}]bool), 121 revTopics: make(map[chan interface{}]map[string]bool), 122 } 123 124 loop: 125 for cmd := range ps.cmdChan { 126 if cmd.topics == nil { 127 switch cmd.op { 128 case unsubAll: 129 reg.removeChannel(cmd.ch) 130 131 case shutdown: 132 break loop 133 } 134 135 continue loop 136 } 137 138 for _, topic := range cmd.topics { 139 switch cmd.op { 140 case sub: 141 reg.add(topic, cmd.ch, false) 142 143 case subOnce: 144 reg.add(topic, cmd.ch, true) 145 146 case tryPub: 147 reg.sendNoWait(topic, cmd.msg) 148 149 case fifoPub: 150 reg.sendFIFO(topic, cmd.msg) 151 152 case pub: 153 reg.send(topic, cmd.msg) 154 155 case unsub: 156 reg.remove(topic, cmd.ch) 157 158 case closeTopic: 159 reg.removeTopic(topic) 160 } 161 } 162 } 163 164 for topic, chans := range reg.topics { 165 for ch := range chans { 166 reg.remove(topic, ch) 167 } 168 } 169 } 170 171 // registry maintains the current subscription state. It's not 172 // safe to access a registry from multiple goroutines simultaneously. 173 type registry struct { 174 topics map[string]map[chan interface{}]bool 175 revTopics map[chan interface{}]map[string]bool 176 } 177 178 func (reg *registry) add(topic string, ch chan interface{}, once bool) { 179 if reg.topics[topic] == nil { 180 reg.topics[topic] = make(map[chan interface{}]bool) 181 } 182 reg.topics[topic][ch] = once 183 184 if reg.revTopics[ch] == nil { 185 reg.revTopics[ch] = make(map[string]bool) 186 } 187 reg.revTopics[ch][topic] = true 188 } 189 190 func (reg *registry) send(topic string, msg interface{}) { 191 for ch, once := range reg.topics[topic] { 192 ch <- msg 193 if once { 194 for topic := range reg.revTopics[ch] { 195 reg.remove(topic, ch) 196 } 197 } 198 } 199 } 200 201 func (reg *registry) sendFIFO(topic string, msg interface{}) { 202 for ch, once := range reg.topics[topic] { 203 for !sendAsyn(ch, msg) { 204 <-ch 205 //log.Println("----sendFIFO", msg2) 206 } 207 if once { 208 for topic := range reg.revTopics[ch] { 209 reg.remove(topic, ch) 210 } 211 } 212 } 213 } 214 215 func sendAsyn(ch chan interface{}, msg interface{}) bool { 216 select { 217 case ch <- msg: 218 //log.Println("----sendFIFO true", msg) 219 return true 220 default: 221 return false 222 } 223 } 224 225 func (reg *registry) sendNoWait(topic string, msg interface{}) { 226 for ch, once := range reg.topics[topic] { 227 select { 228 case ch <- msg: 229 if once { 230 for topic := range reg.revTopics[ch] { 231 reg.remove(topic, ch) 232 } 233 } 234 default: 235 } 236 } 237 } 238 239 func (reg *registry) removeTopic(topic string) { 240 for ch := range reg.topics[topic] { 241 reg.remove(topic, ch) 242 } 243 } 244 245 func (reg *registry) removeChannel(ch chan interface{}) { 246 for topic := range reg.revTopics[ch] { 247 reg.remove(topic, ch) 248 } 249 } 250 251 func (reg *registry) remove(topic string, ch chan interface{}) { 252 if _, ok := reg.topics[topic]; !ok { 253 return 254 } 255 256 if _, ok := reg.topics[topic][ch]; !ok { 257 return 258 } 259 260 delete(reg.topics[topic], ch) 261 delete(reg.revTopics[ch], topic) 262 263 if len(reg.topics[topic]) == 0 { 264 delete(reg.topics, topic) 265 } 266 267 if len(reg.revTopics[ch]) == 0 { 268 close(ch) 269 delete(reg.revTopics, ch) 270 } 271 }