Repository: pantsbuild/pants

Issue:
Improve message when waiting for pantsd to start.
this is somewhat nitpicky...
However this happens when pants is waiting for pantsd to start:
<img width="1121" alt="Screenshot 2020-05-30 07 32 22" src="https://user-images.githubusercontent.com/1268088/83330990-d2215900-a247-11ea-8e71-31c1fd41ec5d.png">
However, I know that because if my involvement in the pants project.
This message is probably confusing for regular pants users.
A better message will be "Waiting for Pants Daemon to start..."

@stuhood 




Discussion:
Agreed. This had been bugging me too.
BTW - I tried to grep the code to figure out where this message is coming from so I can fix this myself.. but got nothing. figured out it is probably coming from an external library that watches files...
<img width="1350" alt="Screen Shot 2020-05-30 at 8 48 11 AM" src="https://user-images.githubusercontent.com/1268088/83332837-4d880800-a252-11ea-8dd9-e765afe0485d.png">


Resolving patch:
diff --git a/src/python/pants/pantsd/process_manager.py b/src/python/pants/pantsd/process_manager.py
--- a/src/python/pants/pantsd/process_manager.py
+++ b/src/python/pants/pantsd/process_manager.py
@@ -9,7 +9,7 @@
 import time
 import traceback
 from contextlib import contextmanager
-from typing import Optional
+from typing import Callable, Optional
 
 import psutil
 
@@ -112,18 +112,22 @@ def _maybe_cast(item, caster):
     @classmethod
     def _deadline_until(
         cls,
-        closure,
-        action_msg,
-        timeout=FAIL_WAIT_SEC,
-        wait_interval=WAIT_INTERVAL_SEC,
-        info_interval=INFO_INTERVAL_SEC,
+        closure: Callable[[], bool],
+        ongoing_msg: str,
+        completed_msg: str,
+        timeout: float = FAIL_WAIT_SEC,
+        wait_interval: float = WAIT_INTERVAL_SEC,
+        info_interval: float = INFO_INTERVAL_SEC,
     ):
         """Execute a function/closure repeatedly until a True condition or timeout is met.
 
         :param func closure: the function/closure to execute (should not block for long periods of time
                              and must return True on success).
-        :param str action_msg: a description of the action that is being executed, to be rendered as
-                               info while we wait, and as part of any rendered exception.
+        :param str ongoing_msg: a description of the action that is being executed, to be rendered as
+                                info while we wait, and as part of any rendered exception.
+        :param str completed_msg: a description of the action that is being executed, to be rendered
+                                after the action has succeeded (but only if we have previously rendered
+                                the ongoing_msg).
         :param float timeout: the maximum amount of time to wait for a true result from the closure in
                               seconds. N.B. this is timing based, so won't be exact if the runtime of
                               the closure exceeds the timeout.
@@ -135,34 +139,44 @@ def _deadline_until(
         now = time.time()
         deadline = now + timeout
         info_deadline = now + info_interval
+        rendered_ongoing = False
         while 1:
             if closure():
+                if rendered_ongoing:
+                    logger.info(completed_msg)
                 return True
 
             now = time.time()
             if now > deadline:
                 raise cls.Timeout(
                     "exceeded timeout of {} seconds while waiting for {}".format(
-                        timeout, action_msg
+                        timeout, ongoing_msg
                     )
                 )
 
             if now > info_deadline:
-                logger.info("waiting for {}...".format(action_msg))
+                logger.info("waiting for {}...".format(ongoing_msg))
+                rendered_ongoing = True
                 info_deadline = info_deadline + info_interval
             elif wait_interval:
                 time.sleep(wait_interval)
 
     @classmethod
-    def _wait_for_file(cls, filename, timeout=FAIL_WAIT_SEC, want_content=True):
+    def _wait_for_file(
+        cls,
+        filename: str,
+        ongoing_msg: str,
+        completed_msg: str,
+        timeout: float = FAIL_WAIT_SEC,
+        want_content: bool = True,
+    ):
         """Wait up to timeout seconds for filename to appear with a non-zero size or raise
         Timeout()."""
 
         def file_waiter():
             return os.path.exists(filename) and (not want_content or os.path.getsize(filename))
 
-        action_msg = "file {} to appear".format(filename)
-        return cls._deadline_until(file_waiter, action_msg, timeout=timeout)
+        return cls._deadline_until(file_waiter, ongoing_msg, completed_msg, timeout=timeout)
 
     @staticmethod
     def _get_metadata_dir_by_name(name, metadata_base_dir):
@@ -208,18 +222,22 @@ def write_metadata_by_name(self, name, metadata_key, metadata_value):
         file_path = self._metadata_file_path(name, metadata_key)
         safe_file_dump(file_path, metadata_value)
 
-    def await_metadata_by_name(self, name, metadata_key, timeout, caster=None):
+    def await_metadata_by_name(
+        self, name, metadata_key, ongoing_msg: str, completed_msg: str, timeout: float, caster=None
+    ):
         """Block up to a timeout for process metadata to arrive on disk.
 
         :param string name: The ProcessMetadataManager identity/name (e.g. 'pantsd').
         :param string metadata_key: The metadata key (e.g. 'pid').
-        :param int timeout: The deadline to write metadata.
+        :param str ongoing_msg: A message that describes what is being waited for while waiting.
+        :param str completed_msg: A message that describes what was being waited for after completion.
+        :param float timeout: The deadline to write metadata.
         :param type caster: A type-casting callable to apply to the read value (e.g. int, str).
         :returns: The value of the metadata key (read from disk post-write).
         :raises: :class:`ProcessMetadataManager.Timeout` on timeout.
         """
         file_path = self._metadata_file_path(name, metadata_key)
-        self._wait_for_file(file_path, timeout=timeout)
+        self._wait_for_file(file_path, ongoing_msg, completed_msg, timeout=timeout)
         return self.read_metadata_by_name(name, metadata_key, caster)
 
     def purge_metadata_by_name(self, name):
@@ -364,11 +382,25 @@ def get_subprocess_output(cls, command, ignore_stderr=True, **kwargs):
 
     def await_pid(self, timeout):
         """Wait up to a given timeout for a process to write pid metadata."""
-        return self.await_metadata_by_name(self._name, "pid", timeout, int)
+        return self.await_metadata_by_name(
+            self._name,
+            "pid",
+            f"{self._name} to start",
+            f"{self._name} started",
+            timeout,
+            caster=int,
+        )
 
     def await_socket(self, timeout):
         """Wait up to a given timeout for a process to write socket info."""
-        return self.await_metadata_by_name(self._name, "socket", timeout, self._socket_type)
+        return self.await_metadata_by_name(
+            self._name,
+            "socket",
+            f"{self._name} socket to be opened",
+            f"{self._name} socket opened",
+            timeout,
+            caster=self._socket_type,
+        )
 
     def write_pid(self, pid=None):
         """Write the current processes PID to the pidfile location."""
@@ -471,7 +503,12 @@ def terminate(self, signal_chain=KILL_CHAIN, kill_wait=KILL_WAIT_SEC, purge=True
 
                 # Wait up to kill_wait seconds to terminate or move onto the next signal.
                 try:
-                    if self._deadline_until(self.is_dead, "daemon to exit", timeout=kill_wait):
+                    if self._deadline_until(
+                        self.is_dead,
+                        f"{self._name} to exit",
+                        f"{self._name} exited",
+                        timeout=kill_wait,
+                    ):
                         alive = False
                         logger.debug("successfully terminated pid {}".format(pid))
                         break


Test patch:
diff --git a/tests/python/pants_test/pantsd/pantsd_integration_test_base.py b/tests/python/pants_test/pantsd/pantsd_integration_test_base.py
--- a/tests/python/pants_test/pantsd/pantsd_integration_test_base.py
+++ b/tests/python/pants_test/pantsd/pantsd_integration_test_base.py
@@ -48,7 +48,12 @@ def assert_started(self, timeout=30):
 
     def assert_pantsd_runner_started(self, client_pid, timeout=12):
         return self.await_metadata_by_name(
-            name="nailgun-client", metadata_key=str(client_pid), timeout=timeout, caster=int,
+            name="nailgun-client",
+            metadata_key=str(client_pid),
+            ongoing_msg="client to start",
+            completed_msg="client started",
+            timeout=timeout,
+            caster=int,
         )
 
     def _check_pantsd_is_alive(self):
diff --git a/tests/python/pants_test/pantsd/test_process_manager.py b/tests/python/pants_test/pantsd/test_process_manager.py
--- a/tests/python/pants_test/pantsd/test_process_manager.py
+++ b/tests/python/pants_test/pantsd/test_process_manager.py
@@ -160,12 +160,19 @@ def test_wait_for_file(self):
         with temporary_dir() as td:
             test_filename = os.path.join(td, "test.out")
             safe_file_dump(test_filename, "test")
-            self.pmm._wait_for_file(test_filename, timeout=0.1)
+            self.pmm._wait_for_file(
+                test_filename, "file to be created", "file was created", timeout=0.1
+            )
 
     def test_wait_for_file_timeout(self):
         with temporary_dir() as td:
             with self.assertRaises(ProcessMetadataManager.Timeout):
-                self.pmm._wait_for_file(os.path.join(td, "non_existent_file"), timeout=0.1)
+                self.pmm._wait_for_file(
+                    os.path.join(td, "non_existent_file"),
+                    "file to be created",
+                    "file was created",
+                    timeout=0.1,
+                )
 
     def test_await_metadata_by_name(self):
         with temporary_dir() as tmpdir, unittest.mock.patch(
@@ -174,7 +181,10 @@ def test_await_metadata_by_name(self):
             self.pmm.write_metadata_by_name(self.NAME, self.TEST_KEY, self.TEST_VALUE)
 
             self.assertEqual(
-                self.pmm.await_metadata_by_name(self.NAME, self.TEST_KEY, 0.1), self.TEST_VALUE
+                self.pmm.await_metadata_by_name(
+                    self.NAME, self.TEST_KEY, "metadata to be created", "metadata was created", 0.1
+                ),
+                self.TEST_VALUE,
             )
 
     def test_purge_metadata(self):
@@ -258,12 +268,21 @@ def test_get_subprocess_output_failure_exception(self):
     def test_await_pid(self):
         with unittest.mock.patch.object(ProcessManager, "await_metadata_by_name") as mock_await:
             self.pm.await_pid(5)
-        mock_await.assert_called_once_with(self.pm.name, "pid", 5, unittest.mock.ANY)
+        mock_await.assert_called_once_with(
+            self.pm.name, "pid", "test to start", "test started", 5, caster=unittest.mock.ANY
+        )
 
     def test_await_socket(self):
         with unittest.mock.patch.object(ProcessManager, "await_metadata_by_name") as mock_await:
             self.pm.await_socket(5)
-        mock_await.assert_called_once_with(self.pm.name, "socket", 5, unittest.mock.ANY)
+        mock_await.assert_called_once_with(
+            self.pm.name,
+            "socket",
+            "test socket to be opened",
+            "test socket opened",
+            5,
+            caster=unittest.mock.ANY,
+        )
 
     def test_write_pid(self):
         with unittest.mock.patch.object(ProcessManager, "write_metadata_by_name") as mock_write: