1
0
mirror of https://github.com/gilbertchen/duplicacy synced 2025-12-15 15:53:26 +00:00

Add a method for debugging which shows the method call chains, to find out where most of the retries come from

This commit is contained in:
TheBestPessimist
2017-09-20 18:52:46 +03:00
parent ef19a3705f
commit d20ea41cd0

View File

@@ -5,23 +5,24 @@
package duplicacy package duplicacy
import ( import (
"io" "io"
"fmt" "fmt"
"net" "net"
"path" "path"
"time" "time"
"sync" "sync"
"strings" "strings"
"net/http" "net/http"
"net/url" "net/url"
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
"encoding/json" "encoding/json"
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"google.golang.org/api/drive/v3" "google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi" "google.golang.org/api/googleapi"
"runtime"
) )
type GCDStorage struct { type GCDStorage struct {
@@ -46,26 +47,18 @@ type GCDConfig struct {
} }
func (storage *GCDStorage) shouldRetry(threadIndex int, err error) (bool, error) { func (storage *GCDStorage) shouldRetry(threadIndex int, err error) (bool, error) {
const LIMIT_BACKOFF_TIME = 32 const LIMIT_BACKOFF_TIME = 64
const MAX_NUMBER_OF_RETRIES = 15 const MAX_NUMBER_OF_RETRIES = 15
minimumSleepRatio := 0.1 minimumSleepRatio := 0.1
maximumSleepRatio := 0.2 maximumSleepRatio := 0.2
minimumSleep := float64(storage.numberOfThreads) * minimumSleepRatio minimumSleep := float64(storage.numberOfThreads) * minimumSleepRatio
maximumSleep := float64(storage.numberOfThreads) * maximumSleepRatio maximumSleep := float64(storage.numberOfThreads) * maximumSleepRatio
rand.Seed(time.Now().UnixNano()) // unsure if this is needed rand.Seed(time.Now().UnixNano()) // unsure if this is needed
retry := false retry := false
message := "" message := ""
if err == nil { if err == nil {
/** storage.backoffs[threadIndex] = computeInitialBackoff(minimumSleep, maximumSleep)
logic for said calculus is here: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range
i chose 0.1*thread number as a minimum sleep time
and 0.2*thread number as a maximum sleep time
for the first sleep of the first backoff of the threads.
This would mean that both when the program is started, and when multiple threads retry, google won't be ddosed :^)
*/
storage.backoffs[threadIndex] = rand.Float64()*(maximumSleep-minimumSleep+1) + minimumSleep
storage.backoffsRetries[threadIndex] = 0 storage.backoffsRetries[threadIndex] = 0
return false, nil return false, nil
} else if e, ok := err.(*googleapi.Error); ok { } else if e, ok := err.(*googleapi.Error); ok {
@@ -79,7 +72,7 @@ func (storage *GCDStorage) shouldRetry(threadIndex int, err error) (bool, error)
retry = true retry = true
} else if e.Code == 403 { } else if e.Code == 403 {
// User Rate Limit Exceeded // User Rate Limit Exceeded
message = e.Message // "User Rate Limit Exceeded" message = e.Message // "User Rate Limit Exceeded"
retry = true retry = true
} else if e.Code == 401 { } else if e.Code == 401 {
@@ -103,25 +96,59 @@ func (storage *GCDStorage) shouldRetry(threadIndex int, err error) (bool, error)
if !retry || storage.backoffsRetries[threadIndex] >= MAX_NUMBER_OF_RETRIES { if !retry || storage.backoffsRetries[threadIndex] >= MAX_NUMBER_OF_RETRIES {
LOG_INFO("GCD_RETRY", "Thread: %03d. Maximum number of retries reached. Backoff time: %.2f. Number of retries: %d", threadIndex, storage.backoffs[threadIndex], storage.backoffsRetries[threadIndex]) LOG_INFO("GCD_RETRY", "Thread: %03d. Maximum number of retries reached. Backoff time: %.2f. Number of retries: %d", threadIndex, storage.backoffs[threadIndex], storage.backoffsRetries[threadIndex])
storage.backoffs[threadIndex] = rand.Float64()*(maximumSleep-minimumSleep+1) + minimumSleep storage.backoffs[threadIndex] = computeInitialBackoff(minimumSleep, maximumSleep)
storage.backoffsRetries[threadIndex] = 0 storage.backoffsRetries[threadIndex] = 0
return false, err return false, err
} }
if storage.backoffs[threadIndex] < LIMIT_BACKOFF_TIME { if storage.backoffs[threadIndex] < LIMIT_BACKOFF_TIME {
storage.backoffs[threadIndex] *= 2.0 storage.backoffs[threadIndex] *= 2.0
} else { } else {
storage.backoffs[threadIndex] = LIMIT_BACKOFF_TIME storage.backoffs[threadIndex] = LIMIT_BACKOFF_TIME
storage.backoffsRetries[threadIndex] += 1 storage.backoffsRetries[threadIndex] += 1
} }
delay := storage.backoffs[threadIndex]*rand.Float64() + storage.backoffs[threadIndex]*rand.Float64() delay := storage.backoffs[threadIndex]*rand.Float64() + storage.backoffs[threadIndex]*rand.Float64()
LOG_INFO("GCD_RETRY", "Thread: %03d. Message: %s. Retrying after %.2f seconds. Current backoff: %.2f. Number of retries: %d", threadIndex, message, delay, storage.backoffs[threadIndex], storage.backoffsRetries[threadIndex]) if storage.backoffs[threadIndex] >= LIMIT_BACKOFF_TIME {
callerChain:=findCallerChain()
LOG_INFO("GCD_RETRY", "Thread: %3d. Message: %s. Retrying after %6.2f seconds. Current backoff: %6.2f. Number of retries: %2d. caller chain: %s", threadIndex, message, delay, storage.backoffs[threadIndex], storage.backoffsRetries[threadIndex], callerChain)
}
time.Sleep(time.Duration(delay * float64(time.Second))) time.Sleep(time.Duration(delay * float64(time.Second)))
return true, nil return true, nil
} }
func findCallerChain() string {
callerStack := ""
pc := make([]uintptr, 15)
n := runtime.Callers(1, pc)
frames := runtime.CallersFrames(pc[:n])
for {
frame, more := frames.Next()
if strings.Contains(frame.File, "runtime/") {
break
}
callerStack += "->" + frame.Function
if !more {
break
}
}
return callerStack
}
/*
logic for said calculus is here: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range
chose 0.1*thread number as a minimum sleep time
and 0.2*thread number as a maximum sleep time
for the first sleep of the first backoff of the threads.
This would mean that both when the program is started, and when multiple threads retry, google won't be ddosed :^)
*/
func computeInitialBackoff(minimumSleep float64, maximumSleep float64) float64 {
return rand.Float64()*(maximumSleep-minimumSleep+1) + minimumSleep
}
func (storage *GCDStorage) convertFilePath(filePath string) string { func (storage *GCDStorage) convertFilePath(filePath string) string {
if strings.HasPrefix(filePath, "chunks/") && strings.HasSuffix(filePath, ".fsl") { if strings.HasPrefix(filePath, "chunks/") && strings.HasSuffix(filePath, ".fsl") {
return "fossils/" + filePath[len("chunks/"):len(filePath)-len(".fsl")] return "fossils/" + filePath[len("chunks/"):len(filePath)-len(".fsl")]
@@ -296,23 +323,23 @@ func CreateGCDStorage(tokenFile string, storagePath string, threads int) (storag
return nil, err return nil, err
} }
storage = &GCDStorage{ storage = &GCDStorage {
service: service, service: service,
numberOfThreads: threads, numberOfThreads: threads,
idCache: make(map[string]string), idCache: make(map[string]string),
idCacheLock: &sync.Mutex{}, idCacheLock: &sync.Mutex{},
backoffs: make([]float64, threads), backoffs: make([]float64, threads),
backoffsRetries: make([]int, threads), backoffsRetries:make([]int, threads),
} }
for b := range storage.backoffs { for b := range storage.backoffs {
storage.backoffs[b] = 0.1 * float64(storage.numberOfThreads) // at the first error, we should still sleep some amount storage.backoffs[b] = 0.1 * float64(storage.numberOfThreads) // at the first error, we should still sleep some amount
} }
storagePathID, err := storage.getIDFromPath(0, storagePath) storagePathID, err := storage.getIDFromPath(0, storagePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
storage.idCache[""] = storagePathID storage.idCache[""] = storagePathID