1# Copyright 2016 gRPC authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Tests clean exit of server/client on Python Interpreter exit/sigint. 15 16The tests in this module spawn a subprocess for each test case, the 17test is considered successful if it doesn't freeze/timeout. 18""" 19 20import atexit 21import datetime 22import logging 23import os 24import signal 25import subprocess 26import sys 27import threading 28import time 29import unittest 30 31from tests.unit import _exit_scenarios 32 33SCENARIO_FILE = os.path.abspath( 34 os.path.join( 35 os.path.dirname(os.path.realpath(__file__)), "_exit_scenarios.py" 36 ) 37) 38INTERPRETER = sys.executable 39BASE_COMMAND = [INTERPRETER, SCENARIO_FILE] 40BASE_SIGTERM_COMMAND = BASE_COMMAND + ["--wait_for_interrupt"] 41 42INIT_TIME = datetime.timedelta(seconds=1) 43WAIT_CHECK_INTERVAL = datetime.timedelta(milliseconds=100) 44WAIT_CHECK_DEFAULT_TIMEOUT = datetime.timedelta(seconds=5) 45 46processes = [] 47process_lock = threading.Lock() 48 49 50# Make sure we attempt to clean up any 51# processes we may have left running 52def cleanup_processes(): 53 with process_lock: 54 for process in processes: 55 try: 56 process.kill() 57 except Exception: # pylint: disable=broad-except 58 pass 59 60 61atexit.register(cleanup_processes) 62 63 64def _process_wait_with_timeout(process, timeout=WAIT_CHECK_DEFAULT_TIMEOUT): 65 """A funciton to mimic 3.3+ only timeout argument in process.wait.""" 66 deadline = datetime.datetime.now() + timeout 67 while (process.poll() is None) and (datetime.datetime.now() < deadline): 68 time.sleep(WAIT_CHECK_INTERVAL.total_seconds()) 69 if process.returncode is None: 70 raise RuntimeError("Process failed to exit within %s" % timeout) 71 72 73def interrupt_and_wait(process): 74 with process_lock: 75 processes.append(process) 76 time.sleep(INIT_TIME.total_seconds()) 77 os.kill(process.pid, signal.SIGINT) 78 _process_wait_with_timeout(process) 79 80 81def wait(process): 82 with process_lock: 83 processes.append(process) 84 _process_wait_with_timeout(process) 85 86 87# TODO(lidiz) enable exit tests once the root cause found. 88@unittest.skip("https://github.com/grpc/grpc/issues/23982") 89@unittest.skip("https://github.com/grpc/grpc/issues/23028") 90class ExitTest(unittest.TestCase): 91 def test_unstarted_server(self): 92 process = subprocess.Popen( 93 BASE_COMMAND + [_exit_scenarios.UNSTARTED_SERVER], 94 stdout=sys.stdout, 95 stderr=sys.stderr, 96 ) 97 wait(process) 98 99 def test_unstarted_server_terminate(self): 100 process = subprocess.Popen( 101 BASE_SIGTERM_COMMAND + [_exit_scenarios.UNSTARTED_SERVER], 102 stdout=sys.stdout, 103 ) 104 interrupt_and_wait(process) 105 106 def test_running_server(self): 107 process = subprocess.Popen( 108 BASE_COMMAND + [_exit_scenarios.RUNNING_SERVER], 109 stdout=sys.stdout, 110 stderr=sys.stderr, 111 ) 112 wait(process) 113 114 def test_running_server_terminate(self): 115 process = subprocess.Popen( 116 BASE_SIGTERM_COMMAND + [_exit_scenarios.RUNNING_SERVER], 117 stdout=sys.stdout, 118 stderr=sys.stderr, 119 ) 120 interrupt_and_wait(process) 121 122 def test_poll_connectivity_no_server(self): 123 process = subprocess.Popen( 124 BASE_COMMAND + [_exit_scenarios.POLL_CONNECTIVITY_NO_SERVER], 125 stdout=sys.stdout, 126 stderr=sys.stderr, 127 ) 128 wait(process) 129 130 def test_poll_connectivity_no_server_terminate(self): 131 process = subprocess.Popen( 132 BASE_SIGTERM_COMMAND 133 + [_exit_scenarios.POLL_CONNECTIVITY_NO_SERVER], 134 stdout=sys.stdout, 135 stderr=sys.stderr, 136 ) 137 interrupt_and_wait(process) 138 139 def test_poll_connectivity(self): 140 process = subprocess.Popen( 141 BASE_COMMAND + [_exit_scenarios.POLL_CONNECTIVITY], 142 stdout=sys.stdout, 143 stderr=sys.stderr, 144 ) 145 wait(process) 146 147 def test_poll_connectivity_terminate(self): 148 process = subprocess.Popen( 149 BASE_SIGTERM_COMMAND + [_exit_scenarios.POLL_CONNECTIVITY], 150 stdout=sys.stdout, 151 stderr=sys.stderr, 152 ) 153 interrupt_and_wait(process) 154 155 @unittest.skipIf( 156 os.name == "nt", "os.kill does not have required permission on Windows" 157 ) 158 def test_in_flight_unary_unary_call(self): 159 process = subprocess.Popen( 160 BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_UNARY_UNARY_CALL], 161 stdout=sys.stdout, 162 stderr=sys.stderr, 163 ) 164 interrupt_and_wait(process) 165 166 @unittest.skipIf( 167 os.name == "nt", "os.kill does not have required permission on Windows" 168 ) 169 def test_in_flight_unary_stream_call(self): 170 process = subprocess.Popen( 171 BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_UNARY_STREAM_CALL], 172 stdout=sys.stdout, 173 stderr=sys.stderr, 174 ) 175 interrupt_and_wait(process) 176 177 @unittest.skipIf( 178 os.name == "nt", "os.kill does not have required permission on Windows" 179 ) 180 def test_in_flight_stream_unary_call(self): 181 process = subprocess.Popen( 182 BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_STREAM_UNARY_CALL], 183 stdout=sys.stdout, 184 stderr=sys.stderr, 185 ) 186 interrupt_and_wait(process) 187 188 @unittest.skipIf( 189 os.name == "nt", "os.kill does not have required permission on Windows" 190 ) 191 def test_in_flight_stream_stream_call(self): 192 process = subprocess.Popen( 193 BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_STREAM_STREAM_CALL], 194 stdout=sys.stdout, 195 stderr=sys.stderr, 196 ) 197 interrupt_and_wait(process) 198 199 @unittest.skipIf( 200 os.name == "nt", "os.kill does not have required permission on Windows" 201 ) 202 def test_in_flight_partial_unary_stream_call(self): 203 process = subprocess.Popen( 204 BASE_COMMAND 205 + [_exit_scenarios.IN_FLIGHT_PARTIAL_UNARY_STREAM_CALL], 206 stdout=sys.stdout, 207 stderr=sys.stderr, 208 ) 209 interrupt_and_wait(process) 210 211 @unittest.skipIf( 212 os.name == "nt", "os.kill does not have required permission on Windows" 213 ) 214 def test_in_flight_partial_stream_unary_call(self): 215 process = subprocess.Popen( 216 BASE_COMMAND 217 + [_exit_scenarios.IN_FLIGHT_PARTIAL_STREAM_UNARY_CALL], 218 stdout=sys.stdout, 219 stderr=sys.stderr, 220 ) 221 interrupt_and_wait(process) 222 223 @unittest.skipIf( 224 os.name == "nt", "os.kill does not have required permission on Windows" 225 ) 226 def test_in_flight_partial_stream_stream_call(self): 227 process = subprocess.Popen( 228 BASE_COMMAND 229 + [_exit_scenarios.IN_FLIGHT_PARTIAL_STREAM_STREAM_CALL], 230 stdout=sys.stdout, 231 stderr=sys.stderr, 232 ) 233 interrupt_and_wait(process) 234 235 236if __name__ == "__main__": 237 logging.basicConfig(level=logging.DEBUG) 238 unittest.main(verbosity=2) 239