@@ -82,6 +82,10 @@ type Client struct {
82
82
manualFragmentNode * fragmentNode
83
83
manualServerURI * URI
84
84
tracer opentracing.Tracer
85
+ // Number of retries if an HTTP request fails
86
+ retries int
87
+ minRetrySleepTime time.Duration
88
+ maxRetrySleepTime time.Duration
85
89
86
90
importLogEncoder encoder
87
91
logLock sync.Mutex
@@ -143,6 +147,9 @@ func newClientWithOptions(options *ClientOptions) *Client {
143
147
} else {
144
148
c .tracer = options .tracer
145
149
}
150
+ c .retries = * options .retries
151
+ c .minRetrySleepTime = 1 * time .Second
152
+ c .maxRetrySleepTime = 2 * time .Minute
146
153
c .importManager = newRecordImportManager (c )
147
154
return c
148
155
@@ -591,7 +598,11 @@ func (c *Client) hasRoaringImportSupport(field *Field) bool {
591
598
}
592
599
// Check whether the roaring import endpoint exists
593
600
path := makeRoaringImportPath (field , 0 , url.Values {})
601
+ // err may contain an HTTP error, but we don't use it.
594
602
resp , _ , _ := c .httpRequest ("GET" , path , nil , nil , false )
603
+ if resp == nil {
604
+ return false
605
+ }
595
606
if resp .StatusCode == http .StatusMethodNotAllowed || resp .StatusCode == http .StatusOK {
596
607
// Roaring import endpoint exists
597
608
return true
@@ -680,7 +691,7 @@ func (c *Client) fetchCoordinatorNode() (fragmentNode, error) {
680
691
}
681
692
682
693
func (c * Client ) importData (uri * URI , path string , data []byte ) error {
683
- resp , err := c .doRequest (uri , "POST" , path , defaultProtobufHeaders (), bytes . NewReader ( data ) )
694
+ resp , err := c .doRequest (uri , "POST" , path , defaultProtobufHeaders (), data )
684
695
if err = anyError (resp , err ); err != nil {
685
696
return errors .Wrap (err , "doing import" )
686
697
}
@@ -716,7 +727,7 @@ func (c *Client) importRoaringBitmap(uri *URI, field *Field, shard uint64, views
716
727
717
728
c .logImport (field .index .Name (), path , shard , true , data )
718
729
719
- resp , err := c .doRequest (uri , "POST" , path , defaultProtobufHeaders (), bytes . NewReader ( data ) )
730
+ resp , err := c .doRequest (uri , "POST" , path , defaultProtobufHeaders (), data )
720
731
if err = anyError (resp , err ); err != nil {
721
732
return errors .Wrap (err , "doing import" )
722
733
}
@@ -835,7 +846,7 @@ func (c *Client) httpRequest(method string, path string, data []byte, headers ma
835
846
if err != nil {
836
847
return nil , nil , err
837
848
}
838
- response , err = c .doRequest (host , method , path , c .augmentHeaders (headers ), bytes . NewReader ( data ) )
849
+ response , err = c .doRequest (host , method , path , c .augmentHeaders (headers ), data )
839
850
if err == nil {
840
851
break
841
852
}
@@ -925,12 +936,45 @@ func anyError(resp *http.Response, err error) error {
925
936
}
926
937
927
938
// doRequest creates and performs an http request.
928
- func (c * Client ) doRequest (host * URI , method , path string , headers map [string ]string , reader io.Reader ) (* http.Response , error ) {
929
- req , err := makeRequest (host , method , path , headers , reader )
930
- if err != nil {
931
- return nil , errors .Wrap (err , "building request" )
939
+ func (c * Client ) doRequest (host * URI , method , path string , headers map [string ]string , data []byte ) (* http.Response , error ) {
940
+ var resp * http.Response
941
+ var err error
942
+ var req * http.Request
943
+ var content []byte
944
+
945
+ tries := 1 + c .retries
946
+ sleepTime := c .minRetrySleepTime
947
+ for tries > 0 {
948
+ req , err = makeRequest (host , method , path , headers , data )
949
+ if err != nil {
950
+ return nil , errors .Wrap (err , "building request" )
951
+ }
952
+ tries --
953
+ resp , err = c .client .Do (req )
954
+ if err == nil {
955
+ if resp .StatusCode >= 200 && resp .StatusCode < 300 {
956
+ return resp , nil
957
+ }
958
+ // Pilosa nodes sometimes return 400, we retry in that case.
959
+ // No need to retry in other 4xx cases.
960
+ if resp .StatusCode > 400 && resp .StatusCode < 500 {
961
+ return resp , nil
962
+ }
963
+ content , err = ioutil .ReadAll (resp .Body )
964
+ resp .Body .Close ()
965
+ if err != nil {
966
+ return nil , err
967
+ }
968
+ err = errors .New (strings .TrimSpace (string (content )))
969
+ }
970
+ c .logger .Printf ("request failed with: %s, retrying (%d)" , err .Error (), tries )
971
+ time .Sleep (sleepTime )
972
+ sleepTime *= 2
973
+ if sleepTime > c .maxRetrySleepTime {
974
+ sleepTime = c .maxRetrySleepTime
975
+ }
932
976
}
933
- return c . client . Do ( req )
977
+ return nil , errors . Wrap ( err , "max retries exceeded" )
934
978
}
935
979
936
980
// statusToNodeShardsForIndex finds the hosts which contains shards for the given index
@@ -1058,7 +1102,7 @@ func (c *Client) ExperimentalReplayImport(r io.Reader, concurrency int) error {
1058
1102
1059
1103
if ! log .IsRoaring {
1060
1104
for _ , node := range nodes {
1061
- resp , err := c .doRequest (node .URI (), "POST" , log .Path , defaultProtobufHeaders (), bytes . NewReader ( log .Data ) )
1105
+ resp , err := c .doRequest (node .URI (), "POST" , log .Path , defaultProtobufHeaders (), log .Data )
1062
1106
if err = anyError (resp , err ); err != nil {
1063
1107
return errors .Wrap (err , "doing import" )
1064
1108
}
@@ -1067,7 +1111,7 @@ func (c *Client) ExperimentalReplayImport(r io.Reader, concurrency int) error {
1067
1111
} else {
1068
1112
// import-roaring forwards on to all replicas, so we only import to
1069
1113
// one node.
1070
- resp , err := c .doRequest (nodes [0 ].URI (), "POST" , log .Path , defaultProtobufHeaders (), bytes . NewReader ( log .Data ) )
1114
+ resp , err := c .doRequest (nodes [0 ].URI (), "POST" , log .Path , defaultProtobufHeaders (), log .Data )
1071
1115
if err = anyError (resp , err ); err != nil {
1072
1116
return errors .Wrap (err , "doing import" )
1073
1117
}
@@ -1121,8 +1165,8 @@ func defaultProtobufHeaders() map[string]string {
1121
1165
}
1122
1166
}
1123
1167
1124
- func makeRequest (host * URI , method , path string , headers map [string ]string , reader io. Reader ) (* http.Request , error ) {
1125
- request , err := http .NewRequest (method , host .Normalize ()+ path , reader )
1168
+ func makeRequest (host * URI , method , path string , headers map [string ]string , data [] byte ) (* http.Request , error ) {
1169
+ request , err := http .NewRequest (method , host .Normalize ()+ path , bytes . NewReader ( data ) )
1126
1170
if err != nil {
1127
1171
return nil , err
1128
1172
}
@@ -1326,6 +1370,7 @@ type ClientOptions struct {
1326
1370
TLSConfig * tls.Config
1327
1371
manualServerAddress bool
1328
1372
tracer opentracing.Tracer
1373
+ retries * int
1329
1374
1330
1375
importLogWriter io.Writer
1331
1376
}
@@ -1410,6 +1455,17 @@ func OptClientTracer(tracer opentracing.Tracer) ClientOption {
1410
1455
}
1411
1456
}
1412
1457
1458
+ // OptClientRetries sets the number of retries on HTTP request failures.
1459
+ func OptClientRetries (retries int ) ClientOption {
1460
+ return func (options * ClientOptions ) error {
1461
+ if retries < 0 {
1462
+ return errors .New ("retries must be non-negative" )
1463
+ }
1464
+ options .retries = & retries
1465
+ return nil
1466
+ }
1467
+ }
1468
+
1413
1469
type versionInfo struct {
1414
1470
Version string `json:"version"`
1415
1471
}
@@ -1434,6 +1490,10 @@ func (co *ClientOptions) withDefaults() (updated *ClientOptions) {
1434
1490
if updated .TLSConfig == nil {
1435
1491
updated .TLSConfig = & tls.Config {}
1436
1492
}
1493
+ if updated .retries == nil {
1494
+ retries := 2
1495
+ updated .retries = & retries
1496
+ }
1437
1497
return
1438
1498
}
1439
1499
0 commit comments