github.com/decred/dcrlnd@v0.7.6/subscribe/subscribe.go (about)

     1  package subscribe
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  	"sync/atomic"
     7  
     8  	"github.com/decred/dcrlnd/queue"
     9  )
    10  
    11  // ErrServerShuttingDown is an error returned in case the server is in the
    12  // process of shutting down.
    13  var ErrServerShuttingDown = errors.New("subscription server shutting down")
    14  
    15  // Client is used to get notified about updates the caller has subscribed to,
    16  type Client struct {
    17  	// cancel should be called in case the client no longer wants to
    18  	// subscribe for updates from the server.
    19  	cancel func()
    20  
    21  	updates *queue.ConcurrentQueue
    22  	quit    chan struct{}
    23  }
    24  
    25  // Updates returns a read-only channel where the updates the client has
    26  // subscribed to will be delivered.
    27  func (c *Client) Updates() <-chan interface{} {
    28  	return c.updates.ChanOut()
    29  }
    30  
    31  // Quit is a channel that will be closed in case the server decides to no
    32  // longer deliver updates to this client.
    33  func (c *Client) Quit() <-chan struct{} {
    34  	return c.quit
    35  }
    36  
    37  // Cancel should be called in case the client no longer wants to
    38  // subscribe for updates from the server.
    39  func (c *Client) Cancel() {
    40  	c.cancel()
    41  }
    42  
    43  // Server is a struct that manages a set of subscriptions and their
    44  // corresponding clients. Any update will be delivered to all active clients.
    45  type Server struct {
    46  	clientCounter uint64 // To be used atomically.
    47  
    48  	started uint32 // To be used atomically.
    49  	stopped uint32 // To be used atomically.
    50  
    51  	clients       map[uint64]*Client
    52  	clientUpdates chan *clientUpdate
    53  
    54  	updates chan interface{}
    55  
    56  	quit chan struct{}
    57  	wg   sync.WaitGroup
    58  }
    59  
    60  // clientUpdate is an internal message sent to the subscriptionHandler to
    61  // either register a new client for subscription or cancel an existing
    62  // subscription.
    63  type clientUpdate struct {
    64  	// cancel indicates if the update to the client is cancelling an
    65  	// existing client's subscription. If not then this update will be to
    66  	// subscribe a new client.
    67  	cancel bool
    68  
    69  	// clientID is the unique identifier for this client. Any further
    70  	// updates (deleting or adding) to this notification client will be
    71  	// dispatched according to the target clientID.
    72  	clientID uint64
    73  
    74  	// client is the new client that will receive updates. Will be nil in
    75  	// case this is a cancallation update.
    76  	client *Client
    77  }
    78  
    79  // NewServer returns a new Server.
    80  func NewServer() *Server {
    81  	return &Server{
    82  		clients:       make(map[uint64]*Client),
    83  		clientUpdates: make(chan *clientUpdate),
    84  		updates:       make(chan interface{}),
    85  		quit:          make(chan struct{}),
    86  	}
    87  }
    88  
    89  // Start starts the Server, making it ready to accept subscriptions and
    90  // updates.
    91  func (s *Server) Start() error {
    92  	if !atomic.CompareAndSwapUint32(&s.started, 0, 1) {
    93  		return nil
    94  	}
    95  
    96  	s.wg.Add(1)
    97  	go s.subscriptionHandler()
    98  
    99  	return nil
   100  }
   101  
   102  // Stop stops the server.
   103  func (s *Server) Stop() error {
   104  	if !atomic.CompareAndSwapUint32(&s.stopped, 0, 1) {
   105  		return nil
   106  	}
   107  
   108  	close(s.quit)
   109  	s.wg.Wait()
   110  
   111  	return nil
   112  }
   113  
   114  // Subscribe returns a Client that will receive updates any time the Server is
   115  // made aware of a new event.
   116  func (s *Server) Subscribe() (*Client, error) {
   117  	// We'll first atomically obtain the next ID for this client from the
   118  	// incrementing client ID counter.
   119  	clientID := atomic.AddUint64(&s.clientCounter, 1)
   120  
   121  	// Create the client that will be returned. The Cancel method is
   122  	// populated to send the cancellation intent to the
   123  	// subscriptionHandler.
   124  	client := &Client{
   125  		updates: queue.NewConcurrentQueue(20),
   126  		quit:    make(chan struct{}),
   127  		cancel: func() {
   128  			select {
   129  			case s.clientUpdates <- &clientUpdate{
   130  				cancel:   true,
   131  				clientID: clientID,
   132  			}:
   133  			case <-s.quit:
   134  				return
   135  			}
   136  		},
   137  	}
   138  
   139  	select {
   140  	case s.clientUpdates <- &clientUpdate{
   141  		cancel:   false,
   142  		clientID: clientID,
   143  		client:   client,
   144  	}:
   145  	case <-s.quit:
   146  		return nil, ErrServerShuttingDown
   147  	}
   148  
   149  	return client, nil
   150  }
   151  
   152  // SendUpdate is called to send the passed update to all currently active
   153  // subscription clients.
   154  func (s *Server) SendUpdate(update interface{}) error {
   155  
   156  	select {
   157  	case s.updates <- update:
   158  		return nil
   159  	case <-s.quit:
   160  		return ErrServerShuttingDown
   161  	}
   162  }
   163  
   164  // subscriptionHandler is the main handler for the Server. It will handle
   165  // incoming updates and subscriptions, and forward the incoming updates to the
   166  // registered clients.
   167  //
   168  // NOTE: MUST be run as a goroutine.
   169  func (s *Server) subscriptionHandler() {
   170  	defer s.wg.Done()
   171  
   172  	for {
   173  		select {
   174  
   175  		// If a client update is received, the either a new
   176  		// subscription becomes active, or we cancel and existing one.
   177  		case update := <-s.clientUpdates:
   178  			clientID := update.clientID
   179  
   180  			// In case this is a cancellation, stop the client's
   181  			// underlying queue, and remove the client from the set
   182  			// of active subscription clients.
   183  			if update.cancel {
   184  				client, ok := s.clients[update.clientID]
   185  				if ok {
   186  					client.updates.Stop()
   187  					close(client.quit)
   188  					delete(s.clients, clientID)
   189  				}
   190  
   191  				continue
   192  			}
   193  
   194  			// If this was not a cancellation, start the underlying
   195  			// queue and add the client to our set of subscription
   196  			// clients. It will be notified about any new updates
   197  			// the server receives.
   198  			update.client.updates.Start()
   199  			s.clients[update.clientID] = update.client
   200  
   201  		// A new update was received, forward it to all active clients.
   202  		case upd := <-s.updates:
   203  			for _, client := range s.clients {
   204  				select {
   205  				case client.updates.ChanIn() <- upd:
   206  				case <-client.quit:
   207  				case <-s.quit:
   208  					return
   209  				}
   210  			}
   211  
   212  		// In case the server is shutting down, stop the clients and
   213  		// close the quit channels to notify them.
   214  		case <-s.quit:
   215  			for _, client := range s.clients {
   216  				client.updates.Stop()
   217  				close(client.quit)
   218  			}
   219  			return
   220  		}
   221  	}
   222  }