github.com/amazechain/amc@v0.1.3/modules/event/v2/subscription.go (about)

     1  // Copyright 2023 The AmazeChain Authors
     2  // This file is part of the AmazeChain library.
     3  //
     4  // The AmazeChain 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 AmazeChain 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 AmazeChain library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package v2
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/amazechain/amc/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  	return ResubscribeErr(backoffMax, func(ctx context.Context, _ error) (Subscription, error) {
    99  		return fn(ctx)
   100  	})
   101  }
   102  
   103  // A ResubscribeFunc attempts to establish a subscription.
   104  type ResubscribeFunc func(context.Context) (Subscription, error)
   105  
   106  // ResubscribeErr calls fn repeatedly to keep a subscription established. When the
   107  // subscription is established, ResubscribeErr waits for it to fail and calls fn again. This
   108  // process repeats until Unsubscribe is called or the active subscription ends
   109  // successfully.
   110  //
   111  // The difference between Resubscribe and ResubscribeErr is that with ResubscribeErr,
   112  // the error of the failing subscription is available to the callback for logging
   113  // purposes.
   114  //
   115  // ResubscribeErr applies backoff between calls to fn. The time between calls is adapted
   116  // based on the error rate, but will never exceed backoffMax.
   117  func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscription {
   118  	s := &resubscribeSub{
   119  		waitTime:   backoffMax / 10,
   120  		backoffMax: backoffMax,
   121  		fn:         fn,
   122  		err:        make(chan error),
   123  		unsub:      make(chan struct{}),
   124  	}
   125  	go s.loop()
   126  	return s
   127  }
   128  
   129  // A ResubscribeErrFunc attempts to establish a subscription.
   130  // For every call but the first, the second argument to this function is
   131  // the error that occurred with the previous subscription.
   132  type ResubscribeErrFunc func(context.Context, error) (Subscription, error)
   133  
   134  type resubscribeSub struct {
   135  	fn                   ResubscribeErrFunc
   136  	err                  chan error
   137  	unsub                chan struct{}
   138  	unsubOnce            sync.Once
   139  	lastTry              mclock.AbsTime
   140  	lastSubErr           error
   141  	waitTime, backoffMax time.Duration
   142  }
   143  
   144  func (s *resubscribeSub) Unsubscribe() {
   145  	s.unsubOnce.Do(func() {
   146  		s.unsub <- struct{}{}
   147  		<-s.err
   148  	})
   149  }
   150  
   151  func (s *resubscribeSub) Err() <-chan error {
   152  	return s.err
   153  }
   154  
   155  func (s *resubscribeSub) loop() {
   156  	defer close(s.err)
   157  	var done bool
   158  	for !done {
   159  		sub := s.subscribe()
   160  		if sub == nil {
   161  			break
   162  		}
   163  		done = s.waitForError(sub)
   164  		sub.Unsubscribe()
   165  	}
   166  }
   167  
   168  func (s *resubscribeSub) subscribe() Subscription {
   169  	subscribed := make(chan error)
   170  	var sub Subscription
   171  	for {
   172  		s.lastTry = mclock.Now()
   173  		ctx, cancel := context.WithCancel(context.Background())
   174  		go func() {
   175  			rsub, err := s.fn(ctx, s.lastSubErr)
   176  			sub = rsub
   177  			subscribed <- err
   178  		}()
   179  		select {
   180  		case err := <-subscribed:
   181  			cancel()
   182  			if err == nil {
   183  				if sub == nil {
   184  					panic("event: ResubscribeFunc returned nil subscription and no error")
   185  				}
   186  				return sub
   187  			}
   188  			// Subscribing failed, wait before launching the next try.
   189  			if s.backoffWait() {
   190  				return nil // unsubscribed during wait
   191  			}
   192  		case <-s.unsub:
   193  			cancel()
   194  			<-subscribed // avoid leaking the s.fn goroutine.
   195  			return nil
   196  		}
   197  	}
   198  }
   199  
   200  func (s *resubscribeSub) waitForError(sub Subscription) bool {
   201  	defer sub.Unsubscribe()
   202  	select {
   203  	case err := <-sub.Err():
   204  		s.lastSubErr = err
   205  		return err == nil
   206  	case <-s.unsub:
   207  		return true
   208  	}
   209  }
   210  
   211  func (s *resubscribeSub) backoffWait() bool {
   212  	if time.Duration(mclock.Now()-s.lastTry) > s.backoffMax {
   213  		s.waitTime = s.backoffMax / 10
   214  	} else {
   215  		s.waitTime *= 2
   216  		if s.waitTime > s.backoffMax {
   217  			s.waitTime = s.backoffMax
   218  		}
   219  	}
   220  
   221  	t := time.NewTimer(s.waitTime)
   222  	defer t.Stop()
   223  	select {
   224  	case <-t.C:
   225  		return false
   226  	case <-s.unsub:
   227  		return true
   228  	}
   229  }
   230  
   231  // SubscriptionScope provides a facility to unsubscribe multiple subscriptions at once.
   232  //
   233  // For code that handle more than one subscription, a scope can be used to conveniently
   234  // unsubscribe all of them with a single call. The example demonstrates a typical use in a
   235  // larger program.
   236  //
   237  // The zero value is ready to use.
   238  type SubscriptionScope struct {
   239  	mu     sync.Mutex
   240  	subs   map[*scopeSub]struct{}
   241  	closed bool
   242  }
   243  
   244  type scopeSub struct {
   245  	sc *SubscriptionScope
   246  	s  Subscription
   247  }
   248  
   249  // Track starts tracking a subscription. If the scope is closed, Track returns nil. The
   250  // returned subscription is a wrapper. Unsubscribing the wrapper removes it from the
   251  // scope.
   252  func (sc *SubscriptionScope) Track(s Subscription) Subscription {
   253  	sc.mu.Lock()
   254  	defer sc.mu.Unlock()
   255  	if sc.closed {
   256  		return nil
   257  	}
   258  	if sc.subs == nil {
   259  		sc.subs = make(map[*scopeSub]struct{})
   260  	}
   261  	ss := &scopeSub{sc, s}
   262  	sc.subs[ss] = struct{}{}
   263  	return ss
   264  }
   265  
   266  // Close calls Unsubscribe on all tracked subscriptions and prevents further additions to
   267  // the tracked set. Calls to Track after Close return nil.
   268  func (sc *SubscriptionScope) Close() {
   269  	sc.mu.Lock()
   270  	defer sc.mu.Unlock()
   271  	if sc.closed {
   272  		return
   273  	}
   274  	sc.closed = true
   275  	for s := range sc.subs {
   276  		s.s.Unsubscribe()
   277  	}
   278  	sc.subs = nil
   279  }
   280  
   281  // Count returns the number of tracked subscriptions.
   282  // It is meant to be used for debugging.
   283  func (sc *SubscriptionScope) Count() int {
   284  	sc.mu.Lock()
   285  	defer sc.mu.Unlock()
   286  	return len(sc.subs)
   287  }
   288  
   289  func (s *scopeSub) Unsubscribe() {
   290  	s.s.Unsubscribe()
   291  	s.sc.mu.Lock()
   292  	defer s.sc.mu.Unlock()
   293  	delete(s.sc.subs, s)
   294  }
   295  
   296  func (s *scopeSub) Err() <-chan error {
   297  	return s.s.Err()
   298  }