Albirew/nyaa-pantsu
Archivé
1
0
Bifurcation 0

Use Mutex when modifying failedOperations, add exponential cooldown

Just to be safe, won't allow concurrent goroutines to modify the map.

The exponential cooldown prevents newer torrents with no seeds
blocking older ones with seeds, when there are enough failures that a
cooldown event would fill the queue with only failed torrents.
Cette révision appartient à :
ElegantMonkey 2017-05-14 19:30:56 -03:00
Parent a55cf2a803
révision 3dced6fdf0
2 fichiers modifiés avec 52 ajouts et 17 suppressions

Voir le fichier

@ -1,22 +1,24 @@
package config package config
type MetainfoFetcherConfig struct { type MetainfoFetcherConfig struct {
QueueSize int `json:"queue_size"` QueueSize int `json:"queue_size"`
Timeout int `json:"timeout"` Timeout int `json:"timeout"`
MaxDays int `json:"max_days"` MaxDays int `json:"max_days"`
FailCooldown int `json:"fail_cooldown"` BaseFailCooldown int `json:"base_fail_cooldown"`
WakeUpInterval int `json:"wake_up_interval"` MaxFailCooldown int `json:"max_fail_cooldown"`
WakeUpInterval int `json:"wake_up_interval"`
UploadRateLimiter int `json:"upload_rate_limiter"` UploadRateLimiter int `json:"upload_rate_limiter"`
DownloadRateLimiter int `json:"download_rate_limiter"` DownloadRateLimiter int `json:"download_rate_limiter"`
} }
var DefaultMetainfoFetcherConfig = MetainfoFetcherConfig{ var DefaultMetainfoFetcherConfig = MetainfoFetcherConfig{
QueueSize: 10, QueueSize: 10,
Timeout: 120, // 2 min Timeout: 120, // 2 min
MaxDays: 90, MaxDays: 90,
FailCooldown: 30 * 60, // in seconds, when failed torrents will be able to be fetched again. BaseFailCooldown: 30 * 60, // in seconds, when failed torrents will be able to be fetched again.
WakeUpInterval: 300, // 5 min MaxFailCooldown: 48 * 60 * 60,
WakeUpInterval: 300, // 5 min
UploadRateLimiter: 1024, // kbps UploadRateLimiter: 1024, // kbps
DownloadRateLimiter: 1024, DownloadRateLimiter: 1024,

Voir le fichier

@ -10,6 +10,7 @@ import (
serviceBase "github.com/ewhal/nyaa/service" serviceBase "github.com/ewhal/nyaa/service"
torrentService "github.com/ewhal/nyaa/service/torrent" torrentService "github.com/ewhal/nyaa/service/torrent"
"golang.org/x/time/rate" "golang.org/x/time/rate"
"math"
"sync" "sync"
"time" "time"
) )
@ -20,11 +21,14 @@ type MetainfoFetcher struct {
queueSize int queueSize int
timeout int timeout int
maxDays int maxDays int
failCooldown int baseFailCooldown int
maxFailCooldown int
done chan int done chan int
queue []*FetchOperation queue []*FetchOperation
queueMutex sync.Mutex queueMutex sync.Mutex
failedOperations map[uint]time.Time failedOperations map[uint]time.Time
numFails map[uint]int
failsMutex sync.Mutex
wakeUp *time.Ticker wakeUp *time.Ticker
wg sync.WaitGroup wg sync.WaitGroup
} }
@ -48,9 +52,11 @@ func New(fetcherConfig *config.MetainfoFetcherConfig) (fetcher *MetainfoFetcher,
queueSize: fetcherConfig.QueueSize, queueSize: fetcherConfig.QueueSize,
timeout: fetcherConfig.Timeout, timeout: fetcherConfig.Timeout,
maxDays: fetcherConfig.MaxDays, maxDays: fetcherConfig.MaxDays,
failCooldown: fetcherConfig.FailCooldown, baseFailCooldown: fetcherConfig.BaseFailCooldown,
maxFailCooldown: fetcherConfig.MaxFailCooldown,
done: make(chan int, 1), done: make(chan int, 1),
failedOperations: make(map[uint]time.Time), failedOperations: make(map[uint]time.Time),
numFails: make(map[uint]int),
wakeUp: time.NewTicker(time.Second * time.Duration(fetcherConfig.WakeUpInterval)), wakeUp: time.NewTicker(time.Second * time.Duration(fetcherConfig.WakeUpInterval)),
} }
@ -95,6 +101,26 @@ func (fetcher *MetainfoFetcher) removeFromQueue(op *FetchOperation) bool {
return false return false
} }
func (fetcher *MetainfoFetcher) markAsFailed(tID uint) {
fetcher.failsMutex.Lock()
defer fetcher.failsMutex.Unlock()
if n, ok := fetcher.numFails[tID]; ok {
fetcher.numFails[tID] = n + 1
} else {
fetcher.numFails[tID] = 1
}
fetcher.failedOperations[tID] = time.Now()
}
func (fetcher *MetainfoFetcher) removeFromFailed(tID uint) {
fetcher.failsMutex.Lock()
defer fetcher.failsMutex.Unlock()
delete(fetcher.failedOperations, tID)
}
func updateFileList(dbEntry model.Torrent, info *metainfo.Info) error { func updateFileList(dbEntry model.Torrent, info *metainfo.Info) error {
torrentFiles := info.UpvertedFiles() torrentFiles := info.UpvertedFiles()
log.Infof("TID %d has %d files.", dbEntry.ID, len(torrentFiles)) log.Infof("TID %d has %d files.", dbEntry.ID, len(torrentFiles))
@ -152,7 +178,7 @@ func (fetcher *MetainfoFetcher) gotResult(r Result) {
} }
if !updatedSuccessfully { if !updatedSuccessfully {
fetcher.failedOperations[r.operation.torrent.ID] = time.Now() fetcher.markAsFailed(r.operation.torrent.ID)
} }
fetcher.removeFromQueue(r.operation) fetcher.removeFromQueue(r.operation)
@ -160,16 +186,23 @@ func (fetcher *MetainfoFetcher) gotResult(r Result) {
func (fetcher *MetainfoFetcher) removeOldFailures() { func (fetcher *MetainfoFetcher) removeOldFailures() {
// Cooldown is disabled // Cooldown is disabled
if fetcher.failCooldown < 0 { if fetcher.baseFailCooldown < 0 {
return return
} }
maxCd := time.Duration(fetcher.maxFailCooldown) * time.Second
now := time.Now() now := time.Now()
for id, failTime := range fetcher.failedOperations { for id, failTime := range fetcher.failedOperations {
if failTime.Add(time.Duration(fetcher.failCooldown) * time.Second).Before(now) { cdMult := int(math.Pow(2, float64(fetcher.numFails[id] - 1)))
log.Infof("Torrent TID %d gone through cooldown, removing from failures") cd := time.Duration(cdMult * fetcher.baseFailCooldown) * time.Second
if cd > maxCd {
cd = maxCd
}
if failTime.Add(cd).Before(now) {
log.Infof("Torrent TID %d gone through cooldown, removing from failures", id)
// Deleting keys inside a loop seems to be safe. // Deleting keys inside a loop seems to be safe.
delete(fetcher.failedOperations, id) fetcher.removeFromFailed(id)
} }
} }
} }