Add connection and read timeouts to requests to prevent app hangs. #590
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Issue
We currently make two requests that have no timeout. These could potentially block the application forever if there are network or server issues. The
requests
library recommends all production code utilize timeouts for this reason.Proposed Solution
I add a five second timeout to the two requests in
common.py
andlauncher.py
as well as a custom HTTP adapter that retries failed requests up to three times.Implementation Details
I implement a custom
HTTPAdapter
which configures a max of three retries in the event of a connection issue. Since this retry strategy has to be implemented via anHTTPAdapter
I also had to implement arequests.Session()
attribute on the two classes in questiontorbrowser_launcher.common.Common
andtorbrowser_launcher.launcher.DownloadThread
, then mount that adapter to theSession()
. Thissession
is then used to make the GET request.Also, instead of the if/else logic checking for a 200 status code on the response, I utilize the
raise_for_status()
method on the response object. If the response is not a 200 OK, this will raise arequests.HTTPError
. I then move the error handling logic intended for that situation into a handler for that exception where, I would argue, it belongs.Exception Handling
In the event of a timeout, the
requests
library will raise either aConnectTimeout
or aReadTimeout
, both of which are subclasses ofTimeout
and in the case ofConnectTimeout
it is also subclassesConnectionError
(see linked docs).In the case of the download timing out in
launcher.py
, if it was aConnectTimeout
the existing error handling forConnectionError
will execute. In the event it was aReadTimeout
an unhandled exception would occur.An open question is if we want a
ReadTimeout
to be unhandled or if we should gracefully handle aReadTimeout
differently or perhaps even just have the handler forConnectionError
also handleReadTimeout
? The existing error language for aConnectionError
here works well in the event there was aConnectTimeout
. It is less accurate if there was aReadTimeout
e.g. we made a connection successfully but the server failed to send us data in the specified timeframe. In such a case we probably don't want to say "Error starting download..." which is why I hesitate to just handleConnectionError
andTimeout
together here. Maybe we can do that and just edit the "starting" language to be something more general like "Error while downloading"?In
common.Common
I just follow the existing strategy of printing an error to the console if there's a request exception. In this case, I catch either type of timeout by catchingrequests.Timeout
.