Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(461)

Side by Side Diff: s3/s3.go

Issue 7264043: s3: introduce retrying on known errors
Patch Set: Created 12 years, 2 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 // 1 //
2 // goamz - Go packages to interact with the Amazon Web Services. 2 // goamz - Go packages to interact with the Amazon Web Services.
3 // 3 //
4 // https://wiki.ubuntu.com/goamz 4 // https://wiki.ubuntu.com/goamz
5 // 5 //
6 // Copyright (c) 2011 Canonical Ltd. 6 // Copyright (c) 2011 Canonical Ltd.
7 // 7 //
8 // Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com> 8 // Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
9 // 9 //
10 10
11 package s3 11 package s3
12 12
13 import ( 13 import (
14 "bytes" 14 "bytes"
15 "encoding/xml" 15 "encoding/xml"
16 "fmt" 16 "fmt"
17 "io" 17 "io"
18 "io/ioutil" 18 "io/ioutil"
19 "launchpad.net/goamz/aws" 19 "launchpad.net/goamz/aws"
20 "log" 20 "log"
21 "net"
21 "net/http" 22 "net/http"
22 "net/http/httputil" 23 "net/http/httputil"
23 "net/url" 24 "net/url"
24 "strconv" 25 "strconv"
25 "strings" 26 "strings"
26 "time" 27 "time"
27 ) 28 )
28 29
29 const debug = false 30 const debug = false
30 31
31 // The S3 type encapsulates operations with an S3 region. 32 // The S3 type encapsulates operations with an S3 region.
32 type S3 struct { 33 type S3 struct {
33 aws.Auth 34 aws.Auth
34 aws.Region 35 aws.Region
35 private byte // Reserve the right of using private data. 36 private byte // Reserve the right of using private data.
36 } 37 }
37 38
38 // The Bucket type encapsulates operations with an S3 bucket. 39 // The Bucket type encapsulates operations with an S3 bucket.
39 type Bucket struct { 40 type Bucket struct {
40 *S3 41 *S3
41 Name string 42 Name string
42 } 43 }
43 44
44 // The Owner type represents the owner of the object in an S3 bucket. 45 // The Owner type represents the owner of the object in an S3 bucket.
45 type Owner struct { 46 type Owner struct {
46 ID string 47 ID string
47 DisplayName string 48 DisplayName string
48 } 49 }
49 50
51 var attempts = aws.AttemptStrategy{
52 Total: 5 * time.Second,
53 Delay: 200 * time.Millisecond,
54 }
55
50 // New creates a new S3. 56 // New creates a new S3.
51 func New(auth aws.Auth, region aws.Region) *S3 { 57 func New(auth aws.Auth, region aws.Region) *S3 {
52 return &S3{auth, region, 0} 58 return &S3{auth, region, 0}
53 } 59 }
54 60
55 // Bucket returns a Bucket with the given name. 61 // Bucket returns a Bucket with the given name.
56 func (s3 *S3) Bucket(name string) *Bucket { 62 func (s3 *S3) Bucket(name string) *Bucket {
57 if s3.Region.S3BucketEndpoint != "" || s3.Region.S3LowercaseBucket { 63 if s3.Region.S3BucketEndpoint != "" || s3.Region.S3LowercaseBucket {
58 name = strings.ToLower(name) 64 name = strings.ToLower(name)
59 } 65 }
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
101 headers: headers, 107 headers: headers,
102 payload: b.locationConstraint(), 108 payload: b.locationConstraint(),
103 } 109 }
104 return b.S3.query(req, nil) 110 return b.S3.query(req, nil)
105 } 111 }
106 112
107 // DelBucket removes an existing S3 bucket. All objects in the bucket must 113 // DelBucket removes an existing S3 bucket. All objects in the bucket must
108 // be removed before the bucket itself can be removed. 114 // be removed before the bucket itself can be removed.
109 // 115 //
110 // See http://goo.gl/GoBrY for details. 116 // See http://goo.gl/GoBrY for details.
111 func (b *Bucket) DelBucket() error { 117 func (b *Bucket) DelBucket() (err error) {
112 req := &request{ 118 req := &request{
113 method: "DELETE", 119 method: "DELETE",
114 bucket: b.Name, 120 bucket: b.Name,
115 path: "/", 121 path: "/",
116 } 122 }
117 » return b.S3.query(req, nil) 123 » for attempt := attempts.Start(); attempt.Next(); {
118 } 124 » » err = b.S3.query(req, nil)
119 125 » » if !shouldRetry(err) || hasCode(err, "NoSuchBucket") {
120 func hasCode(err error, code string) bool { 126 » » » break
121 » s3err, ok := err.(*Error) 127 » » }
122 » return ok && s3err.Code == code 128 » }
129 » return err
123 } 130 }
124 131
125 // Get retrieves an object from an S3 bucket. 132 // Get retrieves an object from an S3 bucket.
126 // 133 //
127 // See http://goo.gl/isCO7 for details. 134 // See http://goo.gl/isCO7 for details.
128 func (b *Bucket) Get(path string) (data []byte, err error) { 135 func (b *Bucket) Get(path string) (data []byte, err error) {
129 body, err := b.GetReader(path) 136 body, err := b.GetReader(path)
130 if err != nil { 137 if err != nil {
131 return nil, err 138 return nil, err
132 } 139 }
133 data, err = ioutil.ReadAll(body) 140 data, err = ioutil.ReadAll(body)
134 body.Close() 141 body.Close()
135 return data, err 142 return data, err
136 } 143 }
137 144
138 // GetReader retrieves an object from an S3 bucket. 145 // GetReader retrieves an object from an S3 bucket.
139 // It is the caller's responsibility to call Close on rc when 146 // It is the caller's responsibility to call Close on rc when
140 // finished reading. 147 // finished reading.
141 func (b *Bucket) GetReader(path string) (rc io.ReadCloser, err error) { 148 func (b *Bucket) GetReader(path string) (rc io.ReadCloser, err error) {
142 req := &request{ 149 req := &request{
143 bucket: b.Name, 150 bucket: b.Name,
144 path: path, 151 path: path,
145 } 152 }
146 err = b.S3.prepare(req) 153 err = b.S3.prepare(req)
147 if err != nil { 154 if err != nil {
148 return nil, err 155 return nil, err
149 } 156 }
150 » resp, err := b.S3.run(req, nil) 157 » for attempt := attempts.Start(); attempt.Next(); {
151 » if err != nil { 158 » » resp, err := b.S3.run(req, nil)
152 » » return nil, err 159 » » if shouldRetry(err) && attempt.HasNext() {
rog 2013/02/01 10:04:01 nice idiom.
160 » » » continue
161 » » }
162 » » if err != nil {
163 » » » return nil, err
164 » » }
165 » » return resp.Body, nil
153 } 166 }
154 » return resp.Body, nil 167 » panic("unreachable")
155 } 168 }
156 169
157 // Put inserts an object into the S3 bucket. 170 // Put inserts an object into the S3 bucket.
158 // 171 //
159 // See http://goo.gl/FEBPD for details. 172 // See http://goo.gl/FEBPD for details.
160 func (b *Bucket) Put(path string, data []byte, contType string, perm ACL) error { 173 func (b *Bucket) Put(path string, data []byte, contType string, perm ACL) error {
161 body := bytes.NewBuffer(data) 174 body := bytes.NewBuffer(data)
162 return b.PutReader(path, body, int64(len(data)), contType, perm) 175 return b.PutReader(path, body, int64(len(data)), contType, perm)
163 } 176 }
164 177
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
283 "marker": {marker}, 296 "marker": {marker},
284 } 297 }
285 if max != 0 { 298 if max != 0 {
286 params["max-keys"] = []string{strconv.FormatInt(int64(max), 10)} 299 params["max-keys"] = []string{strconv.FormatInt(int64(max), 10)}
287 } 300 }
288 req := &request{ 301 req := &request{
289 bucket: b.Name, 302 bucket: b.Name,
290 params: params, 303 params: params,
291 } 304 }
292 result = &ListResp{} 305 result = &ListResp{}
293 » err = b.S3.query(req, result) 306 » for attempt := attempts.Start(); attempt.Next(); {
307 » » err = b.S3.query(req, result)
308 » » if !shouldRetry(err) {
309 » » » break
310 » » }
311 » » println("Retrying List.")
rog 2013/02/01 10:04:01 d
niemeyer 2013/02/01 20:21:58 Done.
312 » }
294 if err != nil { 313 if err != nil {
295 return nil, err 314 return nil, err
296 } 315 }
297 return result, nil 316 return result, nil
298 } 317 }
299 318
300 // URL returns a non-signed URL that allows retriving the 319 // URL returns a non-signed URL that allows retriving the
301 // object at path. It only works if the object is publicly 320 // object at path. It only works if the object is publicly
302 // readable (see SignedURL). 321 // readable (see SignedURL).
303 func (b *Bucket) URL(path string) string { 322 func (b *Bucket) URL(path string) string {
(...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after
494 r.Body.Close() 513 r.Body.Close()
495 err.StatusCode = r.StatusCode 514 err.StatusCode = r.StatusCode
496 if err.Message == "" { 515 if err.Message == "" {
497 err.Message = r.Status 516 err.Message = r.Status
498 } 517 }
499 if debug { 518 if debug {
500 log.Printf("err: %#v\n", err) 519 log.Printf("err: %#v\n", err)
501 } 520 }
502 return &err 521 return &err
503 } 522 }
523
524 func shouldRetry(err error) bool {
525 if err == nil {
526 return false
527 }
528 switch e := err.(type) {
rog 2013/02/01 10:04:01 io.ErrUnexpectedEOF is one i've seen in the past,
niemeyer 2013/02/01 20:21:58 Done.
529 case *net.DNSError:
530 return true
531 case *net.OpError:
rog 2013/02/01 10:04:01 this seems very broad. we don't want to retry for
niemeyer 2013/02/01 20:21:58 True. I'll restrict to "read" and "write".
532 return true
533 case *Error:
534 switch e.Code {
535 case "InternalError", "NoSuchUpload", "NoSuchBucket":
536 return true
537 }
538 }
539 return false
540 }
541
542 func hasCode(err error, code string) bool {
543 s3err, ok := err.(*Error)
544 return ok && s3err.Code == code
545 }
546
OLDNEW

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b