shivaam commented on code in PR #75184:
URL: https://github.com/apache/airflow/pull/75184#discussion_r4521711178
##########
task-sdk/src/airflow/sdk/coordinators/_subprocess.py:
##########
@@ -605,6 +658,220 @@ def _handle_set_task_state_store(
)
return super()._handle_set_task_state_store(msg, log, req_id)
+ def _monitor_subprocess(self):
+ try:
+ super()._monitor_subprocess()
+ finally:
+ self._stop_xcom_backend()
+
+ def _start_xcom_backend_call(
+ self, call: XComBackendCall, finish: Callable[[XComBackendResult],
None]
+ ) -> None:
+ """Send ``call`` to the XCom backend process, which starts on the
first call it handles."""
+ if self._xcom_backend is not None and not
self._xcom_backend.can_run_a_call:
+ self._stop_xcom_backend()
+ if self._xcom_backend is None:
+ self._xcom_backend = XComBackendProcess.start(
+ id=self.id,
+ client=self.client,
+ logger=self.process_log,
+ selector=self.selector,
+ )
+ self._xcom_backend.send_call(call, finish)
+
+ def _stop_xcom_backend(self) -> None:
+ """Stop the XCom backend process, so that the next call starts one
that can answer it."""
+ if (backend := self._xcom_backend) is not None:
+ self._xcom_backend = None
+ backend.stop()
+
+ def _call_xcom_backend(
+ self,
+ call: XComBackendCall,
+ then: Callable[[XComBackendResult], RequestResult],
+ req_id: int,
+ ) -> ResponseSent:
+ """
+ Answer request ``req_id`` with ``then(result)`` once the XCom backend
has run ``call``.
+
+ The request loop keeps servicing the task while the call runs. A call
that fails fails the request.
+ """
+
+ def answer(resp: BaseModel | None, error: ErrorResponse | None,
**dump_opts: bool) -> None:
+ if not send_unless_exited(self, resp, req_id, error, **dump_opts):
+ self.process_log.warning(
+ "The runtime exited before its XCom request was answered",
request_id=req_id
+ )
+
+ def finish(result: XComBackendResult) -> None:
+ try:
+ if result.error is not None:
+ raise XComBackendError(result.error)
+ resp, dump_opts = then(result)
Review Comment:
Could we preserve `API_SERVER_ERROR` and its HTTP status/detail when the
failure comes from `then(result)`? For `SetXCom`, that continuation makes the
API write after the backend has already stored the value. A 403/500 from that
write is caught here and converted to `XCOM_BACKEND_ERROR` with
`status_code=None`, while the default-backend path preserves the API error. A
local mock-API check reproduced both statuses; keeping their origin would make
permission/server failures easier to diagnose.
---
Drafted-by: Codex (GPT-6) (no human review before posting)
##########
task-sdk/src/airflow/sdk/coordinators/_subprocess.py:
##########
@@ -605,6 +658,220 @@ def _handle_set_task_state_store(
)
return super()._handle_set_task_state_store(msg, log, req_id)
+ def _monitor_subprocess(self):
+ try:
+ super()._monitor_subprocess()
+ finally:
+ self._stop_xcom_backend()
+
+ def _start_xcom_backend_call(
+ self, call: XComBackendCall, finish: Callable[[XComBackendResult],
None]
+ ) -> None:
+ """Send ``call`` to the XCom backend process, which starts on the
first call it handles."""
+ if self._xcom_backend is not None and not
self._xcom_backend.can_run_a_call:
+ self._stop_xcom_backend()
+ if self._xcom_backend is None:
+ self._xcom_backend = XComBackendProcess.start(
+ id=self.id,
+ client=self.client,
+ logger=self.process_log,
+ selector=self.selector,
+ )
+ self._xcom_backend.send_call(call, finish)
+
+ def _stop_xcom_backend(self) -> None:
+ """Stop the XCom backend process, so that the next call starts one
that can answer it."""
+ if (backend := self._xcom_backend) is not None:
+ self._xcom_backend = None
+ backend.stop()
+
+ def _call_xcom_backend(
+ self,
+ call: XComBackendCall,
+ then: Callable[[XComBackendResult], RequestResult],
+ req_id: int,
+ ) -> ResponseSent:
+ """
+ Answer request ``req_id`` with ``then(result)`` once the XCom backend
has run ``call``.
+
+ The request loop keeps servicing the task while the call runs. A call
that fails fails the request.
+ """
+
+ def answer(resp: BaseModel | None, error: ErrorResponse | None,
**dump_opts: bool) -> None:
+ if not send_unless_exited(self, resp, req_id, error, **dump_opts):
+ self.process_log.warning(
+ "The runtime exited before its XCom request was answered",
request_id=req_id
+ )
+
+ def finish(result: XComBackendResult) -> None:
+ try:
+ if result.error is not None:
+ raise XComBackendError(result.error)
+ resp, dump_opts = then(result)
+ except Exception as e:
+ self.process_log.exception("Failed to handle the XCom backend
result", request_id=req_id)
+ answer(None, _build_request_error(e))
+ else:
+ answer(resp, None, **dump_opts)
+
+ self._start_xcom_backend_call(call, finish)
+ return ResponseSent.ALREADY_SENT
+
+ def _run_xcom_backend_call(self, call: XComBackendCall) -> None:
+ """
+ Run ``call`` and wait for it, for a caller with no request loop to
return to.
+
+ :raises XComBackendError: when the call fails or is not answered in
time.
+ """
+ answer: list[XComBackendResult] = []
+ self._start_xcom_backend_call(call, answer.append)
+ deadline = time.monotonic() + _XCOM_BACKEND_CALL_TIMEOUT
+ # The task has not started, so it has no requests to read.
+ while not answer and (now := time.monotonic()) < deadline:
+ self._service_subprocess(max_wait_time=deadline - now)
Review Comment:
Could this wait also keep the supervisor's heartbeat checks running?
`_prepare_ti_context()` calls it after `task_instances.start()` has marked the
task running, but this loop only services subprocess I/O; heartbeat checks
happen in the later `_monitor_subprocess()` loop. Several slow, successful
purges can exceed the heartbeat timeout even though each finishes within its
60-second limit. A bounded fake-clock check showed no heartbeat checks across
seven 50-second purges. Would reusing the heartbeat/server-stop checks during
startup waits make sense here?
---
Drafted-by: Codex (GPT-6) (no human review before posting)
--
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]