Remove early adopter code

pull/7338/head
Ken Payson 9 years ago
parent 76c929606e
commit 4d425522bf
  1. 5
      src/python/grpcio/grpc/_adapter/.gitignore
  2. 30
      src/python/grpcio/grpc/_adapter/__init__.py
  3. 76
      src/python/grpcio/grpc/_adapter/_common.py
  4. 258
      src/python/grpcio/grpc/_adapter/_intermediary_low.py
  5. 229
      src/python/grpcio/grpc/_adapter/_low.py
  6. 446
      src/python/grpcio/grpc/_adapter/_types.py
  7. 30
      src/python/grpcio/grpc/_links/__init__.py
  8. 42
      src/python/grpcio/grpc/_links/_constants.py
  9. 453
      src/python/grpcio/grpc/_links/invocation.py
  10. 509
      src/python/grpcio/grpc/_links/service.py
  11. 209
      src/python/grpcio/grpc/beta/_server.py
  12. 155
      src/python/grpcio/grpc/beta/_stub.py
  13. 1
      src/python/grpcio/grpc/beta/implementations.py
  14. 30
      src/python/grpcio/grpc/framework/core/__init__.py
  15. 60
      src/python/grpcio/grpc/framework/core/_constants.py
  16. 94
      src/python/grpcio/grpc/framework/core/_context.py
  17. 100
      src/python/grpcio/grpc/framework/core/_emission.py
  18. 244
      src/python/grpcio/grpc/framework/core/_end.py
  19. 154
      src/python/grpcio/grpc/framework/core/_expiration.py
  20. 439
      src/python/grpcio/grpc/framework/core/_ingestion.py
  21. 331
      src/python/grpcio/grpc/framework/core/_interfaces.py
  22. 204
      src/python/grpcio/grpc/framework/core/_operation.py
  23. 176
      src/python/grpcio/grpc/framework/core/_protocol.py
  24. 159
      src/python/grpcio/grpc/framework/core/_reception.py
  25. 229
      src/python/grpcio/grpc/framework/core/_termination.py
  26. 335
      src/python/grpcio/grpc/framework/core/_transmission.py
  27. 54
      src/python/grpcio/grpc/framework/core/_utilities.py
  28. 62
      src/python/grpcio/grpc/framework/core/implementations.py
  29. 30
      src/python/grpcio/grpc/framework/crust/__init__.py
  30. 223
      src/python/grpcio/grpc/framework/crust/_calls.py
  31. 584
      src/python/grpcio/grpc/framework/crust/_control.py
  32. 173
      src/python/grpcio/grpc/framework/crust/_service.py
  33. 366
      src/python/grpcio/grpc/framework/crust/implementations.py
  34. 228
      src/python/grpcio/grpc/framework/foundation/_timer_future.py
  35. 65
      src/python/grpcio/grpc/framework/foundation/activated.py
  36. 51
      src/python/grpcio/grpc/framework/foundation/later.py
  37. 174
      src/python/grpcio/grpc/framework/foundation/relay.py
  38. 30
      src/python/grpcio/grpc/framework/interfaces/links/__init__.py
  39. 143
      src/python/grpcio/grpc/framework/interfaces/links/links.py
  40. 44
      src/python/grpcio/grpc/framework/interfaces/links/utilities.py
  41. 5
      src/python/grpcio_tests/tests/unit/_adapter/.gitignore
  42. 30
      src/python/grpcio_tests/tests/unit/_adapter/__init__.py
  43. 262
      src/python/grpcio_tests/tests/unit/_adapter/_proto_scenarios.py
  44. 266
      src/python/grpcio_tests/tests/unit/_junkdrawer/math_pb2.py
  45. 30
      src/python/grpcio_tests/tests/unit/_links/__init__.py
  46. 262
      src/python/grpcio_tests/tests/unit/_links/_proto_scenarios.py
  47. 30
      src/python/grpcio_tests/tests/unit/framework/core/__init__.py
  48. 30
      src/python/grpcio_tests/tests/unit/framework/interfaces/base/__init__.py
  49. 570
      src/python/grpcio_tests/tests/unit/framework/interfaces/base/_control.py
  50. 171
      src/python/grpcio_tests/tests/unit/framework/interfaces/base/_sequence.py
  51. 55
      src/python/grpcio_tests/tests/unit/framework/interfaces/base/_state.py
  52. 279
      src/python/grpcio_tests/tests/unit/framework/interfaces/base/test_cases.py
  53. 186
      src/python/grpcio_tests/tests/unit/framework/interfaces/base/test_interfaces.py
  54. 95
      src/python/grpcio_tests/tests/unit/framework/interfaces/face/_receiver.py
  55. 30
      src/python/grpcio_tests/tests/unit/framework/interfaces/links/__init__.py
  56. 327
      src/python/grpcio_tests/tests/unit/framework/interfaces/links/test_cases.py
  57. 167
      src/python/grpcio_tests/tests/unit/framework/interfaces/links/test_utilities.py

@ -1,5 +0,0 @@
*.a
*.so
*.dll
*.pyc
*.pyd

@ -1,30 +0,0 @@
# Copyright 2015, 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.

@ -1,76 +0,0 @@
# Copyright 2015, 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.
"""State used by both invocation-side and service-side code."""
import enum
@enum.unique
class HighWrite(enum.Enum):
"""The possible categories of high-level write state."""
OPEN = 'OPEN'
CLOSED = 'CLOSED'
class WriteState(object):
"""A description of the state of writing to an RPC.
Attributes:
low: A side-specific value describing the low-level state of writing.
high: A HighWrite value describing the high-level state of writing.
pending: A list of bytestrings for the RPC waiting to be written to the
other side of the RPC.
"""
def __init__(self, low, high, pending):
self.low = low
self.high = high
self.pending = pending
class CommonRPCState(object):
"""A description of an RPC's state.
Attributes:
write: A WriteState describing the state of writing to the RPC.
sequence_number: The lowest-unused sequence number for use in generating
tickets locally describing the progress of the RPC.
deserializer: The behavior to be used to deserialize payload bytestreams
taken off the wire.
serializer: The behavior to be used to serialize payloads to be sent on the
wire.
"""
def __init__(self, write, sequence_number, deserializer, serializer):
self.write = write
self.sequence_number = sequence_number
self.deserializer = deserializer
self.serializer = serializer

@ -1,258 +0,0 @@
# Copyright 2015, 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.
"""Temporary old _low-like layer.
Eases refactoring burden while we overhaul the Python framework.
Plan:
The layers used to look like:
... # outside _adapter
fore.py + rear.py # visible outside _adapter
_low
_c
The layers currently look like:
... # outside _adapter
fore.py + rear.py # visible outside _adapter
_low_intermediary # adapter for new '_low' to old '_low'
_low # new '_low'
_c # new '_c'
We will later remove _low_intermediary after refactoring of fore.py and
rear.py according to the ticket system refactoring and get:
... # outside _adapter, refactored
fore.py + rear.py # visible outside _adapter, refactored
_low # new '_low'
_c # new '_c'
"""
import collections
import enum
from grpc._adapter import _low
from grpc._adapter import _types
_IGNORE_ME_TAG = object()
Code = _types.StatusCode
WriteFlags = _types.OpWriteFlags
class Status(collections.namedtuple('Status', ['code', 'details'])):
"""Describes an RPC's overall status."""
class ServiceAcceptance(
collections.namedtuple(
'ServiceAcceptance', ['call', 'method', 'host', 'deadline'])):
"""Describes an RPC on the service side at the start of service."""
class Event(
collections.namedtuple(
'Event',
['kind', 'tag', 'write_accepted', 'complete_accepted',
'service_acceptance', 'bytes', 'status', 'metadata'])):
"""Describes an event emitted from a completion queue."""
@enum.unique
class Kind(enum.Enum):
"""Describes the kind of an event."""
STOP = object()
WRITE_ACCEPTED = object()
COMPLETE_ACCEPTED = object()
SERVICE_ACCEPTED = object()
READ_ACCEPTED = object()
METADATA_ACCEPTED = object()
FINISH = object()
class _TagAdapter(collections.namedtuple('_TagAdapter', [
'user_tag',
'kind'
])):
pass
class Call(object):
"""Adapter from old _low.Call interface to new _low.Call."""
def __init__(self, channel, completion_queue, method, host, deadline):
self._internal = channel._internal.create_call(
completion_queue._internal, method, host, deadline)
self._metadata = []
@staticmethod
def _from_internal(internal):
call = Call.__new__(Call)
call._internal = internal
call._metadata = []
return call
def invoke(self, completion_queue, metadata_tag, finish_tag):
err = self._internal.start_batch([
_types.OpArgs.send_initial_metadata(self._metadata)
], _IGNORE_ME_TAG)
if err != _types.CallError.OK:
return err
err = self._internal.start_batch([
_types.OpArgs.recv_initial_metadata()
], _TagAdapter(metadata_tag, Event.Kind.METADATA_ACCEPTED))
if err != _types.CallError.OK:
return err
err = self._internal.start_batch([
_types.OpArgs.recv_status_on_client()
], _TagAdapter(finish_tag, Event.Kind.FINISH))
return err
def write(self, message, tag, flags):
return self._internal.start_batch([
_types.OpArgs.send_message(message, flags)
], _TagAdapter(tag, Event.Kind.WRITE_ACCEPTED))
def complete(self, tag):
return self._internal.start_batch([
_types.OpArgs.send_close_from_client()
], _TagAdapter(tag, Event.Kind.COMPLETE_ACCEPTED))
def accept(self, completion_queue, tag):
return self._internal.start_batch([
_types.OpArgs.recv_close_on_server()
], _TagAdapter(tag, Event.Kind.FINISH))
def add_metadata(self, key, value):
self._metadata.append((key, value))
def premetadata(self):
result = self._internal.start_batch([
_types.OpArgs.send_initial_metadata(self._metadata)
], _IGNORE_ME_TAG)
self._metadata = []
return result
def read(self, tag):
return self._internal.start_batch([
_types.OpArgs.recv_message()
], _TagAdapter(tag, Event.Kind.READ_ACCEPTED))
def status(self, status, tag):
return self._internal.start_batch([
_types.OpArgs.send_status_from_server(
self._metadata, status.code, status.details)
], _TagAdapter(tag, Event.Kind.COMPLETE_ACCEPTED))
def cancel(self):
return self._internal.cancel()
def peer(self):
return self._internal.peer()
def set_credentials(self, creds):
return self._internal.set_credentials(creds)
class Channel(object):
"""Adapter from old _low.Channel interface to new _low.Channel."""
def __init__(self, hostport, channel_credentials, server_host_override=None):
args = []
if server_host_override:
args.append((_types.GrpcChannelArgumentKeys.SSL_TARGET_NAME_OVERRIDE.value, server_host_override))
self._internal = _low.Channel(hostport, args, channel_credentials)
class CompletionQueue(object):
"""Adapter from old _low.CompletionQueue interface to new _low.CompletionQueue."""
def __init__(self):
self._internal = _low.CompletionQueue()
def get(self, deadline=None):
if deadline is None:
ev = self._internal.next(float('+inf'))
else:
ev = self._internal.next(deadline)
if ev is None:
return None
elif ev.tag is _IGNORE_ME_TAG:
return self.get(deadline)
elif ev.type == _types.EventType.QUEUE_SHUTDOWN:
kind = Event.Kind.STOP
tag = None
write_accepted = None
complete_accepted = None
service_acceptance = None
message_bytes = None
status = None
metadata = None
elif ev.type == _types.EventType.OP_COMPLETE:
kind = ev.tag.kind
tag = ev.tag.user_tag
write_accepted = ev.success if kind == Event.Kind.WRITE_ACCEPTED else None
complete_accepted = ev.success if kind == Event.Kind.COMPLETE_ACCEPTED else None
service_acceptance = ServiceAcceptance(Call._from_internal(ev.call), ev.call_details.method, ev.call_details.host, ev.call_details.deadline) if kind == Event.Kind.SERVICE_ACCEPTED else None
message_bytes = ev.results[0].message if kind == Event.Kind.READ_ACCEPTED else None
status = Status(ev.results[0].status.code, ev.results[0].status.details) if (kind == Event.Kind.FINISH and ev.results[0].status) else Status(_types.StatusCode.CANCELLED if ev.results[0].cancelled else _types.StatusCode.OK, '') if len(ev.results) > 0 and ev.results[0].cancelled is not None else None
metadata = ev.results[0].initial_metadata if (kind in [Event.Kind.SERVICE_ACCEPTED, Event.Kind.METADATA_ACCEPTED]) else (ev.results[0].trailing_metadata if kind == Event.Kind.FINISH else None)
else:
raise RuntimeError('unknown event')
result_ev = Event(kind=kind, tag=tag, write_accepted=write_accepted, complete_accepted=complete_accepted, service_acceptance=service_acceptance, bytes=message_bytes, status=status, metadata=metadata)
return result_ev
def stop(self):
self._internal.shutdown()
class Server(object):
"""Adapter from old _low.Server interface to new _low.Server."""
def __init__(self, completion_queue):
self._internal = _low.Server(completion_queue._internal, [])
self._internal_cq = completion_queue._internal
def add_http2_addr(self, addr):
return self._internal.add_http2_port(addr)
def add_secure_http2_addr(self, addr, server_credentials):
if server_credentials is None:
return self._internal.add_http2_port(addr, None)
else:
return self._internal.add_http2_port(addr, server_credentials)
def start(self):
return self._internal.start()
def service(self, tag):
return self._internal.request_call(self._internal_cq, _TagAdapter(tag, Event.Kind.SERVICE_ACCEPTED))
def cancel_all_calls(self):
self._internal.cancel_all_calls()
def stop(self):
return self._internal.shutdown(_TagAdapter(None, Event.Kind.STOP))

@ -1,229 +0,0 @@
# Copyright 2015, 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.
import threading
from grpc import _grpcio_metadata
from grpc import _plugin_wrapping
from grpc._cython import cygrpc
from grpc._adapter import _types
_USER_AGENT = 'Python-gRPC-{}'.format(_grpcio_metadata.__version__)
ChannelCredentials = cygrpc.ChannelCredentials
CallCredentials = cygrpc.CallCredentials
ServerCredentials = cygrpc.ServerCredentials
channel_credentials_composite = cygrpc.channel_credentials_composite
call_credentials_composite = cygrpc.call_credentials_composite
def server_credentials_ssl(root_credentials, pair_sequence, force_client_auth):
return cygrpc.server_credentials_ssl(
root_credentials,
[cygrpc.SslPemKeyCertPair(key, pem) for key, pem in pair_sequence],
force_client_auth)
def channel_credentials_ssl(
root_certificates, private_key, certificate_chain):
pair = None
if private_key is not None or certificate_chain is not None:
pair = cygrpc.SslPemKeyCertPair(private_key, certificate_chain)
return cygrpc.channel_credentials_ssl(root_certificates, pair)
call_credentials_metadata_plugin = (
_plugin_wrapping.call_credentials_metadata_plugin)
class CompletionQueue(_types.CompletionQueue):
def __init__(self):
self.completion_queue = cygrpc.CompletionQueue()
def next(self, deadline=float('+inf')):
raw_event = self.completion_queue.poll(cygrpc.Timespec(deadline))
if raw_event.type == cygrpc.CompletionType.queue_timeout:
return None
event_type = raw_event.type
event_tag = raw_event.tag
event_call = Call(raw_event.operation_call)
if raw_event.request_call_details:
event_call_details = _types.CallDetails(
raw_event.request_call_details.method,
raw_event.request_call_details.host,
float(raw_event.request_call_details.deadline))
else:
event_call_details = None
event_success = raw_event.success
event_results = []
if raw_event.is_new_request:
event_results.append(_types.OpResult(
_types.OpType.RECV_INITIAL_METADATA, raw_event.request_metadata,
None, None, None, None))
else:
if raw_event.batch_operations:
for operation in raw_event.batch_operations:
result_type = operation.type
result_initial_metadata = operation.received_metadata_or_none
result_trailing_metadata = operation.received_metadata_or_none
result_message = operation.received_message_or_none
if result_message is not None:
result_message = result_message.bytes()
result_cancelled = operation.received_cancelled_or_none
if operation.has_status:
result_status = _types.Status(
operation.received_status_code_or_none,
operation.received_status_details_or_none)
else:
result_status = None
event_results.append(
_types.OpResult(result_type, result_initial_metadata,
result_trailing_metadata, result_message,
result_status, result_cancelled))
return _types.Event(event_type, event_tag, event_call, event_call_details,
event_results, event_success)
def shutdown(self):
self.completion_queue.shutdown()
class Call(_types.Call):
def __init__(self, call):
self.call = call
def start_batch(self, ops, tag):
translated_ops = []
for op in ops:
if op.type == _types.OpType.SEND_INITIAL_METADATA:
translated_op = cygrpc.operation_send_initial_metadata(
cygrpc.Metadata(
cygrpc.Metadatum(key, value)
for key, value in op.initial_metadata),
op.flags)
elif op.type == _types.OpType.SEND_MESSAGE:
translated_op = cygrpc.operation_send_message(op.message, op.flags)
elif op.type == _types.OpType.SEND_CLOSE_FROM_CLIENT:
translated_op = cygrpc.operation_send_close_from_client(op.flags)
elif op.type == _types.OpType.SEND_STATUS_FROM_SERVER:
translated_op = cygrpc.operation_send_status_from_server(
cygrpc.Metadata(
cygrpc.Metadatum(key, value)
for key, value in op.trailing_metadata),
op.status.code,
op.status.details,
op.flags)
elif op.type == _types.OpType.RECV_INITIAL_METADATA:
translated_op = cygrpc.operation_receive_initial_metadata(
op.flags)
elif op.type == _types.OpType.RECV_MESSAGE:
translated_op = cygrpc.operation_receive_message(op.flags)
elif op.type == _types.OpType.RECV_STATUS_ON_CLIENT:
translated_op = cygrpc.operation_receive_status_on_client(
op.flags)
elif op.type == _types.OpType.RECV_CLOSE_ON_SERVER:
translated_op = cygrpc.operation_receive_close_on_server(op.flags)
else:
raise ValueError('unexpected operation type {}'.format(op.type))
translated_ops.append(translated_op)
return self.call.start_batch(cygrpc.Operations(translated_ops), tag)
def cancel(self, code=None, details=None):
if code is None and details is None:
return self.call.cancel()
else:
return self.call.cancel(code, details)
def peer(self):
return self.call.peer()
def set_credentials(self, creds):
return self.call.set_credentials(creds)
class Channel(_types.Channel):
def __init__(self, target, args, creds=None):
args = list(args) + [
(cygrpc.ChannelArgKey.primary_user_agent_string, _USER_AGENT)]
args = cygrpc.ChannelArgs(
cygrpc.ChannelArg(key, value) for key, value in args)
if creds is None:
self.channel = cygrpc.Channel(target, args)
else:
self.channel = cygrpc.Channel(target, args, creds)
def create_call(self, completion_queue, method, host, deadline=None):
internal_call = self.channel.create_call(
None, 0, completion_queue.completion_queue, method, host,
cygrpc.Timespec(deadline))
return Call(internal_call)
def check_connectivity_state(self, try_to_connect):
return self.channel.check_connectivity_state(try_to_connect)
def watch_connectivity_state(self, last_observed_state, deadline,
completion_queue, tag):
self.channel.watch_connectivity_state(
last_observed_state, cygrpc.Timespec(deadline),
completion_queue.completion_queue, tag)
def target(self):
return self.channel.target()
_NO_TAG = object()
class Server(_types.Server):
def __init__(self, completion_queue, args):
args = cygrpc.ChannelArgs(
cygrpc.ChannelArg(key, value) for key, value in args)
self.server = cygrpc.Server(args)
self.server.register_completion_queue(completion_queue.completion_queue)
self.server_queue = completion_queue
def add_http2_port(self, addr, creds=None):
if creds is None:
return self.server.add_http2_port(addr)
else:
return self.server.add_http2_port(addr, creds)
def start(self):
return self.server.start()
def shutdown(self, tag=None):
return self.server.shutdown(self.server_queue.completion_queue, tag)
def request_call(self, completion_queue, tag):
return self.server.request_call(completion_queue.completion_queue,
self.server_queue.completion_queue, tag)
def cancel_all_calls(self):
return self.server.cancel_all_calls()

@ -1,446 +0,0 @@
# Copyright 2015, 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.
import abc
import collections
import enum
import six
from grpc._cython import cygrpc
class GrpcChannelArgumentKeys(enum.Enum):
"""Mirrors keys used in grpc_channel_args for GRPC-specific arguments."""
SSL_TARGET_NAME_OVERRIDE = 'grpc.ssl_target_name_override'
@enum.unique
class CallError(enum.IntEnum):
"""Mirrors grpc_call_error in the C core."""
OK = cygrpc.CallError.ok
ERROR = cygrpc.CallError.error
ERROR_NOT_ON_SERVER = cygrpc.CallError.not_on_server
ERROR_NOT_ON_CLIENT = cygrpc.CallError.not_on_client
ERROR_ALREADY_ACCEPTED = cygrpc.CallError.already_accepted
ERROR_ALREADY_INVOKED = cygrpc.CallError.already_invoked
ERROR_NOT_INVOKED = cygrpc.CallError.not_invoked
ERROR_ALREADY_FINISHED = cygrpc.CallError.already_finished
ERROR_TOO_MANY_OPERATIONS = cygrpc.CallError.too_many_operations
ERROR_INVALID_FLAGS = cygrpc.CallError.invalid_flags
ERROR_INVALID_METADATA = cygrpc.CallError.invalid_metadata
@enum.unique
class StatusCode(enum.IntEnum):
"""Mirrors grpc_status_code in the C core."""
OK = cygrpc.StatusCode.ok
CANCELLED = cygrpc.StatusCode.cancelled
UNKNOWN = cygrpc.StatusCode.unknown
INVALID_ARGUMENT = cygrpc.StatusCode.invalid_argument
DEADLINE_EXCEEDED = cygrpc.StatusCode.deadline_exceeded
NOT_FOUND = cygrpc.StatusCode.not_found
ALREADY_EXISTS = cygrpc.StatusCode.already_exists
PERMISSION_DENIED = cygrpc.StatusCode.permission_denied
RESOURCE_EXHAUSTED = cygrpc.StatusCode.resource_exhausted
FAILED_PRECONDITION = cygrpc.StatusCode.failed_precondition
ABORTED = cygrpc.StatusCode.aborted
OUT_OF_RANGE = cygrpc.StatusCode.out_of_range
UNIMPLEMENTED = cygrpc.StatusCode.unimplemented
INTERNAL = cygrpc.StatusCode.internal
UNAVAILABLE = cygrpc.StatusCode.unavailable
DATA_LOSS = cygrpc.StatusCode.data_loss
UNAUTHENTICATED = cygrpc.StatusCode.unauthenticated
@enum.unique
class OpWriteFlags(enum.IntEnum):
"""Mirrors defined write-flag constants in the C core."""
WRITE_BUFFER_HINT = cygrpc.WriteFlag.buffer_hint
WRITE_NO_COMPRESS = cygrpc.WriteFlag.no_compress
@enum.unique
class OpType(enum.IntEnum):
"""Mirrors grpc_op_type in the C core."""
SEND_INITIAL_METADATA = cygrpc.OperationType.send_initial_metadata
SEND_MESSAGE = cygrpc.OperationType.send_message
SEND_CLOSE_FROM_CLIENT = cygrpc.OperationType.send_close_from_client
SEND_STATUS_FROM_SERVER = cygrpc.OperationType.send_status_from_server
RECV_INITIAL_METADATA = cygrpc.OperationType.receive_initial_metadata
RECV_MESSAGE = cygrpc.OperationType.receive_message
RECV_STATUS_ON_CLIENT = cygrpc.OperationType.receive_status_on_client
RECV_CLOSE_ON_SERVER = cygrpc.OperationType.receive_close_on_server
@enum.unique
class EventType(enum.IntEnum):
"""Mirrors grpc_completion_type in the C core."""
QUEUE_SHUTDOWN = cygrpc.CompletionType.queue_shutdown
QUEUE_TIMEOUT = cygrpc.CompletionType.queue_timeout
OP_COMPLETE = cygrpc.CompletionType.operation_complete
@enum.unique
class ConnectivityState(enum.IntEnum):
"""Mirrors grpc_connectivity_state in the C core."""
IDLE = cygrpc.ConnectivityState.idle
CONNECTING = cygrpc.ConnectivityState.connecting
READY = cygrpc.ConnectivityState.ready
TRANSIENT_FAILURE = cygrpc.ConnectivityState.transient_failure
FATAL_FAILURE = cygrpc.ConnectivityState.shutdown
class Status(collections.namedtuple(
'Status', [
'code',
'details',
])):
"""The end status of a GRPC call.
Attributes:
code (StatusCode): ...
details (str): ...
"""
class CallDetails(collections.namedtuple(
'CallDetails', [
'method',
'host',
'deadline',
])):
"""Provides information to the server about the client's call.
Attributes:
method (str): ...
host (str): ...
deadline (float): ...
"""
class OpArgs(collections.namedtuple(
'OpArgs', [
'type',
'initial_metadata',
'trailing_metadata',
'message',
'status',
'flags',
])):
"""Arguments passed into a GRPC operation.
Attributes:
type (OpType): ...
initial_metadata (sequence of 2-sequence of str): Only valid if type ==
OpType.SEND_INITIAL_METADATA, else is None.
trailing_metadata (sequence of 2-sequence of str): Only valid if type ==
OpType.SEND_STATUS_FROM_SERVER, else is None.
message (bytes): Only valid if type == OpType.SEND_MESSAGE, else is None.
status (Status): Only valid if type == OpType.SEND_STATUS_FROM_SERVER, else
is None.
flags (int): a bitwise OR'ing of 0 or more OpWriteFlags values.
"""
@staticmethod
def send_initial_metadata(initial_metadata):
return OpArgs(OpType.SEND_INITIAL_METADATA, initial_metadata, None, None, None, 0)
@staticmethod
def send_message(message, flags):
return OpArgs(OpType.SEND_MESSAGE, None, None, message, None, flags)
@staticmethod
def send_close_from_client():
return OpArgs(OpType.SEND_CLOSE_FROM_CLIENT, None, None, None, None, 0)
@staticmethod
def send_status_from_server(trailing_metadata, status_code, status_details):
return OpArgs(OpType.SEND_STATUS_FROM_SERVER, None, trailing_metadata, None, Status(status_code, status_details), 0)
@staticmethod
def recv_initial_metadata():
return OpArgs(OpType.RECV_INITIAL_METADATA, None, None, None, None, 0);
@staticmethod
def recv_message():
return OpArgs(OpType.RECV_MESSAGE, None, None, None, None, 0)
@staticmethod
def recv_status_on_client():
return OpArgs(OpType.RECV_STATUS_ON_CLIENT, None, None, None, None, 0)
@staticmethod
def recv_close_on_server():
return OpArgs(OpType.RECV_CLOSE_ON_SERVER, None, None, None, None, 0)
class OpResult(collections.namedtuple(
'OpResult', [
'type',
'initial_metadata',
'trailing_metadata',
'message',
'status',
'cancelled',
])):
"""Results received from a GRPC operation.
Attributes:
type (OpType): ...
initial_metadata (sequence of 2-sequence of str): Only valid if type ==
OpType.RECV_INITIAL_METADATA, else is None.
trailing_metadata (sequence of 2-sequence of str): Only valid if type ==
OpType.RECV_STATUS_ON_CLIENT, else is None.
message (bytes): Only valid if type == OpType.RECV_MESSAGE, else is None.
status (Status): Only valid if type == OpType.RECV_STATUS_ON_CLIENT, else
is None.
cancelled (bool): Only valid if type == OpType.RECV_CLOSE_ON_SERVER, else
is None.
"""
class Event(collections.namedtuple(
'Event', [
'type',
'tag',
'call',
'call_details',
'results',
'success',
])):
"""An event received from a GRPC completion queue.
Attributes:
type (EventType): ...
tag (object): ...
call (Call): The Call object associated with this event (if there is one,
else None).
call_details (CallDetails): The call details associated with the
server-side call (if there is such information, else None).
results (list of OpResult): ...
success (bool): ...
"""
class CompletionQueue(six.with_metaclass(abc.ABCMeta)):
@abc.abstractmethod
def __init__(self):
pass
def __iter__(self):
"""This class may be iterated over.
This is the equivalent of calling next() repeatedly with an absolute
deadline of None (i.e. no deadline).
"""
return self
def __next__(self):
return self.next()
@abc.abstractmethod
def next(self, deadline=float('+inf')):
"""Get the next event on this completion queue.
Args:
deadline (float): absolute deadline in seconds from the Python epoch, or
None for no deadline.
Returns:
Event: ...
"""
pass
@abc.abstractmethod
def shutdown(self):
"""Begin the shutdown process of this completion queue.
Note that this does not immediately destroy the completion queue.
Nevertheless, user code should not pass it around after invoking this.
"""
return None
class Call(six.with_metaclass(abc.ABCMeta)):
@abc.abstractmethod
def start_batch(self, ops, tag):
"""Start a batch of operations.
Args:
ops (sequence of OpArgs): ...
tag (object): ...
Returns:
CallError: ...
"""
return CallError.ERROR
@abc.abstractmethod
def cancel(self, code=None, details=None):
"""Cancel the call.
Args:
code (int): Status code to cancel with (on the server side). If
specified, so must `details`.
details (str): Status details to cancel with (on the server side). If
specified, so must `code`.
Returns:
CallError: ...
"""
return CallError.ERROR
@abc.abstractmethod
def peer(self):
"""Get the peer of this call.
Returns:
str: the peer of this call.
"""
return None
def set_credentials(self, creds):
"""Set per-call credentials.
Args:
creds (CallCredentials): Credentials to be set for this call.
"""
return None
class Channel(six.with_metaclass(abc.ABCMeta)):
@abc.abstractmethod
def __init__(self, target, args, credentials=None):
"""Initialize a Channel.
Args:
target (str): ...
args (sequence of 2-sequence of str, (str|integer)): ...
credentials (ChannelCredentials): If None, create an insecure channel,
else create a secure channel using the client credentials.
"""
@abc.abstractmethod
def create_call(self, completion_queue, method, host, deadline=float('+inf')):
"""Create a call from this channel.
Args:
completion_queue (CompletionQueue): ...
method (str): ...
host (str): ...
deadline (float): absolute deadline in seconds from the Python epoch, or
None for no deadline.
Returns:
Call: call object associated with this Channel and passed parameters.
"""
return None
@abc.abstractmethod
def check_connectivity_state(self, try_to_connect):
"""Check and optionally repair the connectivity state of the channel.
Args:
try_to_connect (bool): whether or not to try to connect the channel if
disconnected.
Returns:
ConnectivityState: state of the channel at the time of this invocation.
"""
return None
@abc.abstractmethod
def watch_connectivity_state(self, last_observed_state, deadline,
completion_queue, tag):
"""Watch for connectivity state changes from the last_observed_state.
Args:
last_observed_state (ConnectivityState): ...
deadline (float): ...
completion_queue (CompletionQueue): ...
tag (object) ...
"""
@abc.abstractmethod
def target(self):
"""Get the target of this channel.
Returns:
str: the target of this channel.
"""
return None
class Server(six.with_metaclass(abc.ABCMeta)):
@abc.abstractmethod
def __init__(self, completion_queue, args):
"""Initialize a server.
Args:
completion_queue (CompletionQueue): ...
args (sequence of 2-sequence of str, (str|integer)): ...
"""
@abc.abstractmethod
def add_http2_port(self, address, credentials=None):
"""Adds an HTTP/2 address+port to the server.
Args:
address (str): ...
credentials (ServerCredentials): If None, create an insecure port, else
create a secure port using the server credentials.
"""
@abc.abstractmethod
def start(self):
"""Starts the server."""
@abc.abstractmethod
def shutdown(self, tag=None):
"""Shuts down the server. Does not immediately destroy the server.
Args:
tag (object): if not None, have the server place an event on its
completion queue notifying it when this server has completely shut down.
"""
@abc.abstractmethod
def request_call(self, completion_queue, tag):
"""Requests a call from the server on the server's completion queue.
Args:
completion_queue (CompletionQueue): Completion queue for the call. May be
the same as the server's completion queue.
tag (object) ...
"""

@ -1,30 +0,0 @@
# Copyright 2015, 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.

@ -1,42 +0,0 @@
# Copyright 2015, 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.
"""Constants for use within this package."""
from grpc._adapter import _intermediary_low
from grpc.beta import interfaces as beta_interfaces
LOW_STATUS_CODE_TO_HIGH_STATUS_CODE = {
low: high for low, high in zip(
_intermediary_low.Code, beta_interfaces.StatusCode)
}
HIGH_STATUS_CODE_TO_LOW_STATUS_CODE = {
high: low for low, high in LOW_STATUS_CODE_TO_HIGH_STATUS_CODE.items()
}

@ -1,453 +0,0 @@
# Copyright 2015, 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.
"""The RPC-invocation-side bridge between RPC Framework and GRPC-on-the-wire."""
import abc
import enum
import logging
import threading
import time
import six
from grpc._adapter import _intermediary_low
from grpc._links import _constants
from grpc.beta import interfaces as beta_interfaces
from grpc.framework.foundation import activated
from grpc.framework.foundation import logging_pool
from grpc.framework.foundation import relay
from grpc.framework.interfaces.links import links
_IDENTITY = lambda x: x
_STOP = _intermediary_low.Event.Kind.STOP
_WRITE = _intermediary_low.Event.Kind.WRITE_ACCEPTED
_COMPLETE = _intermediary_low.Event.Kind.COMPLETE_ACCEPTED
_READ = _intermediary_low.Event.Kind.READ_ACCEPTED
_METADATA = _intermediary_low.Event.Kind.METADATA_ACCEPTED
_FINISH = _intermediary_low.Event.Kind.FINISH
@enum.unique
class _Read(enum.Enum):
AWAITING_METADATA = 'awaiting metadata'
READING = 'reading'
AWAITING_ALLOWANCE = 'awaiting allowance'
CLOSED = 'closed'
@enum.unique
class _HighWrite(enum.Enum):
OPEN = 'open'
CLOSED = 'closed'
@enum.unique
class _LowWrite(enum.Enum):
OPEN = 'OPEN'
ACTIVE = 'ACTIVE'
CLOSED = 'CLOSED'
class _Context(beta_interfaces.GRPCInvocationContext):
def __init__(self):
self._lock = threading.Lock()
self._disable_next_compression = False
def disable_next_request_compression(self):
with self._lock:
self._disable_next_compression = True
def next_compression_disabled(self):
with self._lock:
disabled = self._disable_next_compression
self._disable_next_compression = False
return disabled
class _RPCState(object):
def __init__(
self, call, request_serializer, response_deserializer, sequence_number,
read, allowance, high_write, low_write, due, context):
self.call = call
self.request_serializer = request_serializer
self.response_deserializer = response_deserializer
self.sequence_number = sequence_number
self.read = read
self.allowance = allowance
self.high_write = high_write
self.low_write = low_write
self.due = due
self.context = context
def _no_longer_due(kind, rpc_state, key, rpc_states):
rpc_state.due.remove(kind)
if not rpc_state.due:
del rpc_states[key]
class _Kernel(object):
def __init__(
self, channel, host, metadata_transformer, request_serializers,
response_deserializers, ticket_relay):
self._lock = threading.Lock()
self._channel = channel
self._host = host
self._metadata_transformer = metadata_transformer
self._request_serializers = request_serializers
self._response_deserializers = response_deserializers
self._relay = ticket_relay
self._completion_queue = None
self._rpc_states = {}
self._pool = None
def _on_write_event(self, operation_id, unused_event, rpc_state):
if rpc_state.high_write is _HighWrite.CLOSED:
rpc_state.call.complete(operation_id)
rpc_state.due.add(_COMPLETE)
rpc_state.due.remove(_WRITE)
rpc_state.low_write = _LowWrite.CLOSED
else:
ticket = links.Ticket(
operation_id, rpc_state.sequence_number, None, None, None, None, 1,
None, None, None, None, None, None, None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
rpc_state.low_write = _LowWrite.OPEN
_no_longer_due(_WRITE, rpc_state, operation_id, self._rpc_states)
def _on_read_event(self, operation_id, event, rpc_state):
if event.bytes is None or _FINISH not in rpc_state.due:
rpc_state.read = _Read.CLOSED
_no_longer_due(_READ, rpc_state, operation_id, self._rpc_states)
else:
if 0 < rpc_state.allowance:
rpc_state.allowance -= 1
rpc_state.call.read(operation_id)
else:
rpc_state.read = _Read.AWAITING_ALLOWANCE
_no_longer_due(_READ, rpc_state, operation_id, self._rpc_states)
ticket = links.Ticket(
operation_id, rpc_state.sequence_number, None, None, None, None, None,
None, rpc_state.response_deserializer(event.bytes), None, None, None,
None, None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
def _on_metadata_event(self, operation_id, event, rpc_state):
if _FINISH in rpc_state.due:
rpc_state.allowance -= 1
rpc_state.call.read(operation_id)
rpc_state.read = _Read.READING
rpc_state.due.add(_READ)
rpc_state.due.remove(_METADATA)
ticket = links.Ticket(
operation_id, rpc_state.sequence_number, None, None,
links.Ticket.Subscription.FULL, None, None, event.metadata, None,
None, None, None, None, None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
else:
_no_longer_due(_METADATA, rpc_state, operation_id, self._rpc_states)
def _on_finish_event(self, operation_id, event, rpc_state):
_no_longer_due(_FINISH, rpc_state, operation_id, self._rpc_states)
if event.status.code == _intermediary_low.Code.OK:
termination = links.Ticket.Termination.COMPLETION
elif event.status.code == _intermediary_low.Code.CANCELLED:
termination = links.Ticket.Termination.CANCELLATION
elif event.status.code == _intermediary_low.Code.DEADLINE_EXCEEDED:
termination = links.Ticket.Termination.EXPIRATION
elif event.status.code == _intermediary_low.Code.UNIMPLEMENTED:
termination = links.Ticket.Termination.REMOTE_FAILURE
elif event.status.code == _intermediary_low.Code.UNKNOWN:
termination = links.Ticket.Termination.LOCAL_FAILURE
else:
termination = links.Ticket.Termination.TRANSMISSION_FAILURE
code = _constants.LOW_STATUS_CODE_TO_HIGH_STATUS_CODE[event.status.code]
ticket = links.Ticket(
operation_id, rpc_state.sequence_number, None, None, None, None, None,
None, None, event.metadata, code, event.status.details, termination,
None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
def _spin(self, completion_queue):
while True:
event = completion_queue.get(None)
with self._lock:
rpc_state = self._rpc_states.get(event.tag, None)
if event.kind is _STOP:
pass
elif event.kind is _WRITE:
self._on_write_event(event.tag, event, rpc_state)
elif event.kind is _METADATA:
self._on_metadata_event(event.tag, event, rpc_state)
elif event.kind is _READ:
self._on_read_event(event.tag, event, rpc_state)
elif event.kind is _FINISH:
self._on_finish_event(event.tag, event, rpc_state)
elif event.kind is _COMPLETE:
_no_longer_due(_COMPLETE, rpc_state, event.tag, self._rpc_states)
else:
logging.error('Illegal RPC event! %s', (event,))
if self._completion_queue is None and not self._rpc_states:
completion_queue.stop()
return
def _invoke(
self, operation_id, group, method, initial_metadata, payload, termination,
timeout, allowance, options):
"""Invoke an RPC.
Args:
operation_id: Any object to be used as an operation ID for the RPC.
group: The group to which the RPC method belongs.
method: The RPC method name.
initial_metadata: The initial metadata object for the RPC.
payload: A payload object for the RPC or None if no payload was given at
invocation-time.
termination: A links.Ticket.Termination value or None indicated whether or
not more writes will follow from this side of the RPC.
timeout: A duration of time in seconds to allow for the RPC.
allowance: The number of payloads (beyond the free first one) that the
local ticket exchange mate has granted permission to be read.
options: A beta_interfaces.GRPCCallOptions value or None.
"""
if termination is links.Ticket.Termination.COMPLETION:
high_write = _HighWrite.CLOSED
elif termination is None:
high_write = _HighWrite.OPEN
else:
return
transformed_initial_metadata = self._metadata_transformer(initial_metadata)
request_serializer = self._request_serializers.get(
(group, method), _IDENTITY)
response_deserializer = self._response_deserializers.get(
(group, method), _IDENTITY)
call = _intermediary_low.Call(
self._channel, self._completion_queue, '/%s/%s' % (group, method),
self._host, time.time() + timeout)
if options is not None and options.credentials is not None:
call.set_credentials(options.credentials._low_credentials)
if transformed_initial_metadata is not None:
for metadata_key, metadata_value in transformed_initial_metadata:
call.add_metadata(metadata_key, metadata_value)
call.invoke(self._completion_queue, operation_id, operation_id)
if payload is None:
if high_write is _HighWrite.CLOSED:
call.complete(operation_id)
low_write = _LowWrite.CLOSED
due = set((_METADATA, _COMPLETE, _FINISH,))
else:
low_write = _LowWrite.OPEN
due = set((_METADATA, _FINISH,))
else:
if options is not None and options.disable_compression:
flags = _intermediary_low.WriteFlags.WRITE_NO_COMPRESS
else:
flags = 0
call.write(request_serializer(payload), operation_id, flags)
low_write = _LowWrite.ACTIVE
due = set((_WRITE, _METADATA, _FINISH,))
context = _Context()
self._rpc_states[operation_id] = _RPCState(
call, request_serializer, response_deserializer, 1,
_Read.AWAITING_METADATA, 1 if allowance is None else (1 + allowance),
high_write, low_write, due, context)
protocol = links.Protocol(links.Protocol.Kind.INVOCATION_CONTEXT, context)
ticket = links.Ticket(
operation_id, 0, None, None, None, None, None, None, None, None, None,
None, None, protocol)
self._relay.add_value(ticket)
def _advance(self, operation_id, rpc_state, payload, termination, allowance):
if payload is not None:
disable_compression = rpc_state.context.next_compression_disabled()
if disable_compression:
flags = _intermediary_low.WriteFlags.WRITE_NO_COMPRESS
else:
flags = 0
rpc_state.call.write(
rpc_state.request_serializer(payload), operation_id, flags)
rpc_state.low_write = _LowWrite.ACTIVE
rpc_state.due.add(_WRITE)
if allowance is not None:
if rpc_state.read is _Read.AWAITING_ALLOWANCE:
rpc_state.allowance += allowance - 1
rpc_state.call.read(operation_id)
rpc_state.read = _Read.READING
rpc_state.due.add(_READ)
else:
rpc_state.allowance += allowance
if termination is links.Ticket.Termination.COMPLETION:
rpc_state.high_write = _HighWrite.CLOSED
if rpc_state.low_write is _LowWrite.OPEN:
rpc_state.call.complete(operation_id)
rpc_state.due.add(_COMPLETE)
rpc_state.low_write = _LowWrite.CLOSED
elif termination is not None:
rpc_state.call.cancel()
def add_ticket(self, ticket):
with self._lock:
if ticket.sequence_number == 0:
if self._completion_queue is None:
logging.error('Received invocation ticket %s after stop!', ticket)
else:
if (ticket.protocol is not None and
ticket.protocol.kind is links.Protocol.Kind.CALL_OPTION):
grpc_call_options = ticket.protocol.value
else:
grpc_call_options = None
self._invoke(
ticket.operation_id, ticket.group, ticket.method,
ticket.initial_metadata, ticket.payload, ticket.termination,
ticket.timeout, ticket.allowance, grpc_call_options)
else:
rpc_state = self._rpc_states.get(ticket.operation_id)
if rpc_state is not None:
self._advance(
ticket.operation_id, rpc_state, ticket.payload,
ticket.termination, ticket.allowance)
def start(self):
"""Starts this object.
This method must be called before attempting to exchange tickets with this
object.
"""
with self._lock:
self._completion_queue = _intermediary_low.CompletionQueue()
self._pool = logging_pool.pool(1)
self._pool.submit(self._spin, self._completion_queue)
def stop(self):
"""Stops this object.
This method must be called for proper termination of this object, and no
attempts to exchange tickets with this object may be made after this method
has been called.
"""
with self._lock:
if not self._rpc_states:
self._completion_queue.stop()
self._completion_queue = None
pool = self._pool
pool.shutdown(wait=True)
class InvocationLink(six.with_metaclass(abc.ABCMeta, links.Link, activated.Activated)):
"""A links.Link for use on the invocation-side of a gRPC connection.
Implementations of this interface are only valid for use when activated.
"""
class _InvocationLink(InvocationLink):
def __init__(
self, channel, host, metadata_transformer, request_serializers,
response_deserializers):
self._relay = relay.relay(None)
self._kernel = _Kernel(
channel, host,
_IDENTITY if metadata_transformer is None else metadata_transformer,
{} if request_serializers is None else request_serializers,
{} if response_deserializers is None else response_deserializers,
self._relay)
def _start(self):
self._relay.start()
self._kernel.start()
return self
def _stop(self):
self._kernel.stop()
self._relay.stop()
def accept_ticket(self, ticket):
"""See links.Link.accept_ticket for specification."""
self._kernel.add_ticket(ticket)
def join_link(self, link):
"""See links.Link.join_link for specification."""
self._relay.set_behavior(link.accept_ticket)
def __enter__(self):
"""See activated.Activated.__enter__ for specification."""
return self._start()
def __exit__(self, exc_type, exc_val, exc_tb):
"""See activated.Activated.__exit__ for specification."""
self._stop()
return False
def start(self):
"""See activated.Activated.start for specification."""
return self._start()
def stop(self):
"""See activated.Activated.stop for specification."""
self._stop()
def invocation_link(
channel, host, metadata_transformer, request_serializers,
response_deserializers):
"""Creates an InvocationLink.
Args:
channel: An _intermediary_low.Channel for use by the link.
host: The host to specify when invoking RPCs.
metadata_transformer: A callable that takes an invocation-side initial
metadata value and returns another metadata value to send in its place.
May be None.
request_serializers: A dict from group-method pair to request object
serialization behavior.
response_deserializers: A dict from group-method pair to response object
deserialization behavior.
Returns:
An InvocationLink.
"""
return _InvocationLink(
channel, host, metadata_transformer, request_serializers,
response_deserializers)

@ -1,509 +0,0 @@
# Copyright 2015, 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.
"""The RPC-service-side bridge between RPC Framework and GRPC-on-the-wire."""
import abc
import enum
import logging
import threading
import six
import time
from grpc._adapter import _intermediary_low
from grpc._links import _constants
from grpc.beta import interfaces as beta_interfaces
from grpc.framework.foundation import logging_pool
from grpc.framework.foundation import relay
from grpc.framework.interfaces.links import links
_IDENTITY = lambda x: x
_TERMINATION_KIND_TO_CODE = {
links.Ticket.Termination.COMPLETION: _intermediary_low.Code.OK,
links.Ticket.Termination.CANCELLATION: _intermediary_low.Code.CANCELLED,
links.Ticket.Termination.EXPIRATION:
_intermediary_low.Code.DEADLINE_EXCEEDED,
links.Ticket.Termination.SHUTDOWN: _intermediary_low.Code.UNAVAILABLE,
links.Ticket.Termination.RECEPTION_FAILURE: _intermediary_low.Code.INTERNAL,
links.Ticket.Termination.TRANSMISSION_FAILURE:
_intermediary_low.Code.INTERNAL,
links.Ticket.Termination.LOCAL_FAILURE: _intermediary_low.Code.UNKNOWN,
links.Ticket.Termination.REMOTE_FAILURE: _intermediary_low.Code.UNKNOWN,
}
_STOP = _intermediary_low.Event.Kind.STOP
_WRITE = _intermediary_low.Event.Kind.WRITE_ACCEPTED
_COMPLETE = _intermediary_low.Event.Kind.COMPLETE_ACCEPTED
_SERVICE = _intermediary_low.Event.Kind.SERVICE_ACCEPTED
_READ = _intermediary_low.Event.Kind.READ_ACCEPTED
_FINISH = _intermediary_low.Event.Kind.FINISH
@enum.unique
class _Read(enum.Enum):
READING = 'reading'
# TODO(issue 2916): This state will again be necessary after eliminating the
# "early_read" field of _RPCState and going back to only reading when granted
# allowance to read.
# AWAITING_ALLOWANCE = 'awaiting allowance'
CLOSED = 'closed'
@enum.unique
class _HighWrite(enum.Enum):
OPEN = 'open'
CLOSED = 'closed'
@enum.unique
class _LowWrite(enum.Enum):
"""The possible categories of low-level write state."""
OPEN = 'OPEN'
ACTIVE = 'ACTIVE'
CLOSED = 'CLOSED'
class _Context(beta_interfaces.GRPCServicerContext):
def __init__(self, call):
self._lock = threading.Lock()
self._call = call
self._disable_next_compression = False
def peer(self):
with self._lock:
return self._call.peer()
def disable_next_response_compression(self):
with self._lock:
self._disable_next_compression = True
def next_compression_disabled(self):
with self._lock:
disabled = self._disable_next_compression
self._disable_next_compression = False
return disabled
class _RPCState(object):
def __init__(
self, request_deserializer, response_serializer, sequence_number, read,
early_read, allowance, high_write, low_write, premetadataed,
terminal_metadata, code, message, due, context):
self.request_deserializer = request_deserializer
self.response_serializer = response_serializer
self.sequence_number = sequence_number
self.read = read
# TODO(issue 2916): Eliminate this by eliminating the necessity of calling
# call.read just to advance the RPC.
self.early_read = early_read # A raw (not deserialized) read.
self.allowance = allowance
self.high_write = high_write
self.low_write = low_write
self.premetadataed = premetadataed
self.terminal_metadata = terminal_metadata
self.code = code
self.message = message
self.due = due
self.context = context
def _no_longer_due(kind, rpc_state, key, rpc_states):
rpc_state.due.remove(kind)
if not rpc_state.due:
del rpc_states[key]
def _metadatafy(call, metadata):
for metadata_key, metadata_value in metadata:
call.add_metadata(metadata_key, metadata_value)
def _status(termination_kind, high_code, details):
low_details = b'' if details is None else details
if high_code is None:
low_code = _TERMINATION_KIND_TO_CODE[termination_kind]
else:
low_code = _constants.HIGH_STATUS_CODE_TO_LOW_STATUS_CODE[high_code]
return _intermediary_low.Status(low_code, low_details)
class _Kernel(object):
def __init__(self, request_deserializers, response_serializers, ticket_relay):
self._lock = threading.Lock()
self._request_deserializers = request_deserializers
self._response_serializers = response_serializers
self._relay = ticket_relay
self._completion_queue = None
self._due = set()
self._server = None
self._rpc_states = {}
self._pool = None
def _on_service_acceptance_event(self, event, server):
server.service(None)
service_acceptance = event.service_acceptance
call = service_acceptance.call
call.accept(self._completion_queue, call)
try:
service_method = service_acceptance.method
if six.PY3:
service_method = service_method.decode('latin1')
group, method = service_method.split('/')[1:3]
except ValueError:
logging.info('Illegal path "%s"!', service_acceptance.method)
return
request_deserializer = self._request_deserializers.get(
(group, method), _IDENTITY)
response_serializer = self._response_serializers.get(
(group, method), _IDENTITY)
call.read(call)
context = _Context(call)
self._rpc_states[call] = _RPCState(
request_deserializer, response_serializer, 1, _Read.READING, None, 1,
_HighWrite.OPEN, _LowWrite.OPEN, False, None, None, None,
set((_READ, _FINISH,)), context)
protocol = links.Protocol(links.Protocol.Kind.SERVICER_CONTEXT, context)
ticket = links.Ticket(
call, 0, group, method, links.Ticket.Subscription.FULL,
service_acceptance.deadline - time.time(), None, event.metadata, None,
None, None, None, None, protocol)
self._relay.add_value(ticket)
def _on_read_event(self, event):
call = event.tag
rpc_state = self._rpc_states[call]
if event.bytes is None:
rpc_state.read = _Read.CLOSED
payload = None
termination = links.Ticket.Termination.COMPLETION
_no_longer_due(_READ, rpc_state, call, self._rpc_states)
else:
if 0 < rpc_state.allowance:
payload = rpc_state.request_deserializer(event.bytes)
termination = None
rpc_state.allowance -= 1
call.read(call)
else:
rpc_state.early_read = event.bytes
_no_longer_due(_READ, rpc_state, call, self._rpc_states)
return
# TODO(issue 2916): Instead of returning:
# rpc_state.read = _Read.AWAITING_ALLOWANCE
ticket = links.Ticket(
call, rpc_state.sequence_number, None, None, None, None, None, None,
payload, None, None, None, termination, None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
def _on_write_event(self, event):
call = event.tag
rpc_state = self._rpc_states[call]
if rpc_state.high_write is _HighWrite.CLOSED:
if rpc_state.terminal_metadata is not None:
_metadatafy(call, rpc_state.terminal_metadata)
status = _status(
links.Ticket.Termination.COMPLETION, rpc_state.code,
rpc_state.message)
call.status(status, call)
rpc_state.low_write = _LowWrite.CLOSED
rpc_state.due.add(_COMPLETE)
rpc_state.due.remove(_WRITE)
else:
ticket = links.Ticket(
call, rpc_state.sequence_number, None, None, None, None, 1, None,
None, None, None, None, None, None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
rpc_state.low_write = _LowWrite.OPEN
_no_longer_due(_WRITE, rpc_state, call, self._rpc_states)
def _on_finish_event(self, event):
call = event.tag
rpc_state = self._rpc_states[call]
_no_longer_due(_FINISH, rpc_state, call, self._rpc_states)
code = event.status.code
if code == _intermediary_low.Code.OK:
return
if code == _intermediary_low.Code.CANCELLED:
termination = links.Ticket.Termination.CANCELLATION
elif code == _intermediary_low.Code.DEADLINE_EXCEEDED:
termination = links.Ticket.Termination.EXPIRATION
else:
termination = links.Ticket.Termination.TRANSMISSION_FAILURE
ticket = links.Ticket(
call, rpc_state.sequence_number, None, None, None, None, None, None,
None, None, None, None, termination, None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
def _spin(self, completion_queue, server):
while True:
event = completion_queue.get(None)
with self._lock:
if event.kind is _STOP:
self._due.remove(_STOP)
elif event.kind is _READ:
self._on_read_event(event)
elif event.kind is _WRITE:
self._on_write_event(event)
elif event.kind is _COMPLETE:
_no_longer_due(
_COMPLETE, self._rpc_states.get(event.tag), event.tag,
self._rpc_states)
elif event.kind is _intermediary_low.Event.Kind.FINISH:
self._on_finish_event(event)
elif event.kind is _SERVICE:
if self._server is None:
self._due.remove(_SERVICE)
else:
self._on_service_acceptance_event(event, server)
else:
logging.error('Illegal event! %s', (event,))
if not self._due and not self._rpc_states:
completion_queue.stop()
return
def add_ticket(self, ticket):
with self._lock:
call = ticket.operation_id
rpc_state = self._rpc_states.get(call)
if rpc_state is None:
return
if ticket.initial_metadata is not None:
_metadatafy(call, ticket.initial_metadata)
call.premetadata()
rpc_state.premetadataed = True
elif not rpc_state.premetadataed:
if (ticket.terminal_metadata is not None or
ticket.payload is not None or
ticket.termination is not None or
ticket.code is not None or
ticket.message is not None):
call.premetadata()
rpc_state.premetadataed = True
if ticket.allowance is not None:
if rpc_state.early_read is None:
rpc_state.allowance += ticket.allowance
else:
payload = rpc_state.request_deserializer(rpc_state.early_read)
rpc_state.allowance += ticket.allowance - 1
rpc_state.early_read = None
if rpc_state.read is _Read.READING:
call.read(call)
rpc_state.due.add(_READ)
termination = None
else:
termination = links.Ticket.Termination.COMPLETION
early_read_ticket = links.Ticket(
call, rpc_state.sequence_number, None, None, None, None, None,
None, payload, None, None, None, termination, None)
rpc_state.sequence_number += 1
self._relay.add_value(early_read_ticket)
if ticket.payload is not None:
disable_compression = rpc_state.context.next_compression_disabled()
if disable_compression:
flags = _intermediary_low.WriteFlags.WRITE_NO_COMPRESS
else:
flags = 0
call.write(rpc_state.response_serializer(ticket.payload), call, flags)
rpc_state.due.add(_WRITE)
rpc_state.low_write = _LowWrite.ACTIVE
if ticket.terminal_metadata is not None:
rpc_state.terminal_metadata = ticket.terminal_metadata
if ticket.code is not None:
rpc_state.code = ticket.code
if ticket.message is not None:
rpc_state.message = ticket.message
if ticket.termination is links.Ticket.Termination.COMPLETION:
rpc_state.high_write = _HighWrite.CLOSED
if rpc_state.low_write is _LowWrite.OPEN:
if rpc_state.terminal_metadata is not None:
_metadatafy(call, rpc_state.terminal_metadata)
status = _status(
links.Ticket.Termination.COMPLETION, rpc_state.code,
rpc_state.message)
call.status(status, call)
rpc_state.due.add(_COMPLETE)
rpc_state.low_write = _LowWrite.CLOSED
elif ticket.termination is not None:
if rpc_state.terminal_metadata is not None:
_metadatafy(call, rpc_state.terminal_metadata)
status = _status(
ticket.termination, rpc_state.code, rpc_state.message)
call.status(status, call)
rpc_state.due.add(_COMPLETE)
def add_port(self, address, server_credentials):
with self._lock:
if self._server is None:
self._completion_queue = _intermediary_low.CompletionQueue()
self._server = _intermediary_low.Server(self._completion_queue)
if server_credentials is None:
return self._server.add_http2_addr(address)
else:
return self._server.add_secure_http2_addr(address, server_credentials)
def start(self):
with self._lock:
if self._server is None:
self._completion_queue = _intermediary_low.CompletionQueue()
self._server = _intermediary_low.Server(self._completion_queue)
self._pool = logging_pool.pool(1)
self._pool.submit(self._spin, self._completion_queue, self._server)
self._server.start()
self._server.service(None)
self._due.add(_SERVICE)
def begin_stop(self):
with self._lock:
self._server.stop()
self._due.add(_STOP)
self._server = None
def end_stop(self):
with self._lock:
pool = self._pool
pool.shutdown(wait=True)
class ServiceLink(links.Link):
"""A links.Link for use on the service-side of a gRPC connection.
Implementations of this interface are only valid for use between calls to
their start method and one of their stop methods.
"""
@abc.abstractmethod
def add_port(self, address, server_credentials):
"""Adds a port on which to service RPCs after this link has been started.
Args:
address: The address on which to service RPCs with a port number of zero
requesting that a port number be automatically selected and used.
server_credentials: An _intermediary_low.ServerCredentials object, or
None for insecure service.
Returns:
An integer port on which RPCs will be serviced after this link has been
started. This is typically the same number as the port number contained
in the passed address, but will likely be different if the port number
contained in the passed address was zero.
"""
raise NotImplementedError()
@abc.abstractmethod
def start(self):
"""Starts this object.
This method must be called before attempting to use this Link in ticket
exchange.
"""
raise NotImplementedError()
@abc.abstractmethod
def begin_stop(self):
"""Indicate imminent link stop and immediate rejection of new RPCs.
New RPCs will be rejected as soon as this method is called, but ongoing RPCs
will be allowed to continue until they terminate. This method does not
block.
"""
raise NotImplementedError()
@abc.abstractmethod
def end_stop(self):
"""Finishes stopping this link.
begin_stop must have been called exactly once before calling this method.
All in-progress RPCs will be terminated immediately.
"""
raise NotImplementedError()
class _ServiceLink(ServiceLink):
def __init__(self, request_deserializers, response_serializers):
self._relay = relay.relay(None)
self._kernel = _Kernel(
{} if request_deserializers is None else request_deserializers,
{} if response_serializers is None else response_serializers,
self._relay)
def accept_ticket(self, ticket):
self._kernel.add_ticket(ticket)
def join_link(self, link):
self._relay.set_behavior(link.accept_ticket)
def add_port(self, address, server_credentials):
return self._kernel.add_port(address, server_credentials)
def start(self):
self._relay.start()
return self._kernel.start()
def begin_stop(self):
self._kernel.begin_stop()
def end_stop(self):
self._kernel.end_stop()
self._relay.stop()
def service_link(request_deserializers, response_serializers):
"""Creates a ServiceLink.
Args:
request_deserializers: A dict from group-method pair to request object
deserialization behavior.
response_serializers: A dict from group-method pair to response ojbect
serialization behavior.
Returns:
A ServiceLink.
"""
return _ServiceLink(request_deserializers, response_serializers)

@ -1,209 +0,0 @@
# Copyright 2015, 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.
"""Beta API server implementation."""
import threading
from grpc._links import service
from grpc.beta import interfaces
from grpc.framework.core import implementations as _core_implementations
from grpc.framework.crust import implementations as _crust_implementations
from grpc.framework.foundation import logging_pool
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.links import utilities
_DEFAULT_POOL_SIZE = 8
_DEFAULT_TIMEOUT = 300
_MAXIMUM_TIMEOUT = 24 * 60 * 60
def _set_event():
event = threading.Event()
event.set()
return event
class _GRPCServicer(base.Servicer):
def __init__(self, delegate):
self._delegate = delegate
def service(self, group, method, context, output_operator):
try:
return self._delegate.service(group, method, context, output_operator)
except base.NoSuchMethodError as e:
if e.code is None and e.details is None:
raise base.NoSuchMethodError(
interfaces.StatusCode.UNIMPLEMENTED,
'Method "%s" of service "%s" not implemented!' % (method, group))
else:
raise
class _Server(interfaces.Server):
def __init__(
self, implementations, multi_implementation, pool, pool_size,
default_timeout, maximum_timeout, grpc_link):
self._lock = threading.Lock()
self._implementations = implementations
self._multi_implementation = multi_implementation
self._customer_pool = pool
self._pool_size = pool_size
self._default_timeout = default_timeout
self._maximum_timeout = maximum_timeout
self._grpc_link = grpc_link
self._end_link = None
self._stop_events = None
self._pool = None
def _start(self):
with self._lock:
if self._end_link is not None:
raise ValueError('Cannot start already-started server!')
if self._customer_pool is None:
self._pool = logging_pool.pool(self._pool_size)
assembly_pool = self._pool
else:
assembly_pool = self._customer_pool
servicer = _GRPCServicer(
_crust_implementations.servicer(
self._implementations, self._multi_implementation, assembly_pool))
self._end_link = _core_implementations.service_end_link(
servicer, self._default_timeout, self._maximum_timeout)
self._grpc_link.join_link(self._end_link)
self._end_link.join_link(self._grpc_link)
self._grpc_link.start()
self._end_link.start()
def _dissociate_links_and_shut_down_pool(self):
self._grpc_link.end_stop()
self._grpc_link.join_link(utilities.NULL_LINK)
self._end_link.join_link(utilities.NULL_LINK)
self._end_link = None
if self._pool is not None:
self._pool.shutdown(wait=True)
self._pool = None
def _stop_stopping(self):
self._dissociate_links_and_shut_down_pool()
for stop_event in self._stop_events:
stop_event.set()
self._stop_events = None
def _stop_started(self):
self._grpc_link.begin_stop()
self._end_link.stop(0).wait()
self._dissociate_links_and_shut_down_pool()
def _foreign_thread_stop(self, end_stop_event, stop_events):
end_stop_event.wait()
with self._lock:
if self._stop_events is stop_events:
self._stop_stopping()
def _schedule_stop(self, grace):
with self._lock:
if self._end_link is None:
return _set_event()
server_stop_event = threading.Event()
if self._stop_events is None:
self._stop_events = [server_stop_event]
self._grpc_link.begin_stop()
else:
self._stop_events.append(server_stop_event)
end_stop_event = self._end_link.stop(grace)
end_stop_thread = threading.Thread(
target=self._foreign_thread_stop,
args=(end_stop_event, self._stop_events))
end_stop_thread.start()
return server_stop_event
def _stop_now(self):
with self._lock:
if self._end_link is not None:
if self._stop_events is None:
self._stop_started()
else:
self._stop_stopping()
def add_insecure_port(self, address):
with self._lock:
if self._end_link is None:
return self._grpc_link.add_port(address, None)
else:
raise ValueError('Can\'t add port to serving server!')
def add_secure_port(self, address, server_credentials):
with self._lock:
if self._end_link is None:
return self._grpc_link.add_port(
address, server_credentials._low_credentials) # pylint: disable=protected-access
else:
raise ValueError('Can\'t add port to serving server!')
def start(self):
self._start()
def stop(self, grace):
if 0 < grace:
return self._schedule_stop(grace)
else:
self._stop_now()
return _set_event()
def __enter__(self):
self._start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._stop_now()
return False
def __del__(self):
self._stop_now()
def server(
implementations, multi_implementation, request_deserializers,
response_serializers, thread_pool, thread_pool_size, default_timeout,
maximum_timeout):
grpc_link = service.service_link(request_deserializers, response_serializers)
return _Server(
implementations, multi_implementation, thread_pool,
_DEFAULT_POOL_SIZE if thread_pool_size is None else thread_pool_size,
_DEFAULT_TIMEOUT if default_timeout is None else default_timeout,
_MAXIMUM_TIMEOUT if maximum_timeout is None else maximum_timeout,
grpc_link)

@ -1,155 +0,0 @@
# Copyright 2015, 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.
"""Beta API stub implementation."""
import threading
from grpc._links import invocation
from grpc.framework.core import implementations as _core_implementations
from grpc.framework.crust import implementations as _crust_implementations
from grpc.framework.foundation import logging_pool
from grpc.framework.interfaces.links import utilities
_DEFAULT_POOL_SIZE = 6
class _AutoIntermediary(object):
def __init__(self, up, down, delegate):
self._lock = threading.Lock()
self._up = up
self._down = down
self._in_context = False
self._delegate = delegate
def __getattr__(self, attr):
with self._lock:
if self._delegate is None:
raise AttributeError('No useful attributes out of context!')
else:
return getattr(self._delegate, attr)
def __enter__(self):
with self._lock:
if self._in_context:
raise ValueError('Already in context!')
elif self._delegate is None:
self._delegate = self._up()
self._in_context = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
with self._lock:
if not self._in_context:
raise ValueError('Not in context!')
self._down()
self._in_context = False
self._delegate = None
return False
def __del__(self):
with self._lock:
if self._delegate is not None:
self._down()
self._delegate = None
class _StubAssemblyManager(object):
def __init__(
self, thread_pool, thread_pool_size, end_link, grpc_link, stub_creator):
self._thread_pool = thread_pool
self._pool_size = thread_pool_size
self._end_link = end_link
self._grpc_link = grpc_link
self._stub_creator = stub_creator
self._own_pool = None
def up(self):
if self._thread_pool is None:
self._own_pool = logging_pool.pool(
_DEFAULT_POOL_SIZE if self._pool_size is None else self._pool_size)
assembly_pool = self._own_pool
else:
assembly_pool = self._thread_pool
self._end_link.join_link(self._grpc_link)
self._grpc_link.join_link(self._end_link)
self._end_link.start()
self._grpc_link.start()
return self._stub_creator(self._end_link, assembly_pool)
def down(self):
self._end_link.stop(0).wait()
self._grpc_link.stop()
self._end_link.join_link(utilities.NULL_LINK)
self._grpc_link.join_link(utilities.NULL_LINK)
if self._own_pool is not None:
self._own_pool.shutdown(wait=True)
self._own_pool = None
def _assemble(
channel, host, metadata_transformer, request_serializers,
response_deserializers, thread_pool, thread_pool_size, stub_creator):
end_link = _core_implementations.invocation_end_link()
grpc_link = invocation.invocation_link(
channel, host, metadata_transformer, request_serializers,
response_deserializers)
stub_assembly_manager = _StubAssemblyManager(
thread_pool, thread_pool_size, end_link, grpc_link, stub_creator)
stub = stub_assembly_manager.up()
return _AutoIntermediary(
stub_assembly_manager.up, stub_assembly_manager.down, stub)
def _dynamic_stub_creator(service, cardinalities):
def create_dynamic_stub(end_link, invocation_pool):
return _crust_implementations.dynamic_stub(
end_link, service, cardinalities, invocation_pool)
return create_dynamic_stub
def generic_stub(
channel, host, metadata_transformer, request_serializers,
response_deserializers, thread_pool, thread_pool_size):
return _assemble(
channel, host, metadata_transformer, request_serializers,
response_deserializers, thread_pool, thread_pool_size,
_crust_implementations.generic_stub)
def dynamic_stub(
channel, host, service, cardinalities, metadata_transformer,
request_serializers, response_deserializers, thread_pool,
thread_pool_size):
return _assemble(
channel, host, metadata_transformer, request_serializers,
response_deserializers, thread_pool, thread_pool_size,
_dynamic_stub_creator(service, cardinalities))

@ -37,7 +37,6 @@ import threading # pylint: disable=unused-import
# cardinality and face are referenced from specification in this module.
import grpc
from grpc import _auth
from grpc._adapter import _types
from grpc.beta import _client_adaptations
from grpc.beta import _server_adaptations
from grpc.beta import interfaces

@ -1,30 +0,0 @@
# Copyright 2015, 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.

@ -1,60 +0,0 @@
# Copyright 2015, 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.
"""Private constants for the package."""
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.links import links
TICKET_SUBSCRIPTION_FOR_BASE_SUBSCRIPTION_KIND = {
base.Subscription.Kind.NONE: links.Ticket.Subscription.NONE,
base.Subscription.Kind.TERMINATION_ONLY:
links.Ticket.Subscription.TERMINATION,
base.Subscription.Kind.FULL: links.Ticket.Subscription.FULL,
}
# Mapping from abortive operation outcome to ticket termination to be
# sent to the other side of the operation, or None to indicate that no
# ticket should be sent to the other side in the event of such an
# outcome.
ABORTION_OUTCOME_TO_TICKET_TERMINATION = {
base.Outcome.Kind.CANCELLED: links.Ticket.Termination.CANCELLATION,
base.Outcome.Kind.EXPIRED: links.Ticket.Termination.EXPIRATION,
base.Outcome.Kind.LOCAL_SHUTDOWN: links.Ticket.Termination.SHUTDOWN,
base.Outcome.Kind.REMOTE_SHUTDOWN: None,
base.Outcome.Kind.RECEPTION_FAILURE:
links.Ticket.Termination.RECEPTION_FAILURE,
base.Outcome.Kind.TRANSMISSION_FAILURE: None,
base.Outcome.Kind.LOCAL_FAILURE: links.Ticket.Termination.LOCAL_FAILURE,
base.Outcome.Kind.REMOTE_FAILURE: links.Ticket.Termination.REMOTE_FAILURE,
}
INTERNAL_ERROR_LOG_MESSAGE = ':-( RPC Framework (Core) internal error! )-:'
TERMINATION_CALLBACK_EXCEPTION_LOG_MESSAGE = (
'Exception calling termination callback!')

@ -1,94 +0,0 @@
# Copyright 2015, 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.
"""State and behavior for operation context."""
import time
# _interfaces is referenced from specification in this module.
from grpc.framework.core import _interfaces # pylint: disable=unused-import
from grpc.framework.core import _utilities
from grpc.framework.interfaces.base import base
class OperationContext(base.OperationContext):
"""An implementation of interfaces.OperationContext."""
def __init__(
self, lock, termination_manager, transmission_manager,
expiration_manager):
"""Constructor.
Args:
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
"""
self._lock = lock
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
def _abort(self, outcome_kind):
with self._lock:
if self._termination_manager.outcome is None:
outcome = _utilities.Outcome(outcome_kind, None, None)
self._termination_manager.abort(outcome)
self._transmission_manager.abort(outcome)
self._expiration_manager.terminate()
def outcome(self):
"""See base.OperationContext.outcome for specification."""
with self._lock:
return self._termination_manager.outcome
def add_termination_callback(self, callback):
"""See base.OperationContext.add_termination_callback."""
with self._lock:
if self._termination_manager.outcome is None:
self._termination_manager.add_callback(callback)
return None
else:
return self._termination_manager.outcome
def time_remaining(self):
"""See base.OperationContext.time_remaining for specification."""
with self._lock:
deadline = self._expiration_manager.deadline()
return max(0.0, deadline - time.time())
def cancel(self):
"""See base.OperationContext.cancel for specification."""
self._abort(base.Outcome.Kind.CANCELLED)
def fail(self, exception):
"""See base.OperationContext.fail for specification."""
self._abort(base.Outcome.Kind.LOCAL_FAILURE)

@ -1,100 +0,0 @@
# Copyright 2015, 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.
"""State and behavior for handling emitted values."""
from grpc.framework.core import _interfaces
from grpc.framework.core import _utilities
from grpc.framework.interfaces.base import base
class EmissionManager(_interfaces.EmissionManager):
"""An EmissionManager implementation."""
def __init__(
self, lock, termination_manager, transmission_manager,
expiration_manager):
"""Constructor.
Args:
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
"""
self._lock = lock
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
self._ingestion_manager = None
self._initial_metadata_seen = False
self._payload_seen = False
self._completion_seen = False
def set_ingestion_manager(self, ingestion_manager):
"""Sets the ingestion manager with which this manager will cooperate.
Args:
ingestion_manager: The _interfaces.IngestionManager for the operation.
"""
self._ingestion_manager = ingestion_manager
def advance(
self, initial_metadata=None, payload=None, completion=None,
allowance=None):
initial_metadata_present = initial_metadata is not None
payload_present = payload is not None
completion_present = completion is not None
allowance_present = allowance is not None
with self._lock:
if self._termination_manager.outcome is None:
if (initial_metadata_present and (
self._initial_metadata_seen or self._payload_seen or
self._completion_seen) or
payload_present and self._completion_seen or
completion_present and self._completion_seen or
allowance_present and allowance <= 0):
outcome = _utilities.Outcome(
base.Outcome.Kind.LOCAL_FAILURE, None, None)
self._termination_manager.abort(outcome)
self._transmission_manager.abort(outcome)
self._expiration_manager.terminate()
else:
self._initial_metadata_seen |= initial_metadata_present
self._payload_seen |= payload_present
self._completion_seen |= completion_present
if completion_present:
self._termination_manager.emission_complete()
self._ingestion_manager.local_emissions_done()
self._transmission_manager.advance(
initial_metadata, payload, completion, allowance)
if allowance_present:
self._ingestion_manager.add_local_allowance(allowance)

@ -1,244 +0,0 @@
# Copyright 2015, 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.
"""Implementation of base.End."""
import abc
import threading
import uuid
import six
from grpc.framework.core import _operation
from grpc.framework.core import _utilities
from grpc.framework.foundation import callable_util
from grpc.framework.foundation import later
from grpc.framework.foundation import logging_pool
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.links import links
from grpc.framework.interfaces.links import utilities
_IDLE_ACTION_EXCEPTION_LOG_MESSAGE = 'Exception calling idle action!'
class End(six.with_metaclass(abc.ABCMeta, base.End, links.Link)):
"""A bridge between base.End and links.Link.
Implementations of this interface translate arriving tickets into
calls on application objects implementing base interfaces and
translate calls from application objects implementing base interfaces
into tickets sent to a joined link.
"""
class _Cycle(object):
"""State for a single start-stop End lifecycle."""
def __init__(self, pool):
self.pool = pool
self.grace = False
self.futures = []
self.operations = {}
self.idle_actions = []
def _abort(operations):
for operation in operations:
operation.abort(base.Outcome.Kind.LOCAL_SHUTDOWN)
def _cancel_futures(futures):
for future in futures:
future.cancel()
def _future_shutdown(lock, cycle, event):
def in_future():
with lock:
_abort(cycle.operations.values())
_cancel_futures(cycle.futures)
return in_future
class _End(End):
"""An End implementation."""
def __init__(self, servicer_package):
"""Constructor.
Args:
servicer_package: A _ServicerPackage for servicing operations or None if
this end will not be used to service operations.
"""
self._lock = threading.Condition()
self._servicer_package = servicer_package
self._stats = {outcome_kind: 0 for outcome_kind in base.Outcome.Kind}
self._mate = None
self._cycle = None
def _termination_action(self, operation_id):
"""Constructs the termination action for a single operation.
Args:
operation_id: The operation ID for the termination action.
Returns:
A callable that takes an operation outcome kind as its sole parameter and
that should be used as the termination action for the operation
associated with the given operation ID.
"""
def termination_action(outcome_kind):
with self._lock:
self._stats[outcome_kind] += 1
self._cycle.operations.pop(operation_id, None)
if not self._cycle.operations:
for action in self._cycle.idle_actions:
self._cycle.pool.submit(action)
self._cycle.idle_actions = []
if self._cycle.grace:
_cancel_futures(self._cycle.futures)
self._cycle.pool.shutdown(wait=False)
self._cycle = None
return termination_action
def start(self):
"""See base.End.start for specification."""
with self._lock:
if self._cycle is not None:
raise ValueError('Tried to start a not-stopped End!')
else:
self._cycle = _Cycle(logging_pool.pool(1))
def stop(self, grace):
"""See base.End.stop for specification."""
with self._lock:
if self._cycle is None:
event = threading.Event()
event.set()
return event
elif not self._cycle.operations:
event = threading.Event()
self._cycle.pool.submit(event.set)
self._cycle.pool.shutdown(wait=False)
self._cycle = None
return event
else:
self._cycle.grace = True
event = threading.Event()
self._cycle.idle_actions.append(event.set)
if 0 < grace:
future = later.later(
grace, _future_shutdown(self._lock, self._cycle, event))
self._cycle.futures.append(future)
else:
_abort(self._cycle.operations.values())
return event
def operate(
self, group, method, subscription, timeout, initial_metadata=None,
payload=None, completion=None, protocol_options=None):
"""See base.End.operate for specification."""
operation_id = uuid.uuid4()
with self._lock:
if self._cycle is None or self._cycle.grace:
raise ValueError('Can\'t operate on stopped or stopping End!')
termination_action = self._termination_action(operation_id)
operation = _operation.invocation_operate(
operation_id, group, method, subscription, timeout, protocol_options,
initial_metadata, payload, completion, self._mate.accept_ticket,
termination_action, self._cycle.pool)
self._cycle.operations[operation_id] = operation
return operation.context, operation.operator
def operation_stats(self):
"""See base.End.operation_stats for specification."""
with self._lock:
return dict(self._stats)
def add_idle_action(self, action):
"""See base.End.add_idle_action for specification."""
with self._lock:
if self._cycle is None:
raise ValueError('Can\'t add idle action to stopped End!')
action_with_exceptions_logged = callable_util.with_exceptions_logged(
action, _IDLE_ACTION_EXCEPTION_LOG_MESSAGE)
if self._cycle.operations:
self._cycle.idle_actions.append(action_with_exceptions_logged)
else:
self._cycle.pool.submit(action_with_exceptions_logged)
def accept_ticket(self, ticket):
"""See links.Link.accept_ticket for specification."""
with self._lock:
if self._cycle is not None:
operation = self._cycle.operations.get(ticket.operation_id)
if operation is not None:
operation.handle_ticket(ticket)
elif self._servicer_package is not None and not self._cycle.grace:
termination_action = self._termination_action(ticket.operation_id)
operation = _operation.service_operate(
self._servicer_package, ticket, self._mate.accept_ticket,
termination_action, self._cycle.pool)
if operation is not None:
self._cycle.operations[ticket.operation_id] = operation
def join_link(self, link):
"""See links.Link.join_link for specification."""
with self._lock:
self._mate = utilities.NULL_LINK if link is None else link
def serviceless_end_link():
"""Constructs an End usable only for invoking operations.
Returns:
An End usable for translating operations into ticket exchange.
"""
return _End(None)
def serviceful_end_link(servicer, default_timeout, maximum_timeout):
"""Constructs an End capable of servicing operations.
Args:
servicer: An interfaces.Servicer for servicing operations.
default_timeout: A length of time in seconds to be used as the default
time alloted for a single operation.
maximum_timeout: A length of time in seconds to be used as the maximum
time alloted for a single operation.
Returns:
An End capable of servicing the operations requested of it through ticket
exchange.
"""
return _End(
_utilities.ServicerPackage(servicer, default_timeout, maximum_timeout))

@ -1,154 +0,0 @@
# Copyright 2015, 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.
"""State and behavior for operation expiration."""
import time
from grpc.framework.core import _interfaces
from grpc.framework.core import _utilities
from grpc.framework.foundation import later
from grpc.framework.interfaces.base import base
class _ExpirationManager(_interfaces.ExpirationManager):
"""An implementation of _interfaces.ExpirationManager."""
def __init__(
self, commencement, timeout, maximum_timeout, lock, termination_manager,
transmission_manager):
"""Constructor.
Args:
commencement: The time in seconds since the epoch at which the operation
began.
timeout: A length of time in seconds to allow for the operation to run.
maximum_timeout: The maximum length of time in seconds to allow for the
operation to run despite what is requested via this object's
change_timout method.
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
"""
self._lock = lock
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._commencement = commencement
self._maximum_timeout = maximum_timeout
self._timeout = timeout
self._deadline = commencement + timeout
self._index = None
self._future = None
def _expire(self, index):
def expire():
with self._lock:
if self._future is not None and index == self._index:
self._future = None
self._termination_manager.expire()
self._transmission_manager.abort(
_utilities.Outcome(base.Outcome.Kind.EXPIRED, None, None))
return expire
def start(self):
self._index = 0
self._future = later.later(self._timeout, self._expire(0))
def change_timeout(self, timeout):
if self._future is not None and timeout != self._timeout:
self._future.cancel()
new_timeout = min(timeout, self._maximum_timeout)
new_index = self._index + 1
self._timeout = new_timeout
self._deadline = self._commencement + new_timeout
self._index = new_index
delay = self._deadline - time.time()
self._future = later.later(delay, self._expire(new_index))
if new_timeout != timeout:
self._transmission_manager.timeout(new_timeout)
def deadline(self):
return self._deadline
def terminate(self):
if self._future:
self._future.cancel()
self._future = None
self._deadline_index = None
def invocation_expiration_manager(
timeout, lock, termination_manager, transmission_manager):
"""Creates an _interfaces.ExpirationManager appropriate for front-side use.
Args:
timeout: A length of time in seconds to allow for the operation to run.
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
Returns:
An _interfaces.ExpirationManager appropriate for invocation-side use.
"""
expiration_manager = _ExpirationManager(
time.time(), timeout, timeout, lock, termination_manager,
transmission_manager)
expiration_manager.start()
return expiration_manager
def service_expiration_manager(
timeout, default_timeout, maximum_timeout, lock, termination_manager,
transmission_manager):
"""Creates an _interfaces.ExpirationManager appropriate for back-side use.
Args:
timeout: A length of time in seconds to allow for the operation to run. May
be None in which case default_timeout will be used.
default_timeout: The default length of time in seconds to allow for the
operation to run if the front-side customer has not specified such a value
(or if the value they specified is not yet known).
maximum_timeout: The maximum length of time in seconds to allow for the
operation to run.
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
Returns:
An _interfaces.ExpirationManager appropriate for service-side use.
"""
expiration_manager = _ExpirationManager(
time.time(), default_timeout if timeout is None else timeout,
maximum_timeout, lock, termination_manager, transmission_manager)
expiration_manager.start()
return expiration_manager

@ -1,439 +0,0 @@
# Copyright 2015, 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.
"""State and behavior for ingestion during an operation."""
import abc
import collections
import enum
import six
from grpc.framework.core import _constants
from grpc.framework.core import _interfaces
from grpc.framework.core import _utilities
from grpc.framework.foundation import abandonment
from grpc.framework.foundation import callable_util
from grpc.framework.interfaces.base import base
_CREATE_SUBSCRIPTION_EXCEPTION_LOG_MESSAGE = 'Exception initializing ingestion!'
_INGESTION_EXCEPTION_LOG_MESSAGE = 'Exception during ingestion!'
class _SubscriptionCreation(
collections.namedtuple(
'_SubscriptionCreation',
('kind', 'subscription', 'code', 'details',))):
"""A sum type for the outcome of ingestion initialization.
Attributes:
kind: A Kind value coarsely indicating how subscription creation completed.
subscription: The created subscription. Only present if kind is
Kind.SUBSCRIPTION.
code: A code value to be sent to the other side of the operation along with
an indication that the operation is being aborted due to an error on the
remote side of the operation. Only present if kind is Kind.REMOTE_ERROR.
details: A details value to be sent to the other side of the operation
along with an indication that the operation is being aborted due to an
error on the remote side of the operation. Only present if kind is
Kind.REMOTE_ERROR.
"""
@enum.unique
class Kind(enum.Enum):
SUBSCRIPTION = 'subscription'
REMOTE_ERROR = 'remote error'
ABANDONED = 'abandoned'
class _SubscriptionCreator(six.with_metaclass(abc.ABCMeta)):
"""Common specification of subscription-creating behavior."""
@abc.abstractmethod
def create(self, group, method):
"""Creates the base.Subscription of the local customer.
Any exceptions raised by this method should be attributed to and treated as
defects in the customer code called by this method.
Args:
group: The group identifier of the operation.
method: The method identifier of the operation.
Returns:
A _SubscriptionCreation describing the result of subscription creation.
"""
raise NotImplementedError()
class _ServiceSubscriptionCreator(_SubscriptionCreator):
"""A _SubscriptionCreator appropriate for service-side use."""
def __init__(self, servicer, operation_context, output_operator):
"""Constructor.
Args:
servicer: The base.Servicer that will service the operation.
operation_context: A base.OperationContext for the operation to be passed
to the customer.
output_operator: A base.Operator for the operation to be passed to the
customer and to be called by the customer to accept operation data
emitted by the customer.
"""
self._servicer = servicer
self._operation_context = operation_context
self._output_operator = output_operator
def create(self, group, method):
try:
subscription = self._servicer.service(
group, method, self._operation_context, self._output_operator)
except base.NoSuchMethodError as e:
return _SubscriptionCreation(
_SubscriptionCreation.Kind.REMOTE_ERROR, None, e.code, e.details)
except abandonment.Abandoned:
return _SubscriptionCreation(
_SubscriptionCreation.Kind.ABANDONED, None, None, None)
else:
return _SubscriptionCreation(
_SubscriptionCreation.Kind.SUBSCRIPTION, subscription, None, None)
def _wrap(behavior):
def wrapped(*args, **kwargs):
try:
behavior(*args, **kwargs)
except abandonment.Abandoned:
return False
else:
return True
return wrapped
class _IngestionManager(_interfaces.IngestionManager):
"""An implementation of _interfaces.IngestionManager."""
def __init__(
self, lock, pool, subscription, subscription_creator, termination_manager,
transmission_manager, expiration_manager, protocol_manager):
"""Constructor.
Args:
lock: The operation-wide lock.
pool: A thread pool in which to execute customer code.
subscription: A base.Subscription describing the customer's interest in
operation values from the other side. May be None if
subscription_creator is not None.
subscription_creator: A _SubscriptionCreator wrapping the portion of
customer code that when called returns the base.Subscription describing
the customer's interest in operation values from the other side. May be
None if subscription is not None.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
protocol_manager: The _interfaces.ProtocolManager for the operation.
"""
self._lock = lock
self._pool = pool
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
self._protocol_manager = protocol_manager
if subscription is None:
self._subscription_creator = subscription_creator
self._wrapped_operator = None
elif subscription.kind is base.Subscription.Kind.FULL:
self._subscription_creator = None
self._wrapped_operator = _wrap(subscription.operator.advance)
else:
# TODO(nathaniel): Support other subscriptions.
raise ValueError('Unsupported subscription "%s"!' % subscription.kind)
self._pending_initial_metadata = None
self._pending_payloads = []
self._pending_completion = None
self._local_allowance = 1
# A nonnegative integer or None, with None indicating that the local
# customer is done emitting anyway so there's no need to bother it by
# informing it that the remote customer has granted it further permission to
# emit.
self._remote_allowance = 0
self._processing = False
def _abort_internal_only(self):
self._subscription_creator = None
self._wrapped_operator = None
self._pending_initial_metadata = None
self._pending_payloads = None
self._pending_completion = None
def _abort_and_notify(self, outcome_kind, code, details):
self._abort_internal_only()
if self._termination_manager.outcome is None:
outcome = _utilities.Outcome(outcome_kind, code, details)
self._termination_manager.abort(outcome)
self._transmission_manager.abort(outcome)
self._expiration_manager.terminate()
def _operator_next(self):
"""Computes the next step for full-subscription ingestion.
Returns:
An initial_metadata, payload, completion, allowance, continue quintet
indicating what operation values (if any) are available to pass into
customer code and whether or not there is anything immediately
actionable to call customer code to do.
"""
if self._wrapped_operator is None:
return None, None, None, None, False
else:
initial_metadata, payload, completion, allowance, action = [None] * 5
if self._pending_initial_metadata is not None:
initial_metadata = self._pending_initial_metadata
self._pending_initial_metadata = None
action = True
if self._pending_payloads and 0 < self._local_allowance:
payload = self._pending_payloads.pop(0)
self._local_allowance -= 1
action = True
if not self._pending_payloads and self._pending_completion is not None:
completion = self._pending_completion
self._pending_completion = None
action = True
if self._remote_allowance is not None and 0 < self._remote_allowance:
allowance = self._remote_allowance
self._remote_allowance = 0
action = True
return initial_metadata, payload, completion, allowance, bool(action)
def _operator_process(
self, wrapped_operator, initial_metadata, payload,
completion, allowance):
while True:
advance_outcome = callable_util.call_logging_exceptions(
wrapped_operator, _INGESTION_EXCEPTION_LOG_MESSAGE,
initial_metadata=initial_metadata, payload=payload,
completion=completion, allowance=allowance)
if advance_outcome.exception is None:
if advance_outcome.return_value:
with self._lock:
if self._termination_manager.outcome is not None:
return
if completion is not None:
self._termination_manager.ingestion_complete()
initial_metadata, payload, completion, allowance, moar = (
self._operator_next())
if not moar:
self._processing = False
return
else:
with self._lock:
if self._termination_manager.outcome is None:
self._abort_and_notify(
base.Outcome.Kind.LOCAL_FAILURE, None, None)
return
else:
with self._lock:
if self._termination_manager.outcome is None:
self._abort_and_notify(base.Outcome.Kind.LOCAL_FAILURE, None, None)
return
def _operator_post_create(self, subscription):
wrapped_operator = _wrap(subscription.operator.advance)
with self._lock:
if self._termination_manager.outcome is not None:
return
self._wrapped_operator = wrapped_operator
self._subscription_creator = None
metadata, payload, completion, allowance, moar = self._operator_next()
if not moar:
self._processing = False
return
self._operator_process(
wrapped_operator, metadata, payload, completion, allowance)
def _create(self, subscription_creator, group, name):
outcome = callable_util.call_logging_exceptions(
subscription_creator.create,
_CREATE_SUBSCRIPTION_EXCEPTION_LOG_MESSAGE, group, name)
if outcome.return_value is None:
with self._lock:
if self._termination_manager.outcome is None:
self._abort_and_notify(base.Outcome.Kind.LOCAL_FAILURE, None, None)
elif outcome.return_value.kind is _SubscriptionCreation.Kind.ABANDONED:
with self._lock:
if self._termination_manager.outcome is None:
self._abort_and_notify(base.Outcome.Kind.LOCAL_FAILURE, None, None)
elif outcome.return_value.kind is _SubscriptionCreation.Kind.REMOTE_ERROR:
code = outcome.return_value.code
details = outcome.return_value.details
with self._lock:
if self._termination_manager.outcome is None:
self._abort_and_notify(
base.Outcome.Kind.REMOTE_FAILURE, code, details)
elif outcome.return_value.subscription.kind is base.Subscription.Kind.FULL:
self._protocol_manager.set_protocol_receiver(
outcome.return_value.subscription.protocol_receiver)
self._operator_post_create(outcome.return_value.subscription)
else:
# TODO(nathaniel): Support other subscriptions.
raise ValueError(
'Unsupported "%s"!' % outcome.return_value.subscription.kind)
def _store_advance(self, initial_metadata, payload, completion, allowance):
if initial_metadata is not None:
self._pending_initial_metadata = initial_metadata
if payload is not None:
self._pending_payloads.append(payload)
if completion is not None:
self._pending_completion = completion
if allowance is not None and self._remote_allowance is not None:
self._remote_allowance += allowance
def _operator_advance(self, initial_metadata, payload, completion, allowance):
if self._processing:
self._store_advance(initial_metadata, payload, completion, allowance)
else:
action = False
if initial_metadata is not None:
action = True
if payload is not None:
if 0 < self._local_allowance:
self._local_allowance -= 1
action = True
else:
self._pending_payloads.append(payload)
payload = False
if completion is not None:
if self._pending_payloads:
self._pending_completion = completion
else:
action = True
if allowance is not None and self._remote_allowance is not None:
allowance += self._remote_allowance
self._remote_allowance = 0
action = True
if action:
self._pool.submit(
callable_util.with_exceptions_logged(
self._operator_process, _constants.INTERNAL_ERROR_LOG_MESSAGE),
self._wrapped_operator, initial_metadata, payload, completion,
allowance)
def set_group_and_method(self, group, method):
"""See _interfaces.IngestionManager.set_group_and_method for spec."""
if self._subscription_creator is not None and not self._processing:
self._pool.submit(
callable_util.with_exceptions_logged(
self._create, _constants.INTERNAL_ERROR_LOG_MESSAGE),
self._subscription_creator, group, method)
self._processing = True
def add_local_allowance(self, allowance):
"""See _interfaces.IngestionManager.add_local_allowance for spec."""
if any((self._subscription_creator, self._wrapped_operator,)):
self._local_allowance += allowance
if not self._processing:
initial_metadata, payload, completion, allowance, moar = (
self._operator_next())
if moar:
self._pool.submit(
callable_util.with_exceptions_logged(
self._operator_process,
_constants.INTERNAL_ERROR_LOG_MESSAGE),
initial_metadata, payload, completion, allowance)
def local_emissions_done(self):
self._remote_allowance = None
def advance(self, initial_metadata, payload, completion, allowance):
"""See _interfaces.IngestionManager.advance for specification."""
if self._subscription_creator is not None:
self._store_advance(initial_metadata, payload, completion, allowance)
elif self._wrapped_operator is not None:
self._operator_advance(initial_metadata, payload, completion, allowance)
def invocation_ingestion_manager(
subscription, lock, pool, termination_manager, transmission_manager,
expiration_manager, protocol_manager):
"""Creates an IngestionManager appropriate for invocation-side use.
Args:
subscription: A base.Subscription indicating the customer's interest in the
data and results from the service-side of the operation.
lock: The operation-wide lock.
pool: A thread pool in which to execute customer code.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
protocol_manager: The _interfaces.ProtocolManager for the operation.
Returns:
An IngestionManager appropriate for invocation-side use.
"""
return _IngestionManager(
lock, pool, subscription, None, termination_manager, transmission_manager,
expiration_manager, protocol_manager)
def service_ingestion_manager(
servicer, operation_context, output_operator, lock, pool,
termination_manager, transmission_manager, expiration_manager,
protocol_manager):
"""Creates an IngestionManager appropriate for service-side use.
The returned IngestionManager will require its set_group_and_name method to be
called before its advance method may be called.
Args:
servicer: A base.Servicer for servicing the operation.
operation_context: A base.OperationContext for the operation to be passed to
the customer.
output_operator: A base.Operator for the operation to be passed to the
customer and to be called by the customer to accept operation data output
by the customer.
lock: The operation-wide lock.
pool: A thread pool in which to execute customer code.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
protocol_manager: The _interfaces.ProtocolManager for the operation.
Returns:
An IngestionManager appropriate for service-side use.
"""
subscription_creator = _ServiceSubscriptionCreator(
servicer, operation_context, output_operator)
return _IngestionManager(
lock, pool, None, subscription_creator, termination_manager,
transmission_manager, expiration_manager, protocol_manager)

@ -1,331 +0,0 @@
# Copyright 2015, 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.
"""Package-internal interfaces."""
import abc
import six
from grpc.framework.interfaces.base import base
class TerminationManager(six.with_metaclass(abc.ABCMeta)):
"""An object responsible for handling the termination of an operation.
Attributes:
outcome: None if the operation is active or a base.Outcome value if it has
terminated.
"""
@abc.abstractmethod
def add_callback(self, callback):
"""Registers a callback to be called on operation termination.
If the operation has already terminated the callback will not be called.
Args:
callback: A callable that will be passed a base.Outcome value.
Returns:
None if the operation has not yet terminated and the passed callback will
be called when it does, or a base.Outcome value describing the
operation termination if the operation has terminated and the callback
will not be called as a result of this method call.
"""
raise NotImplementedError()
@abc.abstractmethod
def emission_complete(self):
"""Indicates that emissions from customer code have completed."""
raise NotImplementedError()
@abc.abstractmethod
def transmission_complete(self):
"""Indicates that transmissions to the remote end are complete.
Returns:
True if the operation has terminated or False if the operation remains
ongoing.
"""
raise NotImplementedError()
@abc.abstractmethod
def reception_complete(self, code, details):
"""Indicates that reception from the other side is complete.
Args:
code: An application-specific code value.
details: An application-specific details value.
"""
raise NotImplementedError()
@abc.abstractmethod
def ingestion_complete(self):
"""Indicates that customer code ingestion of received values is complete."""
raise NotImplementedError()
@abc.abstractmethod
def expire(self):
"""Indicates that the operation must abort because it has taken too long."""
raise NotImplementedError()
@abc.abstractmethod
def abort(self, outcome):
"""Indicates that the operation must abort for the indicated reason.
Args:
outcome: A base.Outcome indicating operation abortion.
"""
raise NotImplementedError()
class TransmissionManager(six.with_metaclass(abc.ABCMeta)):
"""A manager responsible for transmitting to the other end of an operation."""
@abc.abstractmethod
def kick_off(
self, group, method, timeout, protocol_options, initial_metadata,
payload, completion, allowance):
"""Transmits the values associated with operation invocation."""
raise NotImplementedError()
@abc.abstractmethod
def advance(self, initial_metadata, payload, completion, allowance):
"""Accepts values for transmission to the other end of the operation.
Args:
initial_metadata: An initial metadata value to be transmitted to the other
side of the operation. May only ever be non-None once.
payload: A payload value.
completion: A base.Completion value. May only ever be non-None in the last
transmission to be made to the other side.
allowance: A positive integer communicating the number of additional
payloads allowed to be transmitted from the other side to this side of
the operation, or None if no additional allowance is being granted in
this call.
"""
raise NotImplementedError()
@abc.abstractmethod
def timeout(self, timeout):
"""Accepts for transmission to the other side a new timeout value.
Args:
timeout: A positive float used as the new timeout value for the operation
to be transmitted to the other side.
"""
raise NotImplementedError()
@abc.abstractmethod
def allowance(self, allowance):
"""Indicates to this manager that the remote customer is allowing payloads.
Args:
allowance: A positive integer indicating the number of additional payloads
the remote customer is allowing to be transmitted from this side of the
operation.
"""
raise NotImplementedError()
@abc.abstractmethod
def remote_complete(self):
"""Indicates to this manager that data from the remote side is complete."""
raise NotImplementedError()
@abc.abstractmethod
def abort(self, outcome):
"""Indicates that the operation has aborted.
Args:
outcome: A base.Outcome for the operation. If None, indicates that the
operation abortion should not be communicated to the other side of the
operation.
"""
raise NotImplementedError()
class ExpirationManager(six.with_metaclass(abc.ABCMeta)):
"""A manager responsible for aborting the operation if it runs out of time."""
@abc.abstractmethod
def change_timeout(self, timeout):
"""Changes the timeout allotted for the operation.
Operation duration is always measure from the beginning of the operation;
calling this method changes the operation's allotted time to timeout total
seconds, not timeout seconds from the time of this method call.
Args:
timeout: A length of time in seconds to allow for the operation.
"""
raise NotImplementedError()
@abc.abstractmethod
def deadline(self):
"""Returns the time until which the operation is allowed to run.
Returns:
The time (seconds since the epoch) at which the operation will expire.
"""
raise NotImplementedError()
@abc.abstractmethod
def terminate(self):
"""Indicates to this manager that the operation has terminated."""
raise NotImplementedError()
class ProtocolManager(six.with_metaclass(abc.ABCMeta)):
"""A manager of protocol-specific values passing through an operation."""
@abc.abstractmethod
def set_protocol_receiver(self, protocol_receiver):
"""Registers the customer object that will receive protocol objects.
Args:
protocol_receiver: A base.ProtocolReceiver to which protocol objects for
the operation should be passed.
"""
raise NotImplementedError()
@abc.abstractmethod
def accept_protocol_context(self, protocol_context):
"""Accepts the protocol context object for the operation.
Args:
protocol_context: An object designated for use as the protocol context
of the operation, with further semantics implementation-determined.
"""
raise NotImplementedError()
class EmissionManager(six.with_metaclass(abc.ABCMeta, base.Operator)):
"""A manager of values emitted by customer code."""
@abc.abstractmethod
def advance(
self, initial_metadata=None, payload=None, completion=None,
allowance=None):
"""Accepts a value emitted by customer code.
This method should only be called by customer code.
Args:
initial_metadata: An initial metadata value emitted by the local customer
to be sent to the other side of the operation.
payload: A payload value emitted by the local customer to be sent to the
other side of the operation.
completion: A Completion value emitted by the local customer to be sent to
the other side of the operation.
allowance: A positive integer indicating an additional number of payloads
that the local customer is willing to accept from the other side of the
operation.
"""
raise NotImplementedError()
class IngestionManager(six.with_metaclass(abc.ABCMeta)):
"""A manager responsible for executing customer code.
This name of this manager comes from its responsibility to pass successive
values from the other side of the operation into the code of the local
customer.
"""
@abc.abstractmethod
def set_group_and_method(self, group, method):
"""Communicates to this IngestionManager the operation group and method.
Args:
group: The group identifier of the operation.
method: The method identifier of the operation.
"""
raise NotImplementedError()
@abc.abstractmethod
def add_local_allowance(self, allowance):
"""Communicates to this IngestionManager that more payloads may be ingested.
Args:
allowance: A positive integer indicating an additional number of payloads
that the local customer is willing to ingest.
"""
raise NotImplementedError()
@abc.abstractmethod
def local_emissions_done(self):
"""Indicates to this manager that local emissions are done."""
raise NotImplementedError()
@abc.abstractmethod
def advance(self, initial_metadata, payload, completion, allowance):
"""Advances the operation by passing values to the local customer."""
raise NotImplementedError()
class ReceptionManager(six.with_metaclass(abc.ABCMeta)):
"""A manager responsible for receiving tickets from the other end."""
@abc.abstractmethod
def receive_ticket(self, ticket):
"""Handle a ticket from the other side of the operation.
Args:
ticket: A links.Ticket for the operation.
"""
raise NotImplementedError()
class Operation(six.with_metaclass(abc.ABCMeta)):
"""An ongoing operation.
Attributes:
context: A base.OperationContext object for the operation.
operator: A base.Operator object for the operation for use by the customer
of the operation.
"""
@abc.abstractmethod
def handle_ticket(self, ticket):
"""Handle a ticket from the other side of the operation.
Args:
ticket: A links.Ticket from the other side of the operation.
"""
raise NotImplementedError()
@abc.abstractmethod
def abort(self, outcome_kind):
"""Aborts the operation.
Args:
outcome_kind: A base.Outcome.Kind value indicating operation abortion.
"""
raise NotImplementedError()

@ -1,204 +0,0 @@
# Copyright 2015, 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.
"""Implementation of operations."""
import threading
from grpc.framework.core import _context
from grpc.framework.core import _emission
from grpc.framework.core import _expiration
from grpc.framework.core import _ingestion
from grpc.framework.core import _interfaces
from grpc.framework.core import _protocol
from grpc.framework.core import _reception
from grpc.framework.core import _termination
from grpc.framework.core import _transmission
from grpc.framework.core import _utilities
class _EasyOperation(_interfaces.Operation):
"""A trivial implementation of interfaces.Operation."""
def __init__(
self, lock, termination_manager, transmission_manager, expiration_manager,
context, operator, reception_manager):
"""Constructor.
Args:
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
context: A base.OperationContext for use by the customer during the
operation.
operator: A base.Operator for use by the customer during the operation.
reception_manager: The _interfaces.ReceptionManager for the operation.
"""
self._lock = lock
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
self._reception_manager = reception_manager
self.context = context
self.operator = operator
def handle_ticket(self, ticket):
with self._lock:
self._reception_manager.receive_ticket(ticket)
def abort(self, outcome_kind):
with self._lock:
if self._termination_manager.outcome is None:
outcome = _utilities.Outcome(outcome_kind, None, None)
self._termination_manager.abort(outcome)
self._transmission_manager.abort(outcome)
self._expiration_manager.terminate()
def invocation_operate(
operation_id, group, method, subscription, timeout, protocol_options,
initial_metadata, payload, completion, ticket_sink, termination_action,
pool):
"""Constructs objects necessary for front-side operation management.
Args:
operation_id: An object identifying the operation.
group: The group identifier of the operation.
method: The method identifier of the operation.
subscription: A base.Subscription describing the customer's interest in the
results of the operation.
timeout: A length of time in seconds to allow for the operation.
protocol_options: A transport-specific, application-specific, and/or
protocol-specific value relating to the invocation. May be None.
initial_metadata: An initial metadata value to be sent to the other side of
the operation. May be None if the initial metadata will be passed later or
if there will be no initial metadata passed at all.
payload: The first payload value to be transmitted to the other side. May be
None if there is no such value or if the customer chose not to pass it at
operation invocation.
completion: A base.Completion value indicating the end of values passed to
the other side of the operation.
ticket_sink: A callable that accepts links.Tickets and delivers them to the
other side of the operation.
termination_action: A callable that accepts the outcome of the operation as
a base.Outcome value to be called on operation completion.
pool: A thread pool with which to do the work of the operation.
Returns:
An _interfaces.Operation for the operation.
"""
lock = threading.Lock()
with lock:
termination_manager = _termination.invocation_termination_manager(
termination_action, pool)
transmission_manager = _transmission.TransmissionManager(
operation_id, ticket_sink, lock, pool, termination_manager)
expiration_manager = _expiration.invocation_expiration_manager(
timeout, lock, termination_manager, transmission_manager)
protocol_manager = _protocol.invocation_protocol_manager(
subscription, lock, pool, termination_manager, transmission_manager,
expiration_manager)
operation_context = _context.OperationContext(
lock, termination_manager, transmission_manager, expiration_manager)
emission_manager = _emission.EmissionManager(
lock, termination_manager, transmission_manager, expiration_manager)
ingestion_manager = _ingestion.invocation_ingestion_manager(
subscription, lock, pool, termination_manager, transmission_manager,
expiration_manager, protocol_manager)
reception_manager = _reception.ReceptionManager(
termination_manager, transmission_manager, expiration_manager,
protocol_manager, ingestion_manager)
termination_manager.set_expiration_manager(expiration_manager)
transmission_manager.set_expiration_manager(expiration_manager)
emission_manager.set_ingestion_manager(ingestion_manager)
transmission_manager.kick_off(
group, method, timeout, protocol_options, initial_metadata, payload,
completion, None)
return _EasyOperation(
lock, termination_manager, transmission_manager, expiration_manager,
operation_context, emission_manager, reception_manager)
def service_operate(
servicer_package, ticket, ticket_sink, termination_action, pool):
"""Constructs an Operation for service of an operation.
Args:
servicer_package: A _utilities.ServicerPackage to be used servicing the
operation.
ticket: The first links.Ticket received for the operation.
ticket_sink: A callable that accepts links.Tickets and delivers them to the
other side of the operation.
termination_action: A callable that accepts the outcome of the operation as
a base.Outcome value to be called on operation completion.
pool: A thread pool with which to do the work of the operation.
Returns:
An _interfaces.Operation for the operation.
"""
lock = threading.Lock()
with lock:
termination_manager = _termination.service_termination_manager(
termination_action, pool)
transmission_manager = _transmission.TransmissionManager(
ticket.operation_id, ticket_sink, lock, pool, termination_manager)
expiration_manager = _expiration.service_expiration_manager(
ticket.timeout, servicer_package.default_timeout,
servicer_package.maximum_timeout, lock, termination_manager,
transmission_manager)
protocol_manager = _protocol.service_protocol_manager(
lock, pool, termination_manager, transmission_manager,
expiration_manager)
operation_context = _context.OperationContext(
lock, termination_manager, transmission_manager, expiration_manager)
emission_manager = _emission.EmissionManager(
lock, termination_manager, transmission_manager, expiration_manager)
ingestion_manager = _ingestion.service_ingestion_manager(
servicer_package.servicer, operation_context, emission_manager, lock,
pool, termination_manager, transmission_manager, expiration_manager,
protocol_manager)
reception_manager = _reception.ReceptionManager(
termination_manager, transmission_manager, expiration_manager,
protocol_manager, ingestion_manager)
termination_manager.set_expiration_manager(expiration_manager)
transmission_manager.set_expiration_manager(expiration_manager)
emission_manager.set_ingestion_manager(ingestion_manager)
reception_manager.receive_ticket(ticket)
return _EasyOperation(
lock, termination_manager, transmission_manager, expiration_manager,
operation_context, emission_manager, reception_manager)

@ -1,176 +0,0 @@
# Copyright 2015, 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.
"""State and behavior for passing protocol objects in an operation."""
import collections
import enum
from grpc.framework.core import _constants
from grpc.framework.core import _interfaces
from grpc.framework.core import _utilities
from grpc.framework.foundation import callable_util
from grpc.framework.interfaces.base import base
_EXCEPTION_LOG_MESSAGE = 'Exception delivering protocol object!'
_LOCAL_FAILURE_OUTCOME = _utilities.Outcome(
base.Outcome.Kind.LOCAL_FAILURE, None, None)
class _Awaited(
collections.namedtuple('_Awaited', ('kind', 'value',))):
@enum.unique
class Kind(enum.Enum):
NOT_YET_ARRIVED = 'not yet arrived'
ARRIVED = 'arrived'
_NOT_YET_ARRIVED = _Awaited(_Awaited.Kind.NOT_YET_ARRIVED, None)
_ARRIVED_AND_NONE = _Awaited(_Awaited.Kind.ARRIVED, None)
class _Transitory(
collections.namedtuple('_Transitory', ('kind', 'value',))):
@enum.unique
class Kind(enum.Enum):
NOT_YET_SEEN = 'not yet seen'
PRESENT = 'present'
GONE = 'gone'
_NOT_YET_SEEN = _Transitory(_Transitory.Kind.NOT_YET_SEEN, None)
_GONE = _Transitory(_Transitory.Kind.GONE, None)
class _ProtocolManager(_interfaces.ProtocolManager):
"""An implementation of _interfaces.ExpirationManager."""
def __init__(
self, protocol_receiver, lock, pool, termination_manager,
transmission_manager, expiration_manager):
"""Constructor.
Args:
protocol_receiver: An _Awaited wrapping of the base.ProtocolReceiver to
which protocol objects should be passed during the operation. May be
of kind _Awaited.Kind.NOT_YET_ARRIVED if the customer's subscription is
not yet known and may be of kind _Awaited.Kind.ARRIVED but with a value
of None if the customer's subscription did not include a
ProtocolReceiver.
lock: The operation-wide lock.
pool: A thread pool.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
"""
self._lock = lock
self._pool = pool
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
self._protocol_receiver = protocol_receiver
self._context = _NOT_YET_SEEN
def _abort_and_notify(self, outcome):
if self._termination_manager.outcome is None:
self._termination_manager.abort(outcome)
self._transmission_manager.abort(outcome)
self._expiration_manager.terminate()
def _deliver(self, behavior, value):
def deliver():
delivery_outcome = callable_util.call_logging_exceptions(
behavior, _EXCEPTION_LOG_MESSAGE, value)
if delivery_outcome.kind is callable_util.Outcome.Kind.RAISED:
with self._lock:
self._abort_and_notify(_LOCAL_FAILURE_OUTCOME)
self._pool.submit(
callable_util.with_exceptions_logged(
deliver, _constants.INTERNAL_ERROR_LOG_MESSAGE))
def set_protocol_receiver(self, protocol_receiver):
"""See _interfaces.ProtocolManager.set_protocol_receiver for spec."""
self._protocol_receiver = _Awaited(_Awaited.Kind.ARRIVED, protocol_receiver)
if (self._context.kind is _Transitory.Kind.PRESENT and
protocol_receiver is not None):
self._deliver(protocol_receiver.context, self._context.value)
self._context = _GONE
def accept_protocol_context(self, protocol_context):
"""See _interfaces.ProtocolManager.accept_protocol_context for spec."""
if self._protocol_receiver.kind is _Awaited.Kind.ARRIVED:
if self._protocol_receiver.value is not None:
self._deliver(self._protocol_receiver.value.context, protocol_context)
self._context = _GONE
else:
self._context = _Transitory(_Transitory.Kind.PRESENT, protocol_context)
def invocation_protocol_manager(
subscription, lock, pool, termination_manager, transmission_manager,
expiration_manager):
"""Creates an _interfaces.ProtocolManager for invocation-side use.
Args:
subscription: The local customer's subscription to the operation.
lock: The operation-wide lock.
pool: A thread pool.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
"""
if subscription.kind is base.Subscription.Kind.FULL:
awaited_protocol_receiver = _Awaited(
_Awaited.Kind.ARRIVED, subscription.protocol_receiver)
else:
awaited_protocol_receiver = _ARRIVED_AND_NONE
return _ProtocolManager(
awaited_protocol_receiver, lock, pool, termination_manager,
transmission_manager, expiration_manager)
def service_protocol_manager(
lock, pool, termination_manager, transmission_manager, expiration_manager):
"""Creates an _interfaces.ProtocolManager for service-side use.
Args:
lock: The operation-wide lock.
pool: A thread pool.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
"""
return _ProtocolManager(
_NOT_YET_ARRIVED, lock, pool, termination_manager, transmission_manager,
expiration_manager)

@ -1,159 +0,0 @@
# Copyright 2015, 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.
"""State and behavior for ticket reception."""
from grpc.framework.core import _interfaces
from grpc.framework.core import _utilities
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.base import utilities
from grpc.framework.interfaces.links import links
_REMOTE_TICKET_TERMINATION_TO_LOCAL_OUTCOME_KIND = {
links.Ticket.Termination.CANCELLATION: base.Outcome.Kind.CANCELLED,
links.Ticket.Termination.EXPIRATION: base.Outcome.Kind.EXPIRED,
links.Ticket.Termination.SHUTDOWN: base.Outcome.Kind.REMOTE_SHUTDOWN,
links.Ticket.Termination.RECEPTION_FAILURE:
base.Outcome.Kind.RECEPTION_FAILURE,
links.Ticket.Termination.TRANSMISSION_FAILURE:
base.Outcome.Kind.TRANSMISSION_FAILURE,
links.Ticket.Termination.LOCAL_FAILURE: base.Outcome.Kind.REMOTE_FAILURE,
links.Ticket.Termination.REMOTE_FAILURE: base.Outcome.Kind.LOCAL_FAILURE,
}
_RECEPTION_FAILURE_OUTCOME = _utilities.Outcome(
base.Outcome.Kind.RECEPTION_FAILURE, None, None)
def _carrying_protocol_context(ticket):
return ticket.protocol is not None and ticket.protocol.kind in (
links.Protocol.Kind.INVOCATION_CONTEXT,
links.Protocol.Kind.SERVICER_CONTEXT,)
class ReceptionManager(_interfaces.ReceptionManager):
"""A ReceptionManager based around a _Receiver passed to it."""
def __init__(
self, termination_manager, transmission_manager, expiration_manager,
protocol_manager, ingestion_manager):
"""Constructor.
Args:
termination_manager: The operation's _interfaces.TerminationManager.
transmission_manager: The operation's _interfaces.TransmissionManager.
expiration_manager: The operation's _interfaces.ExpirationManager.
protocol_manager: The operation's _interfaces.ProtocolManager.
ingestion_manager: The operation's _interfaces.IngestionManager.
"""
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
self._protocol_manager = protocol_manager
self._ingestion_manager = ingestion_manager
self._lowest_unseen_sequence_number = 0
self._out_of_sequence_tickets = {}
self._aborted = False
def _abort(self, outcome):
self._aborted = True
if self._termination_manager.outcome is None:
self._termination_manager.abort(outcome)
self._transmission_manager.abort(None)
self._expiration_manager.terminate()
def _sequence_failure(self, ticket):
"""Determines a just-arrived ticket's sequential legitimacy.
Args:
ticket: A just-arrived ticket.
Returns:
True if the ticket is sequentially legitimate; False otherwise.
"""
if ticket.sequence_number < self._lowest_unseen_sequence_number:
return True
elif ticket.sequence_number in self._out_of_sequence_tickets:
return True
else:
return False
def _process_one(self, ticket):
if ticket.sequence_number == 0:
self._ingestion_manager.set_group_and_method(ticket.group, ticket.method)
if _carrying_protocol_context(ticket):
self._protocol_manager.accept_protocol_context(ticket.protocol.value)
else:
self._protocol_manager.accept_protocol_context(None)
if ticket.timeout is not None:
self._expiration_manager.change_timeout(ticket.timeout)
if ticket.termination is None:
completion = None
else:
completion = utilities.completion(
ticket.terminal_metadata, ticket.code, ticket.message)
self._termination_manager.reception_complete(ticket.code, ticket.message)
self._ingestion_manager.advance(
ticket.initial_metadata, ticket.payload, completion, ticket.allowance)
if ticket.allowance is not None:
self._transmission_manager.allowance(ticket.allowance)
def _process(self, ticket):
"""Process those tickets ready to be processed.
Args:
ticket: A just-arrived ticket the sequence number of which matches this
_ReceptionManager's _lowest_unseen_sequence_number field.
"""
while True:
self._process_one(ticket)
next_ticket = self._out_of_sequence_tickets.pop(
ticket.sequence_number + 1, None)
if next_ticket is None:
self._lowest_unseen_sequence_number = ticket.sequence_number + 1
return
else:
ticket = next_ticket
def receive_ticket(self, ticket):
"""See _interfaces.ReceptionManager.receive_ticket for specification."""
if self._aborted:
return
elif self._sequence_failure(ticket):
self._abort(_RECEPTION_FAILURE_OUTCOME)
elif ticket.termination not in (None, links.Ticket.Termination.COMPLETION):
outcome_kind = _REMOTE_TICKET_TERMINATION_TO_LOCAL_OUTCOME_KIND[
ticket.termination]
self._abort(
_utilities.Outcome(outcome_kind, ticket.code, ticket.message))
elif ticket.sequence_number == self._lowest_unseen_sequence_number:
self._process(ticket)
else:
self._out_of_sequence_tickets[ticket.sequence_number] = ticket

@ -1,229 +0,0 @@
# Copyright 2015, 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.
"""State and behavior for operation termination."""
import abc
import six
from grpc.framework.core import _constants
from grpc.framework.core import _interfaces
from grpc.framework.core import _utilities
from grpc.framework.foundation import callable_util
from grpc.framework.interfaces.base import base
def _invocation_completion_predicate(
unused_emission_complete, unused_transmission_complete,
unused_reception_complete, ingestion_complete):
return ingestion_complete
def _service_completion_predicate(
unused_emission_complete, transmission_complete, unused_reception_complete,
ingestion_complete):
return transmission_complete and ingestion_complete
class TerminationManager(six.with_metaclass(abc.ABCMeta, _interfaces.TerminationManager)):
"""A _interfaces.TransmissionManager on which another manager may be set."""
@abc.abstractmethod
def set_expiration_manager(self, expiration_manager):
"""Sets the expiration manager with which this manager will interact.
Args:
expiration_manager: The _interfaces.ExpirationManager associated with the
current operation.
"""
raise NotImplementedError()
class _TerminationManager(TerminationManager):
"""An implementation of TerminationManager."""
def __init__(self, predicate, action, pool):
"""Constructor.
Args:
predicate: One of _invocation_completion_predicate or
_service_completion_predicate to be used to determine when the operation
has completed.
action: A behavior to pass the operation outcome's kind on operation
termination.
pool: A thread pool.
"""
self._predicate = predicate
self._action = action
self._pool = pool
self._expiration_manager = None
self._callbacks = []
self._code = None
self._details = None
self._emission_complete = False
self._transmission_complete = False
self._reception_complete = False
self._ingestion_complete = False
# The None-ness of outcome is the operation-wide record of whether and how
# the operation has terminated.
self.outcome = None
def set_expiration_manager(self, expiration_manager):
self._expiration_manager = expiration_manager
def _terminate_internal_only(self, outcome):
"""Terminates the operation.
Args:
outcome: A base.Outcome describing the outcome of the operation.
"""
self.outcome = outcome
callbacks = list(self._callbacks)
self._callbacks = None
act = callable_util.with_exceptions_logged(
self._action, _constants.INTERNAL_ERROR_LOG_MESSAGE)
# TODO(issue 3202): Don't call the local application's callbacks if it has
# previously shown a programming defect.
if False and outcome.kind is base.Outcome.Kind.LOCAL_FAILURE:
self._pool.submit(act, base.Outcome.Kind.LOCAL_FAILURE)
else:
def call_callbacks_and_act(callbacks, outcome):
for callback in callbacks:
callback_outcome = callable_util.call_logging_exceptions(
callback, _constants.TERMINATION_CALLBACK_EXCEPTION_LOG_MESSAGE,
outcome)
if callback_outcome.exception is not None:
act_outcome_kind = base.Outcome.Kind.LOCAL_FAILURE
break
else:
act_outcome_kind = outcome.kind
act(act_outcome_kind)
self._pool.submit(
callable_util.with_exceptions_logged(
call_callbacks_and_act, _constants.INTERNAL_ERROR_LOG_MESSAGE),
callbacks, outcome)
def _terminate_and_notify(self, outcome):
self._terminate_internal_only(outcome)
self._expiration_manager.terminate()
def _perhaps_complete(self):
if self._predicate(
self._emission_complete, self._transmission_complete,
self._reception_complete, self._ingestion_complete):
self._terminate_and_notify(
_utilities.Outcome(
base.Outcome.Kind.COMPLETED, self._code, self._details))
return True
else:
return False
def is_active(self):
"""See _interfaces.TerminationManager.is_active for specification."""
return self.outcome is None
def add_callback(self, callback):
"""See _interfaces.TerminationManager.add_callback for specification."""
if self.outcome is None:
self._callbacks.append(callback)
return None
else:
return self.outcome
def emission_complete(self):
"""See superclass method for specification."""
if self.outcome is None:
self._emission_complete = True
self._perhaps_complete()
def transmission_complete(self):
"""See superclass method for specification."""
if self.outcome is None:
self._transmission_complete = True
return self._perhaps_complete()
else:
return False
def reception_complete(self, code, details):
"""See superclass method for specification."""
if self.outcome is None:
self._reception_complete = True
self._code = code
self._details = details
self._perhaps_complete()
def ingestion_complete(self):
"""See superclass method for specification."""
if self.outcome is None:
self._ingestion_complete = True
self._perhaps_complete()
def expire(self):
"""See _interfaces.TerminationManager.expire for specification."""
self._terminate_internal_only(
_utilities.Outcome(base.Outcome.Kind.EXPIRED, None, None))
def abort(self, outcome):
"""See _interfaces.TerminationManager.abort for specification."""
self._terminate_and_notify(outcome)
def invocation_termination_manager(action, pool):
"""Creates a TerminationManager appropriate for invocation-side use.
Args:
action: An action to call on operation termination.
pool: A thread pool in which to execute the passed action and any
termination callbacks that are registered during the operation.
Returns:
A TerminationManager appropriate for invocation-side use.
"""
return _TerminationManager(_invocation_completion_predicate, action, pool)
def service_termination_manager(action, pool):
"""Creates a TerminationManager appropriate for service-side use.
Args:
action: An action to call on operation termination.
pool: A thread pool in which to execute the passed action and any
termination callbacks that are registered during the operation.
Returns:
A TerminationManager appropriate for service-side use.
"""
return _TerminationManager(_service_completion_predicate, action, pool)

@ -1,335 +0,0 @@
# Copyright 2015, 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.
"""State and behavior for ticket transmission during an operation."""
import collections
import enum
from grpc.framework.core import _constants
from grpc.framework.core import _interfaces
from grpc.framework.core import _utilities
from grpc.framework.foundation import callable_util
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.links import links
_TRANSMISSION_EXCEPTION_LOG_MESSAGE = 'Exception during transmission!'
_TRANSMISSION_FAILURE_OUTCOME = _utilities.Outcome(
base.Outcome.Kind.TRANSMISSION_FAILURE, None, None)
def _explode_completion(completion):
if completion is None:
return None, None, None, None
else:
return (
completion.terminal_metadata, completion.code, completion.message,
links.Ticket.Termination.COMPLETION)
class _Abort(
collections.namedtuple(
'_Abort', ('kind', 'termination', 'code', 'details',))):
"""Tracks whether the operation aborted and what is to be done about it.
Attributes:
kind: A Kind value describing the overall kind of the _Abort.
termination: A links.Ticket.Termination value to be sent to the other side
of the operation. Only valid if kind is Kind.ABORTED_NOTIFY_NEEDED.
code: A code value to be sent to the other side of the operation. Only
valid if kind is Kind.ABORTED_NOTIFY_NEEDED.
details: A details value to be sent to the other side of the operation.
Only valid if kind is Kind.ABORTED_NOTIFY_NEEDED.
"""
@enum.unique
class Kind(enum.Enum):
NOT_ABORTED = 'not aborted'
ABORTED_NOTIFY_NEEDED = 'aborted notify needed'
ABORTED_NO_NOTIFY = 'aborted no notify'
_NOT_ABORTED = _Abort(_Abort.Kind.NOT_ABORTED, None, None, None)
_ABORTED_NO_NOTIFY = _Abort(_Abort.Kind.ABORTED_NO_NOTIFY, None, None, None)
class TransmissionManager(_interfaces.TransmissionManager):
"""An _interfaces.TransmissionManager that sends links.Tickets."""
def __init__(
self, operation_id, ticket_sink, lock, pool, termination_manager):
"""Constructor.
Args:
operation_id: The operation's ID.
ticket_sink: A callable that accepts tickets and sends them to the other
side of the operation.
lock: The operation-servicing-wide lock object.
pool: A thread pool in which the work of transmitting tickets will be
performed.
termination_manager: The _interfaces.TerminationManager associated with
this operation.
"""
self._lock = lock
self._pool = pool
self._ticket_sink = ticket_sink
self._operation_id = operation_id
self._termination_manager = termination_manager
self._expiration_manager = None
self._lowest_unused_sequence_number = 0
self._remote_allowance = 1
self._remote_complete = False
self._timeout = None
self._local_allowance = 0
self._initial_metadata = None
self._payloads = []
self._completion = None
self._abort = _NOT_ABORTED
self._transmitting = False
def set_expiration_manager(self, expiration_manager):
"""Sets the ExpirationManager with which this manager will cooperate."""
self._expiration_manager = expiration_manager
def _next_ticket(self):
"""Creates the next ticket to be transmitted.
Returns:
A links.Ticket to be sent to the other side of the operation or None if
there is nothing to be sent at this time.
"""
if self._abort.kind is _Abort.Kind.ABORTED_NO_NOTIFY:
return None
elif self._abort.kind is _Abort.Kind.ABORTED_NOTIFY_NEEDED:
termination = self._abort.termination
code, details = self._abort.code, self._abort.details
self._abort = _ABORTED_NO_NOTIFY
return links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None, None,
None, None, None, None, None, None, code, details, termination, None)
action = False
# TODO(nathaniel): Support other subscriptions.
local_subscription = links.Ticket.Subscription.FULL
timeout = self._timeout
if timeout is not None:
self._timeout = None
action = True
if self._local_allowance <= 0:
allowance = None
else:
allowance = self._local_allowance
self._local_allowance = 0
action = True
initial_metadata = self._initial_metadata
if initial_metadata is not None:
self._initial_metadata = None
action = True
if not self._payloads or self._remote_allowance <= 0:
payload = None
else:
payload = self._payloads.pop(0)
self._remote_allowance -= 1
action = True
if self._completion is None or self._payloads:
terminal_metadata, code, message, termination = None, None, None, None
else:
terminal_metadata, code, message, termination = _explode_completion(
self._completion)
self._completion = None
action = True
if action:
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None, None,
local_subscription, timeout, allowance, initial_metadata, payload,
terminal_metadata, code, message, termination, None)
self._lowest_unused_sequence_number += 1
return ticket
else:
return None
def _transmit(self, ticket):
"""Commences the transmission loop sending tickets.
Args:
ticket: A links.Ticket to be sent to the other side of the operation.
"""
def transmit(ticket):
while True:
transmission_outcome = callable_util.call_logging_exceptions(
self._ticket_sink, _TRANSMISSION_EXCEPTION_LOG_MESSAGE, ticket)
if transmission_outcome.exception is None:
with self._lock:
if ticket.termination is links.Ticket.Termination.COMPLETION:
self._termination_manager.transmission_complete()
ticket = self._next_ticket()
if ticket is None:
self._transmitting = False
return
else:
with self._lock:
self._abort = _ABORTED_NO_NOTIFY
if self._termination_manager.outcome is None:
self._termination_manager.abort(_TRANSMISSION_FAILURE_OUTCOME)
self._expiration_manager.terminate()
return
self._pool.submit(callable_util.with_exceptions_logged(
transmit, _constants.INTERNAL_ERROR_LOG_MESSAGE), ticket)
self._transmitting = True
def kick_off(
self, group, method, timeout, protocol_options, initial_metadata,
payload, completion, allowance):
"""See _interfaces.TransmissionManager.kickoff for specification."""
# TODO(nathaniel): Support other subscriptions.
subscription = links.Ticket.Subscription.FULL
terminal_metadata, code, message, termination = _explode_completion(
completion)
self._remote_allowance = 1 if payload is None else 0
protocol = links.Protocol(links.Protocol.Kind.CALL_OPTION, protocol_options)
ticket = links.Ticket(
self._operation_id, 0, group, method, subscription, timeout, allowance,
initial_metadata, payload, terminal_metadata, code, message,
termination, protocol)
self._lowest_unused_sequence_number = 1
self._transmit(ticket)
def advance(self, initial_metadata, payload, completion, allowance):
"""See _interfaces.TransmissionManager.advance for specification."""
if self._abort.kind is not _Abort.Kind.NOT_ABORTED:
return
effective_initial_metadata = initial_metadata
effective_payload = payload
effective_completion = completion
if allowance is not None and not self._remote_complete:
effective_allowance = allowance
else:
effective_allowance = None
if self._transmitting:
if effective_initial_metadata is not None:
self._initial_metadata = effective_initial_metadata
if effective_payload is not None:
self._payloads.append(effective_payload)
if effective_completion is not None:
self._completion = effective_completion
if effective_allowance is not None:
self._local_allowance += effective_allowance
else:
if effective_payload is not None:
if 0 < self._remote_allowance:
ticket_payload = effective_payload
self._remote_allowance -= 1
else:
self._payloads.append(effective_payload)
ticket_payload = None
else:
ticket_payload = None
if effective_completion is not None and not self._payloads:
ticket_completion = effective_completion
else:
self._completion = effective_completion
ticket_completion = None
if any(
(effective_initial_metadata, ticket_payload, ticket_completion,
effective_allowance)):
terminal_metadata, code, message, termination = _explode_completion(
completion)
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None, None,
None, None, allowance, effective_initial_metadata, ticket_payload,
terminal_metadata, code, message, termination, None)
self._lowest_unused_sequence_number += 1
self._transmit(ticket)
def timeout(self, timeout):
"""See _interfaces.TransmissionManager.timeout for specification."""
if self._abort.kind is not _Abort.Kind.NOT_ABORTED:
return
elif self._transmitting:
self._timeout = timeout
else:
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None, None,
None, timeout, None, None, None, None, None, None, None, None)
self._lowest_unused_sequence_number += 1
self._transmit(ticket)
def allowance(self, allowance):
"""See _interfaces.TransmissionManager.allowance for specification."""
if self._abort.kind is not _Abort.Kind.NOT_ABORTED:
return
elif self._transmitting or not self._payloads:
self._remote_allowance += allowance
else:
self._remote_allowance += allowance - 1
payload = self._payloads.pop(0)
if self._payloads:
completion = None
else:
completion = self._completion
self._completion = None
terminal_metadata, code, message, termination = _explode_completion(
completion)
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None, None,
None, None, None, None, payload, terminal_metadata, code, message,
termination, None)
self._lowest_unused_sequence_number += 1
self._transmit(ticket)
def remote_complete(self):
"""See _interfaces.TransmissionManager.remote_complete for specification."""
self._remote_complete = True
self._local_allowance = 0
def abort(self, outcome):
"""See _interfaces.TransmissionManager.abort for specification."""
if self._abort.kind is _Abort.Kind.NOT_ABORTED:
if outcome is None:
self._abort = _ABORTED_NO_NOTIFY
else:
termination = _constants.ABORTION_OUTCOME_TO_TICKET_TERMINATION.get(
outcome.kind)
if termination is None:
self._abort = _ABORTED_NO_NOTIFY
elif self._transmitting:
self._abort = _Abort(
_Abort.Kind.ABORTED_NOTIFY_NEEDED, termination, outcome.code,
outcome.details)
else:
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None,
None, None, None, None, None, None, None, outcome.code,
outcome.details, termination, None)
self._transmit(ticket)
self._abort = _ABORTED_NO_NOTIFY

@ -1,54 +0,0 @@
# Copyright 2015, 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.
"""Package-internal utilities."""
import collections
from grpc.framework.interfaces.base import base
class ServicerPackage(
collections.namedtuple(
'ServicerPackage', ('servicer', 'default_timeout', 'maximum_timeout'))):
"""A trivial bundle class.
Attributes:
servicer: A base.Servicer.
default_timeout: A float indicating the length of time in seconds to allow
for an operation invoked without a timeout.
maximum_timeout: A float indicating the maximum length of time in seconds to
allow for an operation.
"""
class Outcome(
base.Outcome,
collections.namedtuple('Outcome', ('kind', 'code', 'details',))):
"""A trivial implementation of base.Outcome."""

@ -1,62 +0,0 @@
# Copyright 2015, 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.
"""Entry points into the ticket-exchange-based base layer implementation."""
# base and links are referenced from specification in this module.
from grpc.framework.core import _end
from grpc.framework.interfaces.base import base # pylint: disable=unused-import
from grpc.framework.interfaces.links import links # pylint: disable=unused-import
def invocation_end_link():
"""Creates a base.End-links.Link suitable for operation invocation.
Returns:
An object that is both a base.End and a links.Link, that supports operation
invocation, and that translates operation invocation into ticket exchange.
"""
return _end.serviceless_end_link()
def service_end_link(servicer, default_timeout, maximum_timeout):
"""Creates a base.End-links.Link suitable for operation service.
Args:
servicer: A base.Servicer for servicing operations.
default_timeout: A length of time in seconds to be used as the default
time alloted for a single operation.
maximum_timeout: A length of time in seconds to be used as the maximum
time alloted for a single operation.
Returns:
An object that is both a base.End and a links.Link and that services
operations that arrive at it through ticket exchange.
"""
return _end.serviceful_end_link(servicer, default_timeout, maximum_timeout)

@ -1,30 +0,0 @@
# Copyright 2015, 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.

@ -1,223 +0,0 @@
# Copyright 2015, 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.
"""Utility functions for invoking RPCs."""
from grpc.framework.crust import _control
from grpc.framework.interfaces.base import utilities
from grpc.framework.interfaces.face import face
_ITERATOR_EXCEPTION_LOG_MESSAGE = 'Exception iterating over requests!'
_EMPTY_COMPLETION = utilities.completion(None, None, None)
def _invoke(
end, group, method, timeout, protocol_options, initial_metadata, payload,
complete):
rendezvous = _control.Rendezvous(None, None)
subscription = utilities.full_subscription(
rendezvous, _control.protocol_receiver(rendezvous))
operation_context, operator = end.operate(
group, method, subscription, timeout, protocol_options=protocol_options,
initial_metadata=initial_metadata, payload=payload,
completion=_EMPTY_COMPLETION if complete else None)
rendezvous.set_operator_and_context(operator, operation_context)
outcome = operation_context.add_termination_callback(rendezvous.set_outcome)
if outcome is not None:
rendezvous.set_outcome(outcome)
return rendezvous, operation_context, outcome
def _event_return_unary(
receiver, abortion_callback, rendezvous, operation_context, outcome, pool):
if outcome is None:
def in_pool():
abortion = rendezvous.add_abortion_callback(abortion_callback)
if abortion is None:
try:
receiver.initial_metadata(rendezvous.initial_metadata())
receiver.response(next(rendezvous))
receiver.complete(
rendezvous.terminal_metadata(), rendezvous.code(),
rendezvous.details())
except face.AbortionError:
pass
else:
abortion_callback(abortion)
pool.submit(_control.pool_wrap(in_pool, operation_context))
return rendezvous
def _event_return_stream(
receiver, abortion_callback, rendezvous, operation_context, outcome, pool):
if outcome is None:
def in_pool():
abortion = rendezvous.add_abortion_callback(abortion_callback)
if abortion is None:
try:
receiver.initial_metadata(rendezvous.initial_metadata())
for response in rendezvous:
receiver.response(response)
receiver.complete(
rendezvous.terminal_metadata(), rendezvous.code(),
rendezvous.details())
except face.AbortionError:
pass
else:
abortion_callback(abortion)
pool.submit(_control.pool_wrap(in_pool, operation_context))
return rendezvous
def blocking_unary_unary(
end, group, method, timeout, with_call, protocol_options, initial_metadata,
payload):
"""Services in a blocking fashion a unary-unary servicer method."""
rendezvous, unused_operation_context, unused_outcome = _invoke(
end, group, method, timeout, protocol_options, initial_metadata, payload,
True)
if with_call:
return next(rendezvous), rendezvous
else:
return next(rendezvous)
def future_unary_unary(
end, group, method, timeout, protocol_options, initial_metadata, payload):
"""Services a value-in value-out servicer method by returning a Future."""
rendezvous, unused_operation_context, unused_outcome = _invoke(
end, group, method, timeout, protocol_options, initial_metadata, payload,
True)
return rendezvous
def inline_unary_stream(
end, group, method, timeout, protocol_options, initial_metadata, payload):
"""Services a value-in stream-out servicer method."""
rendezvous, unused_operation_context, unused_outcome = _invoke(
end, group, method, timeout, protocol_options, initial_metadata, payload,
True)
return rendezvous
def blocking_stream_unary(
end, group, method, timeout, with_call, protocol_options, initial_metadata,
payload_iterator, pool):
"""Services in a blocking fashion a stream-in value-out servicer method."""
rendezvous, operation_context, outcome = _invoke(
end, group, method, timeout, protocol_options, initial_metadata, None,
False)
if outcome is None:
def in_pool():
for payload in payload_iterator:
rendezvous.consume(payload)
rendezvous.terminate()
pool.submit(_control.pool_wrap(in_pool, operation_context))
if with_call:
return next(rendezvous), rendezvous
else:
return next(rendezvous)
else:
if with_call:
return next(rendezvous), rendezvous
else:
return next(rendezvous)
def future_stream_unary(
end, group, method, timeout, protocol_options, initial_metadata,
payload_iterator, pool):
"""Services a stream-in value-out servicer method by returning a Future."""
rendezvous, operation_context, outcome = _invoke(
end, group, method, timeout, protocol_options, initial_metadata, None,
False)
if outcome is None:
def in_pool():
for payload in payload_iterator:
rendezvous.consume(payload)
rendezvous.terminate()
pool.submit(_control.pool_wrap(in_pool, operation_context))
return rendezvous
def inline_stream_stream(
end, group, method, timeout, protocol_options, initial_metadata,
payload_iterator, pool):
"""Services a stream-in stream-out servicer method."""
rendezvous, operation_context, outcome = _invoke(
end, group, method, timeout, protocol_options, initial_metadata, None,
False)
if outcome is None:
def in_pool():
for payload in payload_iterator:
rendezvous.consume(payload)
rendezvous.terminate()
pool.submit(_control.pool_wrap(in_pool, operation_context))
return rendezvous
def event_unary_unary(
end, group, method, timeout, protocol_options, initial_metadata, payload,
receiver, abortion_callback, pool):
rendezvous, operation_context, outcome = _invoke(
end, group, method, timeout, protocol_options, initial_metadata, payload,
True)
return _event_return_unary(
receiver, abortion_callback, rendezvous, operation_context, outcome, pool)
def event_unary_stream(
end, group, method, timeout, protocol_options, initial_metadata, payload,
receiver, abortion_callback, pool):
rendezvous, operation_context, outcome = _invoke(
end, group, method, timeout, protocol_options, initial_metadata, payload,
True)
return _event_return_stream(
receiver, abortion_callback, rendezvous, operation_context, outcome, pool)
def event_stream_unary(
end, group, method, timeout, protocol_options, initial_metadata, receiver,
abortion_callback, pool):
rendezvous, operation_context, outcome = _invoke(
end, group, method, timeout, protocol_options, initial_metadata, None,
False)
return _event_return_unary(
receiver, abortion_callback, rendezvous, operation_context, outcome, pool)
def event_stream_stream(
end, group, method, timeout, protocol_options, initial_metadata, receiver,
abortion_callback, pool):
rendezvous, operation_context, outcome = _invoke(
end, group, method, timeout, protocol_options, initial_metadata, None,
False)
return _event_return_stream(
receiver, abortion_callback, rendezvous, operation_context, outcome, pool)

@ -1,584 +0,0 @@
# Copyright 2015, 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.
"""State and behavior for translating between sync and async control flow."""
import collections
import enum
import sys
import threading
import time
from grpc.framework.foundation import abandonment
from grpc.framework.foundation import callable_util
from grpc.framework.foundation import future
from grpc.framework.foundation import stream
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.base import utilities
from grpc.framework.interfaces.face import face
_DONE_CALLBACK_LOG_MESSAGE = 'Exception calling Future "done" callback!'
_INTERNAL_ERROR_LOG_MESSAGE = ':-( RPC Framework (Crust) Internal Error! )-:'
_CANNOT_SET_INITIAL_METADATA = (
'Could not set initial metadata - has it already been set, or has a ' +
'payload already been sent?')
_CANNOT_SET_TERMINAL_METADATA = (
'Could not set terminal metadata - has it already been set, or has RPC ' +
'completion already been indicated?')
_CANNOT_SET_CODE = (
'Could not set code - has it already been set, or has RPC completion ' +
'already been indicated?')
_CANNOT_SET_DETAILS = (
'Could not set details - has it already been set, or has RPC completion ' +
'already been indicated?')
class _DummyOperator(base.Operator):
def advance(
self, initial_metadata=None, payload=None, completion=None,
allowance=None):
pass
_DUMMY_OPERATOR = _DummyOperator()
class _Awaited(
collections.namedtuple('_Awaited', ('kind', 'value',))):
@enum.unique
class Kind(enum.Enum):
NOT_YET_ARRIVED = 'not yet arrived'
ARRIVED = 'arrived'
_NOT_YET_ARRIVED = _Awaited(_Awaited.Kind.NOT_YET_ARRIVED, None)
_ARRIVED_AND_NONE = _Awaited(_Awaited.Kind.ARRIVED, None)
class _Transitory(
collections.namedtuple('_Transitory', ('kind', 'value',))):
@enum.unique
class Kind(enum.Enum):
NOT_YET_SEEN = 'not yet seen'
PRESENT = 'present'
GONE = 'gone'
_NOT_YET_SEEN = _Transitory(_Transitory.Kind.NOT_YET_SEEN, None)
_GONE = _Transitory(_Transitory.Kind.GONE, None)
class _Termination(
collections.namedtuple(
'_Termination', ('terminated', 'abortion', 'abortion_error',))):
"""Values indicating whether and how an RPC has terminated.
Attributes:
terminated: A boolean indicating whether or not the RPC has terminated.
abortion: A face.Abortion value describing the RPC's abortion or None if the
RPC did not abort.
abortion_error: A face.AbortionError describing the RPC's abortion or None
if the RPC did not abort.
"""
_NOT_TERMINATED = _Termination(False, None, None)
_OPERATION_OUTCOME_KIND_TO_TERMINATION_CONSTRUCTOR = {
base.Outcome.Kind.COMPLETED: lambda *unused_args: _Termination(
True, None, None),
base.Outcome.Kind.CANCELLED: lambda *args: _Termination(
True, face.Abortion(face.Abortion.Kind.CANCELLED, *args),
face.CancellationError(*args)),
base.Outcome.Kind.EXPIRED: lambda *args: _Termination(
True, face.Abortion(face.Abortion.Kind.EXPIRED, *args),
face.ExpirationError(*args)),
base.Outcome.Kind.LOCAL_SHUTDOWN: lambda *args: _Termination(
True, face.Abortion(face.Abortion.Kind.LOCAL_SHUTDOWN, *args),
face.LocalShutdownError(*args)),
base.Outcome.Kind.REMOTE_SHUTDOWN: lambda *args: _Termination(
True, face.Abortion(face.Abortion.Kind.REMOTE_SHUTDOWN, *args),
face.RemoteShutdownError(*args)),
base.Outcome.Kind.RECEPTION_FAILURE: lambda *args: _Termination(
True, face.Abortion(face.Abortion.Kind.NETWORK_FAILURE, *args),
face.NetworkError(*args)),
base.Outcome.Kind.TRANSMISSION_FAILURE: lambda *args: _Termination(
True, face.Abortion(face.Abortion.Kind.NETWORK_FAILURE, *args),
face.NetworkError(*args)),
base.Outcome.Kind.LOCAL_FAILURE: lambda *args: _Termination(
True, face.Abortion(face.Abortion.Kind.LOCAL_FAILURE, *args),
face.LocalError(*args)),
base.Outcome.Kind.REMOTE_FAILURE: lambda *args: _Termination(
True, face.Abortion(face.Abortion.Kind.REMOTE_FAILURE, *args),
face.RemoteError(*args)),
}
def _wait_once_until(condition, until):
if until is None:
condition.wait()
else:
remaining = until - time.time()
if remaining < 0:
raise future.TimeoutError()
else:
condition.wait(timeout=remaining)
def _done_callback_as_operation_termination_callback(
done_callback, rendezvous):
def operation_termination_callback(operation_outcome):
rendezvous.set_outcome(operation_outcome)
done_callback(rendezvous)
return operation_termination_callback
def _abortion_callback_as_operation_termination_callback(
rpc_abortion_callback, rendezvous_set_outcome):
def operation_termination_callback(operation_outcome):
termination = rendezvous_set_outcome(operation_outcome)
if termination.abortion is not None:
rpc_abortion_callback(termination.abortion)
return operation_termination_callback
class Rendezvous(base.Operator, future.Future, stream.Consumer, face.Call):
"""A rendez-vous for the threads of an operation.
Instances of this object present iterator and stream.Consumer interfaces for
interacting with application code and present a base.Operator interface and
maintain a base.Operator internally for interacting with base interface code.
"""
def __init__(self, operator, operation_context):
self._condition = threading.Condition()
self._operator = operator
self._operation_context = operation_context
self._protocol_context = _NOT_YET_ARRIVED
self._up_initial_metadata = _NOT_YET_ARRIVED
self._up_payload = None
self._up_allowance = 1
self._up_completion = _NOT_YET_ARRIVED
self._down_initial_metadata = _NOT_YET_SEEN
self._down_payload = None
self._down_allowance = 1
self._down_terminal_metadata = _NOT_YET_SEEN
self._down_code = _NOT_YET_SEEN
self._down_details = _NOT_YET_SEEN
self._termination = _NOT_TERMINATED
# The semantics of future.Future.cancel and future.Future.cancelled are
# slightly wonky, so they have to be tracked separately from the rest of the
# result of the RPC. This field tracks whether cancellation was requested
# prior to termination of the RPC
self._cancelled = False
def set_operator_and_context(self, operator, operation_context):
with self._condition:
self._operator = operator
self._operation_context = operation_context
def _down_completion(self):
if self._down_terminal_metadata.kind is _Transitory.Kind.NOT_YET_SEEN:
terminal_metadata = None
self._down_terminal_metadata = _GONE
elif self._down_terminal_metadata.kind is _Transitory.Kind.PRESENT:
terminal_metadata = self._down_terminal_metadata.value
self._down_terminal_metadata = _GONE
else:
terminal_metadata = None
if self._down_code.kind is _Transitory.Kind.NOT_YET_SEEN:
code = None
self._down_code = _GONE
elif self._down_code.kind is _Transitory.Kind.PRESENT:
code = self._down_code.value
self._down_code = _GONE
else:
code = None
if self._down_details.kind is _Transitory.Kind.NOT_YET_SEEN:
details = None
self._down_details = _GONE
elif self._down_details.kind is _Transitory.Kind.PRESENT:
details = self._down_details.value
self._down_details = _GONE
else:
details = None
return utilities.completion(terminal_metadata, code, details)
def _set_outcome(self, outcome):
if not self._termination.terminated:
self._operator = _DUMMY_OPERATOR
self._operation_context = None
self._down_initial_metadata = _GONE
self._down_payload = None
self._down_terminal_metadata = _GONE
self._down_code = _GONE
self._down_details = _GONE
if self._up_initial_metadata.kind is _Awaited.Kind.NOT_YET_ARRIVED:
initial_metadata = None
else:
initial_metadata = self._up_initial_metadata.value
if self._up_completion.kind is _Awaited.Kind.NOT_YET_ARRIVED:
terminal_metadata = None
else:
terminal_metadata = self._up_completion.value.terminal_metadata
if outcome.kind is base.Outcome.Kind.COMPLETED:
code = self._up_completion.value.code
details = self._up_completion.value.message
else:
code = outcome.code
details = outcome.details
self._termination = _OPERATION_OUTCOME_KIND_TO_TERMINATION_CONSTRUCTOR[
outcome.kind](initial_metadata, terminal_metadata, code, details)
self._condition.notify_all()
return self._termination
def advance(
self, initial_metadata=None, payload=None, completion=None,
allowance=None):
with self._condition:
if initial_metadata is not None:
self._up_initial_metadata = _Awaited(
_Awaited.Kind.ARRIVED, initial_metadata)
if payload is not None:
if self._up_initial_metadata.kind is _Awaited.Kind.NOT_YET_ARRIVED:
self._up_initial_metadata = _ARRIVED_AND_NONE
self._up_payload = payload
self._up_allowance -= 1
if completion is not None:
if self._up_initial_metadata.kind is _Awaited.Kind.NOT_YET_ARRIVED:
self._up_initial_metadata = _ARRIVED_AND_NONE
self._up_completion = _Awaited(
_Awaited.Kind.ARRIVED, completion)
if allowance is not None:
if self._down_payload is not None:
self._operator.advance(payload=self._down_payload)
self._down_payload = None
self._down_allowance += allowance - 1
else:
self._down_allowance += allowance
self._condition.notify_all()
def cancel(self):
with self._condition:
if self._operation_context is not None:
self._operation_context.cancel()
self._cancelled = True
return False
def cancelled(self):
with self._condition:
return self._cancelled
def running(self):
with self._condition:
return not self._termination.terminated
def done(self):
with self._condition:
return self._termination.terminated
def result(self, timeout=None):
until = None if timeout is None else time.time() + timeout
with self._condition:
while True:
if self._termination.terminated:
if self._termination.abortion is None:
return self._up_payload
elif self._termination.abortion.kind is face.Abortion.Kind.CANCELLED:
raise future.CancelledError()
else:
raise self._termination.abortion_error # pylint: disable=raising-bad-type
else:
_wait_once_until(self._condition, until)
def exception(self, timeout=None):
until = None if timeout is None else time.time() + timeout
with self._condition:
while True:
if self._termination.terminated:
if self._termination.abortion is None:
return None
else:
return self._termination.abortion_error
else:
_wait_once_until(self._condition, until)
def traceback(self, timeout=None):
until = None if timeout is None else time.time() + timeout
with self._condition:
while True:
if self._termination.terminated:
if self._termination.abortion_error is None:
return None
else:
abortion_error = self._termination.abortion_error
break
else:
_wait_once_until(self._condition, until)
try:
raise abortion_error
except face.AbortionError:
return sys.exc_info()[2]
def add_done_callback(self, fn):
with self._condition:
if self._operation_context is not None:
outcome = self._operation_context.add_termination_callback(
_done_callback_as_operation_termination_callback(fn, self))
if outcome is None:
return
else:
self._set_outcome(outcome)
fn(self)
def consume(self, value):
with self._condition:
while True:
if self._termination.terminated:
return
elif 0 < self._down_allowance:
self._operator.advance(payload=value)
self._down_allowance -= 1
return
else:
self._condition.wait()
def terminate(self):
with self._condition:
if self._termination.terminated:
return
elif self._down_code.kind is _Transitory.Kind.GONE:
# Conform to specified idempotence of terminate by ignoring extra calls.
return
else:
completion = self._down_completion()
self._operator.advance(completion=completion)
def consume_and_terminate(self, value):
with self._condition:
while True:
if self._termination.terminated:
return
elif 0 < self._down_allowance:
completion = self._down_completion()
self._operator.advance(payload=value, completion=completion)
return
else:
self._condition.wait()
def __iter__(self):
return self
def __next__(self):
return self.next()
def next(self):
with self._condition:
while True:
if self._termination.abortion_error is not None:
raise self._termination.abortion_error
elif self._up_payload is not None:
payload = self._up_payload
self._up_payload = None
if self._up_completion.kind is _Awaited.Kind.NOT_YET_ARRIVED:
self._operator.advance(allowance=1)
return payload
elif self._up_completion.kind is _Awaited.Kind.ARRIVED:
raise StopIteration()
else:
self._condition.wait()
def is_active(self):
with self._condition:
return not self._termination.terminated
def time_remaining(self):
if self._operation_context is None:
return 0
else:
return self._operation_context.time_remaining()
def add_abortion_callback(self, abortion_callback):
with self._condition:
if self._operation_context is None:
return self._termination.abortion
else:
outcome = self._operation_context.add_termination_callback(
_abortion_callback_as_operation_termination_callback(
abortion_callback, self.set_outcome))
if outcome is not None:
return self._set_outcome(outcome).abortion
else:
return self._termination.abortion
def protocol_context(self):
with self._condition:
while True:
if self._protocol_context.kind is _Awaited.Kind.ARRIVED:
return self._protocol_context.value
elif self._termination.abortion_error is not None:
raise self._termination.abortion_error
else:
self._condition.wait()
def initial_metadata(self):
with self._condition:
while True:
if self._up_initial_metadata.kind is _Awaited.Kind.ARRIVED:
return self._up_initial_metadata.value
elif self._termination.terminated:
return None
else:
self._condition.wait()
def terminal_metadata(self):
with self._condition:
while True:
if self._up_completion.kind is _Awaited.Kind.ARRIVED:
return self._up_completion.value.terminal_metadata
elif self._termination.terminated:
return None
else:
self._condition.wait()
def code(self):
with self._condition:
while True:
if self._up_completion.kind is _Awaited.Kind.ARRIVED:
return self._up_completion.value.code
elif self._termination.terminated:
return None
else:
self._condition.wait()
def details(self):
with self._condition:
while True:
if self._up_completion.kind is _Awaited.Kind.ARRIVED:
return self._up_completion.value.message
elif self._termination.terminated:
return None
else:
self._condition.wait()
def set_initial_metadata(self, initial_metadata):
with self._condition:
if (self._down_initial_metadata.kind is not
_Transitory.Kind.NOT_YET_SEEN):
raise ValueError(_CANNOT_SET_INITIAL_METADATA)
else:
self._down_initial_metadata = _GONE
self._operator.advance(initial_metadata=initial_metadata)
def set_terminal_metadata(self, terminal_metadata):
with self._condition:
if (self._down_terminal_metadata.kind is not
_Transitory.Kind.NOT_YET_SEEN):
raise ValueError(_CANNOT_SET_TERMINAL_METADATA)
else:
self._down_terminal_metadata = _Transitory(
_Transitory.Kind.PRESENT, terminal_metadata)
def set_code(self, code):
with self._condition:
if self._down_code.kind is not _Transitory.Kind.NOT_YET_SEEN:
raise ValueError(_CANNOT_SET_CODE)
else:
self._down_code = _Transitory(_Transitory.Kind.PRESENT, code)
def set_details(self, details):
with self._condition:
if self._down_details.kind is not _Transitory.Kind.NOT_YET_SEEN:
raise ValueError(_CANNOT_SET_DETAILS)
else:
self._down_details = _Transitory(_Transitory.Kind.PRESENT, details)
def set_protocol_context(self, protocol_context):
with self._condition:
self._protocol_context = _Awaited(
_Awaited.Kind.ARRIVED, protocol_context)
self._condition.notify_all()
def set_outcome(self, outcome):
with self._condition:
return self._set_outcome(outcome)
class _ProtocolReceiver(base.ProtocolReceiver):
def __init__(self, rendezvous):
self._rendezvous = rendezvous
def context(self, protocol_context):
self._rendezvous.set_protocol_context(protocol_context)
def protocol_receiver(rendezvous):
return _ProtocolReceiver(rendezvous)
def pool_wrap(behavior, operation_context):
"""Wraps an operation-related behavior so that it may be called in a pool.
Args:
behavior: A callable related to carrying out an operation.
operation_context: A base_interfaces.OperationContext for the operation.
Returns:
A callable that when called carries out the behavior of the given callable
and handles whatever exceptions it raises appropriately.
"""
def translation(*args):
try:
behavior(*args)
except (
abandonment.Abandoned,
face.CancellationError,
face.ExpirationError,
face.LocalShutdownError,
face.RemoteShutdownError,
face.NetworkError,
face.RemoteError,
) as e:
if operation_context.outcome() is None:
operation_context.fail(e)
except Exception as e:
operation_context.fail(e)
return callable_util.with_exceptions_logged(
translation, _INTERNAL_ERROR_LOG_MESSAGE)

@ -1,173 +0,0 @@
# Copyright 2015, 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.
"""Behaviors for servicing RPCs."""
from grpc.framework.crust import _control
from grpc.framework.foundation import abandonment
from grpc.framework.interfaces.base import utilities
from grpc.framework.interfaces.face import face
class _ServicerContext(face.ServicerContext):
def __init__(self, rendezvous):
self._rendezvous = rendezvous
def is_active(self):
return self._rendezvous.is_active()
def time_remaining(self):
return self._rendezvous.time_remaining()
def add_abortion_callback(self, abortion_callback):
return self._rendezvous.add_abortion_callback(abortion_callback)
def cancel(self):
self._rendezvous.cancel()
def protocol_context(self):
return self._rendezvous.protocol_context()
def invocation_metadata(self):
return self._rendezvous.initial_metadata()
def initial_metadata(self, initial_metadata):
self._rendezvous.set_initial_metadata(initial_metadata)
def terminal_metadata(self, terminal_metadata):
self._rendezvous.set_terminal_metadata(terminal_metadata)
def code(self, code):
self._rendezvous.set_code(code)
def details(self, details):
self._rendezvous.set_details(details)
def _adaptation(pool, in_pool):
def adaptation(operator, operation_context):
rendezvous = _control.Rendezvous(operator, operation_context)
subscription = utilities.full_subscription(
rendezvous, _control.protocol_receiver(rendezvous))
outcome = operation_context.add_termination_callback(rendezvous.set_outcome)
if outcome is None:
pool.submit(_control.pool_wrap(in_pool, operation_context), rendezvous)
return subscription
else:
raise abandonment.Abandoned()
return adaptation
def adapt_inline_unary_unary(method, pool):
def in_pool(rendezvous):
request = next(rendezvous)
response = method(request, _ServicerContext(rendezvous))
rendezvous.consume_and_terminate(response)
return _adaptation(pool, in_pool)
def adapt_inline_unary_stream(method, pool):
def in_pool(rendezvous):
request = next(rendezvous)
response_iterator = method(request, _ServicerContext(rendezvous))
for response in response_iterator:
rendezvous.consume(response)
rendezvous.terminate()
return _adaptation(pool, in_pool)
def adapt_inline_stream_unary(method, pool):
def in_pool(rendezvous):
response = method(rendezvous, _ServicerContext(rendezvous))
rendezvous.consume_and_terminate(response)
return _adaptation(pool, in_pool)
def adapt_inline_stream_stream(method, pool):
def in_pool(rendezvous):
response_iterator = method(rendezvous, _ServicerContext(rendezvous))
for response in response_iterator:
rendezvous.consume(response)
rendezvous.terminate()
return _adaptation(pool, in_pool)
def adapt_event_unary_unary(method, pool):
def in_pool(rendezvous):
request = next(rendezvous)
method(
request, rendezvous.consume_and_terminate, _ServicerContext(rendezvous))
return _adaptation(pool, in_pool)
def adapt_event_unary_stream(method, pool):
def in_pool(rendezvous):
request = next(rendezvous)
method(request, rendezvous, _ServicerContext(rendezvous))
return _adaptation(pool, in_pool)
def adapt_event_stream_unary(method, pool):
def in_pool(rendezvous):
request_consumer = method(
rendezvous.consume_and_terminate, _ServicerContext(rendezvous))
for request in rendezvous:
request_consumer.consume(request)
request_consumer.terminate()
return _adaptation(pool, in_pool)
def adapt_event_stream_stream(method, pool):
def in_pool(rendezvous):
request_consumer = method(rendezvous, _ServicerContext(rendezvous))
for request in rendezvous:
request_consumer.consume(request)
request_consumer.terminate()
return _adaptation(pool, in_pool)
def adapt_multi_method(multi_method, pool):
def adaptation(group, method, operator, operation_context):
rendezvous = _control.Rendezvous(operator, operation_context)
subscription = utilities.full_subscription(
rendezvous, _control.protocol_receiver(rendezvous))
outcome = operation_context.add_termination_callback(rendezvous.set_outcome)
if outcome is None:
def in_pool():
request_consumer = multi_method.service(
group, method, rendezvous, _ServicerContext(rendezvous))
for request in rendezvous:
request_consumer.consume(request)
request_consumer.terminate()
pool.submit(_control.pool_wrap(in_pool, operation_context), rendezvous)
return subscription
else:
raise abandonment.Abandoned()
return adaptation

@ -1,366 +0,0 @@
# Copyright 2015, 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.
"""Entry points into the Crust layer of RPC Framework."""
import six
from grpc.framework.common import cardinality
from grpc.framework.common import style
from grpc.framework.crust import _calls
from grpc.framework.crust import _service
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.face import face
class _BaseServicer(base.Servicer):
def __init__(self, adapted_methods, adapted_multi_method):
self._adapted_methods = adapted_methods
self._adapted_multi_method = adapted_multi_method
def service(self, group, method, context, output_operator):
adapted_method = self._adapted_methods.get((group, method), None)
if adapted_method is not None:
return adapted_method(output_operator, context)
elif self._adapted_multi_method is not None:
try:
return self._adapted_multi_method(
group, method, output_operator, context)
except face.NoSuchMethodError:
raise base.NoSuchMethodError(None, None)
else:
raise base.NoSuchMethodError(None, None)
class _UnaryUnaryMultiCallable(face.UnaryUnaryMultiCallable):
def __init__(self, end, group, method, pool):
self._end = end
self._group = group
self._method = method
self._pool = pool
def __call__(
self, request, timeout, metadata=None, with_call=False,
protocol_options=None):
return _calls.blocking_unary_unary(
self._end, self._group, self._method, timeout, with_call,
protocol_options, metadata, request)
def future(self, request, timeout, metadata=None, protocol_options=None):
return _calls.future_unary_unary(
self._end, self._group, self._method, timeout, protocol_options,
metadata, request)
def event(
self, request, receiver, abortion_callback, timeout,
metadata=None, protocol_options=None):
return _calls.event_unary_unary(
self._end, self._group, self._method, timeout, protocol_options,
metadata, request, receiver, abortion_callback, self._pool)
class _UnaryStreamMultiCallable(face.UnaryStreamMultiCallable):
def __init__(self, end, group, method, pool):
self._end = end
self._group = group
self._method = method
self._pool = pool
def __call__(self, request, timeout, metadata=None, protocol_options=None):
return _calls.inline_unary_stream(
self._end, self._group, self._method, timeout, protocol_options,
metadata, request)
def event(
self, request, receiver, abortion_callback, timeout,
metadata=None, protocol_options=None):
return _calls.event_unary_stream(
self._end, self._group, self._method, timeout, protocol_options,
metadata, request, receiver, abortion_callback, self._pool)
class _StreamUnaryMultiCallable(face.StreamUnaryMultiCallable):
def __init__(self, end, group, method, pool):
self._end = end
self._group = group
self._method = method
self._pool = pool
def __call__(
self, request_iterator, timeout, metadata=None,
with_call=False, protocol_options=None):
return _calls.blocking_stream_unary(
self._end, self._group, self._method, timeout, with_call,
protocol_options, metadata, request_iterator, self._pool)
def future(
self, request_iterator, timeout, metadata=None, protocol_options=None):
return _calls.future_stream_unary(
self._end, self._group, self._method, timeout, protocol_options,
metadata, request_iterator, self._pool)
def event(
self, receiver, abortion_callback, timeout, metadata=None,
protocol_options=None):
return _calls.event_stream_unary(
self._end, self._group, self._method, timeout, protocol_options,
metadata, receiver, abortion_callback, self._pool)
class _StreamStreamMultiCallable(face.StreamStreamMultiCallable):
def __init__(self, end, group, method, pool):
self._end = end
self._group = group
self._method = method
self._pool = pool
def __call__(
self, request_iterator, timeout, metadata=None, protocol_options=None):
return _calls.inline_stream_stream(
self._end, self._group, self._method, timeout, protocol_options,
metadata, request_iterator, self._pool)
def event(
self, receiver, abortion_callback, timeout, metadata=None,
protocol_options=None):
return _calls.event_stream_stream(
self._end, self._group, self._method, timeout, protocol_options,
metadata, receiver, abortion_callback, self._pool)
class _GenericStub(face.GenericStub):
"""An face.GenericStub implementation."""
def __init__(self, end, pool):
self._end = end
self._pool = pool
def blocking_unary_unary(
self, group, method, request, timeout, metadata=None,
with_call=None, protocol_options=None):
return _calls.blocking_unary_unary(
self._end, group, method, timeout, with_call, protocol_options,
metadata, request)
def future_unary_unary(
self, group, method, request, timeout, metadata=None,
protocol_options=None):
return _calls.future_unary_unary(
self._end, group, method, timeout, protocol_options, metadata, request)
def inline_unary_stream(
self, group, method, request, timeout, metadata=None,
protocol_options=None):
return _calls.inline_unary_stream(
self._end, group, method, timeout, protocol_options, metadata, request)
def blocking_stream_unary(
self, group, method, request_iterator, timeout, metadata=None,
with_call=None, protocol_options=None):
return _calls.blocking_stream_unary(
self._end, group, method, timeout, with_call, protocol_options,
metadata, request_iterator, self._pool)
def future_stream_unary(
self, group, method, request_iterator, timeout, metadata=None,
protocol_options=None):
return _calls.future_stream_unary(
self._end, group, method, timeout, protocol_options, metadata,
request_iterator, self._pool)
def inline_stream_stream(
self, group, method, request_iterator, timeout, metadata=None,
protocol_options=None):
return _calls.inline_stream_stream(
self._end, group, method, timeout, protocol_options, metadata,
request_iterator, self._pool)
def event_unary_unary(
self, group, method, request, receiver, abortion_callback, timeout,
metadata=None, protocol_options=None):
return _calls.event_unary_unary(
self._end, group, method, timeout, protocol_options, metadata, request,
receiver, abortion_callback, self._pool)
def event_unary_stream(
self, group, method, request, receiver, abortion_callback, timeout,
metadata=None, protocol_options=None):
return _calls.event_unary_stream(
self._end, group, method, timeout, protocol_options, metadata, request,
receiver, abortion_callback, self._pool)
def event_stream_unary(
self, group, method, receiver, abortion_callback, timeout,
metadata=None, protocol_options=None):
return _calls.event_stream_unary(
self._end, group, method, timeout, protocol_options, metadata, receiver,
abortion_callback, self._pool)
def event_stream_stream(
self, group, method, receiver, abortion_callback, timeout,
metadata=None, protocol_options=None):
return _calls.event_stream_stream(
self._end, group, method, timeout, protocol_options, metadata, receiver,
abortion_callback, self._pool)
def unary_unary(self, group, method):
return _UnaryUnaryMultiCallable(self._end, group, method, self._pool)
def unary_stream(self, group, method):
return _UnaryStreamMultiCallable(self._end, group, method, self._pool)
def stream_unary(self, group, method):
return _StreamUnaryMultiCallable(self._end, group, method, self._pool)
def stream_stream(self, group, method):
return _StreamStreamMultiCallable(self._end, group, method, self._pool)
class _DynamicStub(face.DynamicStub):
"""An face.DynamicStub implementation."""
def __init__(self, end, group, cardinalities, pool):
self._end = end
self._group = group
self._cardinalities = cardinalities
self._pool = pool
def __getattr__(self, attr):
method_cardinality = self._cardinalities.get(attr)
if method_cardinality is cardinality.Cardinality.UNARY_UNARY:
return _UnaryUnaryMultiCallable(self._end, self._group, attr, self._pool)
elif method_cardinality is cardinality.Cardinality.UNARY_STREAM:
return _UnaryStreamMultiCallable(self._end, self._group, attr, self._pool)
elif method_cardinality is cardinality.Cardinality.STREAM_UNARY:
return _StreamUnaryMultiCallable(self._end, self._group, attr, self._pool)
elif method_cardinality is cardinality.Cardinality.STREAM_STREAM:
return _StreamStreamMultiCallable(
self._end, self._group, attr, self._pool)
else:
raise AttributeError('_DynamicStub object has no attribute "%s"!' % attr)
def _adapt_method_implementations(method_implementations, pool):
adapted_implementations = {}
for name, method_implementation in six.iteritems(method_implementations):
if method_implementation.style is style.Service.INLINE:
if method_implementation.cardinality is cardinality.Cardinality.UNARY_UNARY:
adapted_implementations[name] = _service.adapt_inline_unary_unary(
method_implementation.unary_unary_inline, pool)
elif method_implementation.cardinality is cardinality.Cardinality.UNARY_STREAM:
adapted_implementations[name] = _service.adapt_inline_unary_stream(
method_implementation.unary_stream_inline, pool)
elif method_implementation.cardinality is cardinality.Cardinality.STREAM_UNARY:
adapted_implementations[name] = _service.adapt_inline_stream_unary(
method_implementation.stream_unary_inline, pool)
elif method_implementation.cardinality is cardinality.Cardinality.STREAM_STREAM:
adapted_implementations[name] = _service.adapt_inline_stream_stream(
method_implementation.stream_stream_inline, pool)
elif method_implementation.style is style.Service.EVENT:
if method_implementation.cardinality is cardinality.Cardinality.UNARY_UNARY:
adapted_implementations[name] = _service.adapt_event_unary_unary(
method_implementation.unary_unary_event, pool)
elif method_implementation.cardinality is cardinality.Cardinality.UNARY_STREAM:
adapted_implementations[name] = _service.adapt_event_unary_stream(
method_implementation.unary_stream_event, pool)
elif method_implementation.cardinality is cardinality.Cardinality.STREAM_UNARY:
adapted_implementations[name] = _service.adapt_event_stream_unary(
method_implementation.stream_unary_event, pool)
elif method_implementation.cardinality is cardinality.Cardinality.STREAM_STREAM:
adapted_implementations[name] = _service.adapt_event_stream_stream(
method_implementation.stream_stream_event, pool)
return adapted_implementations
def servicer(method_implementations, multi_method_implementation, pool):
"""Creates a base.Servicer.
It is guaranteed that any passed face.MultiMethodImplementation will
only be called to service an RPC if there is no
face.MethodImplementation for the RPC method in the passed
method_implementations dictionary.
Args:
method_implementations: A dictionary from RPC method name to
face.MethodImplementation object to be used to service the named
RPC method.
multi_method_implementation: An face.MultiMethodImplementation to be
used to service any RPCs not serviced by the
face.MethodImplementations given in the method_implementations
dictionary, or None.
pool: A thread pool.
Returns:
A base.Servicer that services RPCs via the given implementations.
"""
adapted_implementations = _adapt_method_implementations(
method_implementations, pool)
if multi_method_implementation is None:
adapted_multi_method_implementation = None
else:
adapted_multi_method_implementation = _service.adapt_multi_method(
multi_method_implementation, pool)
return _BaseServicer(
adapted_implementations, adapted_multi_method_implementation)
def generic_stub(end, pool):
"""Creates an face.GenericStub.
Args:
end: A base.End.
pool: A futures.ThreadPoolExecutor.
Returns:
A face.GenericStub that performs RPCs via the given base.End.
"""
return _GenericStub(end, pool)
def dynamic_stub(end, group, cardinalities, pool):
"""Creates an face.DynamicStub.
Args:
end: A base.End.
group: The group identifier for all RPCs to be made with the created
face.DynamicStub.
cardinalities: A dict from method identifier to cardinality.Cardinality
value identifying the cardinality of every RPC method to be supported by
the created face.DynamicStub.
pool: A futures.ThreadPoolExecutor.
Returns:
A face.DynamicStub that performs RPCs via the given base.End.
"""
return _DynamicStub(end, group, cardinalities, pool)

@ -1,228 +0,0 @@
# Copyright 2015, 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.
"""Affords a Future implementation based on Python's threading.Timer."""
import sys
import threading
import time
from grpc.framework.foundation import future
class TimerFuture(future.Future):
"""A Future implementation based around Timer objects."""
def __init__(self, compute_time, computation):
"""Constructor.
Args:
compute_time: The time after which to begin this future's computation.
computation: The computation to be performed within this Future.
"""
self._lock = threading.Lock()
self._compute_time = compute_time
self._computation = computation
self._timer = None
self._computing = False
self._computed = False
self._cancelled = False
self._result = None
self._exception = None
self._traceback = None
self._waiting = []
def _compute(self):
"""Performs the computation embedded in this Future.
Or doesn't, if the time to perform it has not yet arrived.
"""
with self._lock:
time_remaining = self._compute_time - time.time()
if 0 < time_remaining:
self._timer = threading.Timer(time_remaining, self._compute)
self._timer.start()
return
else:
self._computing = True
try:
return_value = self._computation()
exception = None
traceback = None
except Exception as e: # pylint: disable=broad-except
return_value = None
exception = e
traceback = sys.exc_info()[2]
with self._lock:
self._computing = False
self._computed = True
self._return_value = return_value
self._exception = exception
self._traceback = traceback
waiting = self._waiting
for callback in waiting:
callback(self)
def start(self):
"""Starts this Future.
This must be called exactly once, immediately after construction.
"""
with self._lock:
self._timer = threading.Timer(
self._compute_time - time.time(), self._compute)
self._timer.start()
def cancel(self):
"""See future.Future.cancel for specification."""
with self._lock:
if self._computing or self._computed:
return False
elif self._cancelled:
return True
else:
self._timer.cancel()
self._cancelled = True
waiting = self._waiting
for callback in waiting:
try:
callback(self)
except Exception: # pylint: disable=broad-except
pass
return True
def cancelled(self):
"""See future.Future.cancelled for specification."""
with self._lock:
return self._cancelled
def running(self):
"""See future.Future.running for specification."""
with self._lock:
return not self._computed and not self._cancelled
def done(self):
"""See future.Future.done for specification."""
with self._lock:
return self._computed or self._cancelled
def result(self, timeout=None):
"""See future.Future.result for specification."""
with self._lock:
if self._cancelled:
raise future.CancelledError()
elif self._computed:
if self._exception is None:
return self._return_value
else:
raise self._exception # pylint: disable=raising-bad-type
condition = threading.Condition()
def notify_condition(unused_future):
with condition:
condition.notify()
self._waiting.append(notify_condition)
with condition:
condition.wait(timeout=timeout)
with self._lock:
if self._cancelled:
raise future.CancelledError()
elif self._computed:
if self._exception is None:
return self._return_value
else:
raise self._exception # pylint: disable=raising-bad-type
else:
raise future.TimeoutError()
def exception(self, timeout=None):
"""See future.Future.exception for specification."""
with self._lock:
if self._cancelled:
raise future.CancelledError()
elif self._computed:
return self._exception
condition = threading.Condition()
def notify_condition(unused_future):
with condition:
condition.notify()
self._waiting.append(notify_condition)
with condition:
condition.wait(timeout=timeout)
with self._lock:
if self._cancelled:
raise future.CancelledError()
elif self._computed:
return self._exception
else:
raise future.TimeoutError()
def traceback(self, timeout=None):
"""See future.Future.traceback for specification."""
with self._lock:
if self._cancelled:
raise future.CancelledError()
elif self._computed:
return self._traceback
condition = threading.Condition()
def notify_condition(unused_future):
with condition:
condition.notify()
self._waiting.append(notify_condition)
with condition:
condition.wait(timeout=timeout)
with self._lock:
if self._cancelled:
raise future.CancelledError()
elif self._computed:
return self._traceback
else:
raise future.TimeoutError()
def add_done_callback(self, fn):
"""See future.Future.add_done_callback for specification."""
with self._lock:
if not self._computed and not self._cancelled:
self._waiting.append(fn)
return
fn(self)

@ -1,65 +0,0 @@
# Copyright 2015, 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.
"""Interfaces related to streams of values or objects."""
import abc
import six
class Activated(six.with_metaclass(abc.ABCMeta)):
"""Interface for objects that may be started and stopped.
Values implementing this type must also implement the context manager
protocol.
"""
@abc.abstractmethod
def __enter__(self):
"""See the context manager protocol for specification."""
raise NotImplementedError()
@abc.abstractmethod
def __exit__(self, exc_type, exc_val, exc_tb):
"""See the context manager protocol for specification."""
raise NotImplementedError()
@abc.abstractmethod
def start(self):
"""Activates this object.
Returns:
A value equal to the value returned by this object's __enter__ method.
"""
raise NotImplementedError()
@abc.abstractmethod
def stop(self):
"""Deactivates this object."""
raise NotImplementedError()

@ -1,51 +0,0 @@
# Copyright 2015, 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.
"""Enables scheduling execution at a later time."""
import time
from grpc.framework.foundation import _timer_future
def later(delay, computation):
"""Schedules later execution of a callable.
Args:
delay: Any numeric value. Represents the minimum length of time in seconds
to allow to pass before beginning the computation. No guarantees are made
about the maximum length of time that will pass.
computation: A callable that accepts no arguments.
Returns:
A Future representing the scheduled computation.
"""
timer_future = _timer_future.TimerFuture(time.time() + delay, computation)
timer_future.start()
return timer_future

@ -1,174 +0,0 @@
# Copyright 2015, 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.
"""Implementations of in-order work deference."""
import abc
import enum
import threading
from grpc.framework.foundation import activated
from grpc.framework.foundation import logging_pool
_NULL_BEHAVIOR = lambda unused_value: None
class Relay(object):
"""Performs work submitted to it in another thread.
Performs work in the order in which work was submitted to it; otherwise there
would be no reason to use an implementation of this interface instead of a
thread pool.
"""
@abc.abstractmethod
def add_value(self, value):
"""Adds a value to be passed to the behavior registered with this Relay.
Args:
value: A value that will be passed to a call made in another thread to the
behavior registered with this Relay.
"""
raise NotImplementedError()
@abc.abstractmethod
def set_behavior(self, behavior):
"""Sets the behavior that this Relay should call when passed values.
Args:
behavior: The behavior that this Relay should call in another thread when
passed a value, or None to have passed values ignored.
"""
raise NotImplementedError()
class _PoolRelay(activated.Activated, Relay):
@enum.unique
class _State(enum.Enum):
INACTIVE = 'inactive'
IDLE = 'idle'
SPINNING = 'spinning'
def __init__(self, pool, behavior):
self._condition = threading.Condition()
self._pool = pool
self._own_pool = pool is None
self._state = _PoolRelay._State.INACTIVE
self._activated = False
self._spinning = False
self._values = []
self._behavior = _NULL_BEHAVIOR if behavior is None else behavior
def _spin(self, behavior, value):
while True:
behavior(value)
with self._condition:
if self._values:
value = self._values.pop(0)
behavior = self._behavior
else:
self._state = _PoolRelay._State.IDLE
self._condition.notify_all()
break
def add_value(self, value):
with self._condition:
if self._state is _PoolRelay._State.INACTIVE:
raise ValueError('add_value not valid on inactive Relay!')
elif self._state is _PoolRelay._State.IDLE:
self._pool.submit(self._spin, self._behavior, value)
self._state = _PoolRelay._State.SPINNING
else:
self._values.append(value)
def set_behavior(self, behavior):
with self._condition:
self._behavior = _NULL_BEHAVIOR if behavior is None else behavior
def _start(self):
with self._condition:
self._state = _PoolRelay._State.IDLE
if self._own_pool:
self._pool = logging_pool.pool(1)
return self
def _stop(self):
with self._condition:
while self._state is _PoolRelay._State.SPINNING:
self._condition.wait()
if self._own_pool:
self._pool.shutdown(wait=True)
self._state = _PoolRelay._State.INACTIVE
def __enter__(self):
return self._start()
def __exit__(self, exc_type, exc_val, exc_tb):
self._stop()
return False
def start(self):
return self._start()
def stop(self):
self._stop()
def relay(behavior):
"""Creates a Relay.
Args:
behavior: The behavior to be called by the created Relay, or None to have
passed values dropped until a different behavior is given to the returned
Relay later.
Returns:
An object that is both an activated.Activated and a Relay. The object is
only valid for use as a Relay when activated.
"""
return _PoolRelay(None, behavior)
def pool_relay(pool, behavior):
"""Creates a Relay that uses a given thread pool.
This object will make use of at most one thread in the given pool.
Args:
pool: A futures.ThreadPoolExecutor for use by the created Relay.
behavior: The behavior to be called by the created Relay, or None to have
passed values dropped until a different behavior is given to the returned
Relay later.
Returns:
An object that is both an activated.Activated and a Relay. The object is
only valid for use as a Relay when activated.
"""
return _PoolRelay(pool, behavior)

@ -1,30 +0,0 @@
# Copyright 2015, 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.

@ -1,143 +0,0 @@
# Copyright 2015, 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.
"""The low-level ticket-exchanging-links interface of RPC Framework."""
import abc
import collections
import enum
import six
class Protocol(collections.namedtuple('Protocol', ('kind', 'value',))):
"""A sum type for handles to a system that transmits tickets.
Attributes:
kind: A Kind value identifying the kind of value being passed.
value: The value being passed between the high-level application and the
system affording ticket transport.
"""
@enum.unique
class Kind(enum.Enum):
CALL_OPTION = 'call option'
SERVICER_CONTEXT = 'servicer context'
INVOCATION_CONTEXT = 'invocation context'
class Ticket(
collections.namedtuple(
'Ticket',
('operation_id', 'sequence_number', 'group', 'method', 'subscription',
'timeout', 'allowance', 'initial_metadata', 'payload',
'terminal_metadata', 'code', 'message', 'termination', 'protocol',))):
"""A sum type for all values sent from a front to a back.
Attributes:
operation_id: A unique-with-respect-to-equality hashable object identifying
a particular operation.
sequence_number: A zero-indexed integer sequence number identifying the
ticket's place in the stream of tickets sent in one direction for the
particular operation.
group: The group to which the method of the operation belongs. Must be
present in the first ticket from invocation side to service side. Ignored
for all other tickets exchanged during the operation.
method: The name of an operation. Must be present in the first ticket from
invocation side to service side. Ignored for all other tickets exchanged
during the operation.
subscription: A Subscription value describing the interest one side has in
receiving information from the other side. Must be present in the first
ticket from either side. Ignored for all other tickets exchanged during
the operation.
timeout: A nonzero length of time (measured from the beginning of the
operation) to allow for the entire operation. Must be present in the first
ticket from invocation side to service side. Optional for all other
tickets exchanged during the operation. Receipt of a value from the other
side of the operation indicates the value in use by that side. Setting a
value on a later ticket allows either side to request time extensions (or
even time reductions!) on in-progress operations.
allowance: A positive integer granting permission for a number of payloads
to be transmitted to the communicating side of the operation, or None if
no additional allowance is being granted with this ticket.
initial_metadata: An optional metadata value communicated from one side to
the other at the beginning of the operation. May be non-None in at most
one ticket from each side. Any non-None value must appear no later than
the first payload value.
payload: A customer payload object. May be None.
terminal_metadata: A metadata value comminicated from one side to the other
at the end of the operation. May be non-None in the same ticket as
the code and message, but must be None for all earlier tickets.
code: A value communicated at operation completion. May be None.
message: A value communicated at operation completion. May be None.
termination: A Termination value describing the end of the operation, or
None if the operation has not yet terminated. If set, no further tickets
may be sent in the same direction.
protocol: A Protocol value or None, with further semantics being a matter
between high-level application and underlying ticket transport.
"""
@enum.unique
class Subscription(enum.Enum):
"""Identifies the level of subscription of a side of an operation."""
NONE = 'none'
TERMINATION = 'termination'
FULL = 'full'
@enum.unique
class Termination(enum.Enum):
"""Identifies the termination of an operation."""
COMPLETION = 'completion'
CANCELLATION = 'cancellation'
EXPIRATION = 'expiration'
SHUTDOWN = 'shutdown'
RECEPTION_FAILURE = 'reception failure'
TRANSMISSION_FAILURE = 'transmission failure'
LOCAL_FAILURE = 'local failure'
REMOTE_FAILURE = 'remote failure'
class Link(six.with_metaclass(abc.ABCMeta)):
"""Accepts and emits tickets."""
@abc.abstractmethod
def accept_ticket(self, ticket):
"""Accept a Ticket.
Args:
ticket: Any Ticket.
"""
raise NotImplementedError()
@abc.abstractmethod
def join_link(self, link):
"""Mates this object with a peer with which it will exchange tickets."""
raise NotImplementedError()

@ -1,44 +0,0 @@
# Copyright 2015, 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.
"""Utilities provided as part of the links interface."""
from grpc.framework.interfaces.links import links
class _NullLink(links.Link):
"""A do-nothing links.Link."""
def accept_ticket(self, ticket):
pass
def join_link(self, link):
pass
NULL_LINK = _NullLink()

@ -1,5 +0,0 @@
*.a
*.so
*.dll
*.pyc
*.pyd

@ -1,30 +0,0 @@
# Copyright 2015, 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.

@ -1,262 +0,0 @@
# Copyright 2015, 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.
"""Test scenarios using protocol buffers."""
import abc
import threading
import six
from tests.unit._junkdrawer import math_pb2
class ProtoScenario(six.with_metaclass(abc.ABCMeta)):
"""An RPC test scenario using protocol buffers."""
@abc.abstractmethod
def method(self):
"""Access the test method name.
Returns:
The test method name.
"""
raise NotImplementedError()
@abc.abstractmethod
def serialize_request(self, request):
"""Serialize a request protocol buffer.
Args:
request: A request protocol buffer.
Returns:
The bytestring serialization of the given request protocol buffer.
"""
raise NotImplementedError()
@abc.abstractmethod
def deserialize_request(self, request_bytestring):
"""Deserialize a request protocol buffer.
Args:
request_bytestring: The bytestring serialization of a request protocol
buffer.
Returns:
The request protocol buffer deserialized from the given byte string.
"""
raise NotImplementedError()
@abc.abstractmethod
def serialize_response(self, response):
"""Serialize a response protocol buffer.
Args:
response: A response protocol buffer.
Returns:
The bytestring serialization of the given response protocol buffer.
"""
raise NotImplementedError()
@abc.abstractmethod
def deserialize_response(self, response_bytestring):
"""Deserialize a response protocol buffer.
Args:
response_bytestring: The bytestring serialization of a response protocol
buffer.
Returns:
The response protocol buffer deserialized from the given byte string.
"""
raise NotImplementedError()
@abc.abstractmethod
def requests(self):
"""Access the sequence of requests for this scenario.
Returns:
A sequence of request protocol buffers.
"""
raise NotImplementedError()
@abc.abstractmethod
def response_for_request(self, request):
"""Access the response for a particular request.
Args:
request: A request protocol buffer.
Returns:
The response protocol buffer appropriate for the given request.
"""
raise NotImplementedError()
@abc.abstractmethod
def verify_requests(self, experimental_requests):
"""Verify the requests transmitted through the system under test.
Args:
experimental_requests: The request protocol buffers transmitted through
the system under test.
Returns:
True if the requests satisfy this test scenario; False otherwise.
"""
raise NotImplementedError()
@abc.abstractmethod
def verify_responses(self, experimental_responses):
"""Verify the responses transmitted through the system under test.
Args:
experimental_responses: The response protocol buffers transmitted through
the system under test.
Returns:
True if the responses satisfy this test scenario; False otherwise.
"""
raise NotImplementedError()
class EmptyScenario(ProtoScenario):
"""A scenario that transmits no protocol buffers in either direction."""
def method(self):
return 'DivMany'
def serialize_request(self, request):
raise ValueError('This should not be necessary to call!')
def deserialize_request(self, request_bytestring):
raise ValueError('This should not be necessary to call!')
def serialize_response(self, response):
raise ValueError('This should not be necessary to call!')
def deserialize_response(self, response_bytestring):
raise ValueError('This should not be necessary to call!')
def requests(self):
return ()
def response_for_request(self, request):
raise ValueError('This should not be necessary to call!')
def verify_requests(self, experimental_requests):
return not experimental_requests
def verify_responses(self, experimental_responses):
return not experimental_responses
class BidirectionallyUnaryScenario(ProtoScenario):
"""A scenario that transmits no protocol buffers in either direction."""
_DIVIDEND = 59
_DIVISOR = 7
_QUOTIENT = 8
_REMAINDER = 3
_REQUEST = math_pb2.DivArgs(dividend=_DIVIDEND, divisor=_DIVISOR)
_RESPONSE = math_pb2.DivReply(quotient=_QUOTIENT, remainder=_REMAINDER)
def method(self):
return 'Div'
def serialize_request(self, request):
return request.SerializeToString()
def deserialize_request(self, request_bytestring):
return math_pb2.DivArgs.FromString(request_bytestring)
def serialize_response(self, response):
return response.SerializeToString()
def deserialize_response(self, response_bytestring):
return math_pb2.DivReply.FromString(response_bytestring)
def requests(self):
return [self._REQUEST]
def response_for_request(self, request):
return self._RESPONSE
def verify_requests(self, experimental_requests):
return tuple(experimental_requests) == (self._REQUEST,)
def verify_responses(self, experimental_responses):
return tuple(experimental_responses) == (self._RESPONSE,)
class BidirectionallyStreamingScenario(ProtoScenario):
"""A scenario that transmits no protocol buffers in either direction."""
_STREAM_LENGTH = 200
_REQUESTS = tuple(
math_pb2.DivArgs(dividend=59 + index, divisor=7 + index)
for index in range(_STREAM_LENGTH))
def __init__(self):
self._lock = threading.Lock()
self._responses = []
def method(self):
return 'DivMany'
def serialize_request(self, request):
return request.SerializeToString()
def deserialize_request(self, request_bytestring):
return math_pb2.DivArgs.FromString(request_bytestring)
def serialize_response(self, response):
return response.SerializeToString()
def deserialize_response(self, response_bytestring):
return math_pb2.DivReply.FromString(response_bytestring)
def requests(self):
return self._REQUESTS
def response_for_request(self, request):
quotient, remainder = divmod(request.dividend, request.divisor)
response = math_pb2.DivReply(quotient=quotient, remainder=remainder)
with self._lock:
self._responses.append(response)
return response
def verify_requests(self, experimental_requests):
return tuple(experimental_requests) == self._REQUESTS
def verify_responses(self, experimental_responses):
with self._lock:
return tuple(experimental_responses) == tuple(self._responses)

@ -1,266 +0,0 @@
# Copyright 2015, 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.
# TODO(nathaniel): Remove this from source control after having made
# generation from the math.proto source part of GRPC's build-and-test
# process.
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: math.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='math.proto',
package='math',
serialized_pb=_b('\n\nmath.proto\x12\x04math\",\n\x07\x44ivArgs\x12\x10\n\x08\x64ividend\x18\x01 \x02(\x03\x12\x0f\n\x07\x64ivisor\x18\x02 \x02(\x03\"/\n\x08\x44ivReply\x12\x10\n\x08quotient\x18\x01 \x02(\x03\x12\x11\n\tremainder\x18\x02 \x02(\x03\"\x18\n\x07\x46ibArgs\x12\r\n\x05limit\x18\x01 \x01(\x03\"\x12\n\x03Num\x12\x0b\n\x03num\x18\x01 \x02(\x03\"\x19\n\x08\x46ibReply\x12\r\n\x05\x63ount\x18\x01 \x02(\x03\x32\xa4\x01\n\x04Math\x12&\n\x03\x44iv\x12\r.math.DivArgs\x1a\x0e.math.DivReply\"\x00\x12.\n\x07\x44ivMany\x12\r.math.DivArgs\x1a\x0e.math.DivReply\"\x00(\x01\x30\x01\x12#\n\x03\x46ib\x12\r.math.FibArgs\x1a\t.math.Num\"\x00\x30\x01\x12\x1f\n\x03Sum\x12\t.math.Num\x1a\t.math.Num\"\x00(\x01')
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_DIVARGS = _descriptor.Descriptor(
name='DivArgs',
full_name='math.DivArgs',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='dividend', full_name='math.DivArgs.dividend', index=0,
number=1, type=3, cpp_type=2, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='divisor', full_name='math.DivArgs.divisor', index=1,
number=2, type=3, cpp_type=2, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=20,
serialized_end=64,
)
_DIVREPLY = _descriptor.Descriptor(
name='DivReply',
full_name='math.DivReply',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='quotient', full_name='math.DivReply.quotient', index=0,
number=1, type=3, cpp_type=2, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='remainder', full_name='math.DivReply.remainder', index=1,
number=2, type=3, cpp_type=2, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=66,
serialized_end=113,
)
_FIBARGS = _descriptor.Descriptor(
name='FibArgs',
full_name='math.FibArgs',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='limit', full_name='math.FibArgs.limit', index=0,
number=1, type=3, cpp_type=2, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=115,
serialized_end=139,
)
_NUM = _descriptor.Descriptor(
name='Num',
full_name='math.Num',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='num', full_name='math.Num.num', index=0,
number=1, type=3, cpp_type=2, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=141,
serialized_end=159,
)
_FIBREPLY = _descriptor.Descriptor(
name='FibReply',
full_name='math.FibReply',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='count', full_name='math.FibReply.count', index=0,
number=1, type=3, cpp_type=2, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=161,
serialized_end=186,
)
DESCRIPTOR.message_types_by_name['DivArgs'] = _DIVARGS
DESCRIPTOR.message_types_by_name['DivReply'] = _DIVREPLY
DESCRIPTOR.message_types_by_name['FibArgs'] = _FIBARGS
DESCRIPTOR.message_types_by_name['Num'] = _NUM
DESCRIPTOR.message_types_by_name['FibReply'] = _FIBREPLY
DivArgs = _reflection.GeneratedProtocolMessageType('DivArgs', (_message.Message,), dict(
DESCRIPTOR = _DIVARGS,
__module__ = 'math_pb2'
# @@protoc_insertion_point(class_scope:math.DivArgs)
))
_sym_db.RegisterMessage(DivArgs)
DivReply = _reflection.GeneratedProtocolMessageType('DivReply', (_message.Message,), dict(
DESCRIPTOR = _DIVREPLY,
__module__ = 'math_pb2'
# @@protoc_insertion_point(class_scope:math.DivReply)
))
_sym_db.RegisterMessage(DivReply)
FibArgs = _reflection.GeneratedProtocolMessageType('FibArgs', (_message.Message,), dict(
DESCRIPTOR = _FIBARGS,
__module__ = 'math_pb2'
# @@protoc_insertion_point(class_scope:math.FibArgs)
))
_sym_db.RegisterMessage(FibArgs)
Num = _reflection.GeneratedProtocolMessageType('Num', (_message.Message,), dict(
DESCRIPTOR = _NUM,
__module__ = 'math_pb2'
# @@protoc_insertion_point(class_scope:math.Num)
))
_sym_db.RegisterMessage(Num)
FibReply = _reflection.GeneratedProtocolMessageType('FibReply', (_message.Message,), dict(
DESCRIPTOR = _FIBREPLY,
__module__ = 'math_pb2'
# @@protoc_insertion_point(class_scope:math.FibReply)
))
_sym_db.RegisterMessage(FibReply)
# @@protoc_insertion_point(module_scope)

@ -1,30 +0,0 @@
# Copyright 2015, 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.

@ -1,262 +0,0 @@
# Copyright 2015, 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.
"""Test scenarios using protocol buffers."""
import abc
import threading
import six
from tests.unit._junkdrawer import math_pb2
from tests.unit.framework.common import test_constants
class ProtoScenario(six.with_metaclass(abc.ABCMeta)):
"""An RPC test scenario using protocol buffers."""
@abc.abstractmethod
def group_and_method(self):
"""Access the test group and method.
Returns:
The test group and method as a pair.
"""
raise NotImplementedError()
@abc.abstractmethod
def serialize_request(self, request):
"""Serialize a request protocol buffer.
Args:
request: A request protocol buffer.
Returns:
The bytestring serialization of the given request protocol buffer.
"""
raise NotImplementedError()
@abc.abstractmethod
def deserialize_request(self, request_bytestring):
"""Deserialize a request protocol buffer.
Args:
request_bytestring: The bytestring serialization of a request protocol
buffer.
Returns:
The request protocol buffer deserialized from the given byte string.
"""
raise NotImplementedError()
@abc.abstractmethod
def serialize_response(self, response):
"""Serialize a response protocol buffer.
Args:
response: A response protocol buffer.
Returns:
The bytestring serialization of the given response protocol buffer.
"""
raise NotImplementedError()
@abc.abstractmethod
def deserialize_response(self, response_bytestring):
"""Deserialize a response protocol buffer.
Args:
response_bytestring: The bytestring serialization of a response protocol
buffer.
Returns:
The response protocol buffer deserialized from the given byte string.
"""
raise NotImplementedError()
@abc.abstractmethod
def requests(self):
"""Access the sequence of requests for this scenario.
Returns:
A sequence of request protocol buffers.
"""
raise NotImplementedError()
@abc.abstractmethod
def response_for_request(self, request):
"""Access the response for a particular request.
Args:
request: A request protocol buffer.
Returns:
The response protocol buffer appropriate for the given request.
"""
raise NotImplementedError()
@abc.abstractmethod
def verify_requests(self, experimental_requests):
"""Verify the requests transmitted through the system under test.
Args:
experimental_requests: The request protocol buffers transmitted through
the system under test.
Returns:
True if the requests satisfy this test scenario; False otherwise.
"""
raise NotImplementedError()
@abc.abstractmethod
def verify_responses(self, experimental_responses):
"""Verify the responses transmitted through the system under test.
Args:
experimental_responses: The response protocol buffers transmitted through
the system under test.
Returns:
True if the responses satisfy this test scenario; False otherwise.
"""
raise NotImplementedError()
class EmptyScenario(ProtoScenario):
"""A scenario that transmits no protocol buffers in either direction."""
def group_and_method(self):
return 'math.Math', 'DivMany'
def serialize_request(self, request):
raise ValueError('This should not be necessary to call!')
def deserialize_request(self, request_bytestring):
raise ValueError('This should not be necessary to call!')
def serialize_response(self, response):
raise ValueError('This should not be necessary to call!')
def deserialize_response(self, response_bytestring):
raise ValueError('This should not be necessary to call!')
def requests(self):
return ()
def response_for_request(self, request):
raise ValueError('This should not be necessary to call!')
def verify_requests(self, experimental_requests):
return not experimental_requests
def verify_responses(self, experimental_responses):
return not experimental_responses
class BidirectionallyUnaryScenario(ProtoScenario):
"""A scenario that transmits no protocol buffers in either direction."""
_DIVIDEND = 59
_DIVISOR = 7
_QUOTIENT = 8
_REMAINDER = 3
_REQUEST = math_pb2.DivArgs(dividend=_DIVIDEND, divisor=_DIVISOR)
_RESPONSE = math_pb2.DivReply(quotient=_QUOTIENT, remainder=_REMAINDER)
def group_and_method(self):
return 'math.Math', 'Div'
def serialize_request(self, request):
return request.SerializeToString()
def deserialize_request(self, request_bytestring):
return math_pb2.DivArgs.FromString(request_bytestring)
def serialize_response(self, response):
return response.SerializeToString()
def deserialize_response(self, response_bytestring):
return math_pb2.DivReply.FromString(response_bytestring)
def requests(self):
return [self._REQUEST]
def response_for_request(self, request):
return self._RESPONSE
def verify_requests(self, experimental_requests):
return tuple(experimental_requests) == (self._REQUEST,)
def verify_responses(self, experimental_responses):
return tuple(experimental_responses) == (self._RESPONSE,)
class BidirectionallyStreamingScenario(ProtoScenario):
"""A scenario that transmits no protocol buffers in either direction."""
_REQUESTS = tuple(
math_pb2.DivArgs(dividend=59 + index, divisor=7 + index)
for index in range(test_constants.STREAM_LENGTH))
def __init__(self):
self._lock = threading.Lock()
self._responses = []
def group_and_method(self):
return 'math.Math', 'DivMany'
def serialize_request(self, request):
return request.SerializeToString()
def deserialize_request(self, request_bytestring):
return math_pb2.DivArgs.FromString(request_bytestring)
def serialize_response(self, response):
return response.SerializeToString()
def deserialize_response(self, response_bytestring):
return math_pb2.DivReply.FromString(response_bytestring)
def requests(self):
return self._REQUESTS
def response_for_request(self, request):
quotient, remainder = divmod(request.dividend, request.divisor)
response = math_pb2.DivReply(quotient=quotient, remainder=remainder)
with self._lock:
self._responses.append(response)
return response
def verify_requests(self, experimental_requests):
return tuple(experimental_requests) == self._REQUESTS
def verify_responses(self, experimental_responses):
with self._lock:
return tuple(experimental_responses) == tuple(self._responses)

@ -1,30 +0,0 @@
# Copyright 2015, 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.

@ -1,30 +0,0 @@
# Copyright 2015, 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.

@ -1,570 +0,0 @@
# Copyright 2015, 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.
"""Part of the tests of the base interface of RPC Framework."""
from __future__ import division
import abc
import collections
import enum
import random # pylint: disable=unused-import
import threading
import time
import six
from grpc.framework.interfaces.base import base
from tests.unit.framework.common import test_constants
from tests.unit.framework.interfaces.base import _sequence
from tests.unit.framework.interfaces.base import _state
from tests.unit.framework.interfaces.base import test_interfaces # pylint: disable=unused-import
_GROUP = 'base test cases test group'
_METHOD = 'base test cases test method'
_PAYLOAD_RANDOM_SECTION_MAXIMUM_SIZE = test_constants.PAYLOAD_SIZE // 20
_MINIMUM_PAYLOAD_SIZE = test_constants.PAYLOAD_SIZE // 600
def _create_payload(randomness):
length = randomness.randint(
_MINIMUM_PAYLOAD_SIZE, test_constants.PAYLOAD_SIZE)
random_section_length = randomness.randint(
0, min(_PAYLOAD_RANDOM_SECTION_MAXIMUM_SIZE, length))
random_section = bytes(
bytearray(
randomness.getrandbits(8) for _ in range(random_section_length)))
sevens_section = b'\x07' * (length - random_section_length)
return b''.join(randomness.sample((random_section, sevens_section), 2))
def _anything_in_flight(state):
return (
state.invocation_initial_metadata_in_flight is not None or
state.invocation_payloads_in_flight or
state.invocation_completion_in_flight is not None or
state.service_initial_metadata_in_flight is not None or
state.service_payloads_in_flight or
state.service_completion_in_flight is not None or
0 < state.invocation_allowance_in_flight or
0 < state.service_allowance_in_flight
)
def _verify_service_advance_and_update_state(
initial_metadata, payload, completion, allowance, state, implementation):
if initial_metadata is not None:
if state.invocation_initial_metadata_received:
return 'Later invocation initial metadata received: %s' % (
initial_metadata,)
if state.invocation_payloads_received:
return 'Invocation initial metadata received after payloads: %s' % (
state.invocation_payloads_received)
if state.invocation_completion_received:
return 'Invocation initial metadata received after invocation completion!'
if not implementation.metadata_transmitted(
state.invocation_initial_metadata_in_flight, initial_metadata):
return 'Invocation initial metadata maltransmitted: %s, %s' % (
state.invocation_initial_metadata_in_flight, initial_metadata)
else:
state.invocation_initial_metadata_in_flight = None
state.invocation_initial_metadata_received = True
if payload is not None:
if state.invocation_completion_received:
return 'Invocation payload received after invocation completion!'
elif not state.invocation_payloads_in_flight:
return 'Invocation payload "%s" received but not in flight!' % (payload,)
elif state.invocation_payloads_in_flight[0] != payload:
return 'Invocation payload mismatch: %s, %s' % (
state.invocation_payloads_in_flight[0], payload)
elif state.service_side_invocation_allowance < 1:
return 'Disallowed invocation payload!'
else:
state.invocation_payloads_in_flight.pop(0)
state.invocation_payloads_received += 1
state.service_side_invocation_allowance -= 1
if completion is not None:
if state.invocation_completion_received:
return 'Later invocation completion received: %s' % (completion,)
elif not implementation.completion_transmitted(
state.invocation_completion_in_flight, completion):
return 'Invocation completion maltransmitted: %s, %s' % (
state.invocation_completion_in_flight, completion)
else:
state.invocation_completion_in_flight = None
state.invocation_completion_received = True
if allowance is not None:
if allowance <= 0:
return 'Illegal allowance value: %s' % (allowance,)
else:
state.service_allowance_in_flight -= allowance
state.service_side_service_allowance += allowance
def _verify_invocation_advance_and_update_state(
initial_metadata, payload, completion, allowance, state, implementation):
if initial_metadata is not None:
if state.service_initial_metadata_received:
return 'Later service initial metadata received: %s' % (initial_metadata,)
if state.service_payloads_received:
return 'Service initial metadata received after service payloads: %s' % (
state.service_payloads_received)
if state.service_completion_received:
return 'Service initial metadata received after service completion!'
if not implementation.metadata_transmitted(
state.service_initial_metadata_in_flight, initial_metadata):
return 'Service initial metadata maltransmitted: %s, %s' % (
state.service_initial_metadata_in_flight, initial_metadata)
else:
state.service_initial_metadata_in_flight = None
state.service_initial_metadata_received = True
if payload is not None:
if state.service_completion_received:
return 'Service payload received after service completion!'
elif not state.service_payloads_in_flight:
return 'Service payload "%s" received but not in flight!' % (payload,)
elif state.service_payloads_in_flight[0] != payload:
return 'Service payload mismatch: %s, %s' % (
state.invocation_payloads_in_flight[0], payload)
elif state.invocation_side_service_allowance < 1:
return 'Disallowed service payload!'
else:
state.service_payloads_in_flight.pop(0)
state.service_payloads_received += 1
state.invocation_side_service_allowance -= 1
if completion is not None:
if state.service_completion_received:
return 'Later service completion received: %s' % (completion,)
elif not implementation.completion_transmitted(
state.service_completion_in_flight, completion):
return 'Service completion maltransmitted: %s, %s' % (
state.service_completion_in_flight, completion)
else:
state.service_completion_in_flight = None
state.service_completion_received = True
if allowance is not None:
if allowance <= 0:
return 'Illegal allowance value: %s' % (allowance,)
else:
state.invocation_allowance_in_flight -= allowance
state.invocation_side_service_allowance += allowance
class Invocation(
collections.namedtuple(
'Invocation',
('group', 'method', 'subscription_kind', 'timeout', 'initial_metadata',
'payload', 'completion',))):
"""A description of operation invocation.
Attributes:
group: The group identifier for the operation.
method: The method identifier for the operation.
subscription_kind: A base.Subscription.Kind value describing the kind of
subscription to use for the operation.
timeout: A duration in seconds to pass as the timeout value for the
operation.
initial_metadata: An object to pass as the initial metadata for the
operation or None.
payload: An object to pass as a payload value for the operation or None.
completion: An object to pass as a completion value for the operation or
None.
"""
class OnAdvance(
collections.namedtuple(
'OnAdvance',
('kind', 'initial_metadata', 'payload', 'completion', 'allowance'))):
"""Describes action to be taken in a test in response to an advance call.
Attributes:
kind: A Kind value describing the overall kind of response.
initial_metadata: An initial metadata value to pass to a call of the advance
method of the operator under test. Only valid if kind is Kind.ADVANCE and
may be None.
payload: A payload value to pass to a call of the advance method of the
operator under test. Only valid if kind is Kind.ADVANCE and may be None.
completion: A base.Completion value to pass to a call of the advance method
of the operator under test. Only valid if kind is Kind.ADVANCE and may be
None.
allowance: An allowance value to pass to a call of the advance method of the
operator under test. Only valid if kind is Kind.ADVANCE and may be None.
"""
@enum.unique
class Kind(enum.Enum):
ADVANCE = 'advance'
DEFECT = 'defect'
IDLE = 'idle'
_DEFECT_ON_ADVANCE = OnAdvance(OnAdvance.Kind.DEFECT, None, None, None, None)
_IDLE_ON_ADVANCE = OnAdvance(OnAdvance.Kind.IDLE, None, None, None, None)
class Instruction(
collections.namedtuple(
'Instruction',
('kind', 'advance_args', 'advance_kwargs', 'conclude_success',
'conclude_message', 'conclude_invocation_outcome_kind',
'conclude_service_outcome_kind',))):
""""""
@enum.unique
class Kind(enum.Enum):
ADVANCE = 'ADVANCE'
CANCEL = 'CANCEL'
CONCLUDE = 'CONCLUDE'
class Controller(six.with_metaclass(abc.ABCMeta)):
@abc.abstractmethod
def failed(self, message):
""""""
raise NotImplementedError()
@abc.abstractmethod
def serialize_request(self, request):
""""""
raise NotImplementedError()
@abc.abstractmethod
def deserialize_request(self, serialized_request):
""""""
raise NotImplementedError()
@abc.abstractmethod
def serialize_response(self, response):
""""""
raise NotImplementedError()
@abc.abstractmethod
def deserialize_response(self, serialized_response):
""""""
raise NotImplementedError()
@abc.abstractmethod
def invocation(self):
""""""
raise NotImplementedError()
@abc.abstractmethod
def poll(self):
""""""
raise NotImplementedError()
@abc.abstractmethod
def on_service_advance(
self, initial_metadata, payload, completion, allowance):
""""""
raise NotImplementedError()
@abc.abstractmethod
def on_invocation_advance(
self, initial_metadata, payload, completion, allowance):
""""""
raise NotImplementedError()
@abc.abstractmethod
def service_on_termination(self, outcome):
""""""
raise NotImplementedError()
@abc.abstractmethod
def invocation_on_termination(self, outcome):
""""""
raise NotImplementedError()
class ControllerCreator(six.with_metaclass(abc.ABCMeta)):
@abc.abstractmethod
def name(self):
""""""
raise NotImplementedError()
@abc.abstractmethod
def controller(self, implementation, randomness):
""""""
raise NotImplementedError()
class _Remainder(
collections.namedtuple(
'_Remainder',
('invocation_payloads', 'service_payloads', 'invocation_completion',
'service_completion',))):
"""Describes work remaining to be done in a portion of a test.
Attributes:
invocation_payloads: The number of payloads to be sent from the invocation
side of the operation to the service side of the operation.
service_payloads: The number of payloads to be sent from the service side of
the operation to the invocation side of the operation.
invocation_completion: Whether or not completion from the invocation side of
the operation should be indicated and has yet to be indicated.
service_completion: Whether or not completion from the service side of the
operation should be indicated and has yet to be indicated.
"""
class _SequenceController(Controller):
def __init__(self, sequence, implementation, randomness):
"""Constructor.
Args:
sequence: A _sequence.Sequence describing the steps to be taken in the
test at a relatively high level.
implementation: A test_interfaces.Implementation encapsulating the
base interface implementation that is the system under test.
randomness: A random.Random instance for use in the test.
"""
self._condition = threading.Condition()
self._sequence = sequence
self._implementation = implementation
self._randomness = randomness
self._until = None
self._remaining_elements = None
self._poll_next = None
self._message = None
self._state = _state.OperationState()
self._todo = None
# called with self._condition
def _failed(self, message):
self._message = message
self._condition.notify_all()
def _passed(self, invocation_outcome, service_outcome):
self._poll_next = Instruction(
Instruction.Kind.CONCLUDE, None, None, True, None, invocation_outcome,
service_outcome)
self._condition.notify_all()
def failed(self, message):
with self._condition:
self._failed(message)
def serialize_request(self, request):
return request + request
def deserialize_request(self, serialized_request):
return serialized_request[:len(serialized_request) // 2]
def serialize_response(self, response):
return response * 3
def deserialize_response(self, serialized_response):
return serialized_response[2 * len(serialized_response) // 3:]
def invocation(self):
with self._condition:
self._until = time.time() + self._sequence.maximum_duration
self._remaining_elements = list(self._sequence.elements)
if self._sequence.invocation.initial_metadata:
initial_metadata = self._implementation.invocation_initial_metadata()
self._state.invocation_initial_metadata_in_flight = initial_metadata
else:
initial_metadata = None
if self._sequence.invocation.payload:
payload = _create_payload(self._randomness)
self._state.invocation_payloads_in_flight.append(payload)
else:
payload = None
if self._sequence.invocation.complete:
completion = self._implementation.invocation_completion()
self._state.invocation_completion_in_flight = completion
else:
completion = None
return Invocation(
_GROUP, _METHOD, base.Subscription.Kind.FULL,
self._sequence.invocation.timeout, initial_metadata, payload,
completion)
def poll(self):
with self._condition:
while True:
if self._message is not None:
return Instruction(
Instruction.Kind.CONCLUDE, None, None, False, self._message, None,
None)
elif self._poll_next:
poll_next = self._poll_next
self._poll_next = None
return poll_next
elif self._until < time.time():
return Instruction(
Instruction.Kind.CONCLUDE, None, None, False,
'overran allotted time!', None, None)
else:
self._condition.wait(timeout=self._until-time.time())
def on_service_advance(
self, initial_metadata, payload, completion, allowance):
with self._condition:
message = _verify_service_advance_and_update_state(
initial_metadata, payload, completion, allowance, self._state,
self._implementation)
if message is not None:
self._failed(message)
if self._todo is not None:
raise ValueError('TODO!!!')
elif _anything_in_flight(self._state):
return _IDLE_ON_ADVANCE
elif self._remaining_elements:
element = self._remaining_elements.pop(0)
if element.kind is _sequence.Element.Kind.SERVICE_TRANSMISSION:
if element.transmission.initial_metadata:
initial_metadata = self._implementation.service_initial_metadata()
self._state.service_initial_metadata_in_flight = initial_metadata
else:
initial_metadata = None
if element.transmission.payload:
payload = _create_payload(self._randomness)
self._state.service_payloads_in_flight.append(payload)
self._state.service_side_service_allowance -= 1
else:
payload = None
if element.transmission.complete:
completion = self._implementation.service_completion()
self._state.service_completion_in_flight = completion
else:
completion = None
if (not self._state.invocation_completion_received and
0 <= self._state.service_side_invocation_allowance):
allowance = 1
self._state.service_side_invocation_allowance += 1
self._state.invocation_allowance_in_flight += 1
else:
allowance = None
return OnAdvance(
OnAdvance.Kind.ADVANCE, initial_metadata, payload, completion,
allowance)
else:
raise ValueError('TODO!!!')
else:
return _IDLE_ON_ADVANCE
def on_invocation_advance(
self, initial_metadata, payload, completion, allowance):
with self._condition:
message = _verify_invocation_advance_and_update_state(
initial_metadata, payload, completion, allowance, self._state,
self._implementation)
if message is not None:
self._failed(message)
if self._todo is not None:
raise ValueError('TODO!!!')
elif _anything_in_flight(self._state):
return _IDLE_ON_ADVANCE
elif self._remaining_elements:
element = self._remaining_elements.pop(0)
if element.kind is _sequence.Element.Kind.INVOCATION_TRANSMISSION:
if element.transmission.initial_metadata:
initial_metadata = self._implementation.invocation_initial_metadata()
self._state.invocation_initial_metadata_in_fight = initial_metadata
else:
initial_metadata = None
if element.transmission.payload:
payload = _create_payload(self._randomness)
self._state.invocation_payloads_in_flight.append(payload)
self._state.invocation_side_invocation_allowance -= 1
else:
payload = None
if element.transmission.complete:
completion = self._implementation.invocation_completion()
self._state.invocation_completion_in_flight = completion
else:
completion = None
if (not self._state.service_completion_received and
0 <= self._state.invocation_side_service_allowance):
allowance = 1
self._state.invocation_side_service_allowance += 1
self._state.service_allowance_in_flight += 1
else:
allowance = None
return OnAdvance(
OnAdvance.Kind.ADVANCE, initial_metadata, payload, completion,
allowance)
else:
raise ValueError('TODO!!!')
else:
return _IDLE_ON_ADVANCE
def service_on_termination(self, outcome):
with self._condition:
self._state.service_side_outcome = outcome
if self._todo is not None or self._remaining_elements:
self._failed('Premature service-side outcome %s!' % (outcome,))
elif outcome.kind is not self._sequence.outcome_kinds.service:
self._failed(
'Incorrect service-side outcome kind: %s should have been %s' % (
outcome.kind, self._sequence.outcome_kinds.service))
elif self._state.invocation_side_outcome is not None:
self._passed(self._state.invocation_side_outcome.kind, outcome.kind)
def invocation_on_termination(self, outcome):
with self._condition:
self._state.invocation_side_outcome = outcome
if self._todo is not None or self._remaining_elements:
self._failed('Premature invocation-side outcome %s!' % (outcome,))
elif outcome.kind is not self._sequence.outcome_kinds.invocation:
self._failed(
'Incorrect invocation-side outcome kind: %s should have been %s' % (
outcome.kind, self._sequence.outcome_kinds.invocation))
elif self._state.service_side_outcome is not None:
self._passed(outcome.kind, self._state.service_side_outcome.kind)
class _SequenceControllerCreator(ControllerCreator):
def __init__(self, sequence):
self._sequence = sequence
def name(self):
return self._sequence.name
def controller(self, implementation, randomness):
return _SequenceController(self._sequence, implementation, randomness)
CONTROLLER_CREATORS = tuple(
_SequenceControllerCreator(sequence) for sequence in _sequence.SEQUENCES)

@ -1,171 +0,0 @@
# Copyright 2015, 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.
"""Part of the tests of the base interface of RPC Framework."""
import collections
import enum
from grpc.framework.interfaces.base import base
from tests.unit.framework.common import test_constants
class Invocation(
collections.namedtuple(
'Invocation', ('timeout', 'initial_metadata', 'payload', 'complete',))):
"""A recipe for operation invocation.
Attributes:
timeout: A duration in seconds to pass to the system under test as the
operation's timeout value.
initial_metadata: A boolean indicating whether or not to pass initial
metadata when invoking the operation.
payload: A boolean indicating whether or not to pass a payload when
invoking the operation.
complete: A boolean indicating whether or not to indicate completion of
transmissions from the invoking side of the operation when invoking the
operation.
"""
class Transmission(
collections.namedtuple(
'Transmission', ('initial_metadata', 'payload', 'complete',))):
"""A recipe for a single transmission in an operation.
Attributes:
initial_metadata: A boolean indicating whether or not to pass initial
metadata as part of the transmission.
payload: A boolean indicating whether or not to pass a payload as part of
the transmission.
complete: A boolean indicating whether or not to indicate completion of
transmission from the transmitting side of the operation as part of the
transmission.
"""
class Intertransmission(
collections.namedtuple('Intertransmission', ('invocation', 'service',))):
"""A recipe for multiple transmissions in an operation.
Attributes:
invocation: An integer describing the number of payloads to send from the
invocation side of the operation to the service side.
service: An integer describing the number of payloads to send from the
service side of the operation to the invocation side.
"""
class Element(collections.namedtuple('Element', ('kind', 'transmission',))):
"""A sum type for steps to perform when testing an operation.
Attributes:
kind: A Kind value describing the kind of step to perform in the test.
transmission: Only valid for kinds Kind.INVOCATION_TRANSMISSION and
Kind.SERVICE_TRANSMISSION, a Transmission value describing the details of
the transmission to be made.
"""
@enum.unique
class Kind(enum.Enum):
INVOCATION_TRANSMISSION = 'invocation transmission'
SERVICE_TRANSMISSION = 'service transmission'
INTERTRANSMISSION = 'intertransmission'
INVOCATION_CANCEL = 'invocation cancel'
SERVICE_CANCEL = 'service cancel'
INVOCATION_FAILURE = 'invocation failure'
SERVICE_FAILURE = 'service failure'
class OutcomeKinds(
collections.namedtuple('Outcome', ('invocation', 'service',))):
"""A description of the expected outcome of an operation test.
Attributes:
invocation: The base.Outcome.Kind value expected on the invocation side of
the operation.
service: The base.Outcome.Kind value expected on the service side of the
operation.
"""
class Sequence(
collections.namedtuple(
'Sequence',
('name', 'maximum_duration', 'invocation', 'elements',
'outcome_kinds',))):
"""Describes at a high level steps to perform in a test.
Attributes:
name: The string name of the sequence.
maximum_duration: A length of time in seconds to allow for the test before
declaring it to have failed.
invocation: An Invocation value describing how to invoke the operation
under test.
elements: A sequence of Element values describing at coarse granularity
actions to take during the operation under test.
outcome_kinds: An OutcomeKinds value describing the expected outcome kinds
of the test.
"""
_EASY = Sequence(
'Easy',
test_constants.TIME_ALLOWANCE,
Invocation(test_constants.LONG_TIMEOUT, True, True, True),
(
Element(
Element.Kind.SERVICE_TRANSMISSION, Transmission(True, True, True)),
),
OutcomeKinds(base.Outcome.Kind.COMPLETED, base.Outcome.Kind.COMPLETED))
_PEASY = Sequence(
'Peasy',
test_constants.TIME_ALLOWANCE,
Invocation(test_constants.LONG_TIMEOUT, True, True, False),
(
Element(
Element.Kind.SERVICE_TRANSMISSION, Transmission(True, True, False)),
Element(
Element.Kind.INVOCATION_TRANSMISSION,
Transmission(False, True, True)),
Element(
Element.Kind.SERVICE_TRANSMISSION, Transmission(False, True, True)),
),
OutcomeKinds(base.Outcome.Kind.COMPLETED, base.Outcome.Kind.COMPLETED))
# TODO(issue 2959): Finish this test suite. This tuple of sequences should
# contain at least the values in the Cartesian product of (half-duplex,
# full-duplex) * (zero payloads, one payload, test_constants.STREAM_LENGTH
# payloads) * (completion, cancellation, expiration, programming defect in
# servicer code).
SEQUENCES = (
_EASY,
_PEASY,
)

@ -1,55 +0,0 @@
# Copyright 2015, 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.
"""Part of the tests of the base interface of RPC Framework."""
class OperationState(object):
def __init__(self):
self.invocation_initial_metadata_in_flight = None
self.invocation_initial_metadata_received = False
self.invocation_payloads_in_flight = []
self.invocation_payloads_received = 0
self.invocation_completion_in_flight = None
self.invocation_completion_received = False
self.service_initial_metadata_in_flight = None
self.service_initial_metadata_received = False
self.service_payloads_in_flight = []
self.service_payloads_received = 0
self.service_completion_in_flight = None
self.service_completion_received = False
self.invocation_side_invocation_allowance = 1
self.invocation_side_service_allowance = 1
self.service_side_invocation_allowance = 1
self.service_side_service_allowance = 1
self.invocation_allowance_in_flight = 0
self.service_allowance_in_flight = 0
self.invocation_side_outcome = None
self.service_side_outcome = None

@ -1,279 +0,0 @@
# Copyright 2015, 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 of the base interface of RPC Framework."""
from __future__ import division
import logging
import random
import threading
import time
import unittest
from grpc.framework.foundation import logging_pool
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.base import utilities
from tests.unit.framework.common import test_constants
from tests.unit.framework.interfaces.base import _control
from tests.unit.framework.interfaces.base import test_interfaces
_SYNCHRONICITY_VARIATION = (('Sync', False), ('Async', True))
_EMPTY_OUTCOME_KIND_DICT = {
outcome_kind: 0 for outcome_kind in base.Outcome.Kind}
class _Serialization(test_interfaces.Serialization):
def serialize_request(self, request):
return request + request
def deserialize_request(self, serialized_request):
return serialized_request[:len(serialized_request) // 2]
def serialize_response(self, response):
return response * 3
def deserialize_response(self, serialized_response):
return serialized_response[2 * len(serialized_response) // 3:]
def _advance(quadruples, operator, controller):
try:
for quadruple in quadruples:
operator.advance(
initial_metadata=quadruple[0], payload=quadruple[1],
completion=quadruple[2], allowance=quadruple[3])
except Exception as e: # pylint: disable=broad-except
controller.failed('Exception on advance: %e' % e)
class _Operator(base.Operator):
def __init__(self, controller, on_advance, pool, operator_under_test):
self._condition = threading.Condition()
self._controller = controller
self._on_advance = on_advance
self._pool = pool
self._operator_under_test = operator_under_test
self._pending_advances = []
def set_operator_under_test(self, operator_under_test):
with self._condition:
self._operator_under_test = operator_under_test
pent_advances = self._pending_advances
self._pending_advances = []
pool = self._pool
controller = self._controller
if pool is None:
_advance(pent_advances, operator_under_test, controller)
else:
pool.submit(_advance, pent_advances, operator_under_test, controller)
def advance(
self, initial_metadata=None, payload=None, completion=None,
allowance=None):
on_advance = self._on_advance(
initial_metadata, payload, completion, allowance)
if on_advance.kind is _control.OnAdvance.Kind.ADVANCE:
with self._condition:
pool = self._pool
operator_under_test = self._operator_under_test
controller = self._controller
quadruple = (
on_advance.initial_metadata, on_advance.payload,
on_advance.completion, on_advance.allowance)
if pool is None:
_advance((quadruple,), operator_under_test, controller)
else:
pool.submit(_advance, (quadruple,), operator_under_test, controller)
elif on_advance.kind is _control.OnAdvance.Kind.DEFECT:
raise ValueError(
'Deliberately raised exception from Operator.advance (in a test)!')
class _ProtocolReceiver(base.ProtocolReceiver):
def __init__(self):
self._condition = threading.Condition()
self._contexts = []
def context(self, protocol_context):
with self._condition:
self._contexts.append(protocol_context)
class _Servicer(base.Servicer):
"""A base.Servicer with instrumented for testing."""
def __init__(self, group, method, controllers, pool):
self._condition = threading.Condition()
self._group = group
self._method = method
self._pool = pool
self._controllers = list(controllers)
def service(self, group, method, context, output_operator):
with self._condition:
controller = self._controllers.pop(0)
if group != self._group or method != self._method:
controller.fail(
'%s != %s or %s != %s' % (group, self._group, method, self._method))
raise base.NoSuchMethodError(None, None)
else:
operator = _Operator(
controller, controller.on_service_advance, self._pool,
output_operator)
outcome = context.add_termination_callback(
controller.service_on_termination)
if outcome is not None:
controller.service_on_termination(outcome)
return utilities.full_subscription(operator, _ProtocolReceiver())
class _OperationTest(unittest.TestCase):
def setUp(self):
if self._synchronicity_variation:
self._pool = logging_pool.pool(test_constants.POOL_SIZE)
else:
self._pool = None
self._controller = self._controller_creator.controller(
self._implementation, self._randomness)
def tearDown(self):
if self._synchronicity_variation:
self._pool.shutdown(wait=True)
else:
self._pool = None
def test_operation(self):
invocation = self._controller.invocation()
if invocation.subscription_kind is base.Subscription.Kind.FULL:
test_operator = _Operator(
self._controller, self._controller.on_invocation_advance,
self._pool, None)
subscription = utilities.full_subscription(
test_operator, _ProtocolReceiver())
else:
# TODO(nathaniel): support and test other subscription kinds.
self.fail('Non-full subscriptions not yet supported!')
servicer = _Servicer(
invocation.group, invocation.method, (self._controller,), self._pool)
invocation_end, service_end, memo = self._implementation.instantiate(
{(invocation.group, invocation.method): _Serialization()}, servicer)
try:
invocation_end.start()
service_end.start()
operation_context, operator_under_test = invocation_end.operate(
invocation.group, invocation.method, subscription, invocation.timeout,
initial_metadata=invocation.initial_metadata, payload=invocation.payload,
completion=invocation.completion)
test_operator.set_operator_under_test(operator_under_test)
outcome = operation_context.add_termination_callback(
self._controller.invocation_on_termination)
if outcome is not None:
self._controller.invocation_on_termination(outcome)
except Exception as e: # pylint: disable=broad-except
self._controller.failed('Exception on invocation: %s' % e)
self.fail(e)
while True:
instruction = self._controller.poll()
if instruction.kind is _control.Instruction.Kind.ADVANCE:
try:
test_operator.advance(
*instruction.advance_args, **instruction.advance_kwargs)
except Exception as e: # pylint: disable=broad-except
self._controller.failed('Exception on instructed advance: %s' % e)
elif instruction.kind is _control.Instruction.Kind.CANCEL:
try:
operation_context.cancel()
except Exception as e: # pylint: disable=broad-except
self._controller.failed('Exception on cancel: %s' % e)
elif instruction.kind is _control.Instruction.Kind.CONCLUDE:
break
invocation_stop_event = invocation_end.stop(0)
service_stop_event = service_end.stop(0)
invocation_stop_event.wait()
service_stop_event.wait()
invocation_stats = invocation_end.operation_stats()
service_stats = service_end.operation_stats()
self._implementation.destantiate(memo)
self.assertTrue(
instruction.conclude_success, msg=instruction.conclude_message)
expected_invocation_stats = dict(_EMPTY_OUTCOME_KIND_DICT)
expected_invocation_stats[
instruction.conclude_invocation_outcome_kind] += 1
self.assertDictEqual(expected_invocation_stats, invocation_stats)
expected_service_stats = dict(_EMPTY_OUTCOME_KIND_DICT)
expected_service_stats[instruction.conclude_service_outcome_kind] += 1
self.assertDictEqual(expected_service_stats, service_stats)
def test_cases(implementation):
"""Creates unittest.TestCase classes for a given Base implementation.
Args:
implementation: A test_interfaces.Implementation specifying creation and
destruction of the Base implementation under test.
Returns:
A sequence of subclasses of unittest.TestCase defining tests of the
specified Base layer implementation.
"""
random_seed = hash(time.time())
logging.warning('Random seed for this execution: %s', random_seed)
randomness = random.Random(x=random_seed)
test_case_classes = []
for synchronicity_variation in _SYNCHRONICITY_VARIATION:
for controller_creator in _control.CONTROLLER_CREATORS:
name = ''.join(
(synchronicity_variation[0], controller_creator.name(), 'Test',))
test_case_classes.append(
type(name, (_OperationTest,),
{'_implementation': implementation,
'_randomness': randomness,
'_synchronicity_variation': synchronicity_variation[1],
'_controller_creator': controller_creator,
'__module__': implementation.__module__,
}))
return test_case_classes

@ -1,186 +0,0 @@
# Copyright 2015, 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.
"""Interfaces used in tests of implementations of the Base layer."""
import abc
import six
from grpc.framework.interfaces.base import base # pylint: disable=unused-import
class Serialization(six.with_metaclass(abc.ABCMeta)):
"""Specifies serialization and deserialization of test payloads."""
def serialize_request(self, request):
"""Serializes a request value used in a test.
Args:
request: A request value created by a test.
Returns:
A bytestring that is the serialization of the given request.
"""
raise NotImplementedError()
def deserialize_request(self, serialized_request):
"""Deserializes a request value used in a test.
Args:
serialized_request: A bytestring that is the serialization of some request
used in a test.
Returns:
The request value encoded by the given bytestring.
"""
raise NotImplementedError()
def serialize_response(self, response):
"""Serializes a response value used in a test.
Args:
response: A response value created by a test.
Returns:
A bytestring that is the serialization of the given response.
"""
raise NotImplementedError()
def deserialize_response(self, serialized_response):
"""Deserializes a response value used in a test.
Args:
serialized_response: A bytestring that is the serialization of some
response used in a test.
Returns:
The response value encoded by the given bytestring.
"""
raise NotImplementedError()
class Implementation(six.with_metaclass(abc.ABCMeta)):
"""Specifies an implementation of the Base layer."""
@abc.abstractmethod
def instantiate(self, serializations, servicer):
"""Instantiates the Base layer implementation to be used in a test.
Args:
serializations: A dict from group-method pair to Serialization object
specifying how to serialize and deserialize payload values used in the
test.
servicer: A base.Servicer object to be called to service RPCs made during
the test.
Returns:
A sequence of length three the first element of which is a
base.End to be used to invoke RPCs, the second element of which is a
base.End to be used to service invoked RPCs, and the third element of
which is an arbitrary memo object to be kept and passed to destantiate
at the conclusion of the test.
"""
raise NotImplementedError()
@abc.abstractmethod
def destantiate(self, memo):
"""Destroys the Base layer implementation under test.
Args:
memo: The object from the third position of the return value of a call to
instantiate.
"""
raise NotImplementedError()
@abc.abstractmethod
def invocation_initial_metadata(self):
"""Provides an operation's invocation-side initial metadata.
Returns:
A value to use for an operation's invocation-side initial metadata, or
None.
"""
raise NotImplementedError()
@abc.abstractmethod
def service_initial_metadata(self):
"""Provides an operation's service-side initial metadata.
Returns:
A value to use for an operation's service-side initial metadata, or
None.
"""
raise NotImplementedError()
@abc.abstractmethod
def invocation_completion(self):
"""Provides an operation's invocation-side completion.
Returns:
A base.Completion to use for an operation's invocation-side completion.
"""
raise NotImplementedError()
@abc.abstractmethod
def service_completion(self):
"""Provides an operation's service-side completion.
Returns:
A base.Completion to use for an operation's service-side completion.
"""
raise NotImplementedError()
@abc.abstractmethod
def metadata_transmitted(self, original_metadata, transmitted_metadata):
"""Identifies whether or not metadata was properly transmitted.
Args:
original_metadata: A metadata value passed to the system under test.
transmitted_metadata: The same metadata value after having been
transmitted through the system under test.
Returns:
Whether or not the metadata was properly transmitted.
"""
raise NotImplementedError()
@abc.abstractmethod
def completion_transmitted(self, original_completion, transmitted_completion):
"""Identifies whether or not a base.Completion was properly transmitted.
Args:
original_completion: A base.Completion passed to the system under test.
transmitted_completion: The same completion value after having been
transmitted through the system under test.
Returns:
Whether or not the completion was properly transmitted.
"""
raise NotImplementedError()

@ -1,95 +0,0 @@
# Copyright 2015, 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.
"""A utility useful in tests of asynchronous, event-driven interfaces."""
import threading
from grpc.framework.interfaces.face import face
class Receiver(face.ResponseReceiver):
"""A utility object useful in tests of asynchronous code."""
def __init__(self):
self._condition = threading.Condition()
self._initial_metadata = None
self._responses = []
self._terminal_metadata = None
self._code = None
self._details = None
self._completed = False
self._abortion = None
def abort(self, abortion):
with self._condition:
self._abortion = abortion
self._condition.notify_all()
def initial_metadata(self, initial_metadata):
with self._condition:
self._initial_metadata = initial_metadata
def response(self, response):
with self._condition:
self._responses.append(response)
def complete(self, terminal_metadata, code, details):
with self._condition:
self._terminal_metadata = terminal_metadata
self._code = code
self._details = details
self._completed = True
self._condition.notify_all()
def block_until_terminated(self):
with self._condition:
while self._abortion is None and not self._completed:
self._condition.wait()
def unary_response(self):
with self._condition:
if self._abortion is not None:
raise AssertionError('Aborted: "{}"!'.format(self._abortion))
elif len(self._responses) != 1:
raise AssertionError(
'%d responses received, not exactly one!', len(self._responses))
else:
return self._responses[0]
def stream_responses(self):
with self._condition:
if self._abortion is None:
return list(self._responses)
else:
raise AssertionError('Aborted: "{}"!'.format(self._abortion))
def abortion(self):
with self._condition:
return self._abortion

@ -1,30 +0,0 @@
# Copyright 2015, 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.

@ -1,327 +0,0 @@
# Copyright 2015, 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 of the links interface of RPC Framework."""
# unittest is referenced from specification in this module.
import abc
import unittest # pylint: disable=unused-import
import six
from grpc.framework.interfaces.links import links
from tests.unit.framework.common import test_constants
from tests.unit.framework.interfaces.links import test_utilities
def at_least_n_payloads_received_predicate(n):
def predicate(ticket_sequence):
payload_count = 0
for ticket in ticket_sequence:
if ticket.payload is not None:
payload_count += 1
if n <= payload_count:
return True
else:
return False
return predicate
def terminated(ticket_sequence):
return ticket_sequence and ticket_sequence[-1].termination is not None
_TRANSMISSION_GROUP = 'test.Group'
_TRANSMISSION_METHOD = 'TestMethod'
class TransmissionTest(six.with_metaclass(abc.ABCMeta)):
"""Tests ticket transmission between two connected links.
This class must be mixed into a unittest.TestCase that implements the abstract
methods it provides.
"""
# This is a unittest.TestCase mix-in.
# pylint: disable=invalid-name
@abc.abstractmethod
def create_transmitting_links(self):
"""Creates two connected links for use in this test.
Returns:
Two links.Links, the first of which will be used on the invocation side
of RPCs and the second of which will be used on the service side of
RPCs.
"""
raise NotImplementedError()
@abc.abstractmethod
def destroy_transmitting_links(self, invocation_side_link, service_side_link):
"""Destroys the two connected links created for this test.
Args:
invocation_side_link: The link used on the invocation side of RPCs in
this test.
service_side_link: The link used on the service side of RPCs in this
test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_invocation_initial_metadata(self):
"""Creates a value for use as invocation-side initial metadata.
Returns:
A metadata value appropriate for use as invocation-side initial metadata
or None if invocation-side initial metadata transmission is not
supported by the links under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_invocation_terminal_metadata(self):
"""Creates a value for use as invocation-side terminal metadata.
Returns:
A metadata value appropriate for use as invocation-side terminal
metadata or None if invocation-side terminal metadata transmission is
not supported by the links under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_service_initial_metadata(self):
"""Creates a value for use as service-side initial metadata.
Returns:
A metadata value appropriate for use as service-side initial metadata or
None if service-side initial metadata transmission is not supported by
the links under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_service_terminal_metadata(self):
"""Creates a value for use as service-side terminal metadata.
Returns:
A metadata value appropriate for use as service-side terminal metadata or
None if service-side terminal metadata transmission is not supported by
the links under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_invocation_completion(self):
"""Creates values for use as invocation-side code and message.
Returns:
An invocation-side code value and an invocation-side message value.
Either or both may be None if invocation-side code and/or
invocation-side message transmission is not supported by the links
under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_service_completion(self):
"""Creates values for use as service-side code and message.
Returns:
A service-side code value and a service-side message value. Either or
both may be None if service-side code and/or service-side message
transmission is not supported by the links under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def assertMetadataTransmitted(self, original_metadata, transmitted_metadata):
"""Asserts that transmitted_metadata contains original_metadata.
Args:
original_metadata: A metadata object used in this test.
transmitted_metadata: A metadata object obtained after transmission
through the system under test.
Raises:
AssertionError: if the transmitted_metadata object does not contain
original_metadata.
"""
raise NotImplementedError()
def group_and_method(self):
"""Returns the group and method used in this test case.
Returns:
A pair of the group and method used in this test case.
"""
return _TRANSMISSION_GROUP, _TRANSMISSION_METHOD
def serialize_request(self, request):
"""Serializes a request value used in this test case.
Args:
request: A request value created by this test case.
Returns:
A bytestring that is the serialization of the given request.
"""
return request
def deserialize_request(self, serialized_request):
"""Deserializes a request value used in this test case.
Args:
serialized_request: A bytestring that is the serialization of some request
used in this test case.
Returns:
The request value encoded by the given bytestring.
"""
return serialized_request
def serialize_response(self, response):
"""Serializes a response value used in this test case.
Args:
response: A response value created by this test case.
Returns:
A bytestring that is the serialization of the given response.
"""
return response
def deserialize_response(self, serialized_response):
"""Deserializes a response value used in this test case.
Args:
serialized_response: A bytestring that is the serialization of some
response used in this test case.
Returns:
The response value encoded by the given bytestring.
"""
return serialized_response
def _assert_is_valid_metadata_payload_sequence(
self, ticket_sequence, payloads, initial_metadata, terminal_metadata):
initial_metadata_seen = False
seen_payloads = []
terminal_metadata_seen = False
for ticket in ticket_sequence:
if ticket.initial_metadata is not None:
self.assertFalse(initial_metadata_seen)
self.assertFalse(seen_payloads)
self.assertFalse(terminal_metadata_seen)
self.assertMetadataTransmitted(initial_metadata, ticket.initial_metadata)
initial_metadata_seen = True
if ticket.payload is not None:
self.assertFalse(terminal_metadata_seen)
seen_payloads.append(ticket.payload)
if ticket.terminal_metadata is not None:
self.assertFalse(terminal_metadata_seen)
self.assertMetadataTransmitted(terminal_metadata, ticket.terminal_metadata)
terminal_metadata_seen = True
self.assertSequenceEqual(payloads, seen_payloads)
def _assert_is_valid_invocation_sequence(
self, ticket_sequence, group, method, payloads, initial_metadata,
terminal_metadata, termination):
self.assertLess(0, len(ticket_sequence))
self.assertEqual(group, ticket_sequence[0].group)
self.assertEqual(method, ticket_sequence[0].method)
self._assert_is_valid_metadata_payload_sequence(
ticket_sequence, payloads, initial_metadata, terminal_metadata)
self.assertIs(termination, ticket_sequence[-1].termination)
def _assert_is_valid_service_sequence(
self, ticket_sequence, payloads, initial_metadata, terminal_metadata,
code, message, termination):
self.assertLess(0, len(ticket_sequence))
self._assert_is_valid_metadata_payload_sequence(
ticket_sequence, payloads, initial_metadata, terminal_metadata)
self.assertEqual(code, ticket_sequence[-1].code)
self.assertEqual(message, ticket_sequence[-1].message)
self.assertIs(termination, ticket_sequence[-1].termination)
def setUp(self):
self._invocation_link, self._service_link = self.create_transmitting_links()
self._invocation_mate = test_utilities.RecordingLink()
self._service_mate = test_utilities.RecordingLink()
self._invocation_link.join_link(self._invocation_mate)
self._service_link.join_link(self._service_mate)
def tearDown(self):
self.destroy_transmitting_links(self._invocation_link, self._service_link)
def testSimplestRoundTrip(self):
"""Tests transmission of one ticket in each direction."""
invocation_operation_id = object()
invocation_payload = b'\x07' * 1023
timeout = test_constants.LONG_TIMEOUT
invocation_initial_metadata = self.create_invocation_initial_metadata()
invocation_terminal_metadata = self.create_invocation_terminal_metadata()
invocation_code, invocation_message = self.create_invocation_completion()
service_payload = b'\x08' * 1025
service_initial_metadata = self.create_service_initial_metadata()
service_terminal_metadata = self.create_service_terminal_metadata()
service_code, service_message = self.create_service_completion()
original_invocation_ticket = links.Ticket(
invocation_operation_id, 0, _TRANSMISSION_GROUP, _TRANSMISSION_METHOD,
links.Ticket.Subscription.FULL, timeout, 0, invocation_initial_metadata,
invocation_payload, invocation_terminal_metadata, invocation_code,
invocation_message, links.Ticket.Termination.COMPLETION, None)
self._invocation_link.accept_ticket(original_invocation_ticket)
self._service_mate.block_until_tickets_satisfy(
at_least_n_payloads_received_predicate(1))
service_operation_id = self._service_mate.tickets()[0].operation_id
self._service_mate.block_until_tickets_satisfy(terminated)
self._assert_is_valid_invocation_sequence(
self._service_mate.tickets(), _TRANSMISSION_GROUP, _TRANSMISSION_METHOD,
(invocation_payload,), invocation_initial_metadata,
invocation_terminal_metadata, links.Ticket.Termination.COMPLETION)
original_service_ticket = links.Ticket(
service_operation_id, 0, None, None, links.Ticket.Subscription.FULL,
timeout, 0, service_initial_metadata, service_payload,
service_terminal_metadata, service_code, service_message,
links.Ticket.Termination.COMPLETION, None)
self._service_link.accept_ticket(original_service_ticket)
self._invocation_mate.block_until_tickets_satisfy(terminated)
self._assert_is_valid_service_sequence(
self._invocation_mate.tickets(), (service_payload,),
service_initial_metadata, service_terminal_metadata, service_code,
service_message, links.Ticket.Termination.COMPLETION)

@ -1,167 +0,0 @@
# Copyright 2015, 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.
"""State and behavior appropriate for use in tests."""
import logging
import threading
import time
from grpc.framework.interfaces.links import links
from grpc.framework.interfaces.links import utilities
# A more-or-less arbitrary limit on the length of raw data values to be logged.
_UNCOMFORTABLY_LONG = 48
def _safe_for_log_ticket(ticket):
"""Creates a safe-for-printing-to-the-log ticket for a given ticket.
Args:
ticket: Any links.Ticket.
Returns:
A links.Ticket that is as much as can be equal to the given ticket but
possibly features values like the string "<payload of length 972321>" in
place of the actual values of the given ticket.
"""
if isinstance(ticket.payload, (basestring,)):
payload_length = len(ticket.payload)
else:
payload_length = -1
if payload_length < _UNCOMFORTABLY_LONG:
return ticket
else:
return links.Ticket(
ticket.operation_id, ticket.sequence_number,
ticket.group, ticket.method, ticket.subscription, ticket.timeout,
ticket.allowance, ticket.initial_metadata,
'<payload of length {}>'.format(payload_length),
ticket.terminal_metadata, ticket.code, ticket.message,
ticket.termination, None)
class RecordingLink(links.Link):
"""A Link that records every ticket passed to it."""
def __init__(self):
self._condition = threading.Condition()
self._tickets = []
def accept_ticket(self, ticket):
with self._condition:
self._tickets.append(ticket)
self._condition.notify_all()
def join_link(self, link):
pass
def block_until_tickets_satisfy(self, predicate):
"""Blocks until the received tickets satisfy the given predicate.
Args:
predicate: A callable that takes a sequence of tickets and returns a
boolean value.
"""
with self._condition:
while not predicate(self._tickets):
self._condition.wait()
def tickets(self):
"""Returns a copy of the list of all tickets received by this Link."""
with self._condition:
return tuple(self._tickets)
class _Pipe(object):
"""A conduit that logs all tickets passed through it."""
def __init__(self, name):
self._lock = threading.Lock()
self._name = name
self._left_mate = utilities.NULL_LINK
self._right_mate = utilities.NULL_LINK
def accept_left_to_right_ticket(self, ticket):
with self._lock:
logging.warning(
'%s: moving left to right through %s: %s', time.time(), self._name,
_safe_for_log_ticket(ticket))
try:
self._right_mate.accept_ticket(ticket)
except Exception as e: # pylint: disable=broad-except
logging.exception(e)
def accept_right_to_left_ticket(self, ticket):
with self._lock:
logging.warning(
'%s: moving right to left through %s: %s', time.time(), self._name,
_safe_for_log_ticket(ticket))
try:
self._left_mate.accept_ticket(ticket)
except Exception as e: # pylint: disable=broad-except
logging.exception(e)
def join_left_mate(self, left_mate):
with self._lock:
self._left_mate = utilities.NULL_LINK if left_mate is None else left_mate
def join_right_mate(self, right_mate):
with self._lock:
self._right_mate = (
utilities.NULL_LINK if right_mate is None else right_mate)
class _Facade(links.Link):
def __init__(self, accept, join):
self._accept = accept
self._join = join
def accept_ticket(self, ticket):
self._accept(ticket)
def join_link(self, link):
self._join(link)
def logging_links(name):
"""Creates a conduit that logs all tickets passed through it.
Args:
name: A name to use for the conduit to identify itself in logging output.
Returns:
Two links.Links, the first of which is the "left" side of the conduit
and the second of which is the "right" side of the conduit.
"""
pipe = _Pipe(name)
left_facade = _Facade(pipe.accept_left_to_right_ticket, pipe.join_left_mate)
right_facade = _Facade(pipe.accept_right_to_left_ticket, pipe.join_right_mate)
return left_facade, right_facade
Loading…
Cancel
Save