1
0
mirror of https://github.com/gilbertchen/duplicacy synced 2025-12-10 05:13:17 +00:00

Better handling of B2 authorization failures

This commit fixed 2 issues wrt Backblaze B2 authorization:
* every thread may call b2_authorize_account at the same time when there
are 401 errors
* if B2 has a login outage, then all threads will call b2_authorize_account
repeatedly without delay

A simple solution is to limit one b2_authorize_account call to once every
30 second regardless of how many threads there are.  If the call to
b2_authorize_account is not allowed, the random exponential backoff will
be performed.
This commit is contained in:
Gilbert Chen
2019-06-13 22:43:07 -04:00
parent 4da7f7b6f9
commit 045be3905b
3 changed files with 21 additions and 8 deletions

View File

@@ -62,6 +62,8 @@ type B2Client struct {
Threads int Threads int
MaximumRetries int MaximumRetries int
TestMode bool TestMode bool
LastAuthorizationTime int64
} }
// URL encode the given path but keep the slashes intact // URL encode the given path but keep the slashes intact
@@ -253,8 +255,12 @@ func (client *B2Client) call(threadIndex int, requestURL string, method string,
if requestURL == B2AuthorizationURL { if requestURL == B2AuthorizationURL {
return nil, nil, 0, fmt.Errorf("Authorization failure") return nil, nil, 0, fmt.Errorf("Authorization failure")
} }
client.AuthorizeAccount(threadIndex)
continue // Attempt authorization again. If authorization is actually not done, run the random backoff
_, allowed := client.AuthorizeAccount(threadIndex)
if allowed {
continue
}
} else if response.StatusCode == 403 { } else if response.StatusCode == 403 {
if !client.TestMode { if !client.TestMode {
return nil, nil, 0, fmt.Errorf("B2 cap exceeded") return nil, nil, 0, fmt.Errorf("B2 cap exceeded")
@@ -291,13 +297,18 @@ type B2AuthorizeAccountOutput struct {
DownloadURL string DownloadURL string
} }
func (client *B2Client) AuthorizeAccount(threadIndex int) (err error) { func (client *B2Client) AuthorizeAccount(threadIndex int) (err error, allowed bool) {
client.Lock.Lock() client.Lock.Lock()
defer client.Lock.Unlock() defer client.Lock.Unlock()
// Don't authorize if the previous one was done less than 30 seconds ago
if client.LastAuthorizationTime != 0 && client.LastAuthorizationTime > time.Now().Unix() - 30 {
return nil, false
}
readCloser, _, _, err := client.call(threadIndex, B2AuthorizationURL, http.MethodPost, nil, make(map[string]string)) readCloser, _, _, err := client.call(threadIndex, B2AuthorizationURL, http.MethodPost, nil, make(map[string]string))
if err != nil { if err != nil {
return err return err, true
} }
defer readCloser.Close() defer readCloser.Close()
@@ -305,7 +316,7 @@ func (client *B2Client) AuthorizeAccount(threadIndex int) (err error) {
output := &B2AuthorizeAccountOutput{} output := &B2AuthorizeAccountOutput{}
if err = json.NewDecoder(readCloser).Decode(&output); err != nil { if err = json.NewDecoder(readCloser).Decode(&output); err != nil {
return err return err, true
} }
// The account id may be different from the application key id so we're getting the account id from the returned // The account id may be different from the application key id so we're getting the account id from the returned
@@ -317,7 +328,9 @@ func (client *B2Client) AuthorizeAccount(threadIndex int) (err error) {
client.DownloadURL = output.DownloadURL client.DownloadURL = output.DownloadURL
client.IsAuthorized = true client.IsAuthorized = true
return nil client.LastAuthorizationTime = time.Now().Unix()
return nil, true
} }
type ListBucketOutput struct { type ListBucketOutput struct {

View File

@@ -50,7 +50,7 @@ func TestB2Client(t *testing.T) {
b2Client.TestMode = true b2Client.TestMode = true
err := b2Client.AuthorizeAccount(0) err, _ := b2Client.AuthorizeAccount(0)
if err != nil { if err != nil {
t.Errorf("Failed to authorize the b2 account: %v", err) t.Errorf("Failed to authorize the b2 account: %v", err)
return return

View File

@@ -19,7 +19,7 @@ func CreateB2Storage(accountID string, applicationKey string, bucket string, sto
client := NewB2Client(accountID, applicationKey, storageDir, threads) client := NewB2Client(accountID, applicationKey, storageDir, threads)
err = client.AuthorizeAccount(0) err, _ = client.AuthorizeAccount(0)
if err != nil { if err != nil {
return nil, err return nil, err
} }