github.com/core-coin/go-core/v2@v2.1.9/event/subscription.go (about)

     1  // Copyright 2016 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core 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/core-coin/go-core/v2/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  	for {
   149  		s.lastTry = mclock.Now()
   150  		ctx, cancel := context.WithCancel(context.Background())
   151  		go func() {
   152  			rsub, err := s.fn(ctx)
   153  			sub = rsub
   154  			subscribed <- err
   155  		}()
   156  		select {
   157  		case err := <-subscribed:
   158  			cancel()
   159  			if err == nil {
   160  				if sub == nil {
   161  					panic("event: ResubscribeFunc returned nil subscription and no error")
   162  				}
   163  				return sub
   164  			}
   165  			// Subscribing failed, wait before launching the next try.
   166  			if s.backoffWait() {
   167  				return nil // unsubscribed during wait
   168  			}
   169  		case <-s.unsub:
   170  			cancel()
   171  			<-subscribed // avoid leaking the s.fn goroutine.
   172  			return nil
   173  		}
   174  	}
   175  }
   176  
   177  func (s *resubscribeSub) waitForError(sub Subscription) bool {
   178  	defer sub.Unsubscribe()
   179  	select {
   180  	case err := <-sub.Err():
   181  		return err == nil
   182  	case <-s.unsub:
   183  		return true
   184  	}
   185  }
   186  
   187  func (s *resubscribeSub) backoffWait() bool {
   188  	if time.Duration(mclock.Now()-s.lastTry) > s.backoffMax {
   189  		s.waitTime = s.backoffMax / 10
   190  	} else {
   191  		s.waitTime *= 2
   192  		if s.waitTime > s.backoffMax {
   193  			s.waitTime = s.backoffMax
   194  		}
   195  	}
   196  
   197  	t := time.NewTimer(s.waitTime)
   198  	defer t.Stop()
   199  	select {
   200  	case <-t.C:
   201  		return false
   202  	case <-s.unsub:
   203  		return true
   204  	}
   205  }
   206  
   207  // SubscriptionScope provides a facility to unsubscribe multiple subscriptions at once.
   208  //
   209  // For code that handle more than one subscription, a scope can be used to conveniently
   210  // unsubscribe all of them with a single call. The example demonstrates a typical use in a
   211  // larger program.
   212  //
   213  // The zero value is ready to use.
   214  type SubscriptionScope struct {
   215  	mu     sync.Mutex
   216  	subs   map[*scopeSub]struct{}
   217  	closed bool
   218  }
   219  
   220  type scopeSub struct {
   221  	sc *SubscriptionScope
   222  	s  Subscription
   223  }
   224  
   225  // Track starts tracking a subscription. If the scope is closed, Track returns nil. The
   226  // returned subscription is a wrapper. Unsubscribing the wrapper removes it from the
   227  // scope.
   228  func (sc *SubscriptionScope) Track(s Subscription) Subscription {
   229  	sc.mu.Lock()
   230  	defer sc.mu.Unlock()
   231  	if sc.closed {
   232  		return nil
   233  	}
   234  	if sc.subs == nil {
   235  		sc.subs = make(map[*scopeSub]struct{})
   236  	}
   237  	ss := &scopeSub{sc, s}
   238  	sc.subs[ss] = struct{}{}
   239  	return ss
   240  }
   241  
   242  // Close calls Unsubscribe on all tracked subscriptions and prevents further additions to
   243  // the tracked set. Calls to Track after Close return nil.
   244  func (sc *SubscriptionScope) Close() {
   245  	sc.mu.Lock()
   246  	defer sc.mu.Unlock()
   247  	if sc.closed {
   248  		return
   249  	}
   250  	sc.closed = true
   251  	for s := range sc.subs {
   252  		s.s.Unsubscribe()
   253  	}
   254  	sc.subs = nil
   255  }
   256  
   257  // Count returns the number of tracked subscriptions.
   258  // It is meant to be used for debugging.
   259  func (sc *SubscriptionScope) Count() int {
   260  	sc.mu.Lock()
   261  	defer sc.mu.Unlock()
   262  	return len(sc.subs)
   263  }
   264  
   265  func (s *scopeSub) Unsubscribe() {
   266  	s.s.Unsubscribe()
   267  	s.sc.mu.Lock()
   268  	defer s.sc.mu.Unlock()
   269  	delete(s.sc.subs, s)
   270  }
   271  
   272  func (s *scopeSub) Err() <-chan error {
   273  	return s.s.Err()
   274  }