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