• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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