|
package pubsub
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/tendermint/tendermint/abci/types"
|
|
"github.com/tendermint/tendermint/internal/libs/queue"
|
|
)
|
|
|
|
var (
|
|
// ErrUnsubscribed is returned by Next when the client has unsubscribed.
|
|
ErrUnsubscribed = errors.New("subscription removed by client")
|
|
|
|
// ErrTerminated is returned by Next when the subscription was terminated by
|
|
// the publisher.
|
|
ErrTerminated = errors.New("subscription terminated by publisher")
|
|
)
|
|
|
|
// A Subscription represents a client subscription for a particular query.
|
|
type Subscription struct {
|
|
id string
|
|
queue *queue.Queue // open until the subscription ends
|
|
stopErr error // after queue is closed, the reason why
|
|
}
|
|
|
|
// newSubscription returns a new subscription with the given queue capacity.
|
|
func newSubscription(quota, limit int) (*Subscription, error) {
|
|
queue, err := queue.New(queue.Options{
|
|
SoftQuota: quota,
|
|
HardLimit: limit,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Subscription{
|
|
id: uuid.NewString(),
|
|
queue: queue,
|
|
}, nil
|
|
}
|
|
|
|
// Next blocks until a message is available, ctx ends, or the subscription
|
|
// ends. Next returns ErrUnsubscribed if s was unsubscribed, ErrTerminated if
|
|
// s was terminated by the publisher, or a context error if ctx ended without a
|
|
// message being available.
|
|
func (s *Subscription) Next(ctx context.Context) (Message, error) {
|
|
next, err := s.queue.Wait(ctx)
|
|
if errors.Is(err, queue.ErrQueueClosed) {
|
|
return Message{}, s.stopErr
|
|
} else if err != nil {
|
|
return Message{}, err
|
|
}
|
|
return next.(Message), nil
|
|
}
|
|
|
|
// ID returns the unique subscription identifier for s.
|
|
func (s *Subscription) ID() string { return s.id }
|
|
|
|
// publish transmits msg to the subscriber. It reports a queue error if the
|
|
// queue cannot accept any further messages.
|
|
func (s *Subscription) publish(msg Message) error { return s.queue.Add(msg) }
|
|
|
|
// stop terminates the subscription with the given error reason.
|
|
func (s *Subscription) stop(err error) {
|
|
if err == nil {
|
|
panic("nil stop error")
|
|
}
|
|
s.stopErr = err
|
|
s.queue.Close()
|
|
}
|
|
|
|
// Message glues data and events together.
|
|
type Message struct {
|
|
subID string
|
|
data interface{}
|
|
events []types.Event
|
|
}
|
|
|
|
// SubscriptionID returns the unique identifier for the subscription
|
|
// that produced this message.
|
|
func (msg Message) SubscriptionID() string { return msg.subID }
|
|
|
|
// Data returns an original data published.
|
|
func (msg Message) Data() interface{} { return msg.data }
|
|
|
|
// Events returns events, which matched the client's query.
|
|
func (msg Message) Events() []types.Event { return msg.events }
|