From 97a265e6da13a6a3827c599c23271b60aadef176 Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Tue, 7 Jun 2022 14:23:10 -0500 Subject: Added new alias 'shutdown-ninja-scons-daemon' to allow ninja to shutdown the daemon --- CHANGES.txt | 2 + RELEASE.txt | 2 + SCons/Tool/ninja/NinjaState.py | 11 +++++ SCons/Tool/ninja/__init__.py | 1 + SCons/Tool/ninja/ninja_daemon_build.py | 14 +++++- SCons/Tool/ninja/ninja_scons_daemon.py | 5 ++- test/ninja/shutdown_scons_daemon.py | 79 ++++++++++++++++++++++++++++++++++ testing/framework/TestCmd.py | 32 ++++++++++++++ 8 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 test/ninja/shutdown_scons_daemon.py diff --git a/CHANGES.txt b/CHANGES.txt index 03692d6..9db19a8 100755 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -116,6 +116,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER Note that these are called for every build command run by SCons. It could have considerable performance impact if not used carefully. to connect to the server during start up. + -Ninja: Added new alias "shutdown-ninja-scons-daemon" to allow ninja to shutdown the daemon. + Also added cleanup to test framework to kill ninja scons daemons and clean ip daemon logs. From Mats Wichmann: - Tweak the way default site_scons paths on Windows are expressed to diff --git a/RELEASE.txt b/RELEASE.txt index cfa8afc..811d957 100755 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -24,6 +24,8 @@ NEW FUNCTIONALITY performance impact if not used carefully. - Added MSVC_USE_SETTINGS variable to pass a dictionary to configure the msvc compiler system environment as an alternative to bypassing Visual Studio autodetection entirely. +- Ninja: Added new alias "shutdown-ninja-scons-daemon" to allow ninja to shutdown the daemon. + Also added cleanup to test framework to kill ninja scons daemons and clean ip daemon logs. DEPRECATED FUNCTIONALITY diff --git a/SCons/Tool/ninja/NinjaState.py b/SCons/Tool/ninja/NinjaState.py index c168c7f..553d8cc 100644 --- a/SCons/Tool/ninja/NinjaState.py +++ b/SCons/Tool/ninja/NinjaState.py @@ -214,6 +214,12 @@ class NinjaState: "pool": "local_pool", "restat": 1 }, + "EXIT_SCONS_DAEMON": { + "command": "$PYTHON_BIN $NINJA_TOOL_DIR/ninja_daemon_build.py $PORT $NINJA_DIR_PATH --exit", + "description": "Shutting down ninja scons daemon server", + "pool": "local_pool", + "restat": 1 + }, "SCONS": { "command": "$SCONS_INVOCATION $out", "description": "$SCONS_INVOCATION $out", @@ -610,6 +616,11 @@ class NinjaState: rule="SCONS_DAEMON", ) + ninja.build( + "shutdown_ninja_scons_daemon_phony", + rule="EXIT_SCONS_DAEMON", + ) + if all_targets is None: # Look in SCons's list of DEFAULT_TARGETS, find the ones that diff --git a/SCons/Tool/ninja/__init__.py b/SCons/Tool/ninja/__init__.py index 04a7abb..6f474ca 100644 --- a/SCons/Tool/ninja/__init__.py +++ b/SCons/Tool/ninja/__init__.py @@ -495,3 +495,4 @@ def generate(env): env.Alias('run-ninja-scons-daemon', 'run_ninja_scons_daemon_phony') + env.Alias('shutdown-ninja-scons-daemon', 'shutdown_ninja_scons_daemon_phony') \ No newline at end of file diff --git a/SCons/Tool/ninja/ninja_daemon_build.py b/SCons/Tool/ninja/ninja_daemon_build.py index 48156f5..63eb136 100644 --- a/SCons/Tool/ninja/ninja_daemon_build.py +++ b/SCons/Tool/ninja/ninja_daemon_build.py @@ -60,11 +60,22 @@ def log_error(msg): while True: try: + if not os.path.exists(daemon_dir / "pidfile"): + if sys.argv[3] != '--exit': + logging.debug(f"ERROR: Server pid not found {daemon_dir / 'pidfile'} for request {sys.argv[3]}") + exit(1) + else: + logging.debug(f"WARNING: Unecessary request to shutfown server, its already shutdown.") + exit(0) + logging.debug(f"Sending request: {sys.argv[3]}") conn = http.client.HTTPConnection( "127.0.0.1", port=int(sys.argv[1]), timeout=60 ) - conn.request("GET", "/?build=" + sys.argv[3]) + if sys.argv[3] == '--exit': + conn.request("GET", "/?exit=1") + else: + conn.request("GET", "/?build=" + sys.argv[3]) response = None while not response: @@ -81,6 +92,7 @@ while True: if status != 200: log_error(msg.decode("utf-8")) exit(1) + logging.debug(f"Request Done: {sys.argv[3]}") exit(0) diff --git a/SCons/Tool/ninja/ninja_scons_daemon.py b/SCons/Tool/ninja/ninja_scons_daemon.py index c4a1d11..35a7789 100644 --- a/SCons/Tool/ninja/ninja_scons_daemon.py +++ b/SCons/Tool/ninja/ninja_scons_daemon.py @@ -168,6 +168,7 @@ def daemon_thread_func(): te.start() daemon_ready = False + building_node = None startup_complete = False @@ -218,12 +219,14 @@ def daemon_thread_func(): except queue.Empty: break if "exit" in building_node: + daemon_log("input: " + "exit") p.stdin.write("exit\n".encode("utf-8")) p.stdin.flush() with building_cv: shared_state.finished_building += [building_node] daemon_ready = False - raise + daemon_needs_to_shutdown = True + break else: input_command = "build " + building_node + "\n" diff --git a/test/ninja/shutdown_scons_daemon.py b/test/ninja/shutdown_scons_daemon.py new file mode 100644 index 0000000..fffbadb --- /dev/null +++ b/test/ninja/shutdown_scons_daemon.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# +# Copyright The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +import os +import psutil +from timeit import default_timer as timer + +import TestSCons +from TestCmd import IS_WINDOWS + +test = TestSCons.TestSCons() + +try: + import ninja +except ImportError: + test.skip_test("Could not find module in python") + +_python_ = TestSCons._python_ +_exe = TestSCons._exe + +ninja_bin = os.path.abspath( + os.path.join(ninja.__file__, os.pardir, "data", "bin", "ninja" + _exe) +) + +test.dir_fixture("ninja-fixture") + +test.file_fixture( + "ninja_test_sconscripts/sconstruct_force_scons_callback", "SConstruct" +) + +test.run(stdout=None) +pid = None +test.must_exist(test.workpath('.ninja/scons_daemon_dirty')) +with open(test.workpath('.ninja/scons_daemon_dirty')) as f: + pid = int(f.read()) + if pid not in [proc.pid for proc in psutil.process_iter()]: + test.fail_test(message="daemon not running!") + +program = test.workpath("run_ninja_env.bat") if IS_WINDOWS else ninja_bin +test.run(program=program, arguments='shutdown-ninja-scons-daemon', stdout=None) + +wait_time = 10 +start_time = timer() +while True: + if wait_time > (timer() - start_time): + if pid not in [proc.pid for proc in psutil.process_iter()]: + break + else: + test.fail_test(message=f"daemon still not shutdown after {wait_time} seconds.") + +test.must_not_exist(test.workpath('.ninja/scons_daemon_dirty')) +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/testing/framework/TestCmd.py b/testing/framework/TestCmd.py index 5759121..34acb4d 100644 --- a/testing/framework/TestCmd.py +++ b/testing/framework/TestCmd.py @@ -299,9 +299,12 @@ __version__ = "1.3" import atexit import difflib import errno +import hashlib import os import re +import psutil import shutil +import signal import stat import subprocess import sys @@ -310,6 +313,7 @@ import threading import time import traceback from collections import UserList, UserString +from pathlib import Path from subprocess import PIPE, STDOUT from typing import Optional @@ -382,6 +386,31 @@ def _caller(tblist, skip): atfrom = "\tfrom" return string +def clean_up_ninja_daemon(self, result_type): + if self: + for path in Path(self.workdir).rglob('.ninja'): + daemon_dir = Path(tempfile.gettempdir()) / ( + "scons_daemon_" + str(hashlib.md5(str(path.resolve()).encode()).hexdigest()) + ) + pidfiles = [daemon_dir / 'pidfile', path / 'scons_daemon_dirty'] + for pidfile in pidfiles: + if pidfile.exists(): + with open(daemon_dir / 'pidfile') as f: + try: + pid = int(f.read()) + os.kill(pid, signal.SIGINT) + except OSError: + pass + + while True: + if pid not in [proc.pid for proc in psutil.process_iter()]: + break + else: + time.sleep(0.1) + + if not self._preserve[result_type]: + if daemon_dir.exists(): + shutil.rmtree(daemon_dir) def fail_test(self=None, condition=True, function=None, skip=0, message=None): """Causes a test to exit with a fail. @@ -402,6 +431,7 @@ def fail_test(self=None, condition=True, function=None, skip=0, message=None): return if function is not None: function() + clean_up_ninja_daemon(self, 'fail_test') of = "" desc = "" sep = " " @@ -447,6 +477,7 @@ def no_result(self=None, condition=True, function=None, skip=0): return if function is not None: function() + clean_up_ninja_daemon(self, 'no_result') of = "" desc = "" sep = " " @@ -483,6 +514,7 @@ def pass_test(self=None, condition=True, function=None): return if function is not None: function() + clean_up_ninja_daemon(self, 'pass_test') sys.stderr.write("PASSED\n") sys.exit(0) -- cgit v0.12