Skip to content

Commit 9bc44e3

Browse files
committed
Introduce ServicerContext.abort to abort an RPC
gRPC Python required RPCs terminating with non-OK status code to still return a valid response value after calling set_code, even though the response value was not supposed to be communicated to the client, and returning None is considered a programming error. This commit introduces an alternative mechanism to terminate RPCs by calling the `abort` method on `ServicerContext` passed to the handler, which raises an exception and signals to the gRPC runtime to abort the RPC with the specified status code and details.
1 parent 5663eac commit 9bc44e3

3 files changed

Lines changed: 50 additions & 14 deletions

File tree

src/python/grpcio/grpc/__init__.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -834,28 +834,48 @@ def set_trailing_metadata(self, trailing_metadata):
834834
"""
835835
raise NotImplementedError()
836836

837+
@abc.abstractmethod
838+
def abort(self, code, details):
839+
"""Raises an exception to terminate the RPC with a non-OK status.
840+
841+
The code and details passed as arguments will supercede any existing
842+
ones.
843+
844+
Args:
845+
code: A StatusCode object to be sent to the client.
846+
It must not be StatusCode.OK.
847+
details: An ASCII-encodable string to be sent to the client upon
848+
termination of the RPC.
849+
850+
Raises:
851+
Exception: An exception is always raised to signal the abortion the
852+
RPC to the gRPC runtime.
853+
"""
854+
raise NotImplementedError()
855+
837856
@abc.abstractmethod
838857
def set_code(self, code):
839858
"""Sets the value to be used as status code upon RPC completion.
840859
841-
This method need not be called by method implementations if they wish the
842-
gRPC runtime to determine the status code of the RPC.
860+
This method need not be called by method implementations if they wish
861+
the gRPC runtime to determine the status code of the RPC.
843862
844-
Args:
845-
code: A StatusCode object to be sent to the client.
846-
"""
863+
Args:
864+
code: A StatusCode object to be sent to the client.
865+
"""
847866
raise NotImplementedError()
848867

849868
@abc.abstractmethod
850869
def set_details(self, details):
851870
"""Sets the value to be used as detail string upon RPC completion.
852871
853-
This method need not be called by method implementations if they have no
854-
details to transmit.
872+
This method need not be called by method implementations if they have
873+
no details to transmit.
855874
856-
Args:
857-
details: An arbitrary string to be sent to the client upon completion.
858-
"""
875+
Args:
876+
details: An ASCII-encodable string to be sent to the client upon
877+
termination of the RPC.
878+
"""
859879
raise NotImplementedError()
860880

861881

src/python/grpcio/grpc/_server.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def __init__(self):
9696
self.statused = False
9797
self.rpc_errors = []
9898
self.callbacks = []
99+
self.abortion = None
99100

100101

101102
def _raise_rpc_error(state):
@@ -273,6 +274,13 @@ def set_trailing_metadata(self, trailing_metadata):
273274
with self._state.condition:
274275
self._state.trailing_metadata = trailing_metadata
275276

277+
def abort(self, code, details):
278+
with self._state.condition:
279+
self._state.code = code
280+
self._state.details = _common.encode(details)
281+
self._state.abortion = Exception()
282+
raise self._state.abortion
283+
276284
def set_code(self, code):
277285
with self._state.condition:
278286
self._state.code = code
@@ -369,7 +377,10 @@ def _call_behavior(rpc_event, state, behavior, argument, request_deserializer):
369377
return behavior(argument, context), True
370378
except Exception as exception: # pylint: disable=broad-except
371379
with state.condition:
372-
if exception not in state.rpc_errors:
380+
if exception is state.abortion:
381+
_abort(state, rpc_event.operation_call,
382+
cygrpc.StatusCode.unknown, b'RPC Aborted')
383+
elif exception not in state.rpc_errors:
373384
details = 'Exception calling application: {}'.format(exception)
374385
logging.exception(details)
375386
_abort(state, rpc_event.operation_call,
@@ -384,7 +395,10 @@ def _take_response_from_response_iterator(rpc_event, state, response_iterator):
384395
return None, True
385396
except Exception as exception: # pylint: disable=broad-except
386397
with state.condition:
387-
if exception not in state.rpc_errors:
398+
if exception is state.abortion:
399+
_abort(state, rpc_event.operation_call,
400+
cygrpc.StatusCode.unknown, b'RPC Aborted')
401+
elif exception not in state.rpc_errors:
388402
details = 'Exception iterating responses: {}'.format(exception)
389403
logging.exception(details)
390404
_abort(state, rpc_event.operation_call,
@@ -430,12 +444,11 @@ def _send_response(rpc_event, state, serialized_response):
430444
def _status(rpc_event, state, serialized_response):
431445
with state.condition:
432446
if state.client is not _CANCELLED:
433-
trailing_metadata = state.trailing_metadata
434447
code = _completion_code(state)
435448
details = _details(state)
436449
operations = [
437450
cygrpc.operation_send_status_from_server(
438-
trailing_metadata, code, details, _EMPTY_FLAGS),
451+
state.trailing_metadata, code, details, _EMPTY_FLAGS),
439452
]
440453
if state.initial_metadata_allowed:
441454
operations.append(

src/python/grpcio_testing/grpc_testing/_server/_servicer_context.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ def set_trailing_metadata(self, trailing_metadata):
6767
self._rpc.set_trailing_metadata(
6868
_common.fuss_with_metadata(trailing_metadata))
6969

70+
def abort(self, code, details):
71+
raise NotImplementedError()
72+
7073
def set_code(self, code):
7174
self._rpc.set_code(code)
7275

0 commit comments

Comments
 (0)