From 4803ce010e335442e4dc56a8f689fd791f3d357f Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Mon, 17 Oct 2016 17:57:09 +0100 Subject: [PATCH] Make exponential backoff work exactly as per google specification - fixes #583 --- drive/drive.go | 5 +---- pacer/pacer.go | 41 ++++++++++++++++++++++++++++++++++++++++- pacer/pacer_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/drive/drive.go b/drive/drive.go index 75c140484..98d3edd11 100644 --- a/drive/drive.go +++ b/drive/drive.go @@ -35,9 +35,6 @@ const ( timeFormatIn = time.RFC3339 timeFormatOut = "2006-01-02T15:04:05.000000000Z07:00" minSleep = 10 * time.Millisecond - maxSleep = 2000 * time.Millisecond - decayConstant = 0 // bigger for slower decay, exponential - attackConstant = 0 // bigger for slower attack, exponential defaultExtensions = "docx,xlsx,pptx,svg" ) @@ -295,7 +292,7 @@ func NewFs(name, path string) (fs.Fs, error) { f := &Fs{ name: name, root: root, - pacer: pacer.New().SetMinSleep(minSleep).SetMaxSleep(maxSleep).SetDecayConstant(decayConstant).SetAttackConstant(attackConstant), + pacer: pacer.New().SetMinSleep(minSleep).SetPacer(pacer.GoogleDrivePacer), } // Create a new authorized Drive client. diff --git a/pacer/pacer.go b/pacer/pacer.go index 48b39fdd2..ec58c01d5 100644 --- a/pacer/pacer.go +++ b/pacer/pacer.go @@ -48,6 +48,16 @@ const ( // // See https://developer.amazon.com/public/apis/experience/cloud-drive/content/restful-api-best-practices AmazonCloudDrivePacer + + // GoogleDrivePacer is a specialised pacer for Google Drive + // + // It implements a truncated exponential backoff strategy with + // randomization. Normally operations are paced at the + // interval set with SetMinSleep. On errors the sleep timer + // is set to (2 ^ n) + random_number_milliseconds seconds + // + // See https://developers.google.com/drive/v2/web/handle-errors#exponential-backoff + GoogleDrivePacer ) // Paced is a function which is called by the Call and CallNoRetry @@ -172,6 +182,8 @@ func (p *Pacer) SetPacer(t Type) *Pacer { switch t { case AmazonCloudDrivePacer: p.calculatePace = p.acdPacer + case GoogleDrivePacer: + p.calculatePace = p.drivePacer default: p.calculatePace = p.defaultPacer } @@ -265,7 +277,34 @@ func (p *Pacer) acdPacer(retry bool) { if p.sleepTime < p.minSleep { p.sleepTime = p.minSleep } - fs.Debug("pacer", "Rate limited, sleeping for %v (%d consecutive low level retries)", p.sleepTime, consecutiveRetries) + fs.Debug("pacer", "Rate limited, sleeping for %v (%d consecutive low level retries)", p.sleepTime, p.consecutiveRetries) + } +} + +// drivePacer implements a truncated exponential backoff strategy with +// randomization for Google Drive +// +// See the description for GoogleDrivePacer +// +// This should calculate a new sleepTime. It takes a boolean as to +// whether the operation should be retried or not. +// +// Call with p.mu held +func (p *Pacer) drivePacer(retry bool) { + consecutiveRetries := p.consecutiveRetries + if consecutiveRetries == 0 { + if p.sleepTime != p.minSleep { + p.sleepTime = p.minSleep + fs.Debug("pacer", "Resetting sleep to minimum %v on success", p.sleepTime) + } + } else { + if consecutiveRetries > 5 { + consecutiveRetries = 5 + } + // consecutiveRetries starts at 1 so go from 1,2,3,4,5,5 => 1,2,4,8,16,16 + // maxSleep is 2**(consecutiveRetries-1) seconds + random milliseconds + p.sleepTime = time.Second< (test.want*11)/10 { + t.Fatalf("%+v: bad sleep want %v+/-10%% got %v", test, test.want, got) + } + } +} + func TestEndCall(t *testing.T) { p := New().SetMaxConnections(5) emptyTokens(p)