yuqi1129 commented on code in PR #14366:
URL: https://github.com/apache/gravitino/pull/14366#discussion_r4424536695


##########
clients/client-python/gravitino/exceptions/base.py:
##########
@@ -30,6 +30,27 @@ def __init__(self, message, *args):
         super().__init__(message % args)
 
 
+class HTTPResponseException(RESTException):
+    """An HTTP failure whose response cannot be used as a domain error.
+
+    Attributes:
+        status_code: The HTTP status received from the server.
+        request_method: The original client request's HTTP method.
+        endpoint: The request origin, without credentials, path or query 
parameters.
+    """
+
+    def __init__(
+        self, status_code: int, request_method: str, endpoint: str, reason: str
+    ):
+        """Create an error from safe context and a fixed diagnostic reason."""
+        self.status_code = status_code
+        self.request_method = request_method
+        self.endpoint = endpoint
+        super().__init__(

Review Comment:
   [Important] Make the new exception reconstructible by pickle/multiprocessing.
   
   This constructor requires four arguments, but `RESTException.__init__` 
leaves only the formatted message in `BaseException.args`. Pickle reconstructs 
the exception with that one argument before restoring its attributes, so this 
fails:
   
   ```python
   pickle.loads(pickle.dumps(
       HTTPResponseException(502, "GET", "http://localhost";, "Empty error 
response")
   ))
   # TypeError: ... missing 3 required positional arguments ...
   ```
   
   I also reproduced the consequence with a real Python 3.11.14 
`ProcessPoolExecutor` worker invoking `HTTPClient.get` against a controlled 
empty HTTPError: `Future.result()` raises `BrokenProcessPool` instead of the 
HTTP exception, breaking the application's process pool. Reverting only 
HTTPClient to the base makes the same worker return the ordinary 
`RESTException` successfully.
   
   Please retain the safe reason/context and provide reconstruction, for 
example an explicit `__reduce__` supplying the constructor arguments. Add a 
pickle round-trip and a cross-process exception propagation regression, 
preserving type/message/status/method/origin.
   
   Validation used the public HTTPClient path with a synthetic empty 502 and an 
actual standard-library process pool. I did not identify an existing repository 
multiprocessing HTTPClient caller or test Ray/cloudpickle; this is a reproduced 
external SDK consumer, not a claim that the current Ray tests hit it.



##########
clients/client-python/gravitino/utils/http_client.py:
##########
@@ -134,25 +139,84 @@ def _make_request(self, opener, request, timeout=None) -> 
Tuple[bool, Response]:
         try:
             return (True, Response(opener.open(request, timeout=timeout)))
         except HTTPError as err:
+            try:
+                return (False, self._read_error_response(err, request))
+            finally:
+                try:
+                    err.close()
+                except OSError:
+                    # Closing a failed response must not replace its HTTP 
failure.
+                    pass
+
+    def _read_error_response(self, err, request) -> ErrorResponse:
+        try:
             err_body = err.read()
+        except (IncompleteRead, OSError):
+            raise self._http_response_exception(
+                err, request, "Unable to read error response"
+            ) from None
 
-            if err_body is None or len(err_body) == 0:
-                return (
-                    False,
-                    ErrorResponse.generate_error_response(RESTException, 
err.reason),
-                )
+        if not err_body:
+            raise self._http_response_exception(
+                err, request, "Empty error response"
+            ) from None
 
-            err_resp = self._parse_error_response(err_body)
-            err_resp.validate()
+        try:
+            response = self._parse_error_response(err_body)
+            response.validate()
+        except (ValueError, TypeError, KeyError, BadRequestException):

Review Comment:
   [Important] Handle the JSON decoder's `RecursionError` through the safe 
fallback as well.
   
   `json.loads` can raise `RecursionError` for excessive nesting, which is 
outside this catch. A roughly 3 KB error body is enough with the ordinary 
recursion limit:
   
   ```python
   body = b"[" * 1500 + b"]" * 1500
   ```
   
   Through the current public `HTTPClient.get` path, an HTTP 502 with that body 
raises `RecursionError`, without `status_code`, instead of 
`HTTPResponseException`. Its default traceback also retains the HTTPError 
context: a synthetic HTTP reason `private-reason` appears in the formatted 
traceback, bypassing the fixed-reason/suppressed-chain protection. The response 
does still close in `finally`.
   
   Please catch `RecursionError` specifically within this parse/validation 
boundary (or reject excessive nesting safely), then raise the same fixed-reason 
fallback `from None`. Keep custom handler failures outside the catch. Add a 
nested-body regression checking the SDK exception, HTTP context, closed 
response and absence of the private HTTP reason/underlying chain.
   
   Verified on Python 3.11.14 with the actual decoder and default traceback 
formatting, using a controlled HTTPError rather than a live gateway. The base 
also exposes RecursionError, so this is an uncovered case of the intended 
malformed-response fix, not a newly introduced parsing regression.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to