Repository: urllib3/urllib3

Issue:
Spurious TypeError if BaseException occurs during getresponse()
This issue is a repeat of #861, but for exceptions that derive from `BaseException` and not `Exception`.

Specifically, if an `Exception` gets raised from `conn.getresponse()` in this line of code: https://github.com/urllib3/urllib3/blob/728d9244665ef5b03103cb74d7b409ebe4f23b43/src/urllib3/connectionpool.py#L411

then `urllib3` calls `raise from None` to remove the spurious `TypeError` from the exception chain: https://github.com/urllib3/urllib3/blob/728d9244665ef5b03103cb74d7b409ebe4f23b43/src/urllib3/connectionpool.py#L405-L415

However, if a `BaseException` like `KeyboardInterrupt` or `SystemExit` is raised during the call to `conn.getresponse()`, then the `TypeError` _isn't_ removed. This is because the try-except only catches and re-raises `Exception` and not `BaseException`. As mentioned in the original #861, seeing the spurious `TypeError` makes it look like there is bug e.g. in the error handling that occurs while the program exits. The `BaseException` can occur e.g. if a request is taking a long time and the application is terminated from another process.

The fix is to change `Exception` to `BaseException` in this line of code: https://github.com/urllib3/urllib3/blob/728d9244665ef5b03103cb74d7b409ebe4f23b43/src/urllib3/connectionpool.py#L412

I would file a PR, but I'm not sure I'd have time to craft a test. I note, though, that the original PR #861 didn't have a test.


Discussion:
cc @bluetech 
By the way, rather than nested try-excepts, another possible approach is simply to use the correct invocation of `conn.getresponse()` depending on the version of the Python. Then no try-excepts would be needed. That seems cleaner / simpler, IMO.

Another issue with the current approach is that if in Python 2 a `TypeError` occurs during the (first) call to `getresponse()` for a _different_ reason (like a bug in the code further down), then that error will get lost / swallowed without any way to find out about it. So if the try-except approach is going to be used, it seems like the right thing to do after catching `TypeError` would be to do a Python version check. If the version is Python 2, the exception should be re-raised as is. And if Python 3, then the call can be redone with the other invocation variant. But if this approach requires a version-check anyways, it seems all the more reason to do the approach I suggested above of checking the Python version in advance, and acting accordingly.

Resolving patch:
diff --git a/src/urllib3/connectionpool.py b/src/urllib3/connectionpool.py
--- a/src/urllib3/connectionpool.py
+++ b/src/urllib3/connectionpool.py
@@ -409,9 +409,10 @@ def _make_request(
                 # Python 3
                 try:
                     httplib_response = conn.getresponse()
-                except Exception as e:
-                    # Remove the TypeError from the exception chain in Python 3;
-                    # otherwise it looks like a programming error was the cause.
+                except BaseException as e:
+                    # Remove the TypeError from the exception chain in
+                    # Python 3 (including for exceptions like SystemExit).
+                    # Otherwise it looks like a bug in the code.
                     six.raise_from(e, None)
         except (SocketTimeout, BaseSSLError, SocketError) as e:
             self._raise_timeout(err=e, url=url, timeout_value=read_timeout)


Test patch: