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.
// 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)
}
}
}