github.com/klaytn/klaytn@v1.10.2/event/subscription.go (about)

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