github.com/prysmaticlabs/prysm@v1.4.4/shared/event/subscription.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 event
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/prysmaticlabs/prysm/shared/mclockutil"
    25  )
    26  
    27  // waitQuotient is divided against the max backoff time, in order to have N requests based on the full
    28  // request backoff time.
    29  const waitQuotient = 10
    30  
    31  // Subscription represents a stream of events. The carrier of the events is typically a
    32  // channel, but isn't part of the interface.
    33  //
    34  // Subscriptions can fail while established. Failures are reported through an error
    35  // channel. It receives a value if there is an issue with the subscription (e.g. the
    36  // network connection delivering the events has been closed). Only one value will ever be
    37  // sent.
    38  //
    39  // The error channel is closed when the subscription ends successfully (i.e. when the
    40  // source of events is closed). It is also closed when Unsubscribe is called.
    41  //
    42  // The Unsubscribe method cancels the sending of events. You must call Unsubscribe in all
    43  // cases to ensure that resources related to the subscription are released. It can be
    44  // called any number of times.
    45  type Subscription interface {
    46  	Err() <-chan error // returns the error channel
    47  	Unsubscribe()      // cancels sending of events, closing the error channel
    48  }
    49  
    50  // NewSubscription runs a producer function as a subscription in a new goroutine. The
    51  // channel given to the producer is closed when Unsubscribe is called. If fn returns an
    52  // error, it is sent on the subscription's error channel.
    53  func NewSubscription(producer func(<-chan struct{}) error) Subscription {
    54  	s := &funcSub{unsub: make(chan struct{}), err: make(chan error, 1)}
    55  	go func() {
    56  		defer close(s.err)
    57  		err := producer(s.unsub)
    58  		s.mu.Lock()
    59  		defer s.mu.Unlock()
    60  		if !s.unsubscribed {
    61  			if err != nil {
    62  				s.err <- err
    63  			}
    64  			s.unsubscribed = true
    65  		}
    66  	}()
    67  	return s
    68  }
    69  
    70  type funcSub struct {
    71  	unsub        chan struct{}
    72  	err          chan error
    73  	mu           sync.Mutex
    74  	unsubscribed bool
    75  }
    76  
    77  // Unsubscribe unsubscribes from subscription.
    78  func (s *funcSub) Unsubscribe() {
    79  	s.mu.Lock()
    80  	if s.unsubscribed {
    81  		s.mu.Unlock()
    82  		return
    83  	}
    84  	s.unsubscribed = true
    85  	close(s.unsub)
    86  	s.mu.Unlock()
    87  	// Wait for producer shutdown.
    88  	<-s.err
    89  }
    90  
    91  // Err exposes error channel.
    92  func (s *funcSub) Err() <-chan error {
    93  	return s.err
    94  }
    95  
    96  // Resubscribe calls fn repeatedly to keep a subscription established. When the
    97  // subscription is established, Resubscribe waits for it to fail and calls fn again. This
    98  // process repeats until Unsubscribe is called or the active subscription ends
    99  // successfully.
   100  //
   101  // Resubscribe applies backoff between calls to fn. The time between calls is adapted
   102  // based on the error rate, but will never exceed backoffMax.
   103  func Resubscribe(backoffMax time.Duration, fn ResubscribeFunc) Subscription {
   104  	s := &resubscribeSub{
   105  		waitTime:   backoffMax / waitQuotient,
   106  		backoffMax: backoffMax,
   107  		fn:         fn,
   108  		err:        make(chan error),
   109  		unsub:      make(chan struct{}),
   110  	}
   111  	go s.loop()
   112  	return s
   113  }
   114  
   115  // A ResubscribeFunc attempts to establish a subscription.
   116  type ResubscribeFunc func(context.Context) (Subscription, error)
   117  
   118  type resubscribeSub struct {
   119  	fn                   ResubscribeFunc
   120  	err                  chan error
   121  	unsub                chan struct{}
   122  	unsubOnce            sync.Once
   123  	lastTry              mclockutil.AbsTime
   124  	waitTime, backoffMax time.Duration
   125  }
   126  
   127  // Unsubscribe unsubscribes from subscription.
   128  func (s *resubscribeSub) Unsubscribe() {
   129  	s.unsubOnce.Do(func() {
   130  		s.unsub <- struct{}{}
   131  		<-s.err
   132  	})
   133  }
   134  
   135  // Err exposes error channel.
   136  func (s *resubscribeSub) Err() <-chan error {
   137  	return s.err
   138  }
   139  
   140  func (s *resubscribeSub) loop() {
   141  	defer close(s.err)
   142  	var done bool
   143  	for !done {
   144  		sub := s.subscribe()
   145  		if sub == nil {
   146  			break
   147  		}
   148  		done = s.waitForError(sub)
   149  		sub.Unsubscribe()
   150  	}
   151  }
   152  
   153  func (s *resubscribeSub) subscribe() Subscription {
   154  	subscribed := make(chan error)
   155  	var sub Subscription
   156  retry:
   157  	for {
   158  		s.lastTry = mclockutil.Now()
   159  		ctx, cancel := context.WithCancel(context.TODO())
   160  		go func() {
   161  			rsub, err := s.fn(ctx)
   162  			sub = rsub
   163  			subscribed <- err
   164  		}()
   165  		select {
   166  		case err := <-subscribed:
   167  			cancel()
   168  			if err != nil {
   169  				// Subscribing failed, wait before launching the next try.
   170  				if s.backoffWait() {
   171  					return nil
   172  				}
   173  				continue retry
   174  			}
   175  			if sub == nil {
   176  				panic("event: ResubscribeFunc returned nil subscription and no error")
   177  			}
   178  			return sub
   179  		case <-s.unsub:
   180  			cancel()
   181  			return nil
   182  		}
   183  	}
   184  }
   185  
   186  func (s *resubscribeSub) waitForError(sub Subscription) bool {
   187  	defer sub.Unsubscribe()
   188  	select {
   189  	case err := <-sub.Err():
   190  		return err == nil
   191  	case <-s.unsub:
   192  		return true
   193  	}
   194  }
   195  
   196  func (s *resubscribeSub) backoffWait() bool {
   197  	if time.Duration(mclockutil.Now()-s.lastTry) > s.backoffMax {
   198  		s.waitTime = s.backoffMax / waitQuotient
   199  	} else {
   200  		s.waitTime *= 2
   201  		if s.waitTime > s.backoffMax {
   202  			s.waitTime = s.backoffMax
   203  		}
   204  	}
   205  
   206  	t := time.NewTimer(s.waitTime)
   207  	defer t.Stop()
   208  	select {
   209  	case <-t.C:
   210  		return false
   211  	case <-s.unsub:
   212  		return true
   213  	}
   214  }
   215  
   216  // SubscriptionScope provides a facility to unsubscribe multiple subscriptions at once.
   217  //
   218  // For code that handle more than one subscription, a scope can be used to conveniently
   219  // unsubscribe all of them with a single call. The example demonstrates a typical use in a
   220  // larger program.
   221  //
   222  // The zero value is ready to use.
   223  type SubscriptionScope struct {
   224  	mu     sync.Mutex
   225  	subs   map[*scopeSub]struct{}
   226  	closed bool
   227  }
   228  
   229  type scopeSub struct {
   230  	sc *SubscriptionScope
   231  	s  Subscription
   232  }
   233  
   234  // Track starts tracking a subscription. If the scope is closed, Track returns nil. The
   235  // returned subscription is a wrapper. Unsubscribing the wrapper removes it from the
   236  // scope.
   237  func (sc *SubscriptionScope) Track(s Subscription) Subscription {
   238  	sc.mu.Lock()
   239  	defer sc.mu.Unlock()
   240  	if sc.closed {
   241  		return nil
   242  	}
   243  	if sc.subs == nil {
   244  		sc.subs = make(map[*scopeSub]struct{})
   245  	}
   246  	ss := &scopeSub{sc, s}
   247  	sc.subs[ss] = struct{}{}
   248  	return ss
   249  }
   250  
   251  // Close calls Unsubscribe on all tracked subscriptions and prevents further additions to
   252  // the tracked set. Calls to Track after Close return nil.
   253  func (sc *SubscriptionScope) Close() {
   254  	sc.mu.Lock()
   255  	defer sc.mu.Unlock()
   256  	if sc.closed {
   257  		return
   258  	}
   259  	sc.closed = true
   260  	for s := range sc.subs {
   261  		s.s.Unsubscribe()
   262  	}
   263  	sc.subs = nil
   264  }
   265  
   266  // Count returns the number of tracked subscriptions.
   267  // It is meant to be used for debugging.
   268  func (sc *SubscriptionScope) Count() int {
   269  	sc.mu.Lock()
   270  	defer sc.mu.Unlock()
   271  	return len(sc.subs)
   272  }
   273  
   274  // Unsubscribe unsubscribes from subscription.
   275  func (s *scopeSub) Unsubscribe() {
   276  	s.s.Unsubscribe()
   277  	s.sc.mu.Lock()
   278  	defer s.sc.mu.Unlock()
   279  	delete(s.sc.subs, s)
   280  }
   281  
   282  // Err exposes error channel.
   283  func (s *scopeSub) Err() <-chan error {
   284  	return s.s.Err()
   285  }