spark-test commented on code in PR #59860:
URL: https://github.com/apache/spark/pull/59860#discussion_r4404098577


##########
python/pyspark/worker.py:
##########
@@ -76,6 +78,33 @@ def report_metrics(outfile: BinaryIO, metrics: dict[str, 
int]) -> None:
     write_with_length(payload, outfile)
 
 
+def report_init_response(
+    outfile: BinaryIO, error: Optional[BaseException] = None, 
is_payload_error: bool = False
+) -> None:
+    """
+    Write the init response as a length-prefixed JSON object after 
START_OF_INIT_RESPONSE.
+
+    It is sent exactly once per task, before any output: ``{}`` once the task 
is initialized, or
+    ``{"error": {...}}`` if initialization failed, in which case no output 
follows. An init error
+    carries the exception message, the traceback as the JVM reports it, the 
exception class name,
+    and whether the worker failed to load the UDF payload (``isPayloadError``) 
rather than to set
+    up the task. Must be called while handling ``error``.
+    """
+    response: dict[str, Any] = {}
+    if error is not None:
+        response["error"] = {
+            "message": str(error),

Review Comment:
   **Non-blocking (P2):** `str(error)` runs before any byte of the init 
response is written, and nothing guards it. If an initialization-phase 
exception cannot be converted to a string, for example a payload whose 
unpickling raises an exception whose `__str__` raises or returns a non-string, 
this line raises. The new fallback in `invoke_udf` then only prints to stderr, 
and the worker exits without writing any frame, while the JVM is blocked in 
`handleInitResponse` waiting for one:
   
   - With the default `spark.python.use.daemon=true`, the forked worker keeps 
draining the socket until the JVM closes it. The JVM has already written all 
input and only waits to read, and `spark.python.worker.idleTimeoutSeconds` 
defaults to 0, so the task hangs until it is cancelled.
   - With the daemon disabled (or on Windows), the worker process exits and the 
task fails with the generic "Python worker exited unexpectedly (crashed)" 
error; the user's traceback reaches only executor stderr.
   
   Before this change, `handle_worker_exception` formatted the same exception 
through `TracebackException`, which renders a failing `__str__` as `<exception 
str() failed>`, so the failure still surfaced as a `PythonException` with the 
traceback. `format_worker_exception` on the next line is already tolerant in 
the same way; deriving `message` defensively too (for example, falling back to 
that placeholder when `str()` fails) would keep the init response, and the 
user-facing error, intact. Only init-phase exceptions whose string conversion 
fails are affected.
   
   **Verification:**
   - **Regression:** Run the worker on a payload whose loading raises an 
exception with a failing string conversion, for example one whose __str__ 
raises or returns a non-string. The worker must emit an init response whose 
error object has a string message, a traceback naming the exception class, the 
class name and isPayloadError true, and then no further output.
   - **Behavior:** The existing init-response scenarios keep producing the same 
responses: success, payload-loading error, task-setup error, and a data-phase 
error that follows a successful init response.
   - **Compatibility:** The error object produced for the failing-conversion 
case still decodes through the JVM init-response decoder: message, traceback 
and errorClass remain JSON strings and isPayloadError remains a boolean.



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to