github.com/yihuang/erigon@v1.9.7/rpc/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 rpc 18 19 import ( 20 "bufio" 21 "container/list" 22 "context" 23 crand "crypto/rand" 24 "encoding/binary" 25 "encoding/hex" 26 "encoding/json" 27 "errors" 28 "math/rand" 29 "reflect" 30 "strings" 31 "sync" 32 "time" 33 ) 34 35 var ( 36 // ErrNotificationsUnsupported is returned when the connection doesn't support notifications 37 ErrNotificationsUnsupported = errors.New("notifications not supported") 38 // ErrNotificationNotFound is returned when the notification for the given id is not found 39 ErrSubscriptionNotFound = errors.New("subscription not found") 40 ) 41 42 var globalGen = randomIDGenerator() 43 44 // ID defines a pseudo random number that is used to identify RPC subscriptions. 45 type ID string 46 47 // NewID returns a new, random ID. 48 func NewID() ID { 49 return globalGen() 50 } 51 52 // randomIDGenerator returns a function generates a random IDs. 53 func randomIDGenerator() func() ID { 54 seed, err := binary.ReadVarint(bufio.NewReader(crand.Reader)) 55 if err != nil { 56 seed = int64(time.Now().Nanosecond()) 57 } 58 var ( 59 mu sync.Mutex 60 rng = rand.New(rand.NewSource(seed)) 61 ) 62 return func() ID { 63 mu.Lock() 64 defer mu.Unlock() 65 id := make([]byte, 16) 66 rng.Read(id) 67 return encodeID(id) 68 } 69 } 70 71 func encodeID(b []byte) ID { 72 id := hex.EncodeToString(b) 73 id = strings.TrimLeft(id, "0") 74 if id == "" { 75 id = "0" // ID's are RPC quantities, no leading zero's and 0 is 0x0. 76 } 77 return ID("0x" + id) 78 } 79 80 type notifierKey struct{} 81 82 // NotifierFromContext returns the Notifier value stored in ctx, if any. 83 func NotifierFromContext(ctx context.Context) (*Notifier, bool) { 84 n, ok := ctx.Value(notifierKey{}).(*Notifier) 85 return n, ok 86 } 87 88 // Notifier is tied to a RPC connection that supports subscriptions. 89 // Server callbacks use the notifier to send notifications. 90 type Notifier struct { 91 h *handler 92 namespace string 93 94 mu sync.Mutex 95 sub *Subscription 96 buffer []json.RawMessage 97 callReturned bool 98 activated bool 99 } 100 101 // CreateSubscription returns a new subscription that is coupled to the 102 // RPC connection. By default subscriptions are inactive and notifications 103 // are dropped until the subscription is marked as active. This is done 104 // by the RPC server after the subscription ID is send to the client. 105 func (n *Notifier) CreateSubscription() *Subscription { 106 n.mu.Lock() 107 defer n.mu.Unlock() 108 109 if n.sub != nil { 110 panic("can't create multiple subscriptions with Notifier") 111 } else if n.callReturned { 112 panic("can't create subscription after subscribe call has returned") 113 } 114 n.sub = &Subscription{ID: n.h.idgen(), namespace: n.namespace, err: make(chan error, 1)} 115 return n.sub 116 } 117 118 // Notify sends a notification to the client with the given data as payload. 119 // If an error occurs the RPC connection is closed and the error is returned. 120 func (n *Notifier) Notify(id ID, data interface{}) error { 121 enc, err := json.Marshal(data) 122 if err != nil { 123 return err 124 } 125 126 n.mu.Lock() 127 defer n.mu.Unlock() 128 129 if n.sub == nil { 130 panic("can't Notify before subscription is created") 131 } else if n.sub.ID != id { 132 panic("Notify with wrong ID") 133 } 134 if n.activated { 135 return n.send(n.sub, enc) 136 } 137 n.buffer = append(n.buffer, enc) 138 return nil 139 } 140 141 // Closed returns a channel that is closed when the RPC connection is closed. 142 // Deprecated: use subscription error channel 143 func (n *Notifier) Closed() <-chan interface{} { 144 return n.h.conn.Closed() 145 } 146 147 // takeSubscription returns the subscription (if one has been created). No subscription can 148 // be created after this call. 149 func (n *Notifier) takeSubscription() *Subscription { 150 n.mu.Lock() 151 defer n.mu.Unlock() 152 n.callReturned = true 153 return n.sub 154 } 155 156 // acticate is called after the subscription ID was sent to client. Notifications are 157 // buffered before activation. This prevents notifications being sent to the client before 158 // the subscription ID is sent to the client. 159 func (n *Notifier) activate() error { 160 n.mu.Lock() 161 defer n.mu.Unlock() 162 163 for _, data := range n.buffer { 164 if err := n.send(n.sub, data); err != nil { 165 return err 166 } 167 } 168 n.activated = true 169 return nil 170 } 171 172 func (n *Notifier) send(sub *Subscription, data json.RawMessage) error { 173 params, _ := json.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data}) 174 ctx := context.Background() 175 return n.h.conn.Write(ctx, &jsonrpcMessage{ 176 Version: vsn, 177 Method: n.namespace + notificationMethodSuffix, 178 Params: params, 179 }) 180 } 181 182 // A Subscription is created by a notifier and tight to that notifier. The client can use 183 // this subscription to wait for an unsubscribe request for the client, see Err(). 184 type Subscription struct { 185 ID ID 186 namespace string 187 err chan error // closed on unsubscribe 188 } 189 190 // Err returns a channel that is closed when the client send an unsubscribe request. 191 func (s *Subscription) Err() <-chan error { 192 return s.err 193 } 194 195 // MarshalJSON marshals a subscription as its ID. 196 func (s *Subscription) MarshalJSON() ([]byte, error) { 197 return json.Marshal(s.ID) 198 } 199 200 // ClientSubscription is a subscription established through the Client's Subscribe or 201 // EthSubscribe methods. 202 type ClientSubscription struct { 203 client *Client 204 etype reflect.Type 205 channel reflect.Value 206 namespace string 207 subid string 208 in chan json.RawMessage 209 210 quitOnce sync.Once // ensures quit is closed once 211 quit chan struct{} // quit is closed when the subscription exits 212 errOnce sync.Once // ensures err is closed once 213 err chan error 214 } 215 216 func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription { 217 sub := &ClientSubscription{ 218 client: c, 219 namespace: namespace, 220 etype: channel.Type().Elem(), 221 channel: channel, 222 quit: make(chan struct{}), 223 err: make(chan error, 1), 224 in: make(chan json.RawMessage), 225 } 226 return sub 227 } 228 229 // Err returns the subscription error channel. The intended use of Err is to schedule 230 // resubscription when the client connection is closed unexpectedly. 231 // 232 // The error channel receives a value when the subscription has ended due 233 // to an error. The received error is nil if Close has been called 234 // on the underlying client and no other error has occurred. 235 // 236 // The error channel is closed when Unsubscribe is called on the subscription. 237 func (sub *ClientSubscription) Err() <-chan error { 238 return sub.err 239 } 240 241 // Unsubscribe unsubscribes the notification and closes the error channel. 242 // It can safely be called more than once. 243 func (sub *ClientSubscription) Unsubscribe() { 244 sub.quitWithError(nil, true) 245 sub.errOnce.Do(func() { close(sub.err) }) 246 } 247 248 func (sub *ClientSubscription) quitWithError(err error, unsubscribeServer bool) { 249 sub.quitOnce.Do(func() { 250 // The dispatch loop won't be able to execute the unsubscribe call 251 // if it is blocked on deliver. Close sub.quit first because it 252 // unblocks deliver. 253 close(sub.quit) 254 if unsubscribeServer { 255 sub.requestUnsubscribe() 256 } 257 if err != nil { 258 if err == ErrClientQuit { 259 err = nil // Adhere to subscription semantics. 260 } 261 sub.err <- err 262 } 263 }) 264 } 265 266 func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) { 267 select { 268 case sub.in <- result: 269 return true 270 case <-sub.quit: 271 return false 272 } 273 } 274 275 func (sub *ClientSubscription) start() { 276 sub.quitWithError(sub.forward()) 277 } 278 279 func (sub *ClientSubscription) forward() (err error, unsubscribeServer bool) { 280 cases := []reflect.SelectCase{ 281 {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)}, 282 {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.in)}, 283 {Dir: reflect.SelectSend, Chan: sub.channel}, 284 } 285 buffer := list.New() 286 defer buffer.Init() 287 for { 288 var chosen int 289 var recv reflect.Value 290 if buffer.Len() == 0 { 291 // Idle, omit send case. 292 chosen, recv, _ = reflect.Select(cases[:2]) 293 } else { 294 // Non-empty buffer, send the first queued item. 295 cases[2].Send = reflect.ValueOf(buffer.Front().Value) 296 chosen, recv, _ = reflect.Select(cases) 297 } 298 299 switch chosen { 300 case 0: // <-sub.quit 301 return nil, false 302 case 1: // <-sub.in 303 val, err := sub.unmarshal(recv.Interface().(json.RawMessage)) 304 if err != nil { 305 return err, true 306 } 307 if buffer.Len() == maxClientSubscriptionBuffer { 308 return ErrSubscriptionQueueOverflow, true 309 } 310 buffer.PushBack(val) 311 case 2: // sub.channel<- 312 cases[2].Send = reflect.Value{} // Don't hold onto the value. 313 buffer.Remove(buffer.Front()) 314 } 315 } 316 } 317 318 func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, error) { 319 val := reflect.New(sub.etype) 320 err := json.Unmarshal(result, val.Interface()) 321 return val.Elem().Interface(), err 322 } 323 324 func (sub *ClientSubscription) requestUnsubscribe() error { 325 var result interface{} 326 return sub.client.Call(&result, sub.namespace+unsubscribeMethodSuffix, sub.subid) 327 }