Tools/ChangeLog

112011-06-13 Dirk Pranke <dpranke@chromium.org>
22
 3 Reviewed by NOBODY (OOPS!).
 4
 5 nrwt: should clean up stale server processes from a previous run
 6 https://bugs.webkit.org/show_bug.cgi?id=62615
 7
 8 This change fixes cases where NRWT might start a server and then
 9 exit, leaving the server still running. Now NRWT should detect
 10 this case and clean up on the next run, without shutting down
 11 any processes it didn't start.
 12
 13 This change also fixes a bug in executive.kill_process on UNIX
 14 where zombies weren't being handled properly.
 15
 16 * Scripts/webkitpy/common/system/executive.py:
 17 * Scripts/webkitpy/layout_tests/port/apache_http_server.py:
 18 * Scripts/webkitpy/layout_tests/port/base.py:
 19 * Scripts/webkitpy/layout_tests/port/http_server.py:
 20 * Scripts/webkitpy/layout_tests/port/http_server_base.py:
 21 * Scripts/webkitpy/layout_tests/port/port_testcase.py:
 22 * Scripts/webkitpy/layout_tests/port/websocket_server.py:
 23
 242011-06-13 Dirk Pranke <dpranke@chromium.org>
 25
326 Reviewed by Tony Chang.
427
528 nrwt: fix http, websocket server startup, shutdown

Tools/Scripts/webkitpy/common/system/executive.py

@@class Executive(object):
227227 try:
228228 retries_left -= 1
229229 os.kill(pid, signal.SIGKILL)
 230 _ = os.waitpid(pid, os.WNOHANG)
230231 except OSError, e:
231232 if e.errno == errno.EAGAIN:
232233 if retries_left <= 0:

Tools/Scripts/webkitpy/layout_tests/port/apache_http_server.py

@@class LayoutTestApacheHttpd(http_server_base.HttpServerBase):
5757 self._name = 'apache'
5858 port_obj.maybe_make_directory(output_dir)
5959
60  # The upstream .conf file assumed the existence of /tmp/WebKit for
61  # placing apache files like the lock file there.
62  self._runtime_path = os.path.join("/tmp", "WebKit")
63  port_obj.maybe_make_directory(self._runtime_path)
64 
6560 # The PID returned when Apache is started goes away (due to dropping
6661 # privileges?). The proper controlling PID is written to a file in the
6762 # apache runtime directory.
68  self.pid_file = os.path.join(self._runtime_path, 'httpd.pid')
 63 self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name)
6964
7065 test_dir = self._port_obj.layout_tests_dir()
7166 js_test_resources_dir = self._cygwin_safe_join(test_dir, "fast", "js",

Tools/Scripts/webkitpy/layout_tests/port/base.py

@@class Port(object):
644644 """Start a web server. Raise an error if it can't start or is already running.
645645
646646 Ports can stub this out if they don't need a web server to be running."""
 647 assert not self._http_server, 'Already running an http server.'
 648
647649 if self.get_option('use_apache'):
648650 server = apache_http_server.LayoutTestApacheHttpd(self, self.results_directory())
649651 else:
650652 server = http_server.Lighttpd(self, self.results_directory())
 653
651654 server.start()
652655 self._http_server = server
653656

@@class Port(object):
655658 """Start a web server. Raise an error if it can't start or is already running.
656659
657660 Ports can stub this out if they don't need a websocket server to be running."""
 661 assert not self._websocket_server, 'Already running a websocket server.'
 662
658663 server = websocket_server.PyWebSocket(self, self.results_directory())
659664 server.start()
660665 self._websocket_server = server

@@class Port(object):
673678 """Shut down the http server if it is running. Do nothing if it isn't."""
674679 if self._http_server:
675680 self._http_server.stop()
 681 self._http_server = None
676682
677683 def stop_websocket_server(self):
678684 """Shut down the websocket server if it is running. Do nothing if it isn't."""
679685 if self._websocket_server:
680686 self._websocket_server.stop()
 687 self._websocket_server = None
681688
682689 def release_http_lock(self):
683690 if self._http_lock:

Tools/Scripts/webkitpy/layout_tests/port/http_server.py

@@class Lighttpd(http_server_base.HttpServerBase):
5454 self._run_background = run_background
5555 self._layout_tests_dir = layout_tests_dir
5656
 57 self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name)
 58
5759 if self._port:
5860 self._port = int(self._port)
5961

Tools/Scripts/webkitpy/layout_tests/port/http_server_base.py

3232import errno
3333import logging
3434import socket
 35import sys
 36import tempfile
3537import time
3638
3739_log = logging.getLogger("webkitpy.layout_tests.port.http_server_base")

@@class HttpServerBase(object):
5355 self._port_obj = port_obj
5456 self._process = None
5557
 58 # We need a non-checkout-dependent place to put lock files, etc. We
 59 # don't use the Python default on the Mac because it defaults to a
 60 # randomly-generated directory under /var/folders and no one would ever
 61 # look there.
 62 tmpdir = tempfile.gettempdir()
 63 if sys.platform == 'darwin':
 64 tmpdir = '/tmp'
 65
 66 self._runtime_path = self._filesystem.join(tmpdir, "WebKit")
 67 port_obj.maybe_make_directory(self._runtime_path)
 68
5669 def start(self):
5770 """Starts the server."""
58  if self._process:
59  raise ServerError('%s already running' % self._name)
 71 assert not self._process, '%s server is already running.'
6072
 73 self._remove_stale_processes()
6174 self._remove_stale_logs()
6275 self._prepare_config()
6376 self._check_that_all_ports_are_available()

@@class HttpServerBase(object):
99112 of initialization required prior to starting the server that may fail."""
100113 pass
101114
 115 def _remove_stale_processes(self):
 116 """Remove any instances of the server left over from a previous run.
 117 There should be at most one of these because of the global http lock."""
 118 def kill_and_check(name, pid):
 119 _log.warning('Previously started %s is still running as pid %d, trying to kill it.' % (name, pid))
 120 self._executive.kill_process(pid)
 121 return not self._executive.check_running_pid(pid)
 122
 123 if self._pid_file and self._filesystem.exists(self._pid_file):
 124 pid = int(self._filesystem.read_text_file(self._pid_file))
 125 if not self._executive.check_running_pid(pid) or self._wait_for_action(lambda: kill_and_check(self._name, pid)):
 126 self._filesystem.remove(self._pid_file)
 127
102128 def _remove_stale_logs(self):
103129 """This routine should be implemented by subclasses to try and remove logs
104130 left over from a prior run. This routine should log warnings if the

Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py

@@class PortTestCase(unittest.TestCase):
168168 # Test that calling start() twice fails.
169169 try:
170170 port.start_http_server()
171  try:
172  port.start_http_server()
173  self.fail('calling port.start_http_server() twice worked')
174  except http_server_base.ServerError, e:
175  pass
 171 self.assertRaises(AssertionError, port.start_http_server)
176172 finally:
177173 port.stop_http_server()
178174
 175 # Test that calling start() once, then resetting the port state, then
 176 # calling start() again so that there is a dangling process that is
 177 # cleaned up works.
 178 try:
 179 port.start_http_server()
 180 port._http_server = None
 181 port.start_http_server()
 182 finally:
 183 port.stop_websocket_server()
 184
179185 # Test that calling stop() twice is harmless.
180186 port.stop_http_server()
181187

@@class PortTestCase(unittest.TestCase):
272278 # Test that calling start() twice fails.
273279 try:
274280 port.start_websocket_server()
275  try:
276  port.start_websocket_server()
277  self.fail('calling port.start_websocket_server() twice worked')
278  except http_server_base.ServerError, e:
279  pass
 281 self.assertRaises(AssertionError, port.start_websocket_server)
 282 finally:
 283 port.stop_websocket_server()
 284
 285 # Test that calling start() once, then resetting the port state, then
 286 # calling start() again so that there is a dangling process that is
 287 # cleaned up works.
 288 try:
 289 port.start_websocket_server()
 290 port._websocket_server = None
 291 port.start_websocket_server()
280292 finally:
281293 port.stop_websocket_server()
282294

Tools/Scripts/webkitpy/layout_tests/port/websocket_server.py

@@class PyWebSocket(http_server.Lighttpd):
7676 self._wsout = None
7777 self._mappings = [{'port': self._port}]
7878
 79 if not self._pid_file:
 80 self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name)
 81
7982 # Webkit tests
8083 if self._root:
8184 self._layout_tests = os.path.abspath(self._root)