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  }