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 }