-
Notifications
You must be signed in to change notification settings - Fork 15
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
Retry Canvas submission on timeout #7018
Conversation
Canvas submissions are sent when the first annotations is made during a "session" on a gradable assignment. We have some error handling and display error dialogs accordingly but due to the indirect nature of the action is tricky to handle transactional errors like timeouts. This commits adds a "flag" on the response of API calls to our own frontend marking the request as "retryable" and the frontend.
69c2c6e
to
6d9f812
Compare
@@ -254,6 +254,7 @@ export default function BasicLTILaunchApp() { | |||
authToken, | |||
path: '/api/lti/submissions', | |||
data: submissionParams, | |||
maxRetries: 2, |
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.
The default, 10, seems excessive
...extraHeaders, | ||
['Retry-Count']: (retryCount + 1).toString(), | ||
}, | ||
retryCount: retryCount + 1, |
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.
This looks like duplicating the same value but I reckon the header and the retryCount are relatively independent, one is part of the request and the other is part of our own code logic.
But I might be missing a nice trick.
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.
A few lines above there's this block:
const headers: Record<string, string> = {
Authorization: authToken,
};
You could update it and set the Retry-Count
header there, if retryCount > 0
.
const headers: Record<string, string> = {
Authorization: authToken,
};
+if (retryCount > 0) {
+ headers['Retry-Count'] = `${retryCount}`;
+}
That would reduce the amount of nested object spreading, and the duplication you mentioned above.
@@ -145,6 +151,7 @@ def record_canvas_speedgrader_submission(self): | |||
self.request.registry.notify( | |||
LTIEvent.from_request(request=self.request, type_=LTIEvent.Type.SUBMISSION) | |||
) | |||
self.request.add_finished_callback(log_retries_callback) |
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.
Only applying this and (err.retryable = True) on this view.
If we merge this approach I'll create an issue to track generalizing this approach for other views.
await delay(retryDelay); | ||
return apiCall({ ...options, retryCount: retryCount + 1 }); | ||
return apiCall({ | ||
...options, |
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.
This feels like a lot of ...
s
And the frontend... What? Is this a cliffhanger for season 2? 😂 |
...extraHeaders, | ||
['Retry-Count']: (retryCount + 1).toString(), | ||
}, | ||
retryCount: retryCount + 1, |
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.
A few lines above there's this block:
const headers: Record<string, string> = {
Authorization: authToken,
};
You could update it and set the Retry-Count
header there, if retryCount > 0
.
const headers: Record<string, string> = {
Authorization: authToken,
};
+if (retryCount > 0) {
+ headers['Retry-Count'] = `${retryCount}`;
+}
That would reduce the amount of nested object spreading, and the duplication you mentioned above.
if err.is_timeout: | ||
# We'll inform the frontend that this is a retryable error for timeouts. | ||
err.retryable = True | ||
raise |
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.
I reckon none of the following conditions would match for a timeout, so you could just set err.retryable = err.is_timeout
, but this is definitely more predictable.
...options, | ||
extraHeaders: { | ||
...extraHeaders, | ||
['Retry-Count']: (retryCount + 1).toString(), |
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.
Being pedantic here, but I think the common practice is to prefix custom headers with X-
, as in X-Retry-Count
.
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.
I did out-pedantic myself before reading this:
https://www.rfc-editor.org/rfc/rfc6648.html#section-3
3. SHOULD NOT prefix their parameter names with "X-" or similar
constructs.
I reckon the rationale there is that you start naming things X-, then become common usage but they endup stuck with the X-. I don't think there's are risk of that happening here so I don't have a strong opinion.
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.
Oh, wow!
I think the rational around adding the X-
prefix is making sure you don't end up using a header that eventually becomes part of the standard, but I don't think there's a high risk that Retry-Count
is added to the spec any time soon, without us noticing.
Let's go with Retry-Count
then.
6d9f812
to
6751751
Compare
Canvas submissions are sent when the first annotations is made during a "session" on a gradable assignment.
We have some error handling and display error dialogs accordingly but due to the indirect nature of the action is tricky to handle transactional errors like timeouts.
This commits adds a "flag" on the response of API calls to our own frontend marking the request as "retryable" allowing the FE to retry them.
Testing
to simulate a retry-success scenario.
Go to https://hypothesis.instructure.com/courses/319/assignments/3336 logged in as a student
Check the devtools, make an annotation.
You'll see to API request to
submissions
and a log line like:2025-02-19 11:14:15,976 DEBUG [lms.views.helpers._logging:13][MainThread] Request to /api/lti/submissions succeeded after 1 retries
on the server logs.