github.com/uber/kraken@v0.1.4/lib/torrent/scheduler/conn/conn.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package conn
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"io"
    20  	"net"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/andres-erbsen/clock"
    25  	"github.com/uber-go/tally"
    26  	"go.uber.org/atomic"
    27  	"go.uber.org/zap"
    28  
    29  	"github.com/uber/kraken/core"
    30  	"github.com/uber/kraken/gen/go/proto/p2p"
    31  	"github.com/uber/kraken/lib/torrent/networkevent"
    32  	"github.com/uber/kraken/lib/torrent/storage"
    33  	"github.com/uber/kraken/lib/torrent/storage/piecereader"
    34  	"github.com/uber/kraken/utils/bandwidth"
    35  	"github.com/uber/kraken/utils/memsize"
    36  )
    37  
    38  // Maximum support protocol message size. Does not include piece payload.
    39  const maxMessageSize = 32 * memsize.KB
    40  
    41  // Events defines Conn events.
    42  type Events interface {
    43  	ConnClosed(*Conn)
    44  }
    45  
    46  // Conn manages peer communication over a connection for multiple torrents. Inbound
    47  // messages are multiplexed based on the torrent they pertain to.
    48  type Conn struct {
    49  	peerID      core.PeerID
    50  	infoHash    core.InfoHash
    51  	createdAt   time.Time
    52  	localPeerID core.PeerID
    53  	bandwidth   *bandwidth.Limiter
    54  
    55  	events Events
    56  
    57  	mu                    sync.Mutex // Protects the following fields:
    58  	lastGoodPieceReceived time.Time
    59  	lastPieceSent         time.Time
    60  
    61  	nc            net.Conn
    62  	config        Config
    63  	clk           clock.Clock
    64  	stats         tally.Scope
    65  	networkEvents networkevent.Producer
    66  
    67  	// Marks whether the connection was opened by the remote peer, or the local peer.
    68  	openedByRemote bool
    69  
    70  	startOnce sync.Once
    71  
    72  	sender   chan *Message
    73  	receiver chan *Message
    74  
    75  	// The following fields orchestrate the closing of the connection:
    76  	closed *atomic.Bool
    77  	done   chan struct{}  // Signals to readLoop / writeLoop to exit.
    78  	wg     sync.WaitGroup // Waits for readLoop / writeLoop to exit.
    79  
    80  	logger *zap.SugaredLogger
    81  }
    82  
    83  func newConn(
    84  	config Config,
    85  	stats tally.Scope,
    86  	clk clock.Clock,
    87  	networkEvents networkevent.Producer,
    88  	bandwidth *bandwidth.Limiter,
    89  	events Events,
    90  	nc net.Conn,
    91  	localPeerID core.PeerID,
    92  	remotePeerID core.PeerID,
    93  	info *storage.TorrentInfo,
    94  	openedByRemote bool,
    95  	logger *zap.SugaredLogger) (*Conn, error) {
    96  
    97  	// Clear all deadlines set during handshake. Once a Conn is created, we
    98  	// rely on our own idle Conn management via preemption events.
    99  	if err := nc.SetDeadline(time.Time{}); err != nil {
   100  		return nil, fmt.Errorf("set deadline: %s", err)
   101  	}
   102  
   103  	c := &Conn{
   104  		peerID:         remotePeerID,
   105  		infoHash:       info.InfoHash(),
   106  		createdAt:      clk.Now(),
   107  		localPeerID:    localPeerID,
   108  		bandwidth:      bandwidth,
   109  		events:         events,
   110  		nc:             nc,
   111  		config:         config,
   112  		clk:            clk,
   113  		stats:          stats,
   114  		networkEvents:  networkEvents,
   115  		openedByRemote: openedByRemote,
   116  		sender:         make(chan *Message, config.SenderBufferSize),
   117  		receiver:       make(chan *Message, config.ReceiverBufferSize),
   118  		closed:         atomic.NewBool(false),
   119  		done:           make(chan struct{}),
   120  		logger:         logger,
   121  	}
   122  
   123  	return c, nil
   124  }
   125  
   126  // Start starts message processing on c. Note, once c has been started, it may
   127  // close itself if it encounters an error reading/writing to the underlying
   128  // socket.
   129  func (c *Conn) Start() {
   130  	c.startOnce.Do(func() {
   131  		c.wg.Add(2)
   132  		go c.readLoop()
   133  		go c.writeLoop()
   134  	})
   135  }
   136  
   137  // PeerID returns the remote peer id.
   138  func (c *Conn) PeerID() core.PeerID {
   139  	return c.peerID
   140  }
   141  
   142  // InfoHash returns the info hash for the torrent being transmitted over this
   143  // connection.
   144  func (c *Conn) InfoHash() core.InfoHash {
   145  	return c.infoHash
   146  }
   147  
   148  // CreatedAt returns the time at which the Conn was created.
   149  func (c *Conn) CreatedAt() time.Time {
   150  	return c.createdAt
   151  }
   152  
   153  func (c *Conn) String() string {
   154  	return fmt.Sprintf("Conn(peer=%s, hash=%s, opened_by_remote=%t)",
   155  		c.peerID, c.infoHash, c.openedByRemote)
   156  }
   157  
   158  // Send writes the given message to the underlying connection.
   159  func (c *Conn) Send(msg *Message) error {
   160  	select {
   161  	case <-c.done:
   162  		return errors.New("conn closed")
   163  	case c.sender <- msg:
   164  		return nil
   165  	default:
   166  		// TODO(codyg): Consider a timeout here instead.
   167  		c.stats.Tagged(map[string]string{
   168  			"dropped_message_type": msg.Message.Type.String(),
   169  		}).Counter("dropped_messages").Inc(1)
   170  		return errors.New("send buffer full")
   171  	}
   172  }
   173  
   174  // Receiver returns a read-only channel for reading incoming messages off the connection.
   175  func (c *Conn) Receiver() <-chan *Message {
   176  	return c.receiver
   177  }
   178  
   179  // Close starts the shutdown sequence for the Conn.
   180  func (c *Conn) Close() {
   181  	if !c.closed.CAS(false, true) {
   182  		return
   183  	}
   184  	go func() {
   185  		close(c.done)
   186  		c.nc.Close()
   187  		c.wg.Wait()
   188  		c.events.ConnClosed(c)
   189  	}()
   190  }
   191  
   192  // IsClosed returns true if the c is closed.
   193  func (c *Conn) IsClosed() bool {
   194  	return c.closed.Load()
   195  }
   196  
   197  func (c *Conn) readPayload(length int32) ([]byte, error) {
   198  	if err := c.bandwidth.ReserveIngress(int64(length)); err != nil {
   199  		c.log().Errorf("Error reserving ingress bandwidth for piece payload: %s", err)
   200  		return nil, fmt.Errorf("ingress bandwidth: %s", err)
   201  	}
   202  	payload := make([]byte, length)
   203  	if _, err := io.ReadFull(c.nc, payload); err != nil {
   204  		return nil, err
   205  	}
   206  	c.countBandwidth("ingress", int64(8*length))
   207  	return payload, nil
   208  }
   209  
   210  func (c *Conn) readMessage() (*Message, error) {
   211  	p2pMessage, err := readMessage(c.nc)
   212  	if err != nil {
   213  		return nil, fmt.Errorf("read message: %s", err)
   214  	}
   215  	var pr storage.PieceReader
   216  	if p2pMessage.Type == p2p.Message_PIECE_PAYLOAD {
   217  		// For payload messages, we must read the actual payload to the connection
   218  		// after reading the message.
   219  		payload, err := c.readPayload(p2pMessage.PiecePayload.Length)
   220  		if err != nil {
   221  			return nil, fmt.Errorf("read payload: %s", err)
   222  		}
   223  		// TODO(codyg): Consider making this reader read directly from the socket.
   224  		pr = piecereader.NewBuffer(payload)
   225  	}
   226  
   227  	return &Message{p2pMessage, pr}, nil
   228  }
   229  
   230  // readLoop reads messages off of the underlying connection and sends them to the
   231  // receiver channel.
   232  func (c *Conn) readLoop() {
   233  	defer func() {
   234  		close(c.receiver)
   235  		c.wg.Done()
   236  		c.Close()
   237  	}()
   238  
   239  	for {
   240  		select {
   241  		case <-c.done:
   242  			return
   243  		default:
   244  			msg, err := c.readMessage()
   245  			if err != nil {
   246  				c.log().Infof("Error reading message from socket, exiting read loop: %s", err)
   247  				return
   248  			}
   249  			c.receiver <- msg
   250  		}
   251  	}
   252  }
   253  
   254  func (c *Conn) sendPiecePayload(pr storage.PieceReader) error {
   255  	defer pr.Close()
   256  
   257  	if err := c.bandwidth.ReserveEgress(int64(pr.Length())); err != nil {
   258  		// TODO(codyg): This is bad. Consider alerting here.
   259  		c.log().Errorf("Error reserving egress bandwidth for piece payload: %s", err)
   260  		return fmt.Errorf("egress bandwidth: %s", err)
   261  	}
   262  	n, err := io.Copy(c.nc, pr)
   263  	if err != nil {
   264  		return fmt.Errorf("copy to socket: %s", err)
   265  	}
   266  	c.countBandwidth("egress", 8*n)
   267  	return nil
   268  }
   269  
   270  func (c *Conn) sendMessage(msg *Message) error {
   271  	if err := sendMessage(c.nc, msg.Message); err != nil {
   272  		return fmt.Errorf("send message: %s", err)
   273  	}
   274  	if msg.Message.Type == p2p.Message_PIECE_PAYLOAD {
   275  		// For payload messages, we must write the actual payload to the connection
   276  		// after writing the message.
   277  		if err := c.sendPiecePayload(msg.Payload); err != nil {
   278  			return fmt.Errorf("send piece payload: %s", err)
   279  		}
   280  	}
   281  	return nil
   282  }
   283  
   284  // writeLoop writes messages the underlying connection by pulling messages off of the sender
   285  // channel.
   286  func (c *Conn) writeLoop() {
   287  	defer func() {
   288  		c.wg.Done()
   289  		c.Close()
   290  	}()
   291  
   292  	for {
   293  		select {
   294  		case <-c.done:
   295  			return
   296  		case msg := <-c.sender:
   297  			if err := c.sendMessage(msg); err != nil {
   298  				c.log().Infof("Error writing message to socket, exiting write loop: %s", err)
   299  				return
   300  			}
   301  		}
   302  	}
   303  }
   304  
   305  func (c *Conn) countBandwidth(direction string, n int64) {
   306  	c.stats.Tagged(map[string]string{
   307  		"piece_bandwidth_direction": direction,
   308  	}).Counter("piece_bandwidth").Inc(n)
   309  }
   310  
   311  func (c *Conn) log(keysAndValues ...interface{}) *zap.SugaredLogger {
   312  	keysAndValues = append(keysAndValues, "remote_peer", c.peerID, "hash", c.infoHash)
   313  	return c.logger.With(keysAndValues...)
   314  }