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  }