From 8c79fc5ee9f439db3d364a2a118a2cedd7eb73f9 Mon Sep 17 00:00:00 2001 From: Alan Li Date: Tue, 4 Aug 2026 08:06:26 -0400 Subject: [PATCH 1/3] fix(embed): define daemon stop() success by port occupancy, not the health probe stop() used is_running() -- a 2s /health probe -- both as its already-stopped guard and as its final success condition. A daemon that is alive but busy (slow provider call, model load) fails that probe, so 'daemon stop' returned True without sending any signal, and a failed termination or missing PID also fell through to a reported success. Resolve the port from the profile and use occupancy for every decision: already stopped only when nothing is bound; bound port with no findable PID is a failure; a False from _kill_process() is a failure; after the kill, success means the listener is gone. This mirrors the occupancy/health separation _clear_port() already uses. Closes #3169 --- .../hindsight_embed/daemon_embed_manager.py | 31 +++--- hindsight-embed/tests/test_daemon_client.py | 105 ++++++++++++++++++ 2 files changed, 123 insertions(+), 13 deletions(-) diff --git a/hindsight-embed/hindsight_embed/daemon_embed_manager.py b/hindsight-embed/hindsight_embed/daemon_embed_manager.py index 4b1508b4b7..efd7a6a9c2 100644 --- a/hindsight-embed/hindsight_embed/daemon_embed_manager.py +++ b/hindsight-embed/hindsight_embed/daemon_embed_manager.py @@ -1006,25 +1006,30 @@ def stop(self, profile: str) -> bool: Returns: True if stopped successfully, False otherwise """ - if not self.is_running(profile): - logger.debug(f"Daemon not running for profile '{profile}'") - return True - - # Get port paths = self._profile_manager.resolve_profile_paths(profile) port = paths.port + # Success is defined by occupancy, not responsiveness: a busy daemon + # can fail the 2s /health probe while still holding the port (#3169). + if not self._is_port_in_use(port): + logger.debug(f"Daemon not running for profile '{profile}'") + return True + pid = self._find_pid_on_port(port) - if pid is not None: - logger.debug(f"Found daemon PID {pid} on port {port}") - self._kill_process(pid) - else: - logger.warning(f"Could not find PID for port {port}") + if pid is None: + logger.warning(f"Port {port} is bound but no PID could be found") + return False - # Wait for health check to fail + logger.debug(f"Found daemon PID {pid} on port {port}") + if not self._kill_process(pid): + logger.warning(f"Daemon process (PID {pid}) did not stop in time") + return False + + # The process is gone; wait for the listener to disappear so a + # follow-up start doesn't race the closing socket. for _ in range(30): - if not self.is_running(profile): + if not self._is_port_in_use(port): return True time.sleep(0.1) - return not self.is_running(profile) + return not self._is_port_in_use(port) diff --git a/hindsight-embed/tests/test_daemon_client.py b/hindsight-embed/tests/test_daemon_client.py index 6f53435b5a..ffcb6e2bb6 100644 --- a/hindsight-embed/tests/test_daemon_client.py +++ b/hindsight-embed/tests/test_daemon_client.py @@ -517,3 +517,108 @@ def test_non_negative_int_falls_back_for_invalid_values(self, caplog): assert _parse_non_negative_int("10MB", 42, "LIMIT") == 42 assert _parse_non_negative_int("-1", 42, "LIMIT") == 42 assert "Invalid LIMIT" in caplog.text + + +class TestStop: + """Tests for DaemonEmbedManager.stop() — regression coverage for #3169. + + stop()'s success must be defined by port occupancy (liveness), not the + 2s /health probe (responsiveness): a busy daemon fails the probe while + still holding the port. + """ + + def _paths(self, tmp_path, port=9700): + from hindsight_embed.profile_manager import ProfilePaths + + return ProfilePaths( + config=tmp_path / "embed", + lock=tmp_path / "daemon.lock", + log=tmp_path / "daemon.log", + port=port, + ) + + def test_busy_daemon_is_terminated_not_reported_stopped(self, tmp_path): + """A live-but-unresponsive daemon must be killed, and /health never consulted.""" + manager = DaemonEmbedManager() + with ( + patch.object( + manager._profile_manager, + "resolve_profile_paths", + return_value=self._paths(tmp_path), + ), + # Port bound at the guard, free once the process is killed. + patch.object(DaemonEmbedManager, "_is_port_in_use", side_effect=[True, False]), + patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=4242), + patch.object(DaemonEmbedManager, "_kill_process", return_value=True) as mock_kill, + # The whole point of #3169: stop() must not route through the + # responsiveness probe on any path. + patch.object( + DaemonEmbedManager, + "is_running", + side_effect=AssertionError("stop() consulted the /health probe"), + ), + ): + assert manager.stop("default") is True + mock_kill.assert_called_once_with(4242) + + def test_failed_termination_returns_false(self, tmp_path): + """_kill_process() returning False must not be converted into success.""" + manager = DaemonEmbedManager() + with ( + patch.object( + manager._profile_manager, + "resolve_profile_paths", + return_value=self._paths(tmp_path), + ), + patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), + patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=4242), + patch.object(DaemonEmbedManager, "_kill_process", return_value=False), + ): + assert manager.stop("default") is False + + def test_bound_port_without_pid_returns_false(self, tmp_path): + """A bound port whose PID can't be found is a failure, not a success.""" + manager = DaemonEmbedManager() + with ( + patch.object( + manager._profile_manager, + "resolve_profile_paths", + return_value=self._paths(tmp_path), + ), + patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), + patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=None), + patch.object(DaemonEmbedManager, "_kill_process") as mock_kill, + ): + assert manager.stop("default") is False + mock_kill.assert_not_called() + + def test_unbound_port_reports_already_stopped(self, tmp_path): + """Nothing bound to the port means there is nothing to stop.""" + manager = DaemonEmbedManager() + with ( + patch.object( + manager._profile_manager, + "resolve_profile_paths", + return_value=self._paths(tmp_path), + ), + patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=False), + patch.object(DaemonEmbedManager, "_find_pid_on_port") as mock_find, + ): + assert manager.stop("default") is True + mock_find.assert_not_called() + + def test_lingering_listener_after_kill_returns_false(self, tmp_path): + """If the listener never disappears after the kill, stop() must not claim success.""" + manager = DaemonEmbedManager() + with ( + patch.object( + manager._profile_manager, + "resolve_profile_paths", + return_value=self._paths(tmp_path), + ), + patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), + patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=4242), + patch.object(DaemonEmbedManager, "_kill_process", return_value=True), + patch("hindsight_embed.daemon_embed_manager.time.sleep"), + ): + assert manager.stop("default") is False From 5fc7d322adbfc9ccd4772f4a64bdb906601d8830 Mon Sep 17 00:00:00 2001 From: Alan Li Date: Fri, 7 Aug 2026 20:40:49 -0400 Subject: [PATCH 2/3] fix(embed): gate daemon stop on health identity, not just occupancy koriyoshi2041 review: occupancy alone is not authorization to kill. An unrelated service on the profile port would be SIGTERM'd by the previous fix. Restore an identity check before signaling: _port_health_ok confirms the listener answers like Hindsight (status/database in /health payload), which is stricter than the old is_running() 200-only check. A busy Hindsight daemon (the #3169 scenario) fails this probe, so stop() now returns False rather than killing blind - a failed stop is recoverable, an unknown kill is not. Port occupancy remains the success condition after termination (koriyoshi endorsed this). Adds test_foreign_listener_is_not_signaled (the regression koriyoshi asked for) and updates the busy-daemon test to assert the new refuse-to-kill behavior. --- .../hindsight_embed/daemon_embed_manager.py | 17 ++++- hindsight-embed/tests/test_daemon_client.py | 63 ++++++++++++++----- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/hindsight-embed/hindsight_embed/daemon_embed_manager.py b/hindsight-embed/hindsight_embed/daemon_embed_manager.py index efd7a6a9c2..4015245788 100644 --- a/hindsight-embed/hindsight_embed/daemon_embed_manager.py +++ b/hindsight-embed/hindsight_embed/daemon_embed_manager.py @@ -1009,12 +1009,25 @@ def stop(self, profile: str) -> bool: paths = self._profile_manager.resolve_profile_paths(profile) port = paths.port - # Success is defined by occupancy, not responsiveness: a busy daemon - # can fail the 2s /health probe while still holding the port (#3169). + # Occupancy tells us whether *something* holds the port, but not whether + # that something is our daemon. Before sending SIGTERM, confirm the + # listener answers like Hindsight (_port_health_ok checks the + # status/database fields in the /health payload) so an unrelated service + # on the same port is not signalled. A busy Hindsight daemon may fail + # this probe (the original #3169 symptom), in which case we refuse to + # kill rather than risk terminating an unknown process - the caller can + # retry once the daemon is responsive (#3171 review). if not self._is_port_in_use(port): logger.debug(f"Daemon not running for profile '{profile}'") return True + if not self._port_health_ok(port): + logger.warning( + f"Port {port} is occupied but does not respond as the Hindsight " + f"daemon; refusing to signal an unknown process" + ) + return False + pid = self._find_pid_on_port(port) if pid is None: logger.warning(f"Port {port} is bound but no PID could be found") diff --git a/hindsight-embed/tests/test_daemon_client.py b/hindsight-embed/tests/test_daemon_client.py index ffcb6e2bb6..3fc822b69f 100644 --- a/hindsight-embed/tests/test_daemon_client.py +++ b/hindsight-embed/tests/test_daemon_client.py @@ -520,11 +520,12 @@ def test_non_negative_int_falls_back_for_invalid_values(self, caplog): class TestStop: - """Tests for DaemonEmbedManager.stop() — regression coverage for #3169. + """Tests for DaemonEmbedManager.stop() - regression coverage for #3169. - stop()'s success must be defined by port occupancy (liveness), not the - 2s /health probe (responsiveness): a busy daemon fails the probe while - still holding the port. + stop() separates two questions: is the port occupied (so something needs + stopping), and is the listener our daemon (so we are authorized to signal + it). Occupancy alone is not kill authorization - an unrelated service on + the profile port must not be SIGTERM'd (#3171 review). """ def _paths(self, tmp_path, port=9700): @@ -537,8 +538,15 @@ def _paths(self, tmp_path, port=9700): port=port, ) - def test_busy_daemon_is_terminated_not_reported_stopped(self, tmp_path): - """A live-but-unresponsive daemon must be killed, and /health never consulted.""" + def test_busy_daemon_refuses_to_kill_without_identity(self, tmp_path): + """A live-but-unresponsive daemon fails the health probe, so stop() + cannot establish it is ours and refuses to signal it. + + This is the #3169 scenario: a busy daemon holds the port but the 2s + /health probe times out. Killing it blind risks terminating an + unrelated process that happens to hold the port, so stop() reports + failure and leaves the process to the caller (#3171 review). + """ manager = DaemonEmbedManager() with ( patch.object( @@ -546,20 +554,36 @@ def test_busy_daemon_is_terminated_not_reported_stopped(self, tmp_path): "resolve_profile_paths", return_value=self._paths(tmp_path), ), - # Port bound at the guard, free once the process is killed. - patch.object(DaemonEmbedManager, "_is_port_in_use", side_effect=[True, False]), - patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=4242), - patch.object(DaemonEmbedManager, "_kill_process", return_value=True) as mock_kill, - # The whole point of #3169: stop() must not route through the - # responsiveness probe on any path. + patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), + patch.object(DaemonEmbedManager, "_port_health_ok", return_value=False), + patch.object(DaemonEmbedManager, "_find_pid_on_port") as mock_find, + patch.object(DaemonEmbedManager, "_kill_process") as mock_kill, + ): + assert manager.stop("default") is False + mock_find.assert_not_called() + mock_kill.assert_not_called() + + def test_foreign_listener_is_not_signaled(self, tmp_path): + """An unrelated service on the profile port must not be SIGTERM'd. + + Occupancy only means *something* holds the port. stop() must confirm + the listener answers like the Hindsight daemon before sending a + signal; otherwise it returns failure without touching the process. + """ + manager = DaemonEmbedManager() + with ( patch.object( - DaemonEmbedManager, - "is_running", - side_effect=AssertionError("stop() consulted the /health probe"), + manager._profile_manager, + "resolve_profile_paths", + return_value=self._paths(tmp_path), ), + patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), + patch.object(DaemonEmbedManager, "_port_health_ok", return_value=False), + patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=9999), + patch.object(DaemonEmbedManager, "_kill_process") as mock_kill, ): - assert manager.stop("default") is True - mock_kill.assert_called_once_with(4242) + assert manager.stop("default") is False + mock_kill.assert_not_called() def test_failed_termination_returns_false(self, tmp_path): """_kill_process() returning False must not be converted into success.""" @@ -571,6 +595,7 @@ def test_failed_termination_returns_false(self, tmp_path): return_value=self._paths(tmp_path), ), patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), + patch.object(DaemonEmbedManager, "_port_health_ok", return_value=True), patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=4242), patch.object(DaemonEmbedManager, "_kill_process", return_value=False), ): @@ -586,6 +611,7 @@ def test_bound_port_without_pid_returns_false(self, tmp_path): return_value=self._paths(tmp_path), ), patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), + patch.object(DaemonEmbedManager, "_port_health_ok", return_value=True), patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=None), patch.object(DaemonEmbedManager, "_kill_process") as mock_kill, ): @@ -602,9 +628,11 @@ def test_unbound_port_reports_already_stopped(self, tmp_path): return_value=self._paths(tmp_path), ), patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=False), + patch.object(DaemonEmbedManager, "_port_health_ok") as mock_health, patch.object(DaemonEmbedManager, "_find_pid_on_port") as mock_find, ): assert manager.stop("default") is True + mock_health.assert_not_called() mock_find.assert_not_called() def test_lingering_listener_after_kill_returns_false(self, tmp_path): @@ -617,6 +645,7 @@ def test_lingering_listener_after_kill_returns_false(self, tmp_path): return_value=self._paths(tmp_path), ), patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), + patch.object(DaemonEmbedManager, "_port_health_ok", return_value=True), patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=4242), patch.object(DaemonEmbedManager, "_kill_process", return_value=True), patch("hindsight_embed.daemon_embed_manager.time.sleep"), From f3aaf6ab683bddb2d6f2d2f025d592c86415bff6 Mon Sep 17 00:00:00 2001 From: Alan Li Date: Wed, 12 Aug 2026 02:39:03 -0400 Subject: [PATCH 3/3] fix(embed): drop the health-identity gate from daemon stop() The gate refused to signal a listener that failed /health, but a busy Hindsight daemon is exactly what fails that probe - the case #3169 is about. It turned "claims success, kills nothing" into "reports failure, kills nothing", leaving a wedged daemon unstoppable, and contradicted _clear_port(), which reclaims the same port state on the start path. stop() now decides on occupancy alone: port free means already stopped, a bound port with no PID or a failed kill is a failure, and success is the listener disappearing. The two identity tests now assert the listener is signalled; one of them also pins stop() and _clear_port() to the same policy for an occupied, unhealthy port. --- .../hindsight_embed/daemon_embed_manager.py | 21 ++---- hindsight-embed/tests/test_daemon_client.py | 74 +++++++++++-------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/hindsight-embed/hindsight_embed/daemon_embed_manager.py b/hindsight-embed/hindsight_embed/daemon_embed_manager.py index 4015245788..65397fde00 100644 --- a/hindsight-embed/hindsight_embed/daemon_embed_manager.py +++ b/hindsight-embed/hindsight_embed/daemon_embed_manager.py @@ -1009,25 +1009,16 @@ def stop(self, profile: str) -> bool: paths = self._profile_manager.resolve_profile_paths(profile) port = paths.port - # Occupancy tells us whether *something* holds the port, but not whether - # that something is our daemon. Before sending SIGTERM, confirm the - # listener answers like Hindsight (_port_health_ok checks the - # status/database fields in the /health payload) so an unrelated service - # on the same port is not signalled. A busy Hindsight daemon may fail - # this probe (the original #3169 symptom), in which case we refuse to - # kill rather than risk terminating an unknown process - the caller can - # retry once the daemon is responsive (#3171 review). + # Every decision here is based on port occupancy, never on /health. + # A daemon that is alive but busy fails the responsiveness probe + # (issue #3169), so using it as the already-stopped guard made stop() + # report success without sending any signal. Reclaiming the profile's + # port from an unresponsive listener is the same policy _clear_port() + # applies on the start path. if not self._is_port_in_use(port): logger.debug(f"Daemon not running for profile '{profile}'") return True - if not self._port_health_ok(port): - logger.warning( - f"Port {port} is occupied but does not respond as the Hindsight " - f"daemon; refusing to signal an unknown process" - ) - return False - pid = self._find_pid_on_port(port) if pid is None: logger.warning(f"Port {port} is bound but no PID could be found") diff --git a/hindsight-embed/tests/test_daemon_client.py b/hindsight-embed/tests/test_daemon_client.py index 3fc822b69f..88c44f7779 100644 --- a/hindsight-embed/tests/test_daemon_client.py +++ b/hindsight-embed/tests/test_daemon_client.py @@ -522,10 +522,10 @@ def test_non_negative_int_falls_back_for_invalid_values(self, caplog): class TestStop: """Tests for DaemonEmbedManager.stop() - regression coverage for #3169. - stop() separates two questions: is the port occupied (so something needs - stopping), and is the listener our daemon (so we are authorized to signal - it). Occupancy alone is not kill authorization - an unrelated service on - the profile port must not be SIGTERM'd (#3171 review). + Every decision in stop() is based on port occupancy. The /health probe + reports responsiveness, not identity or liveness, so a busy daemon fails + it; using it as the already-stopped guard or as the success condition is + what made `daemon stop` report success without sending any signal. """ def _paths(self, tmp_path, port=9700): @@ -538,14 +538,11 @@ def _paths(self, tmp_path, port=9700): port=port, ) - def test_busy_daemon_refuses_to_kill_without_identity(self, tmp_path): - """A live-but-unresponsive daemon fails the health probe, so stop() - cannot establish it is ours and refuses to signal it. + def test_busy_daemon_is_terminated(self, tmp_path): + """A daemon that holds the port but fails /health is still stopped. - This is the #3169 scenario: a busy daemon holds the port but the 2s - /health probe times out. Killing it blind risks terminating an - unrelated process that happens to hold the port, so stop() reports - failure and leaves the process to the caller (#3171 review). + This is the #3169 scenario. Both health probes are patched to raise so + the test fails if any path in stop() consults responsiveness. """ manager = DaemonEmbedManager() with ( @@ -554,21 +551,30 @@ def test_busy_daemon_refuses_to_kill_without_identity(self, tmp_path): "resolve_profile_paths", return_value=self._paths(tmp_path), ), - patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), - patch.object(DaemonEmbedManager, "_port_health_ok", return_value=False), - patch.object(DaemonEmbedManager, "_find_pid_on_port") as mock_find, - patch.object(DaemonEmbedManager, "_kill_process") as mock_kill, + patch.object(DaemonEmbedManager, "_is_port_in_use", side_effect=[True, False]), + patch.object( + DaemonEmbedManager, + "is_running", + side_effect=AssertionError("stop() must not consult /health"), + ), + patch.object( + DaemonEmbedManager, + "_port_health_ok", + side_effect=AssertionError("stop() must not consult /health"), + ), + patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=4242), + patch.object(DaemonEmbedManager, "_kill_process", return_value=True) as mock_kill, ): - assert manager.stop("default") is False - mock_find.assert_not_called() - mock_kill.assert_not_called() + assert manager.stop("default") is True + mock_kill.assert_called_once_with(4242) - def test_foreign_listener_is_not_signaled(self, tmp_path): - """An unrelated service on the profile port must not be SIGTERM'd. + def test_unresponsive_listener_is_reclaimed_like_clear_port(self, tmp_path): + """stop() reclaims an occupied, unhealthy port the way _clear_port() does. - Occupancy only means *something* holds the port. stop() must confirm - the listener answers like the Hindsight daemon before sending a - signal; otherwise it returns failure without touching the process. + Without an ownership receipt "busy" and "foreign" are the same + observable state, so the start path already kills the listener holding + the profile's port. Refusing here instead would leave a wedged daemon + unstoppable, which is the #3169 symptom. """ manager = DaemonEmbedManager() with ( @@ -577,13 +583,22 @@ def test_foreign_listener_is_not_signaled(self, tmp_path): "resolve_profile_paths", return_value=self._paths(tmp_path), ), - patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), + patch.object(DaemonEmbedManager, "_is_port_in_use", side_effect=[True, False]), patch.object(DaemonEmbedManager, "_port_health_ok", return_value=False), patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=9999), - patch.object(DaemonEmbedManager, "_kill_process") as mock_kill, + patch.object(DaemonEmbedManager, "_kill_process", return_value=True) as mock_kill, ): - assert manager.stop("default") is False - mock_kill.assert_not_called() + assert manager.stop("default") is True + mock_kill.assert_called_once_with(9999) + + with ( + patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), + patch.object(DaemonEmbedManager, "_wait_for_port_health", return_value=False), + patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=9999), + patch.object(DaemonEmbedManager, "_kill_process", return_value=True) as mock_kill, + ): + assert manager._clear_port(9700) is True + mock_kill.assert_called_once_with(9999) def test_failed_termination_returns_false(self, tmp_path): """_kill_process() returning False must not be converted into success.""" @@ -595,7 +610,6 @@ def test_failed_termination_returns_false(self, tmp_path): return_value=self._paths(tmp_path), ), patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), - patch.object(DaemonEmbedManager, "_port_health_ok", return_value=True), patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=4242), patch.object(DaemonEmbedManager, "_kill_process", return_value=False), ): @@ -611,7 +625,6 @@ def test_bound_port_without_pid_returns_false(self, tmp_path): return_value=self._paths(tmp_path), ), patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), - patch.object(DaemonEmbedManager, "_port_health_ok", return_value=True), patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=None), patch.object(DaemonEmbedManager, "_kill_process") as mock_kill, ): @@ -628,11 +641,9 @@ def test_unbound_port_reports_already_stopped(self, tmp_path): return_value=self._paths(tmp_path), ), patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=False), - patch.object(DaemonEmbedManager, "_port_health_ok") as mock_health, patch.object(DaemonEmbedManager, "_find_pid_on_port") as mock_find, ): assert manager.stop("default") is True - mock_health.assert_not_called() mock_find.assert_not_called() def test_lingering_listener_after_kill_returns_false(self, tmp_path): @@ -645,7 +656,6 @@ def test_lingering_listener_after_kill_returns_false(self, tmp_path): return_value=self._paths(tmp_path), ), patch.object(DaemonEmbedManager, "_is_port_in_use", return_value=True), - patch.object(DaemonEmbedManager, "_port_health_ok", return_value=True), patch.object(DaemonEmbedManager, "_find_pid_on_port", return_value=4242), patch.object(DaemonEmbedManager, "_kill_process", return_value=True), patch("hindsight_embed.daemon_embed_manager.time.sleep"),