github.com/DFWallet/tendermint-cosmos@v0.0.2/p2p/conn/connection.go (about) 1 package conn 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io" 8 "math" 9 "net" 10 "reflect" 11 "runtime/debug" 12 "sync/atomic" 13 "time" 14 15 "github.com/gogo/protobuf/proto" 16 17 flow "github.com/DFWallet/tendermint-cosmos/libs/flowrate" 18 "github.com/DFWallet/tendermint-cosmos/libs/log" 19 tmmath "github.com/DFWallet/tendermint-cosmos/libs/math" 20 "github.com/DFWallet/tendermint-cosmos/libs/protoio" 21 "github.com/DFWallet/tendermint-cosmos/libs/service" 22 tmsync "github.com/DFWallet/tendermint-cosmos/libs/sync" 23 "github.com/DFWallet/tendermint-cosmos/libs/timer" 24 tmp2p "github.com/DFWallet/tendermint-cosmos/proto/tendermint/p2p" 25 ) 26 27 const ( 28 defaultMaxPacketMsgPayloadSize = 1024 29 30 numBatchPacketMsgs = 10 31 minReadBufferSize = 1024 32 minWriteBufferSize = 65536 33 updateStats = 2 * time.Second 34 35 // some of these defaults are written in the user config 36 // flushThrottle, sendRate, recvRate 37 // TODO: remove values present in config 38 defaultFlushThrottle = 100 * time.Millisecond 39 40 defaultSendQueueCapacity = 1 41 defaultRecvBufferCapacity = 4096 42 defaultRecvMessageCapacity = 22020096 // 21MB 43 defaultSendRate = int64(512000) // 500KB/s 44 defaultRecvRate = int64(512000) // 500KB/s 45 defaultSendTimeout = 10 * time.Second 46 defaultPingInterval = 60 * time.Second 47 defaultPongTimeout = 45 * time.Second 48 ) 49 50 type receiveCbFunc func(chID byte, msgBytes []byte) 51 type errorCbFunc func(interface{}) 52 53 /* 54 Each peer has one `MConnection` (multiplex connection) instance. 55 56 __multiplex__ *noun* a system or signal involving simultaneous transmission of 57 several messages along a single channel of communication. 58 59 Each `MConnection` handles message transmission on multiple abstract communication 60 `Channel`s. Each channel has a globally unique byte id. 61 The byte id and the relative priorities of each `Channel` are configured upon 62 initialization of the connection. 63 64 There are two methods for sending messages: 65 func (m MConnection) Send(chID byte, msgBytes []byte) bool {} 66 func (m MConnection) TrySend(chID byte, msgBytes []byte}) bool {} 67 68 `Send(chID, msgBytes)` is a blocking call that waits until `msg` is 69 successfully queued for the channel with the given id byte `chID`, or until the 70 request times out. The message `msg` is serialized using Protobuf. 71 72 `TrySend(chID, msgBytes)` is a nonblocking call that returns false if the 73 channel's queue is full. 74 75 Inbound message bytes are handled with an onReceive callback function. 76 */ 77 type MConnection struct { 78 service.BaseService 79 80 conn net.Conn 81 bufConnReader *bufio.Reader 82 bufConnWriter *bufio.Writer 83 sendMonitor *flow.Monitor 84 recvMonitor *flow.Monitor 85 send chan struct{} 86 pong chan struct{} 87 channels []*Channel 88 channelsIdx map[byte]*Channel 89 onReceive receiveCbFunc 90 onError errorCbFunc 91 errored uint32 92 config MConnConfig 93 94 // Closing quitSendRoutine will cause the sendRoutine to eventually quit. 95 // doneSendRoutine is closed when the sendRoutine actually quits. 96 quitSendRoutine chan struct{} 97 doneSendRoutine chan struct{} 98 99 // Closing quitRecvRouting will cause the recvRouting to eventually quit. 100 quitRecvRoutine chan struct{} 101 102 // used to ensure FlushStop and OnStop 103 // are safe to call concurrently. 104 stopMtx tmsync.Mutex 105 106 flushTimer *timer.ThrottleTimer // flush writes as necessary but throttled. 107 pingTimer *time.Ticker // send pings periodically 108 109 // close conn if pong is not received in pongTimeout 110 pongTimer *time.Timer 111 pongTimeoutCh chan bool // true - timeout, false - peer sent pong 112 113 chStatsTimer *time.Ticker // update channel stats periodically 114 115 created time.Time // time of creation 116 117 _maxPacketMsgSize int 118 } 119 120 // MConnConfig is a MConnection configuration. 121 type MConnConfig struct { 122 SendRate int64 `mapstructure:"send_rate"` 123 RecvRate int64 `mapstructure:"recv_rate"` 124 125 // Maximum payload size 126 MaxPacketMsgPayloadSize int `mapstructure:"max_packet_msg_payload_size"` 127 128 // Interval to flush writes (throttled) 129 FlushThrottle time.Duration `mapstructure:"flush_throttle"` 130 131 // Interval to send pings 132 PingInterval time.Duration `mapstructure:"ping_interval"` 133 134 // Maximum wait time for pongs 135 PongTimeout time.Duration `mapstructure:"pong_timeout"` 136 } 137 138 // DefaultMConnConfig returns the default config. 139 func DefaultMConnConfig() MConnConfig { 140 return MConnConfig{ 141 SendRate: defaultSendRate, 142 RecvRate: defaultRecvRate, 143 MaxPacketMsgPayloadSize: defaultMaxPacketMsgPayloadSize, 144 FlushThrottle: defaultFlushThrottle, 145 PingInterval: defaultPingInterval, 146 PongTimeout: defaultPongTimeout, 147 } 148 } 149 150 // NewMConnection wraps net.Conn and creates multiplex connection 151 func NewMConnection( 152 conn net.Conn, 153 chDescs []*ChannelDescriptor, 154 onReceive receiveCbFunc, 155 onError errorCbFunc, 156 ) *MConnection { 157 return NewMConnectionWithConfig( 158 conn, 159 chDescs, 160 onReceive, 161 onError, 162 DefaultMConnConfig()) 163 } 164 165 // NewMConnectionWithConfig wraps net.Conn and creates multiplex connection with a config 166 func NewMConnectionWithConfig( 167 conn net.Conn, 168 chDescs []*ChannelDescriptor, 169 onReceive receiveCbFunc, 170 onError errorCbFunc, 171 config MConnConfig, 172 ) *MConnection { 173 if config.PongTimeout >= config.PingInterval { 174 panic("pongTimeout must be less than pingInterval (otherwise, next ping will reset pong timer)") 175 } 176 177 mconn := &MConnection{ 178 conn: conn, 179 bufConnReader: bufio.NewReaderSize(conn, minReadBufferSize), 180 bufConnWriter: bufio.NewWriterSize(conn, minWriteBufferSize), 181 sendMonitor: flow.New(0, 0), 182 recvMonitor: flow.New(0, 0), 183 send: make(chan struct{}, 1), 184 pong: make(chan struct{}, 1), 185 onReceive: onReceive, 186 onError: onError, 187 config: config, 188 created: time.Now(), 189 } 190 191 // Create channels 192 var channelsIdx = map[byte]*Channel{} 193 var channels = []*Channel{} 194 195 for _, desc := range chDescs { 196 channel := newChannel(mconn, *desc) 197 channelsIdx[channel.desc.ID] = channel 198 channels = append(channels, channel) 199 } 200 mconn.channels = channels 201 mconn.channelsIdx = channelsIdx 202 203 mconn.BaseService = *service.NewBaseService(nil, "MConnection", mconn) 204 205 // maxPacketMsgSize() is a bit heavy, so call just once 206 mconn._maxPacketMsgSize = mconn.maxPacketMsgSize() 207 208 return mconn 209 } 210 211 func (c *MConnection) SetLogger(l log.Logger) { 212 c.BaseService.SetLogger(l) 213 for _, ch := range c.channels { 214 ch.SetLogger(l) 215 } 216 } 217 218 // OnStart implements BaseService 219 func (c *MConnection) OnStart() error { 220 if err := c.BaseService.OnStart(); err != nil { 221 return err 222 } 223 c.flushTimer = timer.NewThrottleTimer("flush", c.config.FlushThrottle) 224 c.pingTimer = time.NewTicker(c.config.PingInterval) 225 c.pongTimeoutCh = make(chan bool, 1) 226 c.chStatsTimer = time.NewTicker(updateStats) 227 c.quitSendRoutine = make(chan struct{}) 228 c.doneSendRoutine = make(chan struct{}) 229 c.quitRecvRoutine = make(chan struct{}) 230 go c.sendRoutine() 231 go c.recvRoutine() 232 return nil 233 } 234 235 // stopServices stops the BaseService and timers and closes the quitSendRoutine. 236 // if the quitSendRoutine was already closed, it returns true, otherwise it returns false. 237 // It uses the stopMtx to ensure only one of FlushStop and OnStop can do this at a time. 238 func (c *MConnection) stopServices() (alreadyStopped bool) { 239 c.stopMtx.Lock() 240 defer c.stopMtx.Unlock() 241 242 select { 243 case <-c.quitSendRoutine: 244 // already quit 245 return true 246 default: 247 } 248 249 select { 250 case <-c.quitRecvRoutine: 251 // already quit 252 return true 253 default: 254 } 255 256 c.BaseService.OnStop() 257 c.flushTimer.Stop() 258 c.pingTimer.Stop() 259 c.chStatsTimer.Stop() 260 261 // inform the recvRouting that we are shutting down 262 close(c.quitRecvRoutine) 263 close(c.quitSendRoutine) 264 return false 265 } 266 267 // FlushStop replicates the logic of OnStop. 268 // It additionally ensures that all successful 269 // .Send() calls will get flushed before closing 270 // the connection. 271 func (c *MConnection) FlushStop() { 272 if c.stopServices() { 273 return 274 } 275 276 // this block is unique to FlushStop 277 { 278 // wait until the sendRoutine exits 279 // so we dont race on calling sendSomePacketMsgs 280 <-c.doneSendRoutine 281 282 // Send and flush all pending msgs. 283 // Since sendRoutine has exited, we can call this 284 // safely 285 eof := c.sendSomePacketMsgs() 286 for !eof { 287 eof = c.sendSomePacketMsgs() 288 } 289 c.flush() 290 291 // Now we can close the connection 292 } 293 294 c.conn.Close() 295 296 // We can't close pong safely here because 297 // recvRoutine may write to it after we've stopped. 298 // Though it doesn't need to get closed at all, 299 // we close it @ recvRoutine. 300 301 // c.Stop() 302 } 303 304 // OnStop implements BaseService 305 func (c *MConnection) OnStop() { 306 if c.stopServices() { 307 return 308 } 309 310 c.conn.Close() 311 312 // We can't close pong safely here because 313 // recvRoutine may write to it after we've stopped. 314 // Though it doesn't need to get closed at all, 315 // we close it @ recvRoutine. 316 } 317 318 func (c *MConnection) String() string { 319 return fmt.Sprintf("MConn{%v}", c.conn.RemoteAddr()) 320 } 321 322 func (c *MConnection) flush() { 323 c.Logger.Debug("Flush", "conn", c) 324 err := c.bufConnWriter.Flush() 325 if err != nil { 326 c.Logger.Debug("MConnection flush failed", "err", err) 327 } 328 } 329 330 // Catch panics, usually caused by remote disconnects. 331 func (c *MConnection) _recover() { 332 if r := recover(); r != nil { 333 c.Logger.Error("MConnection panicked", "err", r, "stack", string(debug.Stack())) 334 c.stopForError(fmt.Errorf("recovered from panic: %v", r)) 335 } 336 } 337 338 func (c *MConnection) stopForError(r interface{}) { 339 if err := c.Stop(); err != nil { 340 c.Logger.Error("Error stopping connection", "err", err) 341 } 342 if atomic.CompareAndSwapUint32(&c.errored, 0, 1) { 343 if c.onError != nil { 344 c.onError(r) 345 } 346 } 347 } 348 349 // Queues a message to be sent to channel. 350 func (c *MConnection) Send(chID byte, msgBytes []byte) bool { 351 if !c.IsRunning() { 352 return false 353 } 354 355 c.Logger.Debug("Send", "channel", chID, "conn", c, "msgBytes", fmt.Sprintf("%X", msgBytes)) 356 357 // Send message to channel. 358 channel, ok := c.channelsIdx[chID] 359 if !ok { 360 c.Logger.Error(fmt.Sprintf("Cannot send bytes, unknown channel %X", chID)) 361 return false 362 } 363 364 success := channel.sendBytes(msgBytes) 365 if success { 366 // Wake up sendRoutine if necessary 367 select { 368 case c.send <- struct{}{}: 369 default: 370 } 371 } else { 372 c.Logger.Debug("Send failed", "channel", chID, "conn", c, "msgBytes", fmt.Sprintf("%X", msgBytes)) 373 } 374 return success 375 } 376 377 // Queues a message to be sent to channel. 378 // Nonblocking, returns true if successful. 379 func (c *MConnection) TrySend(chID byte, msgBytes []byte) bool { 380 if !c.IsRunning() { 381 return false 382 } 383 384 c.Logger.Debug("TrySend", "channel", chID, "conn", c, "msgBytes", fmt.Sprintf("%X", msgBytes)) 385 386 // Send message to channel. 387 channel, ok := c.channelsIdx[chID] 388 if !ok { 389 c.Logger.Error(fmt.Sprintf("Cannot send bytes, unknown channel %X", chID)) 390 return false 391 } 392 393 ok = channel.trySendBytes(msgBytes) 394 if ok { 395 // Wake up sendRoutine if necessary 396 select { 397 case c.send <- struct{}{}: 398 default: 399 } 400 } 401 402 return ok 403 } 404 405 // CanSend returns true if you can send more data onto the chID, false 406 // otherwise. Use only as a heuristic. 407 func (c *MConnection) CanSend(chID byte) bool { 408 if !c.IsRunning() { 409 return false 410 } 411 412 channel, ok := c.channelsIdx[chID] 413 if !ok { 414 c.Logger.Error(fmt.Sprintf("Unknown channel %X", chID)) 415 return false 416 } 417 return channel.canSend() 418 } 419 420 // sendRoutine polls for packets to send from channels. 421 func (c *MConnection) sendRoutine() { 422 defer c._recover() 423 424 protoWriter := protoio.NewDelimitedWriter(c.bufConnWriter) 425 426 FOR_LOOP: 427 for { 428 var _n int 429 var err error 430 SELECTION: 431 select { 432 case <-c.flushTimer.Ch: 433 // NOTE: flushTimer.Set() must be called every time 434 // something is written to .bufConnWriter. 435 c.flush() 436 case <-c.chStatsTimer.C: 437 for _, channel := range c.channels { 438 channel.updateStats() 439 } 440 case <-c.pingTimer.C: 441 c.Logger.Debug("Send Ping") 442 _n, err = protoWriter.WriteMsg(mustWrapPacket(&tmp2p.PacketPing{})) 443 if err != nil { 444 c.Logger.Error("Failed to send PacketPing", "err", err) 445 break SELECTION 446 } 447 c.sendMonitor.Update(_n) 448 c.Logger.Debug("Starting pong timer", "dur", c.config.PongTimeout) 449 c.pongTimer = time.AfterFunc(c.config.PongTimeout, func() { 450 select { 451 case c.pongTimeoutCh <- true: 452 default: 453 } 454 }) 455 c.flush() 456 case timeout := <-c.pongTimeoutCh: 457 if timeout { 458 c.Logger.Debug("Pong timeout") 459 err = errors.New("pong timeout") 460 } else { 461 c.stopPongTimer() 462 } 463 case <-c.pong: 464 c.Logger.Debug("Send Pong") 465 _n, err = protoWriter.WriteMsg(mustWrapPacket(&tmp2p.PacketPong{})) 466 if err != nil { 467 c.Logger.Error("Failed to send PacketPong", "err", err) 468 break SELECTION 469 } 470 c.sendMonitor.Update(_n) 471 c.flush() 472 case <-c.quitSendRoutine: 473 break FOR_LOOP 474 case <-c.send: 475 // Send some PacketMsgs 476 eof := c.sendSomePacketMsgs() 477 if !eof { 478 // Keep sendRoutine awake. 479 select { 480 case c.send <- struct{}{}: 481 default: 482 } 483 } 484 } 485 486 if !c.IsRunning() { 487 break FOR_LOOP 488 } 489 if err != nil { 490 c.Logger.Error("Connection failed @ sendRoutine", "conn", c, "err", err) 491 c.stopForError(err) 492 break FOR_LOOP 493 } 494 } 495 496 // Cleanup 497 c.stopPongTimer() 498 close(c.doneSendRoutine) 499 } 500 501 // Returns true if messages from channels were exhausted. 502 // Blocks in accordance to .sendMonitor throttling. 503 func (c *MConnection) sendSomePacketMsgs() bool { 504 // Block until .sendMonitor says we can write. 505 // Once we're ready we send more than we asked for, 506 // but amortized it should even out. 507 c.sendMonitor.Limit(c._maxPacketMsgSize, atomic.LoadInt64(&c.config.SendRate), true) 508 509 // Now send some PacketMsgs. 510 for i := 0; i < numBatchPacketMsgs; i++ { 511 if c.sendPacketMsg() { 512 return true 513 } 514 } 515 return false 516 } 517 518 // Returns true if messages from channels were exhausted. 519 func (c *MConnection) sendPacketMsg() bool { 520 // Choose a channel to create a PacketMsg from. 521 // The chosen channel will be the one whose recentlySent/priority is the least. 522 var leastRatio float32 = math.MaxFloat32 523 var leastChannel *Channel 524 for _, channel := range c.channels { 525 // If nothing to send, skip this channel 526 if !channel.isSendPending() { 527 continue 528 } 529 // Get ratio, and keep track of lowest ratio. 530 ratio := float32(channel.recentlySent) / float32(channel.desc.Priority) 531 if ratio < leastRatio { 532 leastRatio = ratio 533 leastChannel = channel 534 } 535 } 536 537 // Nothing to send? 538 if leastChannel == nil { 539 return true 540 } 541 // c.Logger.Info("Found a msgPacket to send") 542 543 // Make & send a PacketMsg from this channel 544 _n, err := leastChannel.writePacketMsgTo(c.bufConnWriter) 545 if err != nil { 546 c.Logger.Error("Failed to write PacketMsg", "err", err) 547 c.stopForError(err) 548 return true 549 } 550 c.sendMonitor.Update(_n) 551 c.flushTimer.Set() 552 return false 553 } 554 555 // recvRoutine reads PacketMsgs and reconstructs the message using the channels' "recving" buffer. 556 // After a whole message has been assembled, it's pushed to onReceive(). 557 // Blocks depending on how the connection is throttled. 558 // Otherwise, it never blocks. 559 func (c *MConnection) recvRoutine() { 560 defer c._recover() 561 562 protoReader := protoio.NewDelimitedReader(c.bufConnReader, c._maxPacketMsgSize) 563 564 FOR_LOOP: 565 for { 566 // Block until .recvMonitor says we can read. 567 c.recvMonitor.Limit(c._maxPacketMsgSize, atomic.LoadInt64(&c.config.RecvRate), true) 568 569 // Peek into bufConnReader for debugging 570 /* 571 if numBytes := c.bufConnReader.Buffered(); numBytes > 0 { 572 bz, err := c.bufConnReader.Peek(tmmath.MinInt(numBytes, 100)) 573 if err == nil { 574 // return 575 } else { 576 c.Logger.Debug("Error peeking connection buffer", "err", err) 577 // return nil 578 } 579 c.Logger.Info("Peek connection buffer", "numBytes", numBytes, "bz", bz) 580 } 581 */ 582 583 // Read packet type 584 var packet tmp2p.Packet 585 586 _n, err := protoReader.ReadMsg(&packet) 587 c.recvMonitor.Update(_n) 588 if err != nil { 589 // stopServices was invoked and we are shutting down 590 // receiving is excpected to fail since we will close the connection 591 select { 592 case <-c.quitRecvRoutine: 593 break FOR_LOOP 594 default: 595 } 596 597 if c.IsRunning() { 598 if err == io.EOF { 599 c.Logger.Info("Connection is closed @ recvRoutine (likely by the other side)", "conn", c) 600 } else { 601 c.Logger.Debug("Connection failed @ recvRoutine (reading byte)", "conn", c, "err", err) 602 } 603 c.stopForError(err) 604 } 605 break FOR_LOOP 606 } 607 608 // Read more depending on packet type. 609 switch pkt := packet.Sum.(type) { 610 case *tmp2p.Packet_PacketPing: 611 // TODO: prevent abuse, as they cause flush()'s. 612 // https://github.com/DFWallet/tendermint-cosmos/issues/1190 613 c.Logger.Debug("Receive Ping") 614 select { 615 case c.pong <- struct{}{}: 616 default: 617 // never block 618 } 619 case *tmp2p.Packet_PacketPong: 620 c.Logger.Debug("Receive Pong") 621 select { 622 case c.pongTimeoutCh <- false: 623 default: 624 // never block 625 } 626 case *tmp2p.Packet_PacketMsg: 627 channelID := byte(pkt.PacketMsg.ChannelID) 628 channel, ok := c.channelsIdx[channelID] 629 if pkt.PacketMsg.ChannelID < 0 || pkt.PacketMsg.ChannelID > math.MaxUint8 || !ok || channel == nil { 630 err := fmt.Errorf("unknown channel %X", pkt.PacketMsg.ChannelID) 631 c.Logger.Debug("Connection failed @ recvRoutine", "conn", c, "err", err) 632 c.stopForError(err) 633 break FOR_LOOP 634 } 635 636 msgBytes, err := channel.recvPacketMsg(*pkt.PacketMsg) 637 if err != nil { 638 if c.IsRunning() { 639 c.Logger.Debug("Connection failed @ recvRoutine", "conn", c, "err", err) 640 c.stopForError(err) 641 } 642 break FOR_LOOP 643 } 644 if msgBytes != nil { 645 c.Logger.Debug("Received bytes", "chID", channelID, "msgBytes", msgBytes) 646 // NOTE: This means the reactor.Receive runs in the same thread as the p2p recv routine 647 c.onReceive(channelID, msgBytes) 648 } 649 default: 650 err := fmt.Errorf("unknown message type %v", reflect.TypeOf(packet)) 651 c.Logger.Error("Connection failed @ recvRoutine", "conn", c, "err", err) 652 c.stopForError(err) 653 break FOR_LOOP 654 } 655 } 656 657 // Cleanup 658 close(c.pong) 659 for range c.pong { 660 // Drain 661 } 662 } 663 664 // not goroutine-safe 665 func (c *MConnection) stopPongTimer() { 666 if c.pongTimer != nil { 667 _ = c.pongTimer.Stop() 668 c.pongTimer = nil 669 } 670 } 671 672 // maxPacketMsgSize returns a maximum size of PacketMsg 673 func (c *MConnection) maxPacketMsgSize() int { 674 bz, err := proto.Marshal(mustWrapPacket(&tmp2p.PacketMsg{ 675 ChannelID: 0x01, 676 EOF: true, 677 Data: make([]byte, c.config.MaxPacketMsgPayloadSize), 678 })) 679 if err != nil { 680 panic(err) 681 } 682 return len(bz) 683 } 684 685 type ConnectionStatus struct { 686 Duration time.Duration 687 SendMonitor flow.Status 688 RecvMonitor flow.Status 689 Channels []ChannelStatus 690 } 691 692 type ChannelStatus struct { 693 ID byte 694 SendQueueCapacity int 695 SendQueueSize int 696 Priority int 697 RecentlySent int64 698 } 699 700 func (c *MConnection) Status() ConnectionStatus { 701 var status ConnectionStatus 702 status.Duration = time.Since(c.created) 703 status.SendMonitor = c.sendMonitor.Status() 704 status.RecvMonitor = c.recvMonitor.Status() 705 status.Channels = make([]ChannelStatus, len(c.channels)) 706 for i, channel := range c.channels { 707 status.Channels[i] = ChannelStatus{ 708 ID: channel.desc.ID, 709 SendQueueCapacity: cap(channel.sendQueue), 710 SendQueueSize: int(atomic.LoadInt32(&channel.sendQueueSize)), 711 Priority: channel.desc.Priority, 712 RecentlySent: atomic.LoadInt64(&channel.recentlySent), 713 } 714 } 715 return status 716 } 717 718 //----------------------------------------------------------------------------- 719 720 type ChannelDescriptor struct { 721 ID byte 722 Priority int 723 SendQueueCapacity int 724 RecvBufferCapacity int 725 RecvMessageCapacity int 726 } 727 728 func (chDesc ChannelDescriptor) FillDefaults() (filled ChannelDescriptor) { 729 if chDesc.SendQueueCapacity == 0 { 730 chDesc.SendQueueCapacity = defaultSendQueueCapacity 731 } 732 if chDesc.RecvBufferCapacity == 0 { 733 chDesc.RecvBufferCapacity = defaultRecvBufferCapacity 734 } 735 if chDesc.RecvMessageCapacity == 0 { 736 chDesc.RecvMessageCapacity = defaultRecvMessageCapacity 737 } 738 filled = chDesc 739 return 740 } 741 742 // TODO: lowercase. 743 // NOTE: not goroutine-safe. 744 type Channel struct { 745 conn *MConnection 746 desc ChannelDescriptor 747 sendQueue chan []byte 748 sendQueueSize int32 // atomic. 749 recving []byte 750 sending []byte 751 recentlySent int64 // exponential moving average 752 753 maxPacketMsgPayloadSize int 754 755 Logger log.Logger 756 } 757 758 func newChannel(conn *MConnection, desc ChannelDescriptor) *Channel { 759 desc = desc.FillDefaults() 760 if desc.Priority <= 0 { 761 panic("Channel default priority must be a positive integer") 762 } 763 return &Channel{ 764 conn: conn, 765 desc: desc, 766 sendQueue: make(chan []byte, desc.SendQueueCapacity), 767 recving: make([]byte, 0, desc.RecvBufferCapacity), 768 maxPacketMsgPayloadSize: conn.config.MaxPacketMsgPayloadSize, 769 } 770 } 771 772 func (ch *Channel) SetLogger(l log.Logger) { 773 ch.Logger = l 774 } 775 776 // Queues message to send to this channel. 777 // Goroutine-safe 778 // Times out (and returns false) after defaultSendTimeout 779 func (ch *Channel) sendBytes(bytes []byte) bool { 780 select { 781 case ch.sendQueue <- bytes: 782 atomic.AddInt32(&ch.sendQueueSize, 1) 783 return true 784 case <-time.After(defaultSendTimeout): 785 return false 786 } 787 } 788 789 // Queues message to send to this channel. 790 // Nonblocking, returns true if successful. 791 // Goroutine-safe 792 func (ch *Channel) trySendBytes(bytes []byte) bool { 793 select { 794 case ch.sendQueue <- bytes: 795 atomic.AddInt32(&ch.sendQueueSize, 1) 796 return true 797 default: 798 return false 799 } 800 } 801 802 // Goroutine-safe 803 func (ch *Channel) loadSendQueueSize() (size int) { 804 return int(atomic.LoadInt32(&ch.sendQueueSize)) 805 } 806 807 // Goroutine-safe 808 // Use only as a heuristic. 809 func (ch *Channel) canSend() bool { 810 return ch.loadSendQueueSize() < defaultSendQueueCapacity 811 } 812 813 // Returns true if any PacketMsgs are pending to be sent. 814 // Call before calling nextPacketMsg() 815 // Goroutine-safe 816 func (ch *Channel) isSendPending() bool { 817 if len(ch.sending) == 0 { 818 if len(ch.sendQueue) == 0 { 819 return false 820 } 821 ch.sending = <-ch.sendQueue 822 } 823 return true 824 } 825 826 // Creates a new PacketMsg to send. 827 // Not goroutine-safe 828 func (ch *Channel) nextPacketMsg() tmp2p.PacketMsg { 829 packet := tmp2p.PacketMsg{ChannelID: int32(ch.desc.ID)} 830 maxSize := ch.maxPacketMsgPayloadSize 831 packet.Data = ch.sending[:tmmath.MinInt(maxSize, len(ch.sending))] 832 if len(ch.sending) <= maxSize { 833 packet.EOF = true 834 ch.sending = nil 835 atomic.AddInt32(&ch.sendQueueSize, -1) // decrement sendQueueSize 836 } else { 837 packet.EOF = false 838 ch.sending = ch.sending[tmmath.MinInt(maxSize, len(ch.sending)):] 839 } 840 return packet 841 } 842 843 // Writes next PacketMsg to w and updates c.recentlySent. 844 // Not goroutine-safe 845 func (ch *Channel) writePacketMsgTo(w io.Writer) (n int, err error) { 846 packet := ch.nextPacketMsg() 847 n, err = protoio.NewDelimitedWriter(w).WriteMsg(mustWrapPacket(&packet)) 848 atomic.AddInt64(&ch.recentlySent, int64(n)) 849 return 850 } 851 852 // Handles incoming PacketMsgs. It returns a message bytes if message is 853 // complete. NOTE message bytes may change on next call to recvPacketMsg. 854 // Not goroutine-safe 855 func (ch *Channel) recvPacketMsg(packet tmp2p.PacketMsg) ([]byte, error) { 856 ch.Logger.Debug("Read PacketMsg", "conn", ch.conn, "packet", packet) 857 var recvCap, recvReceived = ch.desc.RecvMessageCapacity, len(ch.recving) + len(packet.Data) 858 if recvCap < recvReceived { 859 return nil, fmt.Errorf("received message exceeds available capacity: %v < %v", recvCap, recvReceived) 860 } 861 ch.recving = append(ch.recving, packet.Data...) 862 if packet.EOF { 863 msgBytes := ch.recving 864 865 // clear the slice without re-allocating. 866 // http://stackoverflow.com/questions/16971741/how-do-you-clear-a-slice-in-go 867 // suggests this could be a memory leak, but we might as well keep the memory for the channel until it closes, 868 // at which point the recving slice stops being used and should be garbage collected 869 ch.recving = ch.recving[:0] // make([]byte, 0, ch.desc.RecvBufferCapacity) 870 return msgBytes, nil 871 } 872 return nil, nil 873 } 874 875 // Call this periodically to update stats for throttling purposes. 876 // Not goroutine-safe 877 func (ch *Channel) updateStats() { 878 // Exponential decay of stats. 879 // TODO: optimize. 880 atomic.StoreInt64(&ch.recentlySent, int64(float64(atomic.LoadInt64(&ch.recentlySent))*0.8)) 881 } 882 883 //---------------------------------------- 884 // Packet 885 886 // mustWrapPacket takes a packet kind (oneof) and wraps it in a tmp2p.Packet message. 887 func mustWrapPacket(pb proto.Message) *tmp2p.Packet { 888 var msg tmp2p.Packet 889 890 switch pb := pb.(type) { 891 case *tmp2p.Packet: // already a packet 892 msg = *pb 893 case *tmp2p.PacketPing: 894 msg = tmp2p.Packet{ 895 Sum: &tmp2p.Packet_PacketPing{ 896 PacketPing: pb, 897 }, 898 } 899 case *tmp2p.PacketPong: 900 msg = tmp2p.Packet{ 901 Sum: &tmp2p.Packet_PacketPong{ 902 PacketPong: pb, 903 }, 904 } 905 case *tmp2p.PacketMsg: 906 msg = tmp2p.Packet{ 907 Sum: &tmp2p.Packet_PacketMsg{ 908 PacketMsg: pb, 909 }, 910 } 911 default: 912 panic(fmt.Errorf("unknown packet type %T", pb)) 913 } 914 915 return &msg 916 }