From a3f896f15573e684c825d21396c00874e1f9dab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Mas=C5=82owski?= Date: Mon, 23 Jan 2017 12:04:54 +0100 Subject: [PATCH] Show the possibly listening process in the AlreadyRunning exception message, refs #133 --- src/mirakuru/exceptions.py | 40 ++++++++++++++++++++++++++- tests/executors/test_http_executor.py | 4 +++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/mirakuru/exceptions.py b/src/mirakuru/exceptions.py index cbfaeba6..594d2e95 100644 --- a/src/mirakuru/exceptions.py +++ b/src/mirakuru/exceptions.py @@ -1,5 +1,7 @@ """Mirakuru exceptions.""" +import psutil + class ExecutorError(Exception): """Base exception for executor failures.""" @@ -45,8 +47,43 @@ class AlreadyRunning(ExecutorError): When some other process (not necessary executor) seems to be started with same configuration we can't bind to same port. + + The exception message is less useful for executors not using TCP ports. """ + def __init__(self, executor): + """ + Initialize and detect what service is listening on the configured port. + + :param mirakuru.base.Executor executor: for which exception occurred + """ + super(AlreadyRunning, self).__init__(executor) + try: + self.port = getattr(executor, 'port') + except AttributeError: + self.port = None + if self.port: + # The pid field contains an integer process ID or None if the + # process belongs to a different user. Multiple processes can + # listen on the same port. + pids = [ + sconn.pid + for sconn in psutil.net_connections(kind='tcp') + if sconn.pid and sconn.laddr[1] == self.port] + processes = [] + for pid in pids: + try: + processes.append( + '%r[%d]' % (psutil.Process(pid).cmdline(), pid)) + except psutil.NoSuchProcess: + processes.append('exited[%d]' % pid) + if not processes: + self.listening_service = '[unknown]' + else: + self.listening_service = ' '.join(processes) + else: + self.listening_service = '[unknown]' + def __str__(self): """ Return Exception's string representation. @@ -57,7 +94,8 @@ def __str__(self): return ("Executor {exc.executor} seems to be already running. " "It looks like the previous executor process hasn't been " "terminated or killed. Also there might be some completely " - "different service listening on {exc.executor.port} port." + "different service listening on {exc.port} port. Services " + "found running on that port: {exc.listening_service}." .format(exc=self)) diff --git a/tests/executors/test_http_executor.py b/tests/executors/test_http_executor.py index 4352b553..f25b8d75 100644 --- a/tests/executors/test_http_executor.py +++ b/tests/executors/test_http_executor.py @@ -2,6 +2,7 @@ import sys import socket from functools import partial +import shlex import pytest from mock import patch @@ -124,6 +125,9 @@ def test_fail_if_other_executor_running(): with executor2: pass assert 'seems to be already running' in str(exc) + expected_process = '%r[%d]' % ( + shlex.split(http_server_cmd), executor.process.pid) + assert expected_process in str(exc) @patch.object(HTTPExecutor, 'DEFAULT_PORT', PORT)