github.com/uber/kraken@v0.1.4/lib/torrent/scheduler/announcer/announcer.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 announcer
    15  
    16  import (
    17  	"time"
    18  
    19  	"github.com/uber/kraken/core"
    20  	"github.com/uber/kraken/tracker/announceclient"
    21  
    22  	"github.com/andres-erbsen/clock"
    23  	"go.uber.org/atomic"
    24  	"go.uber.org/zap"
    25  )
    26  
    27  // Config defines Announcer configuration.
    28  type Config struct {
    29  	DefaultInterval time.Duration `yaml:"default_interval"`
    30  	MaxInterval     time.Duration `yaml:"max_interval"`
    31  }
    32  
    33  func (c Config) applyDefaults() Config {
    34  	if c.DefaultInterval == 0 {
    35  		c.DefaultInterval = 5 * time.Second
    36  	}
    37  	if c.MaxInterval == 0 {
    38  		c.MaxInterval = time.Minute
    39  	}
    40  	return c
    41  }
    42  
    43  // Events defines Announcer events.
    44  type Events interface {
    45  	AnnounceTick()
    46  }
    47  
    48  // Announcer is a thin wrapper around an announceclient.Client which handles
    49  // changes to the announce interval.
    50  type Announcer struct {
    51  	config   Config
    52  	client   announceclient.Client
    53  	events   Events
    54  	interval *atomic.Int64
    55  	timer    *clock.Timer
    56  	logger   *zap.SugaredLogger
    57  }
    58  
    59  // New creates a new Announcer.
    60  func New(
    61  	config Config,
    62  	client announceclient.Client,
    63  	events Events,
    64  	clk clock.Clock,
    65  	logger *zap.SugaredLogger) *Announcer {
    66  	config = config.applyDefaults()
    67  	return &Announcer{
    68  		config:   config,
    69  		client:   client,
    70  		events:   events,
    71  		interval: atomic.NewInt64(int64(config.DefaultInterval)),
    72  		timer:    clk.Timer(config.DefaultInterval),
    73  		logger:   logger,
    74  	}
    75  }
    76  
    77  // Default creates a default Announcer.
    78  // TODO(evelynl94): make announce interval configurable.
    79  func Default(
    80  	client announceclient.Client,
    81  	events Events,
    82  	clk clock.Clock,
    83  	logger *zap.SugaredLogger) *Announcer {
    84  	return New(Config{}, client, events, clk, logger)
    85  }
    86  
    87  // Announce announces through the underlying client and returns the resulting
    88  // peer handout. Updates the announce interval if it has changed.
    89  func (a *Announcer) Announce(
    90  	d core.Digest, h core.InfoHash, complete bool) ([]*core.PeerInfo, error) {
    91  
    92  	peers, interval, err := a.client.Announce(d, h, complete, announceclient.V1)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	if interval == 0 {
    97  		// Protect against unset intervals.
    98  		interval = a.config.DefaultInterval
    99  	}
   100  	if interval > a.config.MaxInterval {
   101  		// Since the timer is only reset on ticks, a wildly high interval can lock
   102  		// down future updates to interval. The max interval protects against a
   103  		// mistake in the central authority which will become impossible to correct.
   104  		interval = a.config.DefaultInterval
   105  	}
   106  	if a.interval.Swap(int64(interval)) != int64(interval) {
   107  		// Note: updated interval will take effect after next tick.
   108  		a.logger.Infof("Announce interval updated to %s", interval)
   109  	}
   110  	return peers, nil
   111  }
   112  
   113  // Ticker emits AnnounceTick events at the current announce interval, which may be
   114  // updated by Announce. Ticker exits when done is closed.
   115  func (a *Announcer) Ticker(done <-chan struct{}) {
   116  	for {
   117  		select {
   118  		case <-a.timer.C:
   119  			a.events.AnnounceTick()
   120  			a.timer.Reset(time.Duration(a.interval.Load()))
   121  		case <-done:
   122  			return
   123  		}
   124  	}
   125  }