github.com/uber/kraken@v0.1.4/lib/torrent/scheduler/events.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 scheduler 15 16 import ( 17 "time" 18 19 "github.com/uber/kraken/core" 20 "github.com/uber/kraken/lib/torrent/networkevent" 21 "github.com/uber/kraken/lib/torrent/scheduler/conn" 22 "github.com/uber/kraken/lib/torrent/scheduler/connstate" 23 "github.com/uber/kraken/lib/torrent/scheduler/dispatch" 24 "github.com/uber/kraken/lib/torrent/storage" 25 "github.com/uber/kraken/utils/memsize" 26 "github.com/uber/kraken/utils/timeutil" 27 28 "github.com/willf/bitset" 29 ) 30 31 // event describes an external event which modifies state. While the event is 32 // applying, it is guaranteed to be the only accessor of state. 33 type event interface { 34 apply(*state) 35 } 36 37 // eventLoop represents a serialized list of events to be applied to scheduler 38 // state. 39 type eventLoop interface { 40 send(event) bool 41 sendTimeout(e event, timeout time.Duration) error 42 run(*state) 43 stop() 44 } 45 46 type baseEventLoop struct { 47 events chan event 48 done chan struct{} 49 } 50 51 func newEventLoop() *baseEventLoop { 52 return &baseEventLoop{ 53 events: make(chan event), 54 done: make(chan struct{}), 55 } 56 } 57 58 // send sends a new event into l. Should never be called by the same goroutine 59 // running l (i.e. within apply methods), else deadlock will occur. Returns false 60 // if the l is not running. 61 func (l *baseEventLoop) send(e event) bool { 62 select { 63 case l.events <- e: 64 return true 65 case <-l.done: 66 return false 67 } 68 } 69 70 func (l *baseEventLoop) sendTimeout(e event, timeout time.Duration) error { 71 timer := time.NewTimer(timeout) 72 defer timer.Stop() 73 select { 74 case l.events <- e: 75 return nil 76 case <-l.done: 77 return ErrSchedulerStopped 78 case <-timer.C: 79 return ErrSendEventTimedOut 80 } 81 } 82 83 func (l *baseEventLoop) run(s *state) { 84 for { 85 select { 86 case e := <-l.events: 87 e.apply(s) 88 case <-l.done: 89 return 90 } 91 } 92 } 93 94 func (l *baseEventLoop) stop() { 95 close(l.done) 96 } 97 98 type liftedEventLoop struct { 99 eventLoop 100 } 101 102 // liftEventLoop lifts events from subpackages into an eventLoop. 103 func liftEventLoop(l eventLoop) *liftedEventLoop { 104 return &liftedEventLoop{l} 105 } 106 107 func (l *liftedEventLoop) ConnClosed(c *conn.Conn) { 108 l.send(connClosedEvent{c}) 109 } 110 111 func (l *liftedEventLoop) DispatcherComplete(d *dispatch.Dispatcher) { 112 l.send(dispatcherCompleteEvent{d}) 113 } 114 115 func (l *liftedEventLoop) PeerRemoved(peerID core.PeerID, h core.InfoHash) { 116 l.send(peerRemovedEvent{peerID, h}) 117 } 118 119 func (l *liftedEventLoop) AnnounceTick() { 120 l.send(announceTickEvent{}) 121 } 122 123 // connClosedEvent occurs when a connection is closed. 124 type connClosedEvent struct { 125 c *conn.Conn 126 } 127 128 // apply ejects the conn from the scheduler's active connections. 129 func (e connClosedEvent) apply(s *state) { 130 s.conns.DeleteActive(e.c) 131 if err := s.conns.Blacklist(e.c.PeerID(), e.c.InfoHash()); err != nil { 132 s.log("conn", e.c).Infof("Cannot blacklist active conn: %s", err) 133 } 134 } 135 136 // incomingHandshakeEvent when a handshake was received from a new connection. 137 type incomingHandshakeEvent struct { 138 pc *conn.PendingConn 139 } 140 141 // apply rejects incoming handshakes when the scheduler is at capacity. If the 142 // scheduler has capacity for more connections, adds the peer/hash of the handshake 143 // to the scheduler's pending connections and asynchronously attempts to establish 144 // the connection. 145 func (e incomingHandshakeEvent) apply(s *state) { 146 peerNeighbors := make([]core.PeerID, len(e.pc.RemoteBitfields())) 147 var i int 148 for peerID := range e.pc.RemoteBitfields() { 149 peerNeighbors[i] = peerID 150 i++ 151 } 152 if err := s.conns.AddPending(e.pc.PeerID(), e.pc.InfoHash(), peerNeighbors); err != nil { 153 s.log("peer", e.pc.PeerID(), "hash", e.pc.InfoHash()).Infof( 154 "Rejecting incoming handshake: %s", err) 155 s.sched.torrentlog.IncomingConnectionReject(e.pc.Digest(), e.pc.InfoHash(), e.pc.PeerID(), err) 156 e.pc.Close() 157 return 158 } 159 var rb conn.RemoteBitfields 160 if ctrl, ok := s.torrentControls[e.pc.InfoHash()]; ok { 161 rb = ctrl.dispatcher.RemoteBitfields() 162 } 163 go s.sched.establishIncomingHandshake(e.pc, rb) 164 } 165 166 // failedIncomingHandshakeEvent occurs when a pending incoming connection fails 167 // to handshake. 168 type failedIncomingHandshakeEvent struct { 169 peerID core.PeerID 170 infoHash core.InfoHash 171 } 172 173 func (e failedIncomingHandshakeEvent) apply(s *state) { 174 s.conns.DeletePending(e.peerID, e.infoHash) 175 } 176 177 // incomingConnEvent occurs when a pending incoming connection finishes handshaking. 178 type incomingConnEvent struct { 179 namespace string 180 c *conn.Conn 181 bitfield *bitset.BitSet 182 info *storage.TorrentInfo 183 } 184 185 // apply transitions a fully-handshaked incoming conn from pending to active. 186 func (e incomingConnEvent) apply(s *state) { 187 if err := s.addIncomingConn(e.namespace, e.c, e.bitfield, e.info); err != nil { 188 s.log("conn", e.c).Errorf("Error adding incoming conn: %s", err) 189 e.c.Close() 190 return 191 } 192 s.log("conn", e.c).Info("Added incoming conn") 193 } 194 195 // failedOutgoingHandshakeEvent occurs when a pending incoming connection fails 196 // to handshake. 197 type failedOutgoingHandshakeEvent struct { 198 peerID core.PeerID 199 infoHash core.InfoHash 200 } 201 202 func (e failedOutgoingHandshakeEvent) apply(s *state) { 203 s.conns.DeletePending(e.peerID, e.infoHash) 204 if err := s.conns.Blacklist(e.peerID, e.infoHash); err != nil { 205 s.log("peer", e.peerID, "hash", e.infoHash).Infof("Cannot blacklist pending conn: %s", err) 206 } 207 } 208 209 // outgoingConnEvent occurs when a pending outgoing connection finishes handshaking. 210 type outgoingConnEvent struct { 211 c *conn.Conn 212 bitfield *bitset.BitSet 213 info *storage.TorrentInfo 214 } 215 216 // apply transitions a fully-handshaked outgoing conn from pending to active. 217 func (e outgoingConnEvent) apply(s *state) { 218 if err := s.addOutgoingConn(e.c, e.bitfield, e.info); err != nil { 219 s.log("conn", e.c).Errorf("Error adding outgoing conn: %s", err) 220 e.c.Close() 221 return 222 } 223 s.log("conn", e.c).Infof("Added outgoing conn with %d%% downloaded", e.info.PercentDownloaded()) 224 } 225 226 // announceTickEvent occurs when it is time to announce to the tracker. 227 type announceTickEvent struct{} 228 229 // apply pulls the next dispatcher from the announce queue and asynchronously 230 // makes an announce request to the tracker. 231 func (e announceTickEvent) apply(s *state) { 232 var skipped []core.InfoHash 233 for { 234 h, ok := s.announceQueue.Next() 235 if !ok { 236 s.log().Debug("No torrents in announce queue") 237 break 238 } 239 if s.conns.Saturated(h) { 240 s.log("hash", h).Debug("Skipping announce for fully saturated torrent") 241 skipped = append(skipped, h) 242 continue 243 } 244 ctrl, ok := s.torrentControls[h] 245 if !ok { 246 s.log("hash", h).Error("Pulled unknown torrent off announce queue") 247 continue 248 } 249 go s.sched.announce( 250 ctrl.dispatcher.Digest(), ctrl.dispatcher.InfoHash(), ctrl.dispatcher.Complete()) 251 break 252 } 253 // Re-enqueue any torrents we pulled off and ignored, else we would never 254 // announce them again. 255 for _, h := range skipped { 256 s.announceQueue.Ready(h) 257 } 258 } 259 260 // announceResultEvent occurs when a successfully announced response was received 261 // from the tracker. 262 type announceResultEvent struct { 263 infoHash core.InfoHash 264 peers []*core.PeerInfo 265 } 266 267 // apply selects new peers returned via an announce response to open connections to 268 // if there is capacity. These connections are added to the scheduler's pending 269 // connections and handshaked asynchronously. 270 // 271 // Also marks the dispatcher as ready to announce again. 272 func (e announceResultEvent) apply(s *state) { 273 ctrl, ok := s.torrentControls[e.infoHash] 274 if !ok { 275 s.log("hash", e.infoHash).Info("Dispatcher closed after announce response received") 276 return 277 } 278 s.announceQueue.Ready(e.infoHash) 279 if ctrl.dispatcher.Complete() { 280 // Torrent is already complete, don't open any new connections. 281 return 282 } 283 for _, p := range e.peers { 284 if p.PeerID == s.sched.pctx.PeerID { 285 // Tracker may return our own peer. 286 continue 287 } 288 if s.conns.Blacklisted(p.PeerID, e.infoHash) { 289 continue 290 } 291 if err := s.conns.AddPending(p.PeerID, e.infoHash, nil); err != nil { 292 if err == connstate.ErrTorrentAtCapacity { 293 break 294 } 295 continue 296 } 297 go s.sched.initializeOutgoingHandshake( 298 p, ctrl.dispatcher.Stat(), ctrl.dispatcher.RemoteBitfields(), ctrl.namespace) 299 } 300 } 301 302 // announceErrEvent occurs when an announce request fails. 303 type announceErrEvent struct { 304 infoHash core.InfoHash 305 err error 306 } 307 308 // apply marks the dispatcher as ready to announce again. 309 func (e announceErrEvent) apply(s *state) { 310 s.log("hash", e.infoHash).Errorf("Error announcing: %s", e.err) 311 s.announceQueue.Ready(e.infoHash) 312 } 313 314 // newTorrentEvent occurs when a new torrent was requested for download. 315 type newTorrentEvent struct { 316 namespace string 317 torrent storage.Torrent 318 errc chan error 319 } 320 321 // apply begins seeding / leeching a new torrent. 322 func (e newTorrentEvent) apply(s *state) { 323 ctrl, ok := s.torrentControls[e.torrent.InfoHash()] 324 if !ok { 325 var err error 326 ctrl, err = s.addTorrent(e.namespace, e.torrent, true) 327 if err != nil { 328 e.errc <- err 329 return 330 } 331 s.log("torrent", e.torrent).Info("Added new torrent") 332 } 333 if ctrl.dispatcher.Complete() { 334 e.errc <- nil 335 return 336 } 337 ctrl.errors = append(ctrl.errors, e.errc) 338 339 // Immediately announce new torrents. 340 go s.sched.announce(ctrl.dispatcher.Digest(), ctrl.dispatcher.InfoHash(), ctrl.dispatcher.Complete()) 341 } 342 343 // dispatcherCompleteEvent occurs when a dispatcher finishes downloading its torrent. 344 type dispatcherCompleteEvent struct { 345 dispatcher *dispatch.Dispatcher 346 } 347 348 // apply marks the dispatcher for its final announce. 349 func (e dispatcherCompleteEvent) apply(s *state) { 350 infoHash := e.dispatcher.InfoHash() 351 352 s.conns.ClearBlacklist(infoHash) 353 s.announceQueue.Eject(infoHash) 354 ctrl, ok := s.torrentControls[infoHash] 355 if !ok { 356 s.log("dispatcher", e.dispatcher).Error("Completed dispatcher not found") 357 return 358 } 359 for _, errc := range ctrl.errors { 360 errc <- nil 361 } 362 if ctrl.localRequest { 363 // Normalize the download time for all torrent sizes to a per MB value. 364 // Skip torrents that are less than a MB in size because we can't measure 365 // at that granularity. 366 downloadTime := s.sched.clock.Now().Sub(ctrl.dispatcher.CreatedAt()) 367 lengthMB := ctrl.dispatcher.Length() / int64(memsize.MB) 368 if lengthMB > 0 { 369 s.sched.stats.Timer("download_time_per_mb").Record(downloadTime / time.Duration(lengthMB)) 370 } 371 } 372 373 s.log("hash", infoHash).Info("Torrent complete") 374 s.sched.netevents.Produce(networkevent.TorrentCompleteEvent(infoHash, s.sched.pctx.PeerID)) 375 376 // Immediately announce completed torrents. 377 go s.sched.announce(ctrl.dispatcher.Digest(), ctrl.dispatcher.InfoHash(), true) 378 } 379 380 // peerRemovedEvent occurs when a dispatcher removes a peer with a closed 381 // connection. Currently is a no-op. 382 type peerRemovedEvent struct { 383 peerID core.PeerID 384 infoHash core.InfoHash 385 } 386 387 func (e peerRemovedEvent) apply(s *state) {} 388 389 // preemptionTickEvent occurs periodically to preempt unneeded conns and remove 390 // idle torrentControls. 391 type preemptionTickEvent struct{} 392 393 func (e preemptionTickEvent) apply(s *state) { 394 for _, c := range s.conns.ActiveConns() { 395 ctrl, ok := s.torrentControls[c.InfoHash()] 396 if !ok { 397 s.log("conn", c).Error( 398 "Invariant violation: active conn not assigned to dispatcher") 399 c.Close() 400 continue 401 } 402 lastProgress := timeutil.MostRecent( 403 c.CreatedAt(), 404 ctrl.dispatcher.LastGoodPieceReceived(c.PeerID()), 405 ctrl.dispatcher.LastPieceSent(c.PeerID())) 406 if s.sched.clock.Now().Sub(lastProgress) > s.sched.config.ConnTTI { 407 s.log("conn", c).Info("Closing idle conn") 408 c.Close() 409 continue 410 } 411 if s.sched.clock.Now().Sub(c.CreatedAt()) > s.sched.config.ConnTTL { 412 s.log("conn", c).Info("Closing expired conn") 413 c.Close() 414 continue 415 } 416 } 417 418 for h, ctrl := range s.torrentControls { 419 idleSeeder := 420 ctrl.dispatcher.Complete() && 421 s.sched.clock.Now().Sub(ctrl.dispatcher.LastReadTime()) >= s.sched.config.SeederTTI 422 if idleSeeder { 423 s.sched.torrentlog.SeedTimeout(ctrl.dispatcher.Digest(), h) 424 } 425 426 idleLeecher := 427 !ctrl.dispatcher.Complete() && 428 s.sched.clock.Now().Sub(ctrl.dispatcher.LastWriteTime()) >= s.sched.config.LeecherTTI 429 if idleLeecher { 430 s.sched.torrentlog.LeechTimeout(ctrl.dispatcher.Digest(), h) 431 } 432 433 if idleSeeder || idleLeecher { 434 s.log("hash", h, "inprogress", !ctrl.dispatcher.Complete()).Info("Removing idle torrent") 435 s.removeTorrent(h, ErrTorrentTimeout) 436 } 437 } 438 } 439 440 // emitStatsEvent occurs periodically to emit scheduler stats. 441 type emitStatsEvent struct{} 442 443 func (e emitStatsEvent) apply(s *state) { 444 s.sched.stats.Gauge("torrents").Update(float64(len(s.torrentControls))) 445 } 446 447 type blacklistSnapshotEvent struct { 448 result chan []connstate.BlacklistedConn 449 } 450 451 func (e blacklistSnapshotEvent) apply(s *state) { 452 e.result <- s.conns.BlacklistSnapshot() 453 } 454 455 // removeTorrentEvent occurs when a torrent is manually removed via scheduler API. 456 type removeTorrentEvent struct { 457 digest core.Digest 458 errc chan error 459 } 460 461 func (e removeTorrentEvent) apply(s *state) { 462 for h, ctrl := range s.torrentControls { 463 if ctrl.dispatcher.Digest() == e.digest { 464 s.log( 465 "hash", h, 466 "inprogress", !ctrl.dispatcher.Complete()).Info("Removing torrent") 467 s.removeTorrent(h, ErrTorrentRemoved) 468 } 469 } 470 e.errc <- s.sched.torrentArchive.DeleteTorrent(e.digest) 471 } 472 473 // probeEvent occurs when a probe is manually requested via scheduler API. 474 // The event loop is unbuffered, so if a probe can be successfully sent, then 475 // the event loop is healthy. 476 type probeEvent struct{} 477 478 func (e probeEvent) apply(*state) {} 479 480 // shutdownEvent stops the event loop and tears down all active torrents and 481 // connections. 482 type shutdownEvent struct{} 483 484 func (e shutdownEvent) apply(s *state) { 485 for _, c := range s.conns.ActiveConns() { 486 s.log("conn", c).Info("Closing conn to stop scheduler") 487 c.Close() 488 } 489 // Notify local clients of pending torrents that they will not complete. 490 for _, ctrl := range s.torrentControls { 491 ctrl.dispatcher.TearDown() 492 for _, errc := range ctrl.errors { 493 errc <- ErrSchedulerStopped 494 } 495 } 496 s.sched.eventLoop.stop() 497 }