github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/rpc/notification.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 rpc 18 19 import ( 20 "context" 21 "errors" 22 "sync" 23 "time" 24 25 "github.com/ethereumproject/go-ethereum/logger" 26 "github.com/ethereumproject/go-ethereum/logger/glog" 27 ) 28 29 var ( 30 // ErrNotificationsUnsupported is returned when the connection doesn't support notifications 31 ErrNotificationsUnsupported = errors.New("notifications not supported") 32 33 // ErrNotificationNotFound is returned when the notification for the given id is not found 34 ErrNotificationNotFound = errors.New("notification not found") 35 36 // errNotifierStopped is returned when the notifier is stopped (e.g. codec is closed) 37 errNotifierStopped = errors.New("unable to send notification") 38 39 // errNotificationQueueFull is returns when there are too many notifications in the queue 40 errNotificationQueueFull = errors.New("too many pending notifications") 41 ) 42 43 // unsubSignal is a signal that the subscription is unsubscribed. It is used to flush buffered 44 // notifications that might be pending in the internal queue. 45 var unsubSignal = new(struct{}) 46 47 // UnsubscribeCallback defines a callback that is called when a subcription ends. 48 // It receives the subscription id as argument. 49 type UnsubscribeCallback func(id string) 50 51 // notification is a helper object that holds event data for a subscription 52 type notification struct { 53 sub *bufferedSubscription // subscription id 54 data interface{} // event data 55 } 56 57 // A Notifier type describes the interface for objects that can send create subscriptions 58 type Notifier interface { 59 // Create a new subscription. The given callback is called when this subscription 60 // is cancelled (e.g. client send an unsubscribe, connection closed). 61 NewSubscription(UnsubscribeCallback) (Subscription, error) 62 // Cancel subscription 63 Unsubscribe(id string) error 64 } 65 66 type notifierKey struct{} 67 68 // NotifierFromContext returns the Notifier value stored in ctx, if any. 69 func NotifierFromContext(ctx context.Context) (Notifier, bool) { 70 n, ok := ctx.Value(notifierKey{}).(Notifier) 71 return n, ok 72 } 73 74 // Subscription defines the interface for objects that can notify subscribers 75 type Subscription interface { 76 // Inform client of an event 77 Notify(data interface{}) error 78 // Unique identifier 79 ID() string 80 // Cancel subscription 81 Cancel() error 82 } 83 84 // bufferedSubscription is a subscription that uses a bufferedNotifier to send 85 // notifications to subscribers. 86 type bufferedSubscription struct { 87 id string 88 unsubOnce sync.Once // call unsub method once 89 unsub UnsubscribeCallback // called on Unsubscribed 90 notifier *bufferedNotifier // forward notifications to 91 pending chan interface{} // closed when active 92 flushed chan interface{} // closed when all buffered notifications are send 93 lastNotification time.Time // last time a notification was send 94 } 95 96 // ID returns the subscription identifier that the client uses to refer to this instance. 97 func (s *bufferedSubscription) ID() string { 98 return s.id 99 } 100 101 // Cancel informs the notifier that this subscription is cancelled by the API 102 func (s *bufferedSubscription) Cancel() error { 103 return s.notifier.Unsubscribe(s.id) 104 } 105 106 // Notify the subscriber of a particular event. 107 func (s *bufferedSubscription) Notify(data interface{}) error { 108 return s.notifier.send(s.id, data) 109 } 110 111 // bufferedNotifier is a notifier that queues notifications in an internal queue and 112 // send them as fast as possible to the client from this queue. It will stop if the 113 // queue grows past a given size. 114 type bufferedNotifier struct { 115 codec ServerCodec // underlying connection 116 mu sync.Mutex // guard internal state 117 subscriptions map[string]*bufferedSubscription // keep track of subscriptions associated with codec 118 queueSize int // max number of items in queue 119 queue chan *notification // notification queue 120 stopped bool // indication if this notifier is ordered to stop 121 } 122 123 // newBufferedNotifier returns a notifier that queues notifications in an internal queue 124 // from which notifications are send as fast as possible to the client. If the queue size 125 // limit is reached (client is unable to keep up) it will stop and closes the codec. 126 func newBufferedNotifier(codec ServerCodec, size int) *bufferedNotifier { 127 notifier := &bufferedNotifier{ 128 codec: codec, 129 subscriptions: make(map[string]*bufferedSubscription), 130 queue: make(chan *notification, size), 131 queueSize: size, 132 } 133 134 go notifier.run() 135 136 return notifier 137 } 138 139 // NewSubscription creates a new subscription that forwards events to this instance internal 140 // queue. The given callback is called when the subscription is unsubscribed/cancelled. 141 func (n *bufferedNotifier) NewSubscription(callback UnsubscribeCallback) (Subscription, error) { 142 id, err := newSubscriptionID() 143 if err != nil { 144 return nil, err 145 } 146 147 n.mu.Lock() 148 defer n.mu.Unlock() 149 150 if n.stopped { 151 return nil, errNotifierStopped 152 } 153 154 sub := &bufferedSubscription{ 155 id: id, 156 unsub: callback, 157 notifier: n, 158 pending: make(chan interface{}), 159 flushed: make(chan interface{}), 160 lastNotification: time.Now(), 161 } 162 163 n.subscriptions[id] = sub 164 165 return sub, nil 166 } 167 168 // Remove the given subscription. If subscription is not found notificationNotFoundErr is returned. 169 func (n *bufferedNotifier) Unsubscribe(subid string) error { 170 n.mu.Lock() 171 sub, found := n.subscriptions[subid] 172 n.mu.Unlock() 173 174 if found { 175 // send the unsubscribe signal, this will cause the notifier not to accept new events 176 // for this subscription and will close the flushed channel after the last (buffered) 177 // notification was send to the client. 178 if err := n.send(subid, unsubSignal); err != nil { 179 return err 180 } 181 182 // wait for confirmation that all (buffered) events are send for this subscription. 183 // this ensures that the unsubscribe method response is not send before all buffered 184 // events for this subscription are send. 185 <-sub.flushed 186 187 return nil 188 } 189 190 return ErrNotificationNotFound 191 } 192 193 // Send enques the given data for the subscription with public ID on the internal queue. t returns 194 // an error when the notifier is stopped or the queue is full. If data is the unsubscribe signal it 195 // will remove the subscription with the given id from the subscription collection. 196 func (n *bufferedNotifier) send(id string, data interface{}) error { 197 n.mu.Lock() 198 defer n.mu.Unlock() 199 200 if n.stopped { 201 return errNotifierStopped 202 } 203 204 var ( 205 subscription *bufferedSubscription 206 found bool 207 ) 208 209 // check if subscription is associated with this connection, it might be cancelled 210 // (subscribe/connection closed) 211 if subscription, found = n.subscriptions[id]; !found { 212 glog.V(logger.Error).Infof("received notification for unknown subscription %s\n", id) 213 return ErrNotificationNotFound 214 } 215 216 // received the unsubscribe signal. Add it to the queue to make sure any pending notifications 217 // for this subscription are send. When the run loop receives this singal it will signal that 218 // all pending subscriptions are flushed and that the confirmation of the unsubscribe can be 219 // send to the user. Remove the subscriptions to make sure new notifications are not accepted. 220 if data == unsubSignal { 221 delete(n.subscriptions, id) 222 if subscription.unsub != nil { 223 subscription.unsubOnce.Do(func() { subscription.unsub(id) }) 224 } 225 } 226 227 subscription.lastNotification = time.Now() 228 229 if len(n.queue) >= n.queueSize { 230 glog.V(logger.Warn).Infoln("too many buffered notifications -> close connection") 231 n.codec.Close() 232 return errNotificationQueueFull 233 } 234 235 n.queue <- ¬ification{subscription, data} 236 return nil 237 } 238 239 // run reads notifications from the internal queue and sends them to the client. In case of an 240 // error, or when the codec is closed it will cancel all active subscriptions and returns. 241 func (n *bufferedNotifier) run() { 242 defer func() { 243 n.mu.Lock() 244 defer n.mu.Unlock() 245 246 n.stopped = true 247 close(n.queue) 248 249 // on exit call unsubscribe callback 250 for id, sub := range n.subscriptions { 251 if sub.unsub != nil { 252 sub.unsubOnce.Do(func() { sub.unsub(id) }) 253 } 254 close(sub.flushed) 255 delete(n.subscriptions, id) 256 } 257 }() 258 259 for { 260 select { 261 case notification := <-n.queue: 262 // It can happen that an event is raised before the RPC server was able to send the sub 263 // id to the client. Therefore subscriptions are marked as pending until the sub id was 264 // send. The RPC server will activate the subscription by closing the pending chan. 265 <-notification.sub.pending 266 267 if notification.data == unsubSignal { 268 // unsubSignal is the last accepted message for this subscription. Raise the signal 269 // that all buffered notifications are sent by closing the flushed channel. This 270 // indicates that the response for the unsubscribe can be send to the client. 271 close(notification.sub.flushed) 272 } else { 273 msg := n.codec.CreateNotification(notification.sub.id, notification.data) 274 if err := n.codec.Write(msg); err != nil { 275 n.codec.Close() 276 // unable to send notification to client, unsubscribe all subscriptions 277 glog.V(logger.Warn).Infof("unable to send notification - %v\n", err) 278 return 279 } 280 } 281 case <-n.codec.Closed(): // connection was closed 282 glog.V(logger.Debug).Infoln("codec closed, stop subscriptions") 283 return 284 } 285 } 286 } 287 288 // Marks the subscription as active. This will causes the notifications for this subscription to be 289 // forwarded to the client. 290 func (n *bufferedNotifier) activate(subid string) { 291 n.mu.Lock() 292 defer n.mu.Unlock() 293 294 if sub, found := n.subscriptions[subid]; found { 295 close(sub.pending) 296 } 297 }