github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/net/websocket/wsnotificationmaneger.go (about) 1 package websocket 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "sync" 7 8 log "github.com/sirupsen/logrus" 9 10 "github.com/bytom/bytom/event" 11 "github.com/bytom/bytom/protocol" 12 "github.com/bytom/bytom/protocol/bc" 13 "github.com/bytom/bytom/protocol/bc/types" 14 ) 15 16 // Notification types 17 type notificationBlockConnected types.Block 18 type notificationBlockDisconnected types.Block 19 type notificationTxDescAcceptedByMempool protocol.TxDesc 20 21 // Notification control requests 22 type notificationRegisterClient WSClient 23 type notificationUnregisterClient WSClient 24 type notificationRegisterBlocks WSClient 25 type notificationUnregisterBlocks WSClient 26 type notificationRegisterNewMempoolTxs WSClient 27 type notificationUnregisterNewMempoolTxs WSClient 28 29 // NotificationType represents the type of a notification message. 30 type NotificationType int 31 32 // Constants for the type of a notification message. 33 const ( 34 // NTBlockConnected indicates the associated block was connected to the main chain. 35 NTRawBlockConnected NotificationType = iota 36 // NTBlockDisconnected indicates the associated block was disconnected from the main chain. 37 NTRawBlockDisconnected 38 NTNewTransaction 39 NTRequestStatus 40 ) 41 42 // notificationTypeStrings is a map of notification types back to their constant 43 // names for pretty printing. 44 var notificationTypeStrings = map[NotificationType]string{ 45 NTRawBlockConnected: "raw_blocks_connected", 46 NTRawBlockDisconnected: "raw_blocks_disconnected", 47 NTNewTransaction: "new_transaction", 48 NTRequestStatus: "request_status", 49 } 50 51 // String returns the NotificationType in human-readable form. 52 func (n NotificationType) String() string { 53 if s, ok := notificationTypeStrings[n]; ok { 54 return s 55 } 56 return fmt.Sprintf("Unknown Notification Type (%d)", int(n)) 57 } 58 59 type statusInfo struct { 60 BestHeight uint64 61 BestHash bc.Hash 62 } 63 64 // WSNotificationManager is a connection and notification manager used for 65 // websockets. It allows websocket clients to register for notifications they 66 // are interested in. When an event happens elsewhere in the code such as 67 // transactions being added to the memory pool or block connects/disconnects, 68 // the notification manager is provided with the relevant details needed to 69 // figure out which websocket clients need to be notified based on what they 70 // have registered for and notifies them accordingly. It is also used to keep 71 // track of all connected websocket clients. 72 type WSNotificationManager struct { 73 // queueNotification queues a notification for handling. 74 queueNotification chan interface{} 75 76 // notificationMsgs feeds notificationHandler with notifications 77 // and client (un)registeration requests from a queue as well as 78 // registeration and unregisteration requests from clients. 79 notificationMsgs chan interface{} 80 81 // Access channel for current number of connected clients. 82 numClients chan int 83 84 // Shutdown handling 85 wg sync.WaitGroup 86 quit chan struct{} 87 MaxNumWebsockets int 88 maxNumConcurrentReqs int 89 status statusInfo 90 chain *protocol.Chain 91 eventDispatcher *event.Dispatcher 92 txMsgSub *event.Subscription 93 } 94 95 // NewWsNotificationManager returns a new notification manager ready for use. See WSNotificationManager for more details. 96 func NewWsNotificationManager(maxNumWebsockets int, maxNumConcurrentReqs int, chain *protocol.Chain, dispatcher *event.Dispatcher) *WSNotificationManager { 97 // init status 98 var status statusInfo 99 header := chain.BestBlockHeader() 100 status.BestHeight = header.Height 101 status.BestHash = header.Hash() 102 103 return &WSNotificationManager{ 104 queueNotification: make(chan interface{}), 105 notificationMsgs: make(chan interface{}), 106 numClients: make(chan int), 107 quit: make(chan struct{}), 108 MaxNumWebsockets: maxNumWebsockets, 109 maxNumConcurrentReqs: maxNumConcurrentReqs, 110 status: status, 111 chain: chain, 112 eventDispatcher: dispatcher, 113 } 114 } 115 116 // queueHandler manages a queue of empty interfaces, reading from in and 117 // sending the oldest unsent to out. This handler stops when either of the 118 // in or quit channels are closed, and closes out before returning, without 119 // waiting to send any variables still remaining in the queue. 120 func queueHandler(in <-chan interface{}, out chan<- interface{}, quit <-chan struct{}) { 121 var ( 122 q []interface{} 123 next interface{} 124 dequeue chan<- interface{} 125 ) 126 127 skipQueue := out 128 129 out: 130 for { 131 select { 132 case n, ok := <-in: 133 if !ok { 134 // Sender closed input channel. 135 break out 136 } 137 138 // Either send to out immediately if skipQueue is 139 // non-nil (queue is empty) and reader is ready, 140 // or append to the queue and send later. 141 select { 142 case skipQueue <- n: 143 144 default: 145 q = append(q, n) 146 dequeue = out 147 skipQueue = nil 148 next = q[0] 149 } 150 151 case dequeue <- next: 152 copy(q, q[1:]) 153 q[len(q)-1] = nil // avoid leak 154 q = q[:len(q)-1] 155 if len(q) == 0 { 156 dequeue = nil 157 skipQueue = out 158 } else { 159 next = q[0] 160 } 161 162 case <-quit: 163 break out 164 } 165 } 166 close(out) 167 } 168 169 func (m *WSNotificationManager) sendNotification(typ NotificationType, data interface{}) { 170 switch typ { 171 case NTRawBlockConnected: 172 block, ok := data.(*types.Block) 173 if !ok { 174 log.WithField("module", logModule).Error("Chain connected notification is not a block") 175 break 176 } 177 m.status.BestHeight = block.Height 178 m.status.BestHash = block.Hash() 179 // Notify registered websocket clients of incoming block. 180 m.NotifyBlockConnected(block) 181 182 case NTRawBlockDisconnected: 183 block, ok := data.(*types.Block) 184 if !ok { 185 log.WithField("module", logModule).Error("Chain disconnected notification is not a block") 186 break 187 } 188 m.status.BestHeight = block.Height - 1 189 m.status.BestHash = block.PreviousBlockHash 190 // Notify registered websocket clients. 191 m.NotifyBlockDisconnected(block) 192 } 193 } 194 195 // queueHandler maintains a queue of notifications and notification handler 196 // control messages. 197 func (m *WSNotificationManager) queueHandler() { 198 queueHandler(m.queueNotification, m.notificationMsgs, m.quit) 199 m.wg.Done() 200 } 201 202 // NotifyBlockConnected passes a block newly-connected to the best chain 203 // to the notification manager for block and transaction notification 204 // processing. 205 func (m *WSNotificationManager) NotifyBlockConnected(block *types.Block) { 206 select { 207 case m.queueNotification <- (*notificationBlockConnected)(block): 208 case <-m.quit: 209 } 210 } 211 212 // NotifyBlockDisconnected passes a block disconnected from the best chain 213 // to the notification manager for block notification processing. 214 func (m *WSNotificationManager) NotifyBlockDisconnected(block *types.Block) { 215 select { 216 case m.queueNotification <- (*notificationBlockDisconnected)(block): 217 case <-m.quit: 218 } 219 } 220 221 // memPoolTxQueryLoop constantly pass a transaction accepted by mempool to the 222 // notification manager for transaction notification processing. 223 func (m *WSNotificationManager) memPoolTxQueryLoop() { 224 out: 225 for { 226 select { 227 case obj, ok := <-m.txMsgSub.Chan(): 228 if !ok { 229 log.WithFields(log.Fields{"module": logModule}).Warning("tx pool tx msg subscription channel closed") 230 break out 231 } 232 233 ev, ok := obj.Data.(protocol.TxMsgEvent) 234 if !ok { 235 log.WithFields(log.Fields{"module": logModule}).Error("event type error") 236 continue 237 } 238 239 if ev.TxMsg.MsgType == protocol.MsgNewTx { 240 select { 241 case m.queueNotification <- (*notificationTxDescAcceptedByMempool)(ev.TxMsg.TxDesc): 242 default: 243 } 244 } 245 case <-m.quit: 246 break out 247 } 248 } 249 250 m.wg.Done() 251 } 252 253 // notificationHandler reads notifications and control messages from the queue handler and processes one at a time. 254 func (m *WSNotificationManager) notificationHandler() { 255 // clients is a map of all currently connected websocket clients. 256 clients := make(map[chan struct{}]*WSClient) 257 blockNotifications := make(map[chan struct{}]*WSClient) 258 txNotifications := make(map[chan struct{}]*WSClient) 259 260 out: 261 for { 262 select { 263 case n, ok := <-m.notificationMsgs: 264 if !ok { 265 break out 266 } 267 switch n := n.(type) { 268 case *notificationBlockConnected: 269 block := (*types.Block)(n) 270 if len(blockNotifications) != 0 { 271 m.notifyBlockConnected(blockNotifications, block) 272 } 273 274 case *notificationBlockDisconnected: 275 block := (*types.Block)(n) 276 if len(blockNotifications) != 0 { 277 m.notifyBlockDisconnected(blockNotifications, block) 278 } 279 280 case *notificationTxDescAcceptedByMempool: 281 txDesc := (*protocol.TxDesc)(n) 282 if len(txNotifications) != 0 { 283 m.notifyForNewTx(txNotifications, txDesc) 284 } 285 286 case *notificationRegisterBlocks: 287 wsc := (*WSClient)(n) 288 blockNotifications[wsc.quit] = wsc 289 290 case *notificationUnregisterBlocks: 291 wsc := (*WSClient)(n) 292 delete(blockNotifications, wsc.quit) 293 294 case *notificationRegisterNewMempoolTxs: 295 wsc := (*WSClient)(n) 296 txNotifications[wsc.quit] = wsc 297 298 case *notificationUnregisterNewMempoolTxs: 299 wsc := (*WSClient)(n) 300 delete(txNotifications, wsc.quit) 301 302 case *notificationRegisterClient: 303 wsc := (*WSClient)(n) 304 clients[wsc.quit] = wsc 305 306 case *notificationUnregisterClient: 307 wsc := (*WSClient)(n) 308 delete(blockNotifications, wsc.quit) 309 delete(txNotifications, wsc.quit) 310 delete(clients, wsc.quit) 311 312 default: 313 log.Warnf("Unhandled notification type") 314 } 315 316 case m.numClients <- len(clients): 317 318 case <-m.quit: 319 break out 320 } 321 } 322 323 for _, c := range clients { 324 c.Disconnect() 325 } 326 m.wg.Done() 327 } 328 329 // NumClients returns the number of clients actively being served. 330 func (m *WSNotificationManager) NumClients() (n int) { 331 select { 332 case n = <-m.numClients: 333 case <-m.quit: 334 } 335 return 336 } 337 338 // IsMaxConnect returns whether the maximum connection is exceeded 339 func (m *WSNotificationManager) IsMaxConnect() bool { 340 return m.NumClients() >= m.MaxNumWebsockets 341 } 342 343 // RegisterBlockUpdates requests block update notifications to the passed websocket client. 344 func (m *WSNotificationManager) RegisterBlockUpdates(wsc *WSClient) { 345 m.queueNotification <- (*notificationRegisterBlocks)(wsc) 346 } 347 348 // UnregisterBlockUpdates removes block update notifications for the passed websocket client. 349 func (m *WSNotificationManager) UnregisterBlockUpdates(wsc *WSClient) { 350 m.queueNotification <- (*notificationUnregisterBlocks)(wsc) 351 } 352 353 // notifyBlockConnected notifies websocket clients that have registered for block updates when a block is connected to the main chain. 354 func (*WSNotificationManager) notifyBlockConnected(clients map[chan struct{}]*WSClient, block *types.Block) { 355 resp := NewWSResponse(NTRawBlockConnected.String(), block, nil) 356 marshalledJSON, err := json.Marshal(resp) 357 if err != nil { 358 log.WithFields(log.Fields{"module": logModule, "error": err}).Error("Failed to marshal block connected notification") 359 return 360 } 361 362 for _, wsc := range clients { 363 wsc.QueueNotification(marshalledJSON) 364 } 365 } 366 367 // notifyBlockDisconnected notifies websocket clients that have registered for block updates 368 // when a block is disconnected from the main chain (due to a reorganize). 369 func (*WSNotificationManager) notifyBlockDisconnected(clients map[chan struct{}]*WSClient, block *types.Block) { 370 resp := NewWSResponse(NTRawBlockDisconnected.String(), block, nil) 371 marshalledJSON, err := json.Marshal(resp) 372 if err != nil { 373 log.WithField("error", err).Error("Failed to marshal block Disconnected notification") 374 return 375 } 376 377 for _, wsc := range clients { 378 wsc.QueueNotification(marshalledJSON) 379 } 380 } 381 382 // RegisterNewMempoolTxsUpdates requests notifications to the passed websocket 383 // client when new transactions are added to the memory pool. 384 func (m *WSNotificationManager) RegisterNewMempoolTxsUpdates(wsc *WSClient) { 385 m.queueNotification <- (*notificationRegisterNewMempoolTxs)(wsc) 386 } 387 388 // UnregisterNewMempoolTxsUpdates removes notifications to the passed websocket 389 // client when new transaction are added to the memory pool. 390 func (m *WSNotificationManager) UnregisterNewMempoolTxsUpdates(wsc *WSClient) { 391 m.queueNotification <- (*notificationUnregisterNewMempoolTxs)(wsc) 392 } 393 394 // notifyForNewTx notifies websocket clients that have registered for updates 395 // when a new transaction is added to the memory pool. 396 func (m *WSNotificationManager) notifyForNewTx(clients map[chan struct{}]*WSClient, txDesc *protocol.TxDesc) { 397 resp := NewWSResponse(NTNewTransaction.String(), txDesc, nil) 398 marshalledJSON, err := json.Marshal(resp) 399 if err != nil { 400 log.WithFields(log.Fields{"module": logModule, "error": err}).Error("Failed to marshal tx notification") 401 return 402 } 403 404 for _, wsc := range clients { 405 wsc.QueueNotification(marshalledJSON) 406 } 407 } 408 409 // AddClient adds the passed websocket client to the notification manager. 410 func (m *WSNotificationManager) AddClient(wsc *WSClient) { 411 m.queueNotification <- (*notificationRegisterClient)(wsc) 412 } 413 414 // RemoveClient removes the passed websocket client and all notifications registered for it. 415 func (m *WSNotificationManager) RemoveClient(wsc *WSClient) { 416 select { 417 case m.queueNotification <- (*notificationUnregisterClient)(wsc): 418 case <-m.quit: 419 } 420 } 421 422 func (m *WSNotificationManager) blockNotify() { 423 out: 424 for { 425 select { 426 case <-m.quit: 427 break out 428 429 default: 430 } 431 for !m.chain.InMainChain(m.status.BestHash) { 432 block, err := m.chain.GetBlockByHash(&m.status.BestHash) 433 if err != nil { 434 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("blockNotify GetBlockByHash") 435 return 436 } 437 438 m.sendNotification(NTRawBlockDisconnected, block) 439 } 440 441 block, _ := m.chain.GetBlockByHeight(m.status.BestHeight + 1) 442 if block == nil { 443 m.blockWaiter() 444 continue 445 } 446 447 if m.status.BestHash != block.PreviousBlockHash { 448 log.WithFields(log.Fields{"module": logModule, "blockHeight": block.Height, "previousBlockHash": m.status.BestHash, "rcvBlockPrevHash": block.PreviousBlockHash}).Warning("The previousBlockHash of the received block is not the same as the hash of the previous block") 449 continue 450 } 451 452 m.sendNotification(NTRawBlockConnected, block) 453 } 454 m.wg.Done() 455 } 456 457 func (m *WSNotificationManager) blockWaiter() { 458 select { 459 case <-m.chain.BlockWaiter(m.status.BestHeight + 1): 460 case <-m.quit: 461 } 462 } 463 464 // Start starts the goroutines required for the manager to queue and process websocket client notifications. 465 func (m *WSNotificationManager) Start() error { 466 var err error 467 m.txMsgSub, err = m.eventDispatcher.Subscribe(protocol.TxMsgEvent{}) 468 if err != nil { 469 return err 470 } 471 472 m.wg.Add(4) 473 go m.blockNotify() 474 go m.queueHandler() 475 go m.notificationHandler() 476 go m.memPoolTxQueryLoop() 477 return nil 478 } 479 480 // WaitForShutdown blocks until all notification manager goroutines have finished. 481 func (m *WSNotificationManager) WaitForShutdown() { 482 m.wg.Wait() 483 } 484 485 // Shutdown shuts down the manager, stopping the notification queue and notification handler goroutines. 486 func (m *WSNotificationManager) Shutdown() { 487 close(m.quit) 488 }