From 9cb31f20b341877c933ff5370e6a5f5e2f54eac4 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Fri, 1 Nov 2024 18:36:59 -0700 Subject: [PATCH] Fix pickling of httpclient.HTTPError subclasses and web.HTTPError The `args` member variable is set by `BaseException.__new__` and used by `BaseException.__reduce__` for pickling. To avoid interfering with it, we need to avoid calling `BaseException.__init__` from classes that have subclasses with incompatible constructors, and rename our own `tornado.web.HTTPError.args` member. >>> pickle.loads(pickle.dumps(tornado.simple_httpclient.HTTPTimeoutError("message"))) Traceback (most recent call last): File "", line 1, in TypeError: HTTPTimeoutError.__init__() takes 2 positional arguments but 4 were given >>> str(pickle.loads(pickle.dumps(tornado.web.HTTPError(500, "%s", "foo")))) Traceback (most recent call last): File "", line 1, in File "/home/anders/python/tornado/tornado/web.py", line 2488, in __str__ return message + " (" + (self.log_message % self.args) + ")" ~~~~~~~~~~~~~~~~~^~~~~~~~~~~ TypeError: not enough arguments for format string Signed-off-by: Anders Kaseorg --- tornado/httpclient.py | 1 - tornado/web.py | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tornado/httpclient.py b/tornado/httpclient.py index 3011c371b8..afe1da5774 100644 --- a/tornado/httpclient.py +++ b/tornado/httpclient.py @@ -717,7 +717,6 @@ def __init__( self.code = code self.message = message or httputil.responses.get(code, "Unknown") self.response = response - super().__init__(code, message, response) def __str__(self) -> str: return "HTTP %d: %s" % (self.code, self.message) diff --git a/tornado/web.py b/tornado/web.py index 039396470f..93be9e8f5d 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -1869,7 +1869,9 @@ def log_exception( if isinstance(value, HTTPError): if value.log_message: format = "%d %s: " + value.log_message - args = [value.status_code, self._request_summary()] + list(value.args) + args = [value.status_code, self._request_summary()] + list( + value.log_args + ) gen_log.warning(format, *args) else: app_log.error( @@ -2474,7 +2476,7 @@ def __init__( ) -> None: self.status_code = status_code self.log_message = log_message - self.args = args + self.log_args = args self.reason = kwargs.get("reason", None) if log_message and not args: self.log_message = log_message.replace("%", "%%") @@ -2485,7 +2487,7 @@ def __str__(self) -> str: self.reason or httputil.responses.get(self.status_code, "Unknown"), ) if self.log_message: - return message + " (" + (self.log_message % self.args) + ")" + return message + " (" + (self.log_message % self.log_args) + ")" else: return message