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 }