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 }