This repository was archived by the owner on Feb 13, 2024. It is now read-only.

Improve Retry Behaviour. - #146

Merged
f2prateek merged 1 commit into
masterfrom
retries
Jan 15, 2018
Merged

Improve Retry Behaviour.#146
f2prateek merged 1 commit into
masterfrom
retries

Conversation

@f2prateek

@f2prateekf2prateek commented Jan 11, 2018

Copy link
Copy Markdown
Contributor

Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).

The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).

For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):

  1. Retry network errors (socket timed out, etc.)
  2. Retry server errors (HTTP 5xx)
  3. Retry HTTP 429.

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

hey @vadimdemedes, @stephenmathieson; wanted to get your thoughts on this. I think the behaviour is functionally correct, but not sure on how to test it. https://github.com/softonic/axios-retry doesn't seem to have tests. My initial idea was to test the custom retry function directly (something simple like t.true(retryCondition({ response: { status: 429 }}))) but wasn't sure how to access access a private function in the test.

Comment threadindex.js Outdated
}
}

var retryCondition = function (error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an arrow function + const instead of var.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's an arrow function?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to be const.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Why would that be easier? This seems like a stateless function that can be tested without depending on the analytics class.

Also per my understanding, adding it to the analytics class will automatically expose this function to the public API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to test it directly, we'd have to export it. Attaching it directly to exports (or module.exports) make it look like part of the public API. If we add it as a method of the class, it will be exposed, but won't look like it's part of the public API.

Consider the following:

// exporting directlyexports.somePrivateMethod=()=>{}// exporting as part of the classmodule.exports=classAnalytics{track(){}somePrivateMethod(){}}

IMO the first looks like it's exposed for consumption by the end user, while the second just looks like a regular class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Makes sense about testing, chatted with Roland and he suggested using _ as a prefix to denote it as a private, so I'll do that.

Comment threadindex.js
// Retry if rate limited.
if (error.response.status === 429) {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should return false at the end.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment threadindex.js Outdated
}
}

const retryCondition = function (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constretryCondition=error=>{// ...}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! roland just explained what this was to me :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

function _retryCondition(error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow, but either way, we need to expose this logic somehow in order to test it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

Why not?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me function _retryCondition(error) { looks cleaner than const _retryCondition = error => {. 🤷‍♂️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚲 🏠

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

reminder to myself, don't merge without tests!

@f2prateek

f2prateek commented Jan 11, 2018

Copy link
Copy Markdown
ContributorAuthor

Ok, should be ready for review now! Added tests and made some changes from the original approach:

  • function moved to Analytics class for testing
  • function named with _ prefix to denote this is not meant to be part of the public API.

@Rowno showed me how I could make the function static too. It wasn't clear if that was fully compatible with what we support today and if it cause issues, so I left it as a simple class function for now.

Comment threadindex.js Outdated
})
}

_retryCondition (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might rename this shouldRetry or isErrorRetryable for clarity.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to isErrorRetryable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might have forgotten to push?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! Pushed now.

Comment threadtest.js Outdated
t.false(client._retryCondition({ code: 'ECONNABORTED' }))

t.true(client._retryCondition({ response: { status: 500 } }))
t.true(client._retryCondition({ response: { status: 429 } }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add one last test to verify the default return false behavior.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added one, though it's pretty contrived; we'd never really expect to see and error for a 2xx response.

@f2prateek
f2prateekforce-pushed the retries branch 2 times, most recently from c48865a to b4dc6adCompareJanuary 12, 2018 19:32
Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are effectively idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).
For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):
1. Retry network errors (socket timed out, etc.)
2. Retry server errors (HTTP 5xx)
3. Retry HTTP 429.

@stephenmathiesonstephenmathieson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@f2prateek@stephenmathieson@Rowno@vadimdemedes
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Feb 13, 2024. It is now read-only.

Improve Retry Behaviour. - #146

Merged
f2prateek merged 1 commit into
masterfrom
retries
Jan 15, 2018
Merged

Improve Retry Behaviour.#146
f2prateek merged 1 commit into
masterfrom
retries

Conversation

@f2prateek

@f2prateekf2prateek commented Jan 11, 2018

Copy link
Copy Markdown
Contributor

Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).

The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).

For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):

  1. Retry network errors (socket timed out, etc.)
  2. Retry server errors (HTTP 5xx)
  3. Retry HTTP 429.

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

hey @vadimdemedes, @stephenmathieson; wanted to get your thoughts on this. I think the behaviour is functionally correct, but not sure on how to test it. https://github.com/softonic/axios-retry doesn't seem to have tests. My initial idea was to test the custom retry function directly (something simple like t.true(retryCondition({ response: { status: 429 }}))) but wasn't sure how to access access a private function in the test.

Comment threadindex.js Outdated
}
}

var retryCondition = function (error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an arrow function + const instead of var.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's an arrow function?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to be const.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Why would that be easier? This seems like a stateless function that can be tested without depending on the analytics class.

Also per my understanding, adding it to the analytics class will automatically expose this function to the public API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to test it directly, we'd have to export it. Attaching it directly to exports (or module.exports) make it look like part of the public API. If we add it as a method of the class, it will be exposed, but won't look like it's part of the public API.

Consider the following:

// exporting directlyexports.somePrivateMethod=()=>{}// exporting as part of the classmodule.exports=classAnalytics{track(){}somePrivateMethod(){}}

IMO the first looks like it's exposed for consumption by the end user, while the second just looks like a regular class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Makes sense about testing, chatted with Roland and he suggested using _ as a prefix to denote it as a private, so I'll do that.

Comment threadindex.js
// Retry if rate limited.
if (error.response.status === 429) {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should return false at the end.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment threadindex.js Outdated
}
}

const retryCondition = function (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constretryCondition=error=>{// ...}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! roland just explained what this was to me :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

function _retryCondition(error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow, but either way, we need to expose this logic somehow in order to test it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

Why not?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me function _retryCondition(error) { looks cleaner than const _retryCondition = error => {. 🤷‍♂️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚲 🏠

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

reminder to myself, don't merge without tests!

@f2prateek

f2prateek commented Jan 11, 2018

Copy link
Copy Markdown
ContributorAuthor

Ok, should be ready for review now! Added tests and made some changes from the original approach:

  • function moved to Analytics class for testing
  • function named with _ prefix to denote this is not meant to be part of the public API.

@Rowno showed me how I could make the function static too. It wasn't clear if that was fully compatible with what we support today and if it cause issues, so I left it as a simple class function for now.

Comment threadindex.js Outdated
})
}

_retryCondition (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might rename this shouldRetry or isErrorRetryable for clarity.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to isErrorRetryable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might have forgotten to push?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! Pushed now.

Comment threadtest.js Outdated
t.false(client._retryCondition({ code: 'ECONNABORTED' }))

t.true(client._retryCondition({ response: { status: 500 } }))
t.true(client._retryCondition({ response: { status: 429 } }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add one last test to verify the default return false behavior.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added one, though it's pretty contrived; we'd never really expect to see and error for a 2xx response.

@f2prateek
f2prateekforce-pushed the retries branch 2 times, most recently from c48865a to b4dc6adCompareJanuary 12, 2018 19:32
Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are effectively idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).
For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):
1. Retry network errors (socket timed out, etc.)
2. Retry server errors (HTTP 5xx)
3. Retry HTTP 429.

@stephenmathiesonstephenmathieson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@f2prateek@stephenmathieson@Rowno@vadimdemedes
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 13, 2024. It is now read-only.

Improve Retry Behaviour. - #146

Merged
f2prateek merged 1 commit into
masterfrom
retries
Jan 15, 2018
Merged

Improve Retry Behaviour.#146
f2prateek merged 1 commit into
masterfrom
retries

Conversation

@f2prateek

@f2prateekf2prateek commented Jan 11, 2018

Copy link
Copy Markdown
Contributor

Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).

The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).

For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):

  1. Retry network errors (socket timed out, etc.)
  2. Retry server errors (HTTP 5xx)
  3. Retry HTTP 429.

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

hey @vadimdemedes, @stephenmathieson; wanted to get your thoughts on this. I think the behaviour is functionally correct, but not sure on how to test it. https://github.com/softonic/axios-retry doesn't seem to have tests. My initial idea was to test the custom retry function directly (something simple like t.true(retryCondition({ response: { status: 429 }}))) but wasn't sure how to access access a private function in the test.

Comment threadindex.js Outdated
}
}

var retryCondition = function (error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an arrow function + const instead of var.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's an arrow function?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to be const.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Why would that be easier? This seems like a stateless function that can be tested without depending on the analytics class.

Also per my understanding, adding it to the analytics class will automatically expose this function to the public API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to test it directly, we'd have to export it. Attaching it directly to exports (or module.exports) make it look like part of the public API. If we add it as a method of the class, it will be exposed, but won't look like it's part of the public API.

Consider the following:

// exporting directlyexports.somePrivateMethod=()=>{}// exporting as part of the classmodule.exports=classAnalytics{track(){}somePrivateMethod(){}}

IMO the first looks like it's exposed for consumption by the end user, while the second just looks like a regular class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Makes sense about testing, chatted with Roland and he suggested using _ as a prefix to denote it as a private, so I'll do that.

Comment threadindex.js
// Retry if rate limited.
if (error.response.status === 429) {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should return false at the end.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment threadindex.js Outdated
}
}

const retryCondition = function (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constretryCondition=error=>{// ...}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! roland just explained what this was to me :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

function _retryCondition(error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow, but either way, we need to expose this logic somehow in order to test it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

Why not?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me function _retryCondition(error) { looks cleaner than const _retryCondition = error => {. 🤷‍♂️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚲 🏠

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

reminder to myself, don't merge without tests!

@f2prateek

f2prateek commented Jan 11, 2018

Copy link
Copy Markdown
ContributorAuthor

Ok, should be ready for review now! Added tests and made some changes from the original approach:

  • function moved to Analytics class for testing
  • function named with _ prefix to denote this is not meant to be part of the public API.

@Rowno showed me how I could make the function static too. It wasn't clear if that was fully compatible with what we support today and if it cause issues, so I left it as a simple class function for now.

Comment threadindex.js Outdated
})
}

_retryCondition (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might rename this shouldRetry or isErrorRetryable for clarity.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to isErrorRetryable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might have forgotten to push?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! Pushed now.

Comment threadtest.js Outdated
t.false(client._retryCondition({ code: 'ECONNABORTED' }))

t.true(client._retryCondition({ response: { status: 500 } }))
t.true(client._retryCondition({ response: { status: 429 } }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add one last test to verify the default return false behavior.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added one, though it's pretty contrived; we'd never really expect to see and error for a 2xx response.

@f2prateek
f2prateekforce-pushed the retries branch 2 times, most recently from c48865a to b4dc6adCompareJanuary 12, 2018 19:32
Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are effectively idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).
For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):
1. Retry network errors (socket timed out, etc.)
2. Retry server errors (HTTP 5xx)
3. Retry HTTP 429.

@stephenmathiesonstephenmathieson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@f2prateek@stephenmathieson@Rowno@vadimdemedes
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 13, 2024. It is now read-only.

Improve Retry Behaviour. - #146

Merged
f2prateek merged 1 commit into
masterfrom
retries
Jan 15, 2018
Merged

Improve Retry Behaviour.#146
f2prateek merged 1 commit into
masterfrom
retries

Conversation

@f2prateek

@f2prateekf2prateek commented Jan 11, 2018

Copy link
Copy Markdown
Contributor

Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).

The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).

For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):

  1. Retry network errors (socket timed out, etc.)
  2. Retry server errors (HTTP 5xx)
  3. Retry HTTP 429.

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

hey @vadimdemedes, @stephenmathieson; wanted to get your thoughts on this. I think the behaviour is functionally correct, but not sure on how to test it. https://github.com/softonic/axios-retry doesn't seem to have tests. My initial idea was to test the custom retry function directly (something simple like t.true(retryCondition({ response: { status: 429 }}))) but wasn't sure how to access access a private function in the test.

Comment threadindex.js Outdated
}
}

var retryCondition = function (error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an arrow function + const instead of var.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's an arrow function?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to be const.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Why would that be easier? This seems like a stateless function that can be tested without depending on the analytics class.

Also per my understanding, adding it to the analytics class will automatically expose this function to the public API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to test it directly, we'd have to export it. Attaching it directly to exports (or module.exports) make it look like part of the public API. If we add it as a method of the class, it will be exposed, but won't look like it's part of the public API.

Consider the following:

// exporting directlyexports.somePrivateMethod=()=>{}// exporting as part of the classmodule.exports=classAnalytics{track(){}somePrivateMethod(){}}

IMO the first looks like it's exposed for consumption by the end user, while the second just looks like a regular class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Makes sense about testing, chatted with Roland and he suggested using _ as a prefix to denote it as a private, so I'll do that.

Comment threadindex.js
// Retry if rate limited.
if (error.response.status === 429) {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should return false at the end.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment threadindex.js Outdated
}
}

const retryCondition = function (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constretryCondition=error=>{// ...}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! roland just explained what this was to me :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

function _retryCondition(error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow, but either way, we need to expose this logic somehow in order to test it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

Why not?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me function _retryCondition(error) { looks cleaner than const _retryCondition = error => {. 🤷‍♂️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚲 🏠

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

reminder to myself, don't merge without tests!

@f2prateek

f2prateek commented Jan 11, 2018

Copy link
Copy Markdown
ContributorAuthor

Ok, should be ready for review now! Added tests and made some changes from the original approach:

  • function moved to Analytics class for testing
  • function named with _ prefix to denote this is not meant to be part of the public API.

@Rowno showed me how I could make the function static too. It wasn't clear if that was fully compatible with what we support today and if it cause issues, so I left it as a simple class function for now.

Comment threadindex.js Outdated
})
}

_retryCondition (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might rename this shouldRetry or isErrorRetryable for clarity.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to isErrorRetryable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might have forgotten to push?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! Pushed now.

Comment threadtest.js Outdated
t.false(client._retryCondition({ code: 'ECONNABORTED' }))

t.true(client._retryCondition({ response: { status: 500 } }))
t.true(client._retryCondition({ response: { status: 429 } }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add one last test to verify the default return false behavior.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added one, though it's pretty contrived; we'd never really expect to see and error for a 2xx response.

@f2prateek
f2prateekforce-pushed the retries branch 2 times, most recently from c48865a to b4dc6adCompareJanuary 12, 2018 19:32
Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are effectively idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).
For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):
1. Retry network errors (socket timed out, etc.)
2. Retry server errors (HTTP 5xx)
3. Retry HTTP 429.

@stephenmathiesonstephenmathieson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@f2prateek@stephenmathieson@Rowno@vadimdemedes
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Feb 13, 2024. It is now read-only.

Improve Retry Behaviour. - #146

Merged
f2prateek merged 1 commit into
masterfrom
retries
Jan 15, 2018
Merged

Improve Retry Behaviour.#146
f2prateek merged 1 commit into
masterfrom
retries

Conversation

@f2prateek

@f2prateekf2prateek commented Jan 11, 2018

Copy link
Copy Markdown
Contributor

Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).

The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).

For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):

  1. Retry network errors (socket timed out, etc.)
  2. Retry server errors (HTTP 5xx)
  3. Retry HTTP 429.

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

hey @vadimdemedes, @stephenmathieson; wanted to get your thoughts on this. I think the behaviour is functionally correct, but not sure on how to test it. https://github.com/softonic/axios-retry doesn't seem to have tests. My initial idea was to test the custom retry function directly (something simple like t.true(retryCondition({ response: { status: 429 }}))) but wasn't sure how to access access a private function in the test.

Comment threadindex.js Outdated
}
}

var retryCondition = function (error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an arrow function + const instead of var.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's an arrow function?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to be const.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Why would that be easier? This seems like a stateless function that can be tested without depending on the analytics class.

Also per my understanding, adding it to the analytics class will automatically expose this function to the public API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to test it directly, we'd have to export it. Attaching it directly to exports (or module.exports) make it look like part of the public API. If we add it as a method of the class, it will be exposed, but won't look like it's part of the public API.

Consider the following:

// exporting directlyexports.somePrivateMethod=()=>{}// exporting as part of the classmodule.exports=classAnalytics{track(){}somePrivateMethod(){}}

IMO the first looks like it's exposed for consumption by the end user, while the second just looks like a regular class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Makes sense about testing, chatted with Roland and he suggested using _ as a prefix to denote it as a private, so I'll do that.

Comment threadindex.js
// Retry if rate limited.
if (error.response.status === 429) {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should return false at the end.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment threadindex.js Outdated
}
}

const retryCondition = function (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constretryCondition=error=>{// ...}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! roland just explained what this was to me :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

function _retryCondition(error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow, but either way, we need to expose this logic somehow in order to test it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

Why not?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me function _retryCondition(error) { looks cleaner than const _retryCondition = error => {. 🤷‍♂️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚲 🏠

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

reminder to myself, don't merge without tests!

@f2prateek

f2prateek commented Jan 11, 2018

Copy link
Copy Markdown
ContributorAuthor

Ok, should be ready for review now! Added tests and made some changes from the original approach:

  • function moved to Analytics class for testing
  • function named with _ prefix to denote this is not meant to be part of the public API.

@Rowno showed me how I could make the function static too. It wasn't clear if that was fully compatible with what we support today and if it cause issues, so I left it as a simple class function for now.

Comment threadindex.js Outdated
})
}

_retryCondition (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might rename this shouldRetry or isErrorRetryable for clarity.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to isErrorRetryable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might have forgotten to push?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! Pushed now.

Comment threadtest.js Outdated
t.false(client._retryCondition({ code: 'ECONNABORTED' }))

t.true(client._retryCondition({ response: { status: 500 } }))
t.true(client._retryCondition({ response: { status: 429 } }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add one last test to verify the default return false behavior.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added one, though it's pretty contrived; we'd never really expect to see and error for a 2xx response.

@f2prateek
f2prateekforce-pushed the retries branch 2 times, most recently from c48865a to b4dc6adCompareJanuary 12, 2018 19:32
Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are effectively idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).
For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):
1. Retry network errors (socket timed out, etc.)
2. Retry server errors (HTTP 5xx)
3. Retry HTTP 429.

@stephenmathiesonstephenmathieson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@f2prateek@stephenmathieson@Rowno@vadimdemedes
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 13, 2024. It is now read-only.

Improve Retry Behaviour. - #146

Merged
f2prateek merged 1 commit into
masterfrom
retries
Jan 15, 2018
Merged

Improve Retry Behaviour.#146
f2prateek merged 1 commit into
masterfrom
retries

Conversation

@f2prateek

@f2prateekf2prateek commented Jan 11, 2018

Copy link
Copy Markdown
Contributor

Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).

The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).

For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):

  1. Retry network errors (socket timed out, etc.)
  2. Retry server errors (HTTP 5xx)
  3. Retry HTTP 429.

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

hey @vadimdemedes, @stephenmathieson; wanted to get your thoughts on this. I think the behaviour is functionally correct, but not sure on how to test it. https://github.com/softonic/axios-retry doesn't seem to have tests. My initial idea was to test the custom retry function directly (something simple like t.true(retryCondition({ response: { status: 429 }}))) but wasn't sure how to access access a private function in the test.

Comment threadindex.js Outdated
}
}

var retryCondition = function (error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an arrow function + const instead of var.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's an arrow function?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to be const.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Why would that be easier? This seems like a stateless function that can be tested without depending on the analytics class.

Also per my understanding, adding it to the analytics class will automatically expose this function to the public API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to test it directly, we'd have to export it. Attaching it directly to exports (or module.exports) make it look like part of the public API. If we add it as a method of the class, it will be exposed, but won't look like it's part of the public API.

Consider the following:

// exporting directlyexports.somePrivateMethod=()=>{}// exporting as part of the classmodule.exports=classAnalytics{track(){}somePrivateMethod(){}}

IMO the first looks like it's exposed for consumption by the end user, while the second just looks like a regular class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Makes sense about testing, chatted with Roland and he suggested using _ as a prefix to denote it as a private, so I'll do that.

Comment threadindex.js
// Retry if rate limited.
if (error.response.status === 429) {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should return false at the end.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment threadindex.js Outdated
}
}

const retryCondition = function (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constretryCondition=error=>{// ...}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! roland just explained what this was to me :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

function _retryCondition(error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow, but either way, we need to expose this logic somehow in order to test it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

Why not?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me function _retryCondition(error) { looks cleaner than const _retryCondition = error => {. 🤷‍♂️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚲 🏠

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

reminder to myself, don't merge without tests!

@f2prateek

f2prateek commented Jan 11, 2018

Copy link
Copy Markdown
ContributorAuthor

Ok, should be ready for review now! Added tests and made some changes from the original approach:

  • function moved to Analytics class for testing
  • function named with _ prefix to denote this is not meant to be part of the public API.

@Rowno showed me how I could make the function static too. It wasn't clear if that was fully compatible with what we support today and if it cause issues, so I left it as a simple class function for now.

Comment threadindex.js Outdated
})
}

_retryCondition (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might rename this shouldRetry or isErrorRetryable for clarity.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to isErrorRetryable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might have forgotten to push?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! Pushed now.

Comment threadtest.js Outdated
t.false(client._retryCondition({ code: 'ECONNABORTED' }))

t.true(client._retryCondition({ response: { status: 500 } }))
t.true(client._retryCondition({ response: { status: 429 } }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add one last test to verify the default return false behavior.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added one, though it's pretty contrived; we'd never really expect to see and error for a 2xx response.

@f2prateek
f2prateekforce-pushed the retries branch 2 times, most recently from c48865a to b4dc6adCompareJanuary 12, 2018 19:32
Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are effectively idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).
For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):
1. Retry network errors (socket timed out, etc.)
2. Retry server errors (HTTP 5xx)
3. Retry HTTP 429.

@stephenmathiesonstephenmathieson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@f2prateek@stephenmathieson@Rowno@vadimdemedes
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 13, 2024. It is now read-only.

Improve Retry Behaviour. - #146

Merged
f2prateek merged 1 commit into
masterfrom
retries
Jan 15, 2018
Merged

Improve Retry Behaviour.#146
f2prateek merged 1 commit into
masterfrom
retries

Conversation

@f2prateek

@f2prateekf2prateek commented Jan 11, 2018

Copy link
Copy Markdown
Contributor

Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).

The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).

For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):

  1. Retry network errors (socket timed out, etc.)
  2. Retry server errors (HTTP 5xx)
  3. Retry HTTP 429.

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

hey @vadimdemedes, @stephenmathieson; wanted to get your thoughts on this. I think the behaviour is functionally correct, but not sure on how to test it. https://github.com/softonic/axios-retry doesn't seem to have tests. My initial idea was to test the custom retry function directly (something simple like t.true(retryCondition({ response: { status: 429 }}))) but wasn't sure how to access access a private function in the test.

Comment threadindex.js Outdated
}
}

var retryCondition = function (error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an arrow function + const instead of var.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's an arrow function?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to be const.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Why would that be easier? This seems like a stateless function that can be tested without depending on the analytics class.

Also per my understanding, adding it to the analytics class will automatically expose this function to the public API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to test it directly, we'd have to export it. Attaching it directly to exports (or module.exports) make it look like part of the public API. If we add it as a method of the class, it will be exposed, but won't look like it's part of the public API.

Consider the following:

// exporting directlyexports.somePrivateMethod=()=>{}// exporting as part of the classmodule.exports=classAnalytics{track(){}somePrivateMethod(){}}

IMO the first looks like it's exposed for consumption by the end user, while the second just looks like a regular class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Makes sense about testing, chatted with Roland and he suggested using _ as a prefix to denote it as a private, so I'll do that.

Comment threadindex.js
// Retry if rate limited.
if (error.response.status === 429) {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should return false at the end.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment threadindex.js Outdated
}
}

const retryCondition = function (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constretryCondition=error=>{// ...}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! roland just explained what this was to me :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

function _retryCondition(error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow, but either way, we need to expose this logic somehow in order to test it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

Why not?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me function _retryCondition(error) { looks cleaner than const _retryCondition = error => {. 🤷‍♂️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚲 🏠

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

reminder to myself, don't merge without tests!

@f2prateek

f2prateek commented Jan 11, 2018

Copy link
Copy Markdown
ContributorAuthor

Ok, should be ready for review now! Added tests and made some changes from the original approach:

  • function moved to Analytics class for testing
  • function named with _ prefix to denote this is not meant to be part of the public API.

@Rowno showed me how I could make the function static too. It wasn't clear if that was fully compatible with what we support today and if it cause issues, so I left it as a simple class function for now.

Comment threadindex.js Outdated
})
}

_retryCondition (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might rename this shouldRetry or isErrorRetryable for clarity.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to isErrorRetryable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might have forgotten to push?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! Pushed now.

Comment threadtest.js Outdated
t.false(client._retryCondition({ code: 'ECONNABORTED' }))

t.true(client._retryCondition({ response: { status: 500 } }))
t.true(client._retryCondition({ response: { status: 429 } }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add one last test to verify the default return false behavior.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added one, though it's pretty contrived; we'd never really expect to see and error for a 2xx response.

@f2prateek
f2prateekforce-pushed the retries branch 2 times, most recently from c48865a to b4dc6adCompareJanuary 12, 2018 19:32
Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are effectively idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).
For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):
1. Retry network errors (socket timed out, etc.)
2. Retry server errors (HTTP 5xx)
3. Retry HTTP 429.

@stephenmathiesonstephenmathieson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@f2prateek@stephenmathieson@Rowno@vadimdemedes
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Feb 13, 2024. It is now read-only.

Improve Retry Behaviour. - #146

Merged
f2prateek merged 1 commit into
masterfrom
retries
Jan 15, 2018
Merged

Improve Retry Behaviour.#146
f2prateek merged 1 commit into
masterfrom
retries

Conversation

@f2prateek

@f2prateekf2prateek commented Jan 11, 2018

Copy link
Copy Markdown
Contributor

Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).

The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).

For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):

  1. Retry network errors (socket timed out, etc.)
  2. Retry server errors (HTTP 5xx)
  3. Retry HTTP 429.

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

hey @vadimdemedes, @stephenmathieson; wanted to get your thoughts on this. I think the behaviour is functionally correct, but not sure on how to test it. https://github.com/softonic/axios-retry doesn't seem to have tests. My initial idea was to test the custom retry function directly (something simple like t.true(retryCondition({ response: { status: 429 }}))) but wasn't sure how to access access a private function in the test.

Comment threadindex.js Outdated
}
}

var retryCondition = function (error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an arrow function + const instead of var.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's an arrow function?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to be const.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also might suggest adding this method to the Analytics class so we can more easily write tests for it.

Why would that be easier? This seems like a stateless function that can be tested without depending on the analytics class.

Also per my understanding, adding it to the analytics class will automatically expose this function to the public API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to test it directly, we'd have to export it. Attaching it directly to exports (or module.exports) make it look like part of the public API. If we add it as a method of the class, it will be exposed, but won't look like it's part of the public API.

Consider the following:

// exporting directlyexports.somePrivateMethod=()=>{}// exporting as part of the classmodule.exports=classAnalytics{track(){}somePrivateMethod(){}}

IMO the first looks like it's exposed for consumption by the end user, while the second just looks like a regular class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Makes sense about testing, chatted with Roland and he suggested using _ as a prefix to denote it as a private, so I'll do that.

Comment threadindex.js
// Retry if rate limited.
if (error.response.status === 429) {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should return false at the end.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment threadindex.js Outdated
}
}

const retryCondition = function (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constretryCondition=error=>{// ...}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! roland just explained what this was to me :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

function _retryCondition(error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow, but either way, we need to expose this logic somehow in order to test it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roland actually suggested not assigning and using it as a function variable at all, which makes sense to me.

Why not?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me function _retryCondition(error) { looks cleaner than const _retryCondition = error => {. 🤷‍♂️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚲 🏠

@f2prateek

Copy link
Copy Markdown
ContributorAuthor

reminder to myself, don't merge without tests!

@f2prateek

f2prateek commented Jan 11, 2018

Copy link
Copy Markdown
ContributorAuthor

Ok, should be ready for review now! Added tests and made some changes from the original approach:

  • function moved to Analytics class for testing
  • function named with _ prefix to denote this is not meant to be part of the public API.

@Rowno showed me how I could make the function static too. It wasn't clear if that was fully compatible with what we support today and if it cause issues, so I left it as a simple class function for now.

Comment threadindex.js Outdated
})
}

_retryCondition (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might rename this shouldRetry or isErrorRetryable for clarity.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to isErrorRetryable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might have forgotten to push?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! Pushed now.

Comment threadtest.js Outdated
t.false(client._retryCondition({ code: 'ECONNABORTED' }))

t.true(client._retryCondition({ response: { status: 500 } }))
t.true(client._retryCondition({ response: { status: 429 } }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add one last test to verify the default return false behavior.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added one, though it's pretty contrived; we'd never really expect to see and error for a 2xx response.

@f2prateek
f2prateekforce-pushed the retries branch 2 times, most recently from c48865a to b4dc6adCompareJanuary 12, 2018 19:32
Previously we were only retrying network errors or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
The latter doesn't really guard/apply to us since we use POST for uploading messages. Our POST requests are effectively idempotent since we send messages with a unique message ID that is guarded by our dedupe layer (https://segment.com/blog/exactly-once-delivery/).
For us, our retry policy should be (as per https://paper.dropbox.com/doc/analytics-foo-library-guidelines-2trBhLKQD4Soz4VatvuGR#:uid=189560039280582553736180&h2=Handling-Network-Errors):
1. Retry network errors (socket timed out, etc.)
2. Retry server errors (HTTP 5xx)
3. Retry HTTP 429.

@stephenmathiesonstephenmathieson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@f2prateek@stephenmathieson@Rowno@vadimdemedes