burrow event pubsub

Usage:

Publish TxExecutions and blockExecution to subscriber(eg: transactor and rpcEvent) After commit state.

func (exe *executor) publishBlock(blockExecution *exec.BlockExecution) {
	// update the numTx counter
	atomic.AddUint64(&exe.numBlocks, 1)

	for _, txe := range blockExecution.TxExecutions {
		publishErr := exe.emitter.Publish(context.Background(), txe, txe)
		if publishErr != nil {
			exe.logger.InfoMsg("Error publishing TxExecution",
				"height", blockExecution.Height,
				structure.TxHashKey, txe.TxHash,
				structure.ErrorKey, publishErr)
		}
	}
	publishErr := exe.emitter.Publish(context.Background(), blockExecution, blockExecution)
	if publishErr != nil {
		exe.logger.InfoMsg("Error publishing BlockExecution",
			"height", blockExecution.Height,
			structure.ErrorKey, publishErr)
	}
}

// Publish tells the emitter to publish with the given message and tags
func (em *Emitter) Publish(ctx context.Context, message interface{}, tags query.Tagged) error {
	if em == nil || em.pubsubServer == nil {
		return nil
	}
	return em.pubsubServer.PublishWithTags(ctx, message, tags)
}

// Subscribe tells the emitter to listen for messages on the given query
func (em *Emitter) Subscribe(ctx context.Context, subscriber string, queryable query.Queryable, bufferSize int) (<-chan interface{}, error) {
	qry, err := queryable.Query()
	if err != nil {
		return nil, err
	}
	return em.pubsubServer.Subscribe(ctx, subscriber, qry, bufferSize)
}

We will show Queryable below.

pubsub

// Subscribe creates a subscription for the given client. It accepts a channel
// on which messages matching the given query can be received. An error will be
// returned to the caller if the context is canceled or if subscription already
// exist for pair clientID and query.
func (s *Server) Subscribe(ctx context.Context, clientID string, qry query.Query, outBuffer int) (<-chan interface{}, error) {
	s.mtx.RLock()
	clientSubscriptions, ok := s.subscriptions[clientID]
	if ok {
		_, ok = clientSubscriptions[qry.String()]
	}
	s.mtx.RUnlock()
	if ok {
		return nil, ErrAlreadySubscribed
	}
	// We are responsible for closing this channel so we create it
	out := make(chan interface{}, outBuffer)
	select {
	case s.cmds <- cmd{op: sub, clientID: clientID, query: qry, ch: out}:
		s.mtx.Lock()
		if _, ok = s.subscriptions[clientID]; !ok {
			s.subscriptions[clientID] = make(map[string]query.Query)
		}
		// preserve original query
		// see Unsubscribe
		s.subscriptions[clientID][qry.String()] = qry
		s.mtx.Unlock()
		return out, nil
	case <-ctx.Done():
		return nil, ctx.Err()
	}
}

// Publish publishes the given message. An error will be returned to the caller
// if the context is canceled.
func (s *Server) Publish(ctx context.Context, msg interface{}) error {
	return s.PublishWithTags(ctx, msg, query.TagMap(make(map[string]interface{})))
}

// PublishWithTags publishes the given message with the set of tags. The set is
// matched with clients queries. If there is a match, the message is sent to
// the client.
func (s *Server) PublishWithTags(ctx context.Context, msg interface{}, tags query.Tagged) error {
	select {
	case s.cmds <- cmd{op: pub, msg: msg, tags: tags}:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

func (s *Server) loop(state state) {
loop:
	for cmd := range s.cmds {
		switch cmd.op {
		case unsub:
			if cmd.query != nil {
				state.remove(cmd.clientID, cmd.query)
			} else {
				state.removeAll(cmd.clientID)
			}
		case shutdown:
			for clientID := range state.clients {
				state.removeAll(clientID)
			}
			break loop
		case sub:
			state.add(cmd.clientID, cmd.query, cmd.ch)
		case pub:
			state.send(cmd.msg, cmd.tags)
		}
	}
}

Subscribe has 3 elements:
1, clientID: who subscribe
2, query: subscribe what (topic)
3, ch: channel to receive data

func (state *state) add(clientID string, q query.Query, ch chan interface{}) {
	// add query if needed
	if _, ok := state.queries[q]; !ok {
		state.queries[q] = make(map[string]chan interface{})
	}

	// create subscription
	state.queries[q][clientID] = ch

	// add client if needed
	if _, ok := state.clients[clientID]; !ok {
		state.clients[clientID] = make(map[query.Query]struct{})
	}
	state.clients[clientID][q] = struct{}{}
}

publish to channel if query match tag. we will discuss it in burrow event query.

func (state *state) send(msg interface{}, tags query.Tagged) {
	for q, clientToChannelMap := range state.queries {
		if q.Matches(tags) {
			for _, ch := range clientToChannelMap {
				select {
				case ch <- msg:
				default:
					// It's difficult to do anything sensible here with retries/times outs since we may reorder a client's
					// view of events by sending a later message before an earlier message we retry. If per-client order
					// matters then we need a queue per client. Possible for us it does not...
				}
			}
		}
		err := q.MatchError()
		if err != nil {
			state.logger.InfoMsg("pubsub Server could not execute query", structure.ErrorKey, err)
		}
	}
}

你可能感兴趣的:(burrow)