Skip to content

Commit 70f0015

Browse files
authored
Merge pull request grpc#23632 from lidizheng/raise-when-bind-failed
Raises an exception when port binding failed
2 parents eaa6705 + d13c583 commit 70f0015

File tree

6 files changed

+70
-7
lines changed

6 files changed

+70
-7
lines changed

src/python/grpcio/grpc/_common.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161

6262
MAXIMUM_WAIT_TIMEOUT = 0.1
6363

64+
_ERROR_MESSAGE_PORT_BINDING_FAILED = 'Failed to bind to address %s; set ' \
65+
'GRPC_VERBOSITY=debug environment variable to see detailed error message.'
66+
6467

6568
def encode(s):
6669
if isinstance(s, bytes):
@@ -144,3 +147,22 @@ def wait(wait_fn, wait_complete_fn, timeout=None, spin_cb=None):
144147
return True
145148
_wait_once(wait_fn, remaining, spin_cb)
146149
return False
150+
151+
152+
def validate_port_binding_result(address, port):
153+
"""Validates if the port binding succeed.
154+
155+
If the port returned by Core is 0, the binding is failed. However, in that
156+
case, the Core API doesn't return a detailed failing reason. The best we
157+
can do is raising an exception to prevent further confusion.
158+
159+
Args:
160+
address: The address string to be bound.
161+
port: An int returned by core
162+
"""
163+
if port == 0:
164+
# The Core API doesn't return a failure message. The best we can do
165+
# is raising an exception to prevent further confusion.
166+
raise RuntimeError(_ERROR_MESSAGE_PORT_BINDING_FAILED % address)
167+
else:
168+
return port

src/python/grpcio/grpc/_server.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -958,11 +958,14 @@ def add_generic_rpc_handlers(self, generic_rpc_handlers):
958958
_add_generic_handlers(self._state, generic_rpc_handlers)
959959

960960
def add_insecure_port(self, address):
961-
return _add_insecure_port(self._state, _common.encode(address))
961+
return _common.validate_port_binding_result(
962+
address, _add_insecure_port(self._state, _common.encode(address)))
962963

963964
def add_secure_port(self, address, server_credentials):
964-
return _add_secure_port(self._state, _common.encode(address),
965-
server_credentials)
965+
return _common.validate_port_binding_result(
966+
address,
967+
_add_secure_port(self._state, _common.encode(address),
968+
server_credentials))
966969

967970
def start(self):
968971
_start(self._state)

src/python/grpcio/grpc/experimental/aio/_server.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ def add_insecure_port(self, address: str) -> int:
8080
Returns:
8181
An integer port on which the server will accept RPC requests.
8282
"""
83-
return self._server.add_insecure_port(_common.encode(address))
83+
return _common.validate_port_binding_result(
84+
address, self._server.add_insecure_port(_common.encode(address)))
8485

8586
def add_secure_port(self, address: str,
8687
server_credentials: grpc.ServerCredentials) -> int:
@@ -97,8 +98,10 @@ def add_secure_port(self, address: str,
9798
Returns:
9899
An integer port on which the server will accept RPC requests.
99100
"""
100-
return self._server.add_secure_port(_common.encode(address),
101-
server_credentials)
101+
return _common.validate_port_binding_result(
102+
address,
103+
self._server.add_secure_port(_common.encode(address),
104+
server_credentials))
102105

103106
async def start(self) -> None:
104107
"""Starts this Server.

src/python/grpcio_tests/commands.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,10 @@ class TestGevent(setuptools.Command):
227227
)
228228
BANNED_WINDOWS_TESTS = (
229229
# TODO(https://github.com/grpc/grpc/pull/15411) enable this test
230-
'unit._dns_resolver_test.DNSResolverTest.test_connect_loopback',)
230+
'unit._dns_resolver_test.DNSResolverTest.test_connect_loopback',
231+
# TODO(https://github.com/grpc/grpc/pull/15411) enable this test
232+
'unit._server_test.ServerTest.test_failed_port_binding_exception',
233+
)
231234
description = 'run tests with gevent. Assumes grpc/gevent are installed'
232235
user_options = []
233236

src/python/grpcio_tests/tests/unit/_server_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import grpc
2020

21+
from tests.unit import resources
22+
2123

2224
class _ActualGenericRpcHandler(grpc.GenericRpcHandler):
2325

@@ -47,6 +49,20 @@ def test_not_a_generic_rpc_handler_after_construction(self):
4749
self.assertIn('grpc.GenericRpcHandler',
4850
str(exception_context.exception))
4951

52+
def test_failed_port_binding_exception(self):
53+
server = grpc.server(None, options=(('grpc.so_reuseport', 0),))
54+
port = server.add_insecure_port('localhost:0')
55+
bind_address = "localhost:%d" % port
56+
57+
with self.assertRaises(RuntimeError):
58+
server.add_insecure_port(bind_address)
59+
60+
server_credentials = grpc.ssl_server_credentials([
61+
(resources.private_key(), resources.certificate_chain())
62+
])
63+
with self.assertRaises(RuntimeError):
64+
server.add_secure_port(bind_address, server_credentials)
65+
5066

5167
if __name__ == '__main__':
5268
logging.basicConfig()

src/python/grpcio_tests/tests_aio/unit/server_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
import asyncio
1616
import gc
1717
import logging
18+
import socket
1819
import time
1920
import unittest
2021

2122
import grpc
2223
from grpc.experimental import aio
2324

25+
from tests.unit import resources
2426
from tests.unit.framework.common import test_constants
2527
from tests_aio.unit._test_base import AioTestBase
2628

@@ -464,6 +466,20 @@ async def test_error_without_raise_in_stream_stream(self):
464466

465467
self.assertEqual(grpc.StatusCode.INTERNAL, await call.code())
466468

469+
async def test_port_binding_exception(self):
470+
server = aio.server(options=(('grpc.so_reuseport', 0),))
471+
port = server.add_insecure_port('localhost:0')
472+
bind_address = "localhost:%d" % port
473+
474+
with self.assertRaises(RuntimeError):
475+
server.add_insecure_port(bind_address)
476+
477+
server_credentials = grpc.ssl_server_credentials([
478+
(resources.private_key(), resources.certificate_chain())
479+
])
480+
with self.assertRaises(RuntimeError):
481+
server.add_secure_port(bind_address, server_credentials)
482+
467483

468484
if __name__ == '__main__':
469485
logging.basicConfig(level=logging.DEBUG)

0 commit comments

Comments
 (0)