uranusjr commented on code in PR #75707:
URL: https://github.com/apache/airflow/pull/75707#discussion_r4385347122


##########
task-sdk/tests/task_sdk/coordinators/test_subprocess.py:
##########
@@ -1150,6 +1171,164 @@ def handle_request(request: httpx2.Request) -> 
httpx2.Response:
         } in captured_logs
 
 
+_RUNTIME_SPAWNING_A_CHILD = """
+import socket
+import subprocess
+import sys
+
+def connect(flag):
+    host, _, port = next(arg for arg in sys.argv if 
arg.startswith(flag)).split("=", 1)[1].rpartition(":")
+    return socket.create_connection((host, int(port)))
+
+comm = connect("--comm=")
+logs = connect("--logs=")
+child = subprocess.Popen([sys.executable, "-c", "import time; 
time.sleep(300)"])
+with open(sys.argv[1], "w") as handle:
+    handle.write(str(child.pid))
+comm.settimeout(30)
+try:
+    comm.recv(1)
+except TimeoutError:
+    sys.exit(3)
+"""
+
+
+def _wait_until_gone(pid: int, timeout: float = 10.0) -> bool:
+    """Return whether *pid* has stopped running, treating a not-yet-reaped 
zombie as gone."""
+    deadline = time.monotonic() + timeout
+    while True:
+        try:
+            if psutil.Process(pid).status() == psutil.STATUS_ZOMBIE:
+                return True
+        except psutil.NoSuchProcess:
+            return True
+        if time.monotonic() >= deadline:
+            return False
+        time.sleep(0.05)
+
+
+class TestSignalProcessTree:
+    """``_signal_process_tree`` reaches the runtime's group, or the pid when 
it cannot."""
+
+    def test_signals_the_group(self):
+        proc = MagicMock(spec=subprocess.Popen)
+        proc.pid = 6767
+        with (
+            patch("os.getpgid", side_effect=lambda pid: 6767 if pid == 6767 
else os.getpid()),
+            patch("os.killpg") as mock_killpg,
+        ):
+            _signal_process_tree(proc, signal.SIGTERM)
+        mock_killpg.assert_called_once_with(6767, signal.SIGTERM)
+        proc.send_signal.assert_not_called()
+
+    def test_signals_the_pid_when_the_runtime_shares_our_group(self):
+        """Signalling our own group would hit the supervisor itself and its 
siblings."""
+        proc = MagicMock(spec=subprocess.Popen)
+        proc.pid = 6767
+        with (
+            patch("os.getpgid", return_value=os.getpid()),
+            patch("os.killpg") as mock_killpg,
+        ):
+            _signal_process_tree(proc, signal.SIGTERM)
+        mock_killpg.assert_not_called()
+        proc.send_signal.assert_called_once_with(signal.SIGTERM)
+
+    @pytest.mark.parametrize("failing", ["getpgid", "killpg"])
+    @pytest.mark.parametrize("error", [ProcessLookupError, PermissionError])
+    def test_signals_the_pid_when_the_group_cannot_be_signalled(self, failing, 
error):
+        proc = MagicMock(spec=subprocess.Popen)
+        proc.pid = 6767
+        own_group = os.getpgid(0)
+
+        def resolve(pid):
+            return 6767 if pid == 6767 else own_group
+
+        getpgid = {"side_effect": error if failing == "getpgid" else resolve}
+        killpg = {"side_effect": error} if failing == "killpg" else {}
+        with patch("os.getpgid", **getpgid), patch("os.killpg", **killpg) as 
mock_killpg:
+            _signal_process_tree(proc, signal.SIGTERM)
+        assert mock_killpg.called is (failing == "killpg")
+        proc.send_signal.assert_called_once_with(signal.SIGTERM)
+
+
+class TestPopenActivitySubprocessProcessGroup:
+    """A language SDK runtime runs in its own process group so a stop reaches 
what it spawned."""
+
+    def test_popen_asks_for_a_new_process_group(self, mock_client):
+        mock_proc = MagicMock(spec=subprocess.Popen)
+        mock_proc.pid = 12345
+        with (
+            patch(
+                "airflow.sdk.coordinators._subprocess.subprocess.Popen", 
return_value=mock_proc
+            ) as popen_mock,
+            patch(
+                "airflow.sdk.coordinators._subprocess._accept_connections",
+                side_effect=lambda servers, drains, proc, **kw: (
+                    {soc: MagicMock(spec=socket.socket) for soc in 
servers.values()},
+                    {soc: b"" for soc in drains.values()},
+                ),
+            ),
+            patch.object(ActivitySubprocess, "_register_pipe_readers"),
+            patch.object(ActivitySubprocess, "_on_child_started"),
+        ):
+            proc = _PopenActivitySubprocess.start(
+                what=_make_ti(),
+                dag_rel_path="bundle",
+                bundle_info=MagicMock(),
+                client=mock_client,
+                command=["/bin/true"],
+                subprocess_logs_to_stdout=False,
+            )
+
+        assert popen_mock.call_args.kwargs["process_group"] == 0
+        # The supervisor only uses killpg when it knows the child has its own 
group.
+        assert proc._new_process_group is True

Review Comment:
   ```suggestion
           assert proc._new_process_group is True
   ```



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