mirror of https://github.com/grpc/grpc.git
parent
082b63e095
commit
a76d72e0a6
3 changed files with 201 additions and 0 deletions
@ -0,0 +1,113 @@ |
|||||||
|
# Copyright 2018 gRPC authors. |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
"""Defines a number of module-scope gRPC scenarios to test server shutdown.""" |
||||||
|
|
||||||
|
import argparse |
||||||
|
import os |
||||||
|
import threading |
||||||
|
import time |
||||||
|
import logging |
||||||
|
|
||||||
|
import grpc |
||||||
|
|
||||||
|
from concurrent import futures |
||||||
|
from six.moves import queue |
||||||
|
|
||||||
|
WAIT_TIME = 1000 |
||||||
|
|
||||||
|
REQUEST = b'request' |
||||||
|
RESPONSE = b'response' |
||||||
|
|
||||||
|
SERVER_RAISES_EXCEPTION = 'server_raises_exception' |
||||||
|
SERVER_DEALLOCATED = 'server_deallocated' |
||||||
|
SERVER_FORK_CAN_EXIT = 'server_fork_can_exit' |
||||||
|
|
||||||
|
FORK_EXIT = '/test/ForkExit' |
||||||
|
|
||||||
|
|
||||||
|
class ForkExitHandler(object): |
||||||
|
|
||||||
|
def unary_unary(self, request, servicer_context): |
||||||
|
pid = os.fork() |
||||||
|
if pid == 0: |
||||||
|
os._exit(0) |
||||||
|
return RESPONSE |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
self.request_streaming = None |
||||||
|
self.response_streaming = None |
||||||
|
self.request_deserializer = None |
||||||
|
self.response_serializer = None |
||||||
|
self.unary_stream = None |
||||||
|
self.stream_unary = None |
||||||
|
self.stream_stream = None |
||||||
|
|
||||||
|
|
||||||
|
class GenericHandler(grpc.GenericRpcHandler): |
||||||
|
|
||||||
|
def service(self, handler_call_details): |
||||||
|
if handler_call_details.method == FORK_EXIT: |
||||||
|
return ForkExitHandler() |
||||||
|
else: |
||||||
|
return None |
||||||
|
|
||||||
|
|
||||||
|
def run_server(port_queue,): |
||||||
|
server = grpc.server( |
||||||
|
futures.ThreadPoolExecutor(max_workers=10), |
||||||
|
options=(('grpc.so_reuseport', 0),)) |
||||||
|
port = server.add_insecure_port('[::]:0') |
||||||
|
port_queue.put(port) |
||||||
|
server.add_generic_rpc_handlers((GenericHandler(),)) |
||||||
|
server.start() |
||||||
|
# threading.Event.wait() does not exhibit the bug identified in |
||||||
|
# https://github.com/grpc/grpc/issues/17093, sleep instead |
||||||
|
time.sleep(WAIT_TIME) |
||||||
|
|
||||||
|
|
||||||
|
def run_test(args): |
||||||
|
if args.scenario == SERVER_RAISES_EXCEPTION: |
||||||
|
server = grpc.server( |
||||||
|
futures.ThreadPoolExecutor(max_workers=1), |
||||||
|
options=(('grpc.so_reuseport', 0),)) |
||||||
|
server.start() |
||||||
|
raise Exception() |
||||||
|
elif args.scenario == SERVER_DEALLOCATED: |
||||||
|
server = grpc.server( |
||||||
|
futures.ThreadPoolExecutor(max_workers=1), |
||||||
|
options=(('grpc.so_reuseport', 0),)) |
||||||
|
server.start() |
||||||
|
server.__del__() |
||||||
|
while server._state.stage != grpc._server._ServerStage.STOPPED: |
||||||
|
pass |
||||||
|
elif args.scenario == SERVER_FORK_CAN_EXIT: |
||||||
|
port_queue = queue.Queue() |
||||||
|
thread = threading.Thread(target=run_server, args=(port_queue,)) |
||||||
|
thread.daemon = True |
||||||
|
thread.start() |
||||||
|
port = port_queue.get() |
||||||
|
channel = grpc.insecure_channel('[::]:%d' % port) |
||||||
|
multi_callable = channel.unary_unary(FORK_EXIT) |
||||||
|
result, call = multi_callable.with_call(REQUEST, wait_for_ready=True) |
||||||
|
os.wait() |
||||||
|
else: |
||||||
|
raise ValueError('unknown test scenario') |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
logging.basicConfig() |
||||||
|
parser = argparse.ArgumentParser() |
||||||
|
parser.add_argument('scenario', type=str) |
||||||
|
args = parser.parse_args() |
||||||
|
run_test(args) |
@ -0,0 +1,87 @@ |
|||||||
|
# Copyright 2018 gRPC authors. |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
"""Tests clean shutdown of server on various interpreter exit conditions. |
||||||
|
|
||||||
|
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 subprocess |
||||||
|
import sys |
||||||
|
import threading |
||||||
|
import unittest |
||||||
|
import logging |
||||||
|
|
||||||
|
from tests.unit import _server_shutdown_scenarios |
||||||
|
|
||||||
|
SCENARIO_FILE = os.path.abspath( |
||||||
|
os.path.join( |
||||||
|
os.path.dirname(os.path.realpath(__file__)), |
||||||
|
'_server_shutdown_scenarios.py')) |
||||||
|
INTERPRETER = sys.executable |
||||||
|
BASE_COMMAND = [INTERPRETER, SCENARIO_FILE] |
||||||
|
|
||||||
|
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: # pylint: disable=broad-except |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
atexit.register(cleanup_processes) |
||||||
|
|
||||||
|
|
||||||
|
def wait(process): |
||||||
|
with process_lock: |
||||||
|
processes.append(process) |
||||||
|
process.wait() |
||||||
|
|
||||||
|
|
||||||
|
class ServerShutdown(unittest.TestCase): |
||||||
|
|
||||||
|
def test_deallocated_server_stops(self): |
||||||
|
process = subprocess.Popen( |
||||||
|
BASE_COMMAND + [_server_shutdown_scenarios.SERVER_DEALLOCATED], |
||||||
|
stdout=sys.stdout, |
||||||
|
stderr=sys.stderr) |
||||||
|
wait(process) |
||||||
|
|
||||||
|
def test_server_exception_exits(self): |
||||||
|
process = subprocess.Popen( |
||||||
|
BASE_COMMAND + [_server_shutdown_scenarios.SERVER_RAISES_EXCEPTION], |
||||||
|
stdout=sys.stdout, |
||||||
|
stderr=sys.stderr) |
||||||
|
wait(process) |
||||||
|
|
||||||
|
def test_server_fork_can_exit(self): |
||||||
|
process = subprocess.Popen( |
||||||
|
BASE_COMMAND + [_server_shutdown_scenarios.SERVER_FORK_CAN_EXIT], |
||||||
|
stdout=sys.stdout, |
||||||
|
stderr=sys.stderr) |
||||||
|
wait(process) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
logging.basicConfig() |
||||||
|
unittest.main(verbosity=2) |
Loading…
Reference in new issue