|  | @@ -118,6 +118,8 @@ def _abort(state, code, details):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def _handle_event(event, state, response_deserializer):
 | 
	
		
			
				|  |  |      callbacks = []
 | 
	
		
			
				|  |  | +    # print("************* Handling event with operations: {}".format(event.batch_operations))
 | 
	
		
			
				|  |  | +    # import traceback; traceback.print_stack()
 | 
	
		
			
				|  |  |      for batch_operation in event.batch_operations:
 | 
	
		
			
				|  |  |          operation_type = batch_operation.type()
 | 
	
		
			
				|  |  |          state.due.remove(operation_type)
 | 
	
	
		
			
				|  | @@ -125,6 +127,8 @@ def _handle_event(event, state, response_deserializer):
 | 
	
		
			
				|  |  |              state.initial_metadata = batch_operation.initial_metadata()
 | 
	
		
			
				|  |  |          elif operation_type == cygrpc.OperationType.receive_message:
 | 
	
		
			
				|  |  |              serialized_response = batch_operation.message()
 | 
	
		
			
				|  |  | +            # print("Batch operation message: {}".format(batch_operation.message()))
 | 
	
		
			
				|  |  | +            # print("Serialized response is '{}'".format(serialized_response))
 | 
	
		
			
				|  |  |              if serialized_response is not None:
 | 
	
		
			
				|  |  |                  response = _common.deserialize(serialized_response,
 | 
	
		
			
				|  |  |                                                 response_deserializer)
 | 
	
	
		
			
				|  | @@ -248,6 +252,101 @@ def _consume_request_iterator(request_iterator, state, call, request_serializer,
 | 
	
		
			
				|  |  |      consumption_thread.start()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +# TODO: Docstrings.
 | 
	
		
			
				|  |  | +class _SingleThreadedRendezvous(grpc.Call):  # pylint: disable=too-many-ancestors
 | 
	
		
			
				|  |  | +    def __init__(self, state, call, response_deserializer, deadline):
 | 
	
		
			
				|  |  | +        super(_SingleThreadedRendezvous, self).__init__()
 | 
	
		
			
				|  |  | +        # TODO: Is this still needed? Or is it just for inter-thread
 | 
	
		
			
				|  |  | +        # synchronization?
 | 
	
		
			
				|  |  | +        self._state = state
 | 
	
		
			
				|  |  | +        self._call = call
 | 
	
		
			
				|  |  | +        self._response_deserializer = response_deserializer
 | 
	
		
			
				|  |  | +        self._deadline = deadline
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def is_active(self):
 | 
	
		
			
				|  |  | +        """See grpc.RpcContext.is_active"""
 | 
	
		
			
				|  |  | +        raise NotImplementedError()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def time_remaining(self):
 | 
	
		
			
				|  |  | +        """See grpc.RpcContext.time_remaining"""
 | 
	
		
			
				|  |  | +        raise NotImplementedError()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def cancel(self):
 | 
	
		
			
				|  |  | +        """See grpc.RpcContext.cancel"""
 | 
	
		
			
				|  |  | +        raise NotImplementedError()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def add_callback(self, callback):
 | 
	
		
			
				|  |  | +        """See grpc.RpcContext.add_callback"""
 | 
	
		
			
				|  |  | +        raise NotImplementedError()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def initial_metadata(self):
 | 
	
		
			
				|  |  | +        """See grpc.Call.initial_metadata"""
 | 
	
		
			
				|  |  | +        raise NotImplementedError()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def trailing_metadata(self):
 | 
	
		
			
				|  |  | +        """See grpc.Call.trailing_metadata"""
 | 
	
		
			
				|  |  | +        raise NotImplementedError()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def code(self):
 | 
	
		
			
				|  |  | +        """See grpc.Call.code"""
 | 
	
		
			
				|  |  | +        raise NotImplementedError()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def details(self):
 | 
	
		
			
				|  |  | +        """See grpc.Call.details"""
 | 
	
		
			
				|  |  | +        raise NotImplementedError()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    # TODO: How does this work when the server sends back zero messages?
 | 
	
		
			
				|  |  | +    def _next(self):
 | 
	
		
			
				|  |  | +        # Since no other thread has access to self._state, we can access it
 | 
	
		
			
				|  |  | +        # without taking the lock. If we ever add a Future interface, we'll
 | 
	
		
			
				|  |  | +        # have to add synchronization.
 | 
	
		
			
				|  |  | +        if self._state.code is None:
 | 
	
		
			
				|  |  | +            operating = self._call.operate((cygrpc.ReceiveMessageOperation(_EMPTY_FLAGS),), None)
 | 
	
		
			
				|  |  | +            if operating:
 | 
	
		
			
				|  |  | +                # TODO: Justify this step. Is anyone actually using it?
 | 
	
		
			
				|  |  | +                self._state.due.add(cygrpc.OperationType.receive_message)
 | 
	
		
			
				|  |  | +        elif self._state.code is grpc.StatusCode.OK:
 | 
	
		
			
				|  |  | +            raise StopIteration()
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +            # TODO: Figure out what to raise instead.
 | 
	
		
			
				|  |  | +            # Alternatively, become a grpc.RPCError
 | 
	
		
			
				|  |  | +            raise ValueError("I should be a rendezvous! Or something...")
 | 
	
		
			
				|  |  | +        while True:
 | 
	
		
			
				|  |  | +            # TODO: Consider how this interacts with fork support.
 | 
	
		
			
				|  |  | +            event = self._call.next_event()
 | 
	
		
			
				|  |  | +            callbacks = _handle_event(event, self._state, self._response_deserializer)
 | 
	
		
			
				|  |  | +            for callback in callbacks:
 | 
	
		
			
				|  |  | +                try:
 | 
	
		
			
				|  |  | +                    callback()
 | 
	
		
			
				|  |  | +                except Exception as e:  # pylint: disable=broad-except
 | 
	
		
			
				|  |  | +                    # NOTE(rbellevi): We suppress but log errors here so as not to
 | 
	
		
			
				|  |  | +                    # kill the channel spin thread.
 | 
	
		
			
				|  |  | +                    logging.error('Exception in callback %s: %s', repr(
 | 
	
		
			
				|  |  | +                        callback.func), repr(e))
 | 
	
		
			
				|  |  | +            if self._state.response is not None:
 | 
	
		
			
				|  |  | +                response = self._state.response
 | 
	
		
			
				|  |  | +                self._state.response = None
 | 
	
		
			
				|  |  | +                return response
 | 
	
		
			
				|  |  | +            elif cygrpc.OperationType.receive_message not in self._state.due:
 | 
	
		
			
				|  |  | +                # TODO: Justify this. When can this even happen?
 | 
	
		
			
				|  |  | +                if self._state.code is grpc.StatusCode.OK:
 | 
	
		
			
				|  |  | +                    raise StopIteration()
 | 
	
		
			
				|  |  | +                else:
 | 
	
		
			
				|  |  | +                    pass
 | 
	
		
			
				|  |  | +                    # print(self._state.__dict__)
 | 
	
		
			
				|  |  | +                    # TODO: Figure out what to raise instead.
 | 
	
		
			
				|  |  | +                    # raise ValueError()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __next__(self):
 | 
	
		
			
				|  |  | +        return self._next()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def next(self):
 | 
	
		
			
				|  |  | +        return self._next()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __iter__(self):
 | 
	
		
			
				|  |  | +        return self
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  class _Rendezvous(grpc.RpcError, grpc.Future, grpc.Call):  # pylint: disable=too-many-ancestors
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def __init__(self, state, call, response_deserializer, deadline):
 | 
	
	
		
			
				|  | @@ -483,6 +582,8 @@ class _Rendezvous(grpc.RpcError, grpc.Future, grpc.Call):  # pylint: disable=too
 | 
	
		
			
				|  |  |                  self._state.condition.notify_all()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +# TODO: Audit usages of this. The logic is weird. Why not just raise the
 | 
	
		
			
				|  |  | +# exception right here?
 | 
	
		
			
				|  |  |  def _start_unary_request(request, timeout, request_serializer):
 | 
	
		
			
				|  |  |      deadline = _deadline(timeout)
 | 
	
		
			
				|  |  |      serialized_request = _common.serialize(request, request_serializer)
 | 
	
	
		
			
				|  | @@ -635,6 +736,58 @@ class _UnaryUnaryMultiCallable(grpc.UnaryUnaryMultiCallable):
 | 
	
		
			
				|  |  |              return _Rendezvous(state, call, self._response_deserializer,
 | 
	
		
			
				|  |  |                                 deadline)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class _SingleThreadedUnaryStreamMultiCallable(grpc.UnaryStreamMultiCallable):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    # pylint: disable=too-many-arguments
 | 
	
		
			
				|  |  | +    def __init__(self, channel, managed_call, method, request_serializer,
 | 
	
		
			
				|  |  | +                 response_deserializer):
 | 
	
		
			
				|  |  | +        self._channel = channel
 | 
	
		
			
				|  |  | +        # TODO: What is managed_call? Does it fit here?
 | 
	
		
			
				|  |  | +        self._managed_call = managed_call
 | 
	
		
			
				|  |  | +        self._method = method
 | 
	
		
			
				|  |  | +        self._request_serializer = request_serializer
 | 
	
		
			
				|  |  | +        self._response_deserializer = response_deserializer
 | 
	
		
			
				|  |  | +        self._context = cygrpc.build_census_context()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __call__(  # pylint: disable=too-many-locals
 | 
	
		
			
				|  |  | +            self,
 | 
	
		
			
				|  |  | +            request,
 | 
	
		
			
				|  |  | +            timeout=None,
 | 
	
		
			
				|  |  | +            metadata=None,
 | 
	
		
			
				|  |  | +            credentials=None,
 | 
	
		
			
				|  |  | +            wait_for_ready=None,
 | 
	
		
			
				|  |  | +            compression=None):
 | 
	
		
			
				|  |  | +        # TODO: Dedupe between here and _start_unary_request
 | 
	
		
			
				|  |  | +        deadline = _deadline(timeout)
 | 
	
		
			
				|  |  | +        serialized_request = _common.serialize(request, self._request_serializer)
 | 
	
		
			
				|  |  | +        if serialized_request is None:
 | 
	
		
			
				|  |  | +            raise  _RPCState((), (), (), grpc.StatusCode.INTERNAL,
 | 
	
		
			
				|  |  | +                              'Exception serializing request!')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # TODO: Is the initial_due data still used here?
 | 
	
		
			
				|  |  | +        state = _RPCState(_UNARY_STREAM_INITIAL_DUE, None, None, None, None)
 | 
	
		
			
				|  |  | +        # TODO: Factor this call_credentials logic out somewhere else. This is
 | 
	
		
			
				|  |  | +        # duplicated in UnaryUnaryMultiCallable._blocking.
 | 
	
		
			
				|  |  | +        call_credentials = None if credentials is None else credentials._credentials
 | 
	
		
			
				|  |  | +        initial_metadata_flags = _InitialMetadataFlags().with_wait_for_ready(
 | 
	
		
			
				|  |  | +            wait_for_ready)
 | 
	
		
			
				|  |  | +        augmented_metadata = _compression.augment_metadata(
 | 
	
		
			
				|  |  | +            metadata, compression)
 | 
	
		
			
				|  |  | +        operations_and_tags = (
 | 
	
		
			
				|  |  | +            ((cygrpc.SendInitialMetadataOperation(augmented_metadata,
 | 
	
		
			
				|  |  | +                                                initial_metadata_flags),
 | 
	
		
			
				|  |  | +            cygrpc.SendMessageOperation(serialized_request, _EMPTY_FLAGS),
 | 
	
		
			
				|  |  | +            cygrpc.SendCloseFromClientOperation(_EMPTY_FLAGS),
 | 
	
		
			
				|  |  | +            cygrpc.ReceiveStatusOnClientOperation(_EMPTY_FLAGS)), None),
 | 
	
		
			
				|  |  | +            ((cygrpc.ReceiveInitialMetadataOperation(_EMPTY_FLAGS),), None),
 | 
	
		
			
				|  |  | +        )
 | 
	
		
			
				|  |  | +        call = self._channel.segregated_call(cygrpc.PropagationConstants.GRPC_PROPAGATE_DEFAULTS,
 | 
	
		
			
				|  |  | +                                             self._method, None, _determine_deadline(deadline),
 | 
	
		
			
				|  |  | +                                             metadata, call_credentials, operations_and_tags, self._context)
 | 
	
		
			
				|  |  | +        return _SingleThreadedRendezvous(state, call, self._response_deserializer, deadline)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  class _UnaryStreamMultiCallable(grpc.UnaryStreamMultiCallable):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      # pylint: disable=too-many-arguments
 | 
	
	
		
			
				|  | @@ -1079,7 +1232,10 @@ class Channel(grpc.Channel):
 | 
	
		
			
				|  |  |                       method,
 | 
	
		
			
				|  |  |                       request_serializer=None,
 | 
	
		
			
				|  |  |                       response_deserializer=None):
 | 
	
		
			
				|  |  | -        return _UnaryStreamMultiCallable(
 | 
	
		
			
				|  |  | +        # return _UnaryStreamMultiCallable(
 | 
	
		
			
				|  |  | +        #     self._channel, _channel_managed_call_management(self._call_state),
 | 
	
		
			
				|  |  | +        #     _common.encode(method), request_serializer, response_deserializer)
 | 
	
		
			
				|  |  | +        return _SingleThreadedUnaryStreamMultiCallable(
 | 
	
		
			
				|  |  |              self._channel, _channel_managed_call_management(self._call_state),
 | 
	
		
			
				|  |  |              _common.encode(method), request_serializer, response_deserializer)
 | 
	
		
			
				|  |  |  
 |