github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/rpc/notification.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package rpc
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/ethereumproject/go-ethereum/logger"
    26  	"github.com/ethereumproject/go-ethereum/logger/glog"
    27  )
    28  
    29  var (
    30  	// ErrNotificationsUnsupported is returned when the connection doesn't support notifications
    31  	ErrNotificationsUnsupported = errors.New("notifications not supported")
    32  
    33  	// ErrNotificationNotFound is returned when the notification for the given id is not found
    34  	ErrNotificationNotFound = errors.New("notification not found")
    35  
    36  	// errNotifierStopped is returned when the notifier is stopped (e.g. codec is closed)
    37  	errNotifierStopped = errors.New("unable to send notification")
    38  
    39  	// errNotificationQueueFull is returns when there are too many notifications in the queue
    40  	errNotificationQueueFull = errors.New("too many pending notifications")
    41  )
    42  
    43  // unsubSignal is a signal that the subscription is unsubscribed. It is used to flush buffered
    44  // notifications that might be pending in the internal queue.
    45  var unsubSignal = new(struct{})
    46  
    47  // UnsubscribeCallback defines a callback that is called when a subcription ends.
    48  // It receives the subscription id as argument.
    49  type UnsubscribeCallback func(id string)
    50  
    51  // notification is a helper object that holds event data for a subscription
    52  type notification struct {
    53  	sub  *bufferedSubscription // subscription id
    54  	data interface{}           // event data
    55  }
    56  
    57  // A Notifier type describes the interface for objects that can send create subscriptions
    58  type Notifier interface {
    59  	// Create a new subscription. The given callback is called when this subscription
    60  	// is cancelled (e.g. client send an unsubscribe, connection closed).
    61  	NewSubscription(UnsubscribeCallback) (Subscription, error)
    62  	// Cancel subscription
    63  	Unsubscribe(id string) error
    64  }
    65  
    66  type notifierKey struct{}
    67  
    68  // NotifierFromContext returns the Notifier value stored in ctx, if any.
    69  func NotifierFromContext(ctx context.Context) (Notifier, bool) {
    70  	n, ok := ctx.Value(notifierKey{}).(Notifier)
    71  	return n, ok
    72  }
    73  
    74  // Subscription defines the interface for objects that can notify subscribers
    75  type Subscription interface {
    76  	// Inform client of an event
    77  	Notify(data interface{}) error
    78  	// Unique identifier
    79  	ID() string
    80  	// Cancel subscription
    81  	Cancel() error
    82  }
    83  
    84  // bufferedSubscription is a subscription that uses a bufferedNotifier to send
    85  // notifications to subscribers.
    86  type bufferedSubscription struct {
    87  	id               string
    88  	unsubOnce        sync.Once           // call unsub method once
    89  	unsub            UnsubscribeCallback // called on Unsubscribed
    90  	notifier         *bufferedNotifier   // forward notifications to
    91  	pending          chan interface{}    // closed when active
    92  	flushed          chan interface{}    // closed when all buffered notifications are send
    93  	lastNotification time.Time           // last time a notification was send
    94  }
    95  
    96  // ID returns the subscription identifier that the client uses to refer to this instance.
    97  func (s *bufferedSubscription) ID() string {
    98  	return s.id
    99  }
   100  
   101  // Cancel informs the notifier that this subscription is cancelled by the API
   102  func (s *bufferedSubscription) Cancel() error {
   103  	return s.notifier.Unsubscribe(s.id)
   104  }
   105  
   106  // Notify the subscriber of a particular event.
   107  func (s *bufferedSubscription) Notify(data interface{}) error {
   108  	return s.notifier.send(s.id, data)
   109  }
   110  
   111  // bufferedNotifier is a notifier that queues notifications in an internal queue and
   112  // send them as fast as possible to the client from this queue. It will stop if the
   113  // queue grows past a given size.
   114  type bufferedNotifier struct {
   115  	codec         ServerCodec                      // underlying connection
   116  	mu            sync.Mutex                       // guard internal state
   117  	subscriptions map[string]*bufferedSubscription // keep track of subscriptions associated with codec
   118  	queueSize     int                              // max number of items in queue
   119  	queue         chan *notification               // notification queue
   120  	stopped       bool                             // indication if this notifier is ordered to stop
   121  }
   122  
   123  // newBufferedNotifier returns a notifier that queues notifications in an internal queue
   124  // from which notifications are send as fast as possible to the client. If the queue size
   125  // limit is reached (client is unable to keep up) it will stop and closes the codec.
   126  func newBufferedNotifier(codec ServerCodec, size int) *bufferedNotifier {
   127  	notifier := &bufferedNotifier{
   128  		codec:         codec,
   129  		subscriptions: make(map[string]*bufferedSubscription),
   130  		queue:         make(chan *notification, size),
   131  		queueSize:     size,
   132  	}
   133  
   134  	go notifier.run()
   135  
   136  	return notifier
   137  }
   138  
   139  // NewSubscription creates a new subscription that forwards events to this instance internal
   140  // queue. The given callback is called when the subscription is unsubscribed/cancelled.
   141  func (n *bufferedNotifier) NewSubscription(callback UnsubscribeCallback) (Subscription, error) {
   142  	id, err := newSubscriptionID()
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	n.mu.Lock()
   148  	defer n.mu.Unlock()
   149  
   150  	if n.stopped {
   151  		return nil, errNotifierStopped
   152  	}
   153  
   154  	sub := &bufferedSubscription{
   155  		id:               id,
   156  		unsub:            callback,
   157  		notifier:         n,
   158  		pending:          make(chan interface{}),
   159  		flushed:          make(chan interface{}),
   160  		lastNotification: time.Now(),
   161  	}
   162  
   163  	n.subscriptions[id] = sub
   164  
   165  	return sub, nil
   166  }
   167  
   168  // Remove the given subscription. If subscription is not found notificationNotFoundErr is returned.
   169  func (n *bufferedNotifier) Unsubscribe(subid string) error {
   170  	n.mu.Lock()
   171  	sub, found := n.subscriptions[subid]
   172  	n.mu.Unlock()
   173  
   174  	if found {
   175  		// send the unsubscribe signal, this will cause the notifier not to accept new events
   176  		// for this subscription and will close the flushed channel after the last (buffered)
   177  		// notification was send to the client.
   178  		if err := n.send(subid, unsubSignal); err != nil {
   179  			return err
   180  		}
   181  
   182  		// wait for confirmation that all (buffered) events are send for this subscription.
   183  		// this ensures that the unsubscribe method response is not send before all buffered
   184  		// events for this subscription are send.
   185  		<-sub.flushed
   186  
   187  		return nil
   188  	}
   189  
   190  	return ErrNotificationNotFound
   191  }
   192  
   193  // Send enques the given data for the subscription with public ID on the internal queue. t returns
   194  // an error when the notifier is stopped or the queue is full. If data is the unsubscribe signal it
   195  // will remove the subscription with the given id from the subscription collection.
   196  func (n *bufferedNotifier) send(id string, data interface{}) error {
   197  	n.mu.Lock()
   198  	defer n.mu.Unlock()
   199  
   200  	if n.stopped {
   201  		return errNotifierStopped
   202  	}
   203  
   204  	var (
   205  		subscription *bufferedSubscription
   206  		found        bool
   207  	)
   208  
   209  	// check if subscription is associated with this connection, it might be cancelled
   210  	// (subscribe/connection closed)
   211  	if subscription, found = n.subscriptions[id]; !found {
   212  		glog.V(logger.Error).Infof("received notification for unknown subscription %s\n", id)
   213  		return ErrNotificationNotFound
   214  	}
   215  
   216  	// received the unsubscribe signal. Add it to the queue to make sure any pending notifications
   217  	// for this subscription are send. When the run loop receives this singal it will signal that
   218  	// all pending subscriptions are flushed and that the confirmation of the unsubscribe can be
   219  	// send to the user. Remove the subscriptions to make sure new notifications are not accepted.
   220  	if data == unsubSignal {
   221  		delete(n.subscriptions, id)
   222  		if subscription.unsub != nil {
   223  			subscription.unsubOnce.Do(func() { subscription.unsub(id) })
   224  		}
   225  	}
   226  
   227  	subscription.lastNotification = time.Now()
   228  
   229  	if len(n.queue) >= n.queueSize {
   230  		glog.V(logger.Warn).Infoln("too many buffered notifications -> close connection")
   231  		n.codec.Close()
   232  		return errNotificationQueueFull
   233  	}
   234  
   235  	n.queue <- &notification{subscription, data}
   236  	return nil
   237  }
   238  
   239  // run reads notifications from the internal queue and sends them to the client. In case of an
   240  // error, or when the codec is closed it will cancel all active subscriptions and returns.
   241  func (n *bufferedNotifier) run() {
   242  	defer func() {
   243  		n.mu.Lock()
   244  		defer n.mu.Unlock()
   245  
   246  		n.stopped = true
   247  		close(n.queue)
   248  
   249  		// on exit call unsubscribe callback
   250  		for id, sub := range n.subscriptions {
   251  			if sub.unsub != nil {
   252  				sub.unsubOnce.Do(func() { sub.unsub(id) })
   253  			}
   254  			close(sub.flushed)
   255  			delete(n.subscriptions, id)
   256  		}
   257  	}()
   258  
   259  	for {
   260  		select {
   261  		case notification := <-n.queue:
   262  			// It can happen that an event is raised before the RPC server was able to send the sub
   263  			// id to the client. Therefore subscriptions are marked as pending until the sub id was
   264  			// send. The RPC server will activate the subscription by closing the pending chan.
   265  			<-notification.sub.pending
   266  
   267  			if notification.data == unsubSignal {
   268  				// unsubSignal is the last accepted message for this subscription. Raise the signal
   269  				// that all buffered notifications are sent by closing the flushed channel. This
   270  				// indicates that the response for the unsubscribe can be send to the client.
   271  				close(notification.sub.flushed)
   272  			} else {
   273  				msg := n.codec.CreateNotification(notification.sub.id, notification.data)
   274  				if err := n.codec.Write(msg); err != nil {
   275  					n.codec.Close()
   276  					// unable to send notification to client, unsubscribe all subscriptions
   277  					glog.V(logger.Warn).Infof("unable to send notification - %v\n", err)
   278  					return
   279  				}
   280  			}
   281  		case <-n.codec.Closed(): // connection was closed
   282  			glog.V(logger.Debug).Infoln("codec closed, stop subscriptions")
   283  			return
   284  		}
   285  	}
   286  }
   287  
   288  // Marks the subscription as active. This will causes the notifications for this subscription to be
   289  // forwarded to the client.
   290  func (n *bufferedNotifier) activate(subid string) {
   291  	n.mu.Lock()
   292  	defer n.mu.Unlock()
   293  
   294  	if sub, found := n.subscriptions[subid]; found {
   295  		close(sub.pending)
   296  	}
   297  }