mirror of https://github.com/grpc/grpc.git
parent
be22335879
commit
aa4bb51d95
5 changed files with 501 additions and 37 deletions
@ -0,0 +1,249 @@ |
||||
# Copyright 2016, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Defines a number of module-scope gRPC scenarios to test clean exit.""" |
||||
|
||||
import argparse |
||||
import threading |
||||
import time |
||||
|
||||
import grpc |
||||
|
||||
from tests.unit.framework.common import test_constants |
||||
|
||||
WAIT_TIME = 1000 |
||||
|
||||
REQUEST = b'request' |
||||
|
||||
UNSTARTED_SERVER = 'unstarted_server' |
||||
RUNNING_SERVER = 'running_server' |
||||
POLL_CONNECTIVITY_NO_SERVER = 'poll_connectivity_no_server' |
||||
POLL_CONNECTIVITY = 'poll_connectivity' |
||||
IN_FLIGHT_UNARY_UNARY_CALL = 'in_flight_unary_unary_call' |
||||
IN_FLIGHT_UNARY_STREAM_CALL = 'in_flight_unary_stream_call' |
||||
IN_FLIGHT_STREAM_UNARY_CALL = 'in_flight_stream_unary_call' |
||||
IN_FLIGHT_STREAM_STREAM_CALL = 'in_flight_stream_stream_call' |
||||
IN_FLIGHT_PARTIAL_UNARY_STREAM_CALL = 'in_flight_partial_unary_stream_call' |
||||
IN_FLIGHT_PARTIAL_STREAM_UNARY_CALL = 'in_flight_partial_stream_unary_call' |
||||
IN_FLIGHT_PARTIAL_STREAM_STREAM_CALL = 'in_flight_partial_stream_stream_call' |
||||
|
||||
UNARY_UNARY = b'/test/UnaryUnary' |
||||
UNARY_STREAM = b'/test/UnaryStream' |
||||
STREAM_UNARY = b'/test/StreamUnary' |
||||
STREAM_STREAM = b'/test/StreamStream' |
||||
PARTIAL_UNARY_STREAM = b'/test/PartialUnaryStream' |
||||
PARTIAL_STREAM_UNARY = b'/test/PartialStreamUnary' |
||||
PARTIAL_STREAM_STREAM = b'/test/PartialStreamStream' |
||||
|
||||
TEST_TO_METHOD = { |
||||
IN_FLIGHT_UNARY_UNARY_CALL: UNARY_UNARY, |
||||
IN_FLIGHT_UNARY_STREAM_CALL: UNARY_STREAM, |
||||
IN_FLIGHT_STREAM_UNARY_CALL: STREAM_UNARY, |
||||
IN_FLIGHT_STREAM_STREAM_CALL: STREAM_STREAM, |
||||
IN_FLIGHT_PARTIAL_UNARY_STREAM_CALL: PARTIAL_UNARY_STREAM, |
||||
IN_FLIGHT_PARTIAL_STREAM_UNARY_CALL: PARTIAL_STREAM_UNARY, |
||||
IN_FLIGHT_PARTIAL_STREAM_STREAM_CALL: PARTIAL_STREAM_STREAM, |
||||
} |
||||
|
||||
|
||||
def hang_unary_unary(request, servicer_context): |
||||
time.sleep(WAIT_TIME) |
||||
|
||||
|
||||
def hang_unary_stream(request, servicer_context): |
||||
time.sleep(WAIT_TIME) |
||||
|
||||
|
||||
def hang_partial_unary_stream(request, servicer_context): |
||||
for _ in range(test_constants.STREAM_LENGTH // 2): |
||||
yield request |
||||
time.sleep(WAIT_TIME) |
||||
|
||||
|
||||
def hang_stream_unary(request_iterator, servicer_context): |
||||
time.sleep(WAIT_TIME) |
||||
|
||||
|
||||
def hang_partial_stream_unary(request_iterator, servicer_context): |
||||
for _ in range(test_constants.STREAM_LENGTH // 2): |
||||
next(request_iterator) |
||||
time.sleep(WAIT_TIME) |
||||
|
||||
|
||||
def hang_stream_stream(request_iterator, servicer_context): |
||||
time.sleep(WAIT_TIME) |
||||
|
||||
|
||||
def hang_partial_stream_stream(request_iterator, servicer_context): |
||||
for _ in range(test_constants.STREAM_LENGTH // 2): |
||||
yield next(request_iterator) |
||||
time.sleep(WAIT_TIME) |
||||
|
||||
|
||||
class MethodHandler(grpc.RpcMethodHandler): |
||||
|
||||
def __init__(self, request_streaming, response_streaming, partial_hang): |
||||
self.request_streaming = request_streaming |
||||
self.response_streaming = response_streaming |
||||
self.request_deserializer = None |
||||
self.response_serializer = None |
||||
self.unary_unary = None |
||||
self.unary_stream = None |
||||
self.stream_unary = None |
||||
self.stream_stream = None |
||||
if self.request_streaming and self.response_streaming: |
||||
if partial_hang: |
||||
self.stream_stream = hang_partial_stream_stream |
||||
else: |
||||
self.stream_stream = hang_stream_stream |
||||
elif self.request_streaming: |
||||
if partial_hang: |
||||
self.stream_unary = hang_partial_stream_unary |
||||
else: |
||||
self.stream_unary = hang_stream_unary |
||||
elif self.response_streaming: |
||||
if partial_hang: |
||||
self.unary_stream = hang_partial_unary_stream |
||||
else: |
||||
self.unary_stream = hang_unary_stream |
||||
else: |
||||
self.unary_unary = hang_unary_unary |
||||
|
||||
|
||||
class GenericHandler(grpc.GenericRpcHandler): |
||||
|
||||
def service(self, handler_call_details): |
||||
if handler_call_details.method == UNARY_UNARY: |
||||
return MethodHandler(False, False, False) |
||||
elif handler_call_details.method == UNARY_STREAM: |
||||
return MethodHandler(False, True, False) |
||||
elif handler_call_details.method == STREAM_UNARY: |
||||
return MethodHandler(True, False, False) |
||||
elif handler_call_details.method == STREAM_STREAM: |
||||
return MethodHandler(True, True, False) |
||||
elif handler_call_details.method == PARTIAL_UNARY_STREAM: |
||||
return MethodHandler(False, True, True) |
||||
elif handler_call_details.method == PARTIAL_STREAM_UNARY: |
||||
return MethodHandler(True, False, True) |
||||
elif handler_call_details.method == PARTIAL_STREAM_STREAM: |
||||
return MethodHandler(True, True, True) |
||||
else: |
||||
return None |
||||
|
||||
|
||||
# Traditional executors will not exit until all their |
||||
# current jobs complete. Because we submit jobs that will |
||||
# never finish, we don't want to block exit on these jobs. |
||||
class DaemonPool(object): |
||||
|
||||
def submit(self, fn, *args, **kwargs): |
||||
thread = threading.Thread(target=fn, args=args, kwargs=kwargs) |
||||
thread.daemon = True |
||||
thread.start() |
||||
|
||||
def shutdown(self, wait=True): |
||||
pass |
||||
|
||||
|
||||
def infinite_request_iterator(): |
||||
while True: |
||||
yield REQUEST |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
parser = argparse.ArgumentParser() |
||||
parser.add_argument('scenario', type=str) |
||||
parser.add_argument( |
||||
'--wait_for_interrupt', dest='wait_for_interrupt', action='store_true') |
||||
args = parser.parse_args() |
||||
|
||||
if args.scenario == UNSTARTED_SERVER: |
||||
server = grpc.server((), DaemonPool()) |
||||
if args.wait_for_interrupt: |
||||
time.sleep(WAIT_TIME) |
||||
elif args.scenario == RUNNING_SERVER: |
||||
server = grpc.server((), DaemonPool()) |
||||
port = server.add_insecure_port('[::]:0') |
||||
server.start() |
||||
if args.wait_for_interrupt: |
||||
time.sleep(WAIT_TIME) |
||||
elif args.scenario == POLL_CONNECTIVITY_NO_SERVER: |
||||
channel = grpc.insecure_channel('localhost:12345') |
||||
|
||||
def connectivity_callback(connectivity): |
||||
pass |
||||
|
||||
channel.subscribe(connectivity_callback, try_to_connect=True) |
||||
if args.wait_for_interrupt: |
||||
time.sleep(WAIT_TIME) |
||||
elif args.scenario == POLL_CONNECTIVITY: |
||||
server = grpc.server((), DaemonPool()) |
||||
port = server.add_insecure_port('[::]:0') |
||||
server.start() |
||||
channel = grpc.insecure_channel('localhost:%d' % port) |
||||
|
||||
def connectivity_callback(connectivity): |
||||
pass |
||||
|
||||
channel.subscribe(connectivity_callback, try_to_connect=True) |
||||
if args.wait_for_interrupt: |
||||
time.sleep(WAIT_TIME) |
||||
|
||||
else: |
||||
handler = GenericHandler() |
||||
server = grpc.server((), DaemonPool()) |
||||
port = server.add_insecure_port('[::]:0') |
||||
server.add_generic_rpc_handlers((handler,)) |
||||
server.start() |
||||
channel = grpc.insecure_channel('localhost:%d' % port) |
||||
|
||||
method = TEST_TO_METHOD[args.scenario] |
||||
|
||||
if args.scenario == IN_FLIGHT_UNARY_UNARY_CALL: |
||||
multi_callable = channel.unary_unary(method) |
||||
future = multi_callable.future(REQUEST) |
||||
result, call = multi_callable.with_call(REQUEST) |
||||
elif (args.scenario == IN_FLIGHT_UNARY_STREAM_CALL or |
||||
args.scenario == IN_FLIGHT_PARTIAL_UNARY_STREAM_CALL): |
||||
multi_callable = channel.unary_stream(method) |
||||
response_iterator = multi_callable(REQUEST) |
||||
for response in response_iterator: |
||||
pass |
||||
elif (args.scenario == IN_FLIGHT_STREAM_UNARY_CALL or |
||||
args.scenario == IN_FLIGHT_PARTIAL_STREAM_UNARY_CALL): |
||||
multi_callable = channel.stream_unary(method) |
||||
future = multi_callable.future(infinite_request_iterator()) |
||||
result, call = multi_callable.with_call( |
||||
[REQUEST] * test_constants.STREAM_LENGTH) |
||||
elif (args.scenario == IN_FLIGHT_STREAM_STREAM_CALL or |
||||
args.scenario == IN_FLIGHT_PARTIAL_STREAM_STREAM_CALL): |
||||
multi_callable = channel.stream_stream(method) |
||||
response_iterator = multi_callable(infinite_request_iterator()) |
||||
for response in response_iterator: |
||||
pass |
@ -0,0 +1,185 @@ |
||||
# Copyright 2016, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Tests clean exit of server/client on Python Interpreter exit/sigint. |
||||
|
||||
The tests in this module spawn a subprocess for each test case, the |
||||
test is considered successful if it doesn't hang/timeout. |
||||
""" |
||||
|
||||
import atexit |
||||
import os |
||||
import signal |
||||
import six |
||||
import subprocess |
||||
import sys |
||||
import threading |
||||
import time |
||||
import unittest |
||||
|
||||
from tests.unit import _exit_scenarios |
||||
|
||||
SCENARIO_FILE = os.path.abspath(os.path.join( |
||||
os.path.dirname(os.path.realpath(__file__)), '_exit_scenarios.py')) |
||||
INTERPRETER = sys.executable |
||||
BASE_COMMAND = [INTERPRETER, SCENARIO_FILE] |
||||
BASE_SIGTERM_COMMAND = BASE_COMMAND + ['--wait_for_interrupt'] |
||||
|
||||
INIT_TIME = 1.0 |
||||
|
||||
|
||||
processes = [] |
||||
process_lock = threading.Lock() |
||||
|
||||
|
||||
# Make sure we attempt to clean up any |
||||
# processes we may have left running |
||||
def cleanup_processes(): |
||||
with process_lock: |
||||
for process in processes: |
||||
try: |
||||
process.kill() |
||||
except Exception: |
||||
pass |
||||
atexit.register(cleanup_processes) |
||||
|
||||
|
||||
def interrupt_and_wait(process): |
||||
with process_lock: |
||||
processes.append(process) |
||||
time.sleep(INIT_TIME) |
||||
os.kill(process.pid, signal.SIGINT) |
||||
process.wait() |
||||
|
||||
|
||||
def wait(process): |
||||
with process_lock: |
||||
processes.append(process) |
||||
process.wait() |
||||
|
||||
|
||||
class ExitTest(unittest.TestCase): |
||||
|
||||
def test_unstarted_server(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.UNSTARTED_SERVER], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
wait(process) |
||||
|
||||
def test_unstarted_server_terminate(self): |
||||
process = subprocess.Popen( |
||||
BASE_SIGTERM_COMMAND + [_exit_scenarios.UNSTARTED_SERVER], |
||||
stdout=sys.stdout) |
||||
interrupt_and_wait(process) |
||||
|
||||
def test_running_server(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.RUNNING_SERVER], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
wait(process) |
||||
|
||||
def test_running_server_terminate(self): |
||||
process = subprocess.Popen( |
||||
BASE_SIGTERM_COMMAND + [_exit_scenarios.RUNNING_SERVER], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
interrupt_and_wait(process) |
||||
|
||||
def test_poll_connectivity_no_server(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.POLL_CONNECTIVITY_NO_SERVER], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
wait(process) |
||||
|
||||
def test_poll_connectivity_no_server_terminate(self): |
||||
process = subprocess.Popen( |
||||
BASE_SIGTERM_COMMAND + [_exit_scenarios.POLL_CONNECTIVITY_NO_SERVER], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
interrupt_and_wait(process) |
||||
|
||||
def test_poll_connectivity(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.POLL_CONNECTIVITY], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
wait(process) |
||||
|
||||
def test_poll_connectivity_terminate(self): |
||||
process = subprocess.Popen( |
||||
BASE_SIGTERM_COMMAND + [_exit_scenarios.POLL_CONNECTIVITY], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
interrupt_and_wait(process) |
||||
|
||||
def test_in_flight_unary_unary_call(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_UNARY_UNARY_CALL], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
interrupt_and_wait(process) |
||||
|
||||
@unittest.skipIf(six.PY2, 'https://github.com/grpc/grpc/issues/6999') |
||||
def test_in_flight_unary_stream_call(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_UNARY_STREAM_CALL], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
interrupt_and_wait(process) |
||||
|
||||
def test_in_flight_stream_unary_call(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_STREAM_UNARY_CALL], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
interrupt_and_wait(process) |
||||
|
||||
@unittest.skipIf(six.PY2, 'https://github.com/grpc/grpc/issues/6999') |
||||
def test_in_flight_stream_stream_call(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_STREAM_STREAM_CALL], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
interrupt_and_wait(process) |
||||
|
||||
@unittest.skipIf(six.PY2, 'https://github.com/grpc/grpc/issues/6999') |
||||
def test_in_flight_partial_unary_stream_call(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_PARTIAL_UNARY_STREAM_CALL], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
interrupt_and_wait(process) |
||||
|
||||
def test_in_flight_partial_stream_unary_call(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_PARTIAL_STREAM_UNARY_CALL], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
interrupt_and_wait(process) |
||||
|
||||
@unittest.skipIf(six.PY2, 'https://github.com/grpc/grpc/issues/6999') |
||||
def test_in_flight_partial_stream_stream_call(self): |
||||
process = subprocess.Popen( |
||||
BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_PARTIAL_STREAM_STREAM_CALL], |
||||
stdout=sys.stdout, stderr=sys.stderr) |
||||
interrupt_and_wait(process) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
unittest.main(verbosity=2) |
Loading…
Reference in new issue