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 }