Repository: pantsbuild/pants

Issue:
[pantsd] holding control-c during a thin client run can result in an unhandled trace on os.kill
```
$ PANTS_ENABLE_PANTSD=True ./pants list ::
^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^CException caught: (<type 'exceptions.OSError'>)
  File "/Users/kwilson/dev/pants/src/python/pants/bin/pants_loader.py", line 73, in <module>
    main()
  File "/Users/kwilson/dev/pants/src/python/pants/bin/pants_loader.py", line 69, in main
    PantsLoader.run()
  File "/Users/kwilson/dev/pants/src/python/pants/bin/pants_loader.py", line 65, in run
    cls.load_and_execute(entrypoint)
  File "/Users/kwilson/dev/pants/src/python/pants/bin/pants_loader.py", line 58, in load_and_execute
    entrypoint_main()
  File "/Users/kwilson/dev/pants/src/python/pants/bin/pants_exe.py", line 26, in main
    PantsRunner(exiter).run()
  File "/Users/kwilson/dev/pants/src/python/pants/bin/pants_runner.py", line 38, in run
    return RemotePantsRunner(self._exiter, self._args, self._env, bootstrap_options).run()
  File "/Users/kwilson/dev/pants/src/python/pants/bin/remote_pants_runner.py", line 118, in run
    self._connect_and_execute(port)
  File "/Users/kwilson/dev/pants/src/python/pants/bin/remote_pants_runner.py", line 107, in _connect_and_execute
    result = client.execute(self.PANTS_COMMAND, *self._args, **modified_env)
  File "/Users/kwilson/dev/pants/src/python/pants/java/nailgun_client.py", line 174, in execute
    return self._session.execute(cwd, main_class, *args, **environment)
  File "/Users/kwilson/dev/pants/src/python/pants/java/nailgun_client.py", line 91, in execute
    return self._process_session()
  File "/Users/kwilson/dev/pants/src/python/pants/java/nailgun_client.py", line 84, in _process_session
    self._maybe_stop_input_writer()
  File "/Users/kwilson/dev/pants/src/python/pants/java/nailgun_client.py", line 48, in _maybe_stop_input_writer
    self._input_writer.join()
  File "/Users/kwilson/dev/pants/src/python/pants/java/nailgun_io.py", line 48, in join
    return super(_StoppableDaemonThread, self).join(timeout or self.JOIN_TIMEOUT)
  File "/Users/kwilson/Python/CPython-2.7.13/lib/python2.7/threading.py", line 951, in join
    self.__block.wait(delay)
  File "/Users/kwilson/Python/CPython-2.7.13/lib/python2.7/threading.py", line 359, in wait
    _sleep(delay)
  File "/Users/kwilson/dev/pants/src/python/pants/bin/remote_pants_runner.py", line 62, in handle_control_c
    client.send_control_c()
  File "/Users/kwilson/dev/pants/src/python/pants/java/nailgun_client.py", line 151, in send_control_c
    os.kill(self._session.remote_pid, signal.SIGINT)

Exception message: [Errno 3] No such process
```


Discussion:

Resolving patch:
diff --git a/src/python/pants/java/nailgun_client.py b/src/python/pants/java/nailgun_client.py
--- a/src/python/pants/java/nailgun_client.py
+++ b/src/python/pants/java/nailgun_client.py
@@ -148,7 +148,12 @@ def try_connect(self):
   def send_control_c(self):
     """Sends SIGINT to a nailgun server using pid information from the active session."""
     if self._session and self._session.remote_pid is not None:
-      os.kill(self._session.remote_pid, signal.SIGINT)
+      try:
+        os.kill(self._session.remote_pid, signal.SIGINT)
+      except (OSError, IOError) as e:
+        # Ignore "No such process" errors.
+        if e.errno != errno.ESRCH:
+          raise
 
   def execute(self, main_class, cwd=None, *args, **environment):
     """Executes the given main_class with any supplied args in the given environment.


Test patch: