-
Notifications
You must be signed in to change notification settings - Fork 12
Re-enable TCP keep-alive and handle S3's XML errors #463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| #include <curl/curl.h> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| #include <array> | ||||||||||||||||||||||||||||||||||||||
| #include <cmath> | ||||||||||||||||||||||||||||||||||||||
| #include <cstring> | ||||||||||||||||||||||||||||||||||||||
| #include <queue> | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -579,6 +580,14 @@ struct curlFileTransfer : public FileTransfer | |||||||||||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||||||||||||
| curl_easy_setopt(req, CURLOPT_CONNECTTIMEOUT, fileTransfer.settings.connectTimeout.get()); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /* Enable TCP keepalive to detect dead connections and server closures. | ||||||||||||||||||||||||||||||||||||||
| Probes every 30s to catch network failures and idle timeouts early. */ | ||||||||||||||||||||||||||||||||||||||
| curl_easy_setopt(req, CURLOPT_TCP_KEEPALIVE, 1L); | ||||||||||||||||||||||||||||||||||||||
| curl_easy_setopt(req, CURLOPT_TCP_KEEPIDLE, 30L); | ||||||||||||||||||||||||||||||||||||||
| curl_easy_setopt(req, CURLOPT_TCP_KEEPINTVL, 30L); | ||||||||||||||||||||||||||||||||||||||
| /* Don't reuse idle connections older than 90s. */ | ||||||||||||||||||||||||||||||||||||||
| curl_easy_setopt(req, CURLOPT_MAXAGE_CONN, 90L); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| curl_easy_setopt(req, CURLOPT_LOW_SPEED_LIMIT, 1L); | ||||||||||||||||||||||||||||||||||||||
| curl_easy_setopt(req, CURLOPT_LOW_SPEED_TIME, fileTransfer.settings.stalledDownloadTimeout.get()); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
@@ -682,7 +691,37 @@ struct curlFileTransfer : public FileTransfer | |||||||||||||||||||||||||||||||||||||
| // We treat most errors as transient, but won't retry when hopeless | ||||||||||||||||||||||||||||||||||||||
| Error err = Transient; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (httpStatus == 404 || httpStatus == 410 || code == CURLE_FILE_COULDNT_READ_FILE) { | ||||||||||||||||||||||||||||||||||||||
| // S3 returns certain retryable errors as HTTP 400/500/503 with XML error codes. | ||||||||||||||||||||||||||||||||||||||
| // These take precedence over the generic HTTP status handling below. | ||||||||||||||||||||||||||||||||||||||
| // Only parse the response body on status codes where S3 XML errors can appear. | ||||||||||||||||||||||||||||||||||||||
| static constexpr std::array<std::string_view, 12> s3RetryableErrors{{ | ||||||||||||||||||||||||||||||||||||||
| "IncompleteBody", // HTTP 400 - network issue | ||||||||||||||||||||||||||||||||||||||
| "InternalError", // HTTP 500 - S3 internal failure | ||||||||||||||||||||||||||||||||||||||
| "InternalFailure", // HTTP 500 - alias for InternalError | ||||||||||||||||||||||||||||||||||||||
| "InternalServerError", // HTTP 500 - alias for InternalError | ||||||||||||||||||||||||||||||||||||||
| "RequestExpired", // HTTP 400 - clock skew / slow upload | ||||||||||||||||||||||||||||||||||||||
| "RequestTimeout", // HTTP 400 - stale connection reuse | ||||||||||||||||||||||||||||||||||||||
| "RequestTimeTooSkewed", // HTTP 403 - clock drift | ||||||||||||||||||||||||||||||||||||||
| "RequestThrottled", // HTTP 400 - throttling variant | ||||||||||||||||||||||||||||||||||||||
| "SlowDown", // HTTP 503 - throttling | ||||||||||||||||||||||||||||||||||||||
| "ServiceUnavailable", // HTTP 503 - temporary unavailability | ||||||||||||||||||||||||||||||||||||||
| "Throttling", // HTTP 400 - throttling variant | ||||||||||||||||||||||||||||||||||||||
| "ThrottledException", // HTTP 400 - throttling variant | ||||||||||||||||||||||||||||||||||||||
| }}; | ||||||||||||||||||||||||||||||||||||||
| // S3 error responses have the form <Error><Code>...</Code>...</Error>. | ||||||||||||||||||||||||||||||||||||||
| // Require the <Error> root to avoid matching unrelated XML with a <Code> element. | ||||||||||||||||||||||||||||||||||||||
| static std::regex s3ErrorCodeRegex("<Error>[^]*<Code>([^<]+)</Code>"); | ||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am a tiny bit worried about:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The upstream PR has a test but it didn't apply cleanly so I left it out. |
||||||||||||||||||||||||||||||||||||||
| std::smatch s3Match; | ||||||||||||||||||||||||||||||||||||||
| bool isS3XmlStatus = httpStatus == 400 || httpStatus == 403 || httpStatus == 500 || httpStatus == 503; | ||||||||||||||||||||||||||||||||||||||
| auto s3ErrorCode = | ||||||||||||||||||||||||||||||||||||||
| (isS3XmlStatus && errorSink && std::regex_search(errorSink->s, s3Match, s3ErrorCodeRegex)) | ||||||||||||||||||||||||||||||||||||||
| ? s3Match[1].str() | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+711
to
+718
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tomberek Might be worth looking into this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I compiled a small test program that constructed EDIT: on both linux and darwin |
||||||||||||||||||||||||||||||||||||||
| : ""; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (std::find(s3RetryableErrors.begin(), s3RetryableErrors.end(), s3ErrorCode) | ||||||||||||||||||||||||||||||||||||||
| != s3RetryableErrors.end()) { | ||||||||||||||||||||||||||||||||||||||
| debug("S3 error '%s', will retry", s3ErrorCode); | ||||||||||||||||||||||||||||||||||||||
| } else if (httpStatus == 404 || httpStatus == 410 || code == CURLE_FILE_COULDNT_READ_FILE) { | ||||||||||||||||||||||||||||||||||||||
| // The file is definitely not there | ||||||||||||||||||||||||||||||||||||||
| err = NotFound; | ||||||||||||||||||||||||||||||||||||||
| } else if (httpStatus == 401 || httpStatus == 407) { | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woah, I've never seen
[^]*used like that........ that's actually blowing my mind.