aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/python/grpcio
diff options
context:
space:
mode:
Diffstat (limited to 'src/python/grpcio')
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/channel.c5
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c20
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c13
-rw-r--r--src/python/grpcio/grpc/_adapter/_intermediary_low.py5
-rw-r--r--src/python/grpcio/grpc/_adapter/fore.py2
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd2
-rw-r--r--src/python/grpcio/grpc/_links/service.py8
-rw-r--r--src/python/grpcio/grpc/beta/__init__.py28
-rw-r--r--src/python/grpcio/grpc/beta/_connectivity_channel.py148
-rw-r--r--src/python/grpcio/grpc/beta/beta.py114
-rw-r--r--src/python/grpcio/grpc/beta/utilities.py161
-rw-r--r--src/python/grpcio/grpc/framework/core/_context.py2
-rw-r--r--src/python/grpcio/grpc/framework/core/_emission.py3
-rw-r--r--src/python/grpcio/grpc/framework/core/_expiration.py2
-rw-r--r--src/python/grpcio/grpc/framework/core/_ingestion.py66
-rw-r--r--src/python/grpcio/grpc/framework/core/_interfaces.py8
-rw-r--r--src/python/grpcio/grpc/framework/core/_operation.py2
-rw-r--r--src/python/grpcio/grpc/framework/core/_reception.py2
-rw-r--r--src/python/grpcio/grpc/framework/core/_transmission.py14
-rw-r--r--src/python/grpcio/grpc/framework/crust/__init__.py30
-rw-r--r--src/python/grpcio/grpc/framework/crust/_calls.py204
-rw-r--r--src/python/grpcio/grpc/framework/crust/_control.py545
-rw-r--r--src/python/grpcio/grpc/framework/crust/_service.py166
-rw-r--r--src/python/grpcio/grpc/framework/crust/implementations.py352
-rw-r--r--src/python/grpcio/grpc/framework/interfaces/base/base.py21
25 files changed, 1862 insertions, 61 deletions
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/channel.c b/src/python/grpcio/grpc/_adapter/_c/types/channel.c
index c577ac05eb..79d39c4391 100644
--- a/src/python/grpcio/grpc/_adapter/_c/types/channel.c
+++ b/src/python/grpcio/grpc/_adapter/_c/types/channel.c
@@ -106,7 +106,8 @@ Channel *pygrpc_Channel_new(
}
self = (Channel *)type->tp_alloc(type, 0);
if (creds) {
- self->c_chan = grpc_secure_channel_create(creds->c_creds, target, &c_args);
+ self->c_chan =
+ grpc_secure_channel_create(creds->c_creds, target, &c_args, NULL);
} else {
self->c_chan = grpc_insecure_channel_create(target, &c_args, NULL);
}
@@ -164,7 +165,7 @@ PyObject *pygrpc_Channel_watch_connectivity_state(
int last_observed_state;
CompletionQueue *completion_queue;
char *keywords[] = {"last_observed_state", "deadline",
- "completion_queue", "tag"};
+ "completion_queue", "tag", NULL};
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "idO!O:watch_connectivity_state", keywords,
&last_observed_state, &deadline, &pygrpc_CompletionQueue_type,
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c b/src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c
index e314c15324..36fd207464 100644
--- a/src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c
+++ b/src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c
@@ -135,9 +135,10 @@ ClientCredentials *pygrpc_ClientCredentials_ssl(
if (private_key && cert_chain) {
key_cert_pair.private_key = private_key;
key_cert_pair.cert_chain = cert_chain;
- self->c_creds = grpc_ssl_credentials_create(root_certs, &key_cert_pair);
+ self->c_creds =
+ grpc_ssl_credentials_create(root_certs, &key_cert_pair, NULL);
} else {
- self->c_creds = grpc_ssl_credentials_create(root_certs, NULL);
+ self->c_creds = grpc_ssl_credentials_create(root_certs, NULL, NULL);
}
if (!self->c_creds) {
Py_DECREF(self);
@@ -159,8 +160,8 @@ ClientCredentials *pygrpc_ClientCredentials_composite(
return NULL;
}
self = (ClientCredentials *)type->tp_alloc(type, 0);
- self->c_creds = grpc_composite_credentials_create(
- creds1->c_creds, creds2->c_creds);
+ self->c_creds =
+ grpc_composite_credentials_create(creds1->c_creds, creds2->c_creds, NULL);
if (!self->c_creds) {
Py_DECREF(self);
PyErr_SetString(PyExc_RuntimeError, "couldn't create composite credentials");
@@ -172,7 +173,7 @@ ClientCredentials *pygrpc_ClientCredentials_composite(
ClientCredentials *pygrpc_ClientCredentials_compute_engine(
PyTypeObject *type, PyObject *ignored) {
ClientCredentials *self = (ClientCredentials *)type->tp_alloc(type, 0);
- self->c_creds = grpc_compute_engine_credentials_create();
+ self->c_creds = grpc_compute_engine_credentials_create(NULL);
if (!self->c_creds) {
Py_DECREF(self);
PyErr_SetString(PyExc_RuntimeError,
@@ -195,7 +196,7 @@ ClientCredentials *pygrpc_ClientCredentials_service_account(
}
self = (ClientCredentials *)type->tp_alloc(type, 0);
self->c_creds = grpc_service_account_credentials_create(
- json_key, scope, pygrpc_cast_double_to_gpr_timespec(lifetime));
+ json_key, scope, pygrpc_cast_double_to_gpr_timespec(lifetime), NULL);
if (!self->c_creds) {
Py_DECREF(self);
PyErr_SetString(PyExc_RuntimeError,
@@ -218,7 +219,7 @@ ClientCredentials *pygrpc_ClientCredentials_jwt(
}
self = (ClientCredentials *)type->tp_alloc(type, 0);
self->c_creds = grpc_service_account_jwt_access_credentials_create(
- json_key, pygrpc_cast_double_to_gpr_timespec(lifetime));
+ json_key, pygrpc_cast_double_to_gpr_timespec(lifetime), NULL);
if (!self->c_creds) {
Py_DECREF(self);
PyErr_SetString(PyExc_RuntimeError, "couldn't create JWT credentials");
@@ -237,7 +238,8 @@ ClientCredentials *pygrpc_ClientCredentials_refresh_token(
return NULL;
}
self = (ClientCredentials *)type->tp_alloc(type, 0);
- self->c_creds = grpc_refresh_token_credentials_create(json_refresh_token);
+ self->c_creds =
+ grpc_refresh_token_credentials_create(json_refresh_token, NULL);
if (!self->c_creds) {
Py_DECREF(self);
PyErr_SetString(PyExc_RuntimeError,
@@ -259,7 +261,7 @@ ClientCredentials *pygrpc_ClientCredentials_iam(
}
self = (ClientCredentials *)type->tp_alloc(type, 0);
self->c_creds = grpc_iam_credentials_create(authorization_token,
- authority_selector);
+ authority_selector, NULL);
if (!self->c_creds) {
Py_DECREF(self);
PyErr_SetString(PyExc_RuntimeError, "couldn't create IAM credentials");
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c b/src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c
index f6859b79d7..df51a99b6a 100644
--- a/src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c
+++ b/src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c
@@ -99,11 +99,13 @@ ServerCredentials *pygrpc_ServerCredentials_ssl(
const char *root_certs;
PyObject *py_key_cert_pairs;
grpc_ssl_pem_key_cert_pair *key_cert_pairs;
+ int force_client_auth;
size_t num_key_cert_pairs;
size_t i;
- static char *keywords[] = {"root_certs", "key_cert_pairs", NULL};
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "zO:ssl", keywords,
- &root_certs, &py_key_cert_pairs)) {
+ static char *keywords[] = {
+ "root_certs", "key_cert_pairs", "force_client_auth", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "zOi:ssl", keywords,
+ &root_certs, &py_key_cert_pairs, &force_client_auth)) {
return NULL;
}
if (!PyList_Check(py_key_cert_pairs)) {
@@ -128,11 +130,8 @@ ServerCredentials *pygrpc_ServerCredentials_ssl(
}
self = (ServerCredentials *)type->tp_alloc(type, 0);
- /* TODO: Add a force_client_auth parameter in the python object and pass it
- here as the last arg. */
self->c_creds = grpc_ssl_server_credentials_create(
- root_certs, key_cert_pairs, num_key_cert_pairs, 0);
+ root_certs, key_cert_pairs, num_key_cert_pairs, force_client_auth, NULL);
gpr_free(key_cert_pairs);
return self;
}
-
diff --git a/src/python/grpcio/grpc/_adapter/_intermediary_low.py b/src/python/grpcio/grpc/_adapter/_intermediary_low.py
index e7bf9dc462..06358e72bc 100644
--- a/src/python/grpcio/grpc/_adapter/_intermediary_low.py
+++ b/src/python/grpcio/grpc/_adapter/_intermediary_low.py
@@ -255,5 +255,6 @@ class ClientCredentials(object):
class ServerCredentials(object):
"""Adapter from old _low.ServerCredentials interface to new _low.ServerCredentials."""
- def __init__(self, root_credentials, pair_sequence):
- self._internal = _low.ServerCredentials.ssl(root_credentials, list(pair_sequence))
+ def __init__(self, root_credentials, pair_sequence, force_client_auth):
+ self._internal = _low.ServerCredentials.ssl(
+ root_credentials, list(pair_sequence), force_client_auth)
diff --git a/src/python/grpcio/grpc/_adapter/fore.py b/src/python/grpcio/grpc/_adapter/fore.py
index 7d88bda263..daa41e8bde 100644
--- a/src/python/grpcio/grpc/_adapter/fore.py
+++ b/src/python/grpcio/grpc/_adapter/fore.py
@@ -288,7 +288,7 @@ class ForeLink(base_interfaces.ForeLink, activated.Activated):
self._port = self._server.add_http2_addr(address)
else:
server_credentials = _low.ServerCredentials(
- self._root_certificates, self._key_chain_pairs)
+ self._root_certificates, self._key_chain_pairs, False)
self._server = _low.Server(self._completion_queue)
self._port = self._server.add_secure_http2_addr(
address, server_credentials)
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd
index d065383587..c793774c8d 100644
--- a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd
@@ -332,7 +332,7 @@ cdef extern from "grpc/grpc_security.h":
grpc_server_credentials *grpc_ssl_server_credentials_create(
const char *pem_root_certs,
grpc_ssl_pem_key_cert_pair *pem_key_cert_pairs,
- size_t num_key_cert_pairs);
+ size_t num_key_cert_pairs)
void grpc_server_credentials_release(grpc_server_credentials *creds)
int grpc_server_add_secure_http2_port(grpc_server *server, const char *addr,
diff --git a/src/python/grpcio/grpc/_links/service.py b/src/python/grpcio/grpc/_links/service.py
index 43c4c0e80c..10634e43b5 100644
--- a/src/python/grpcio/grpc/_links/service.py
+++ b/src/python/grpcio/grpc/_links/service.py
@@ -366,10 +366,10 @@ class ServiceLink(links.Link):
"""Adds a port on which to service RPCs after this link has been started.
Args:
- port: The port on which to service RPCs, or zero to request that a port be
- automatically selected and used.
- server_credentials: A ServerCredentials object, or None for insecure
- service.
+ port: The port on which to service RPCs, or zero to request that a port
+ be automatically selected and used.
+ server_credentials: An _intermediary_low.ServerCredentials object, or
+ None for insecure service.
Returns:
A port on which RPCs will be serviced after this link has been started.
diff --git a/src/python/grpcio/grpc/beta/__init__.py b/src/python/grpcio/grpc/beta/__init__.py
new file mode 100644
index 0000000000..b89398809f
--- /dev/null
+++ b/src/python/grpcio/grpc/beta/__init__.py
@@ -0,0 +1,28 @@
+# 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.
diff --git a/src/python/grpcio/grpc/beta/_connectivity_channel.py b/src/python/grpcio/grpc/beta/_connectivity_channel.py
new file mode 100644
index 0000000000..457ede79f2
--- /dev/null
+++ b/src/python/grpcio/grpc/beta/_connectivity_channel.py
@@ -0,0 +1,148 @@
+# 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 connectivity-state-listenable channel."""
+
+import threading
+import time
+
+from grpc._adapter import _low
+from grpc.framework.foundation import callable_util
+
+_CHANNEL_SUBSCRIPTION_CALLBACK_ERROR_LOG_MESSAGE = (
+ 'Exception calling channel subscription callback!')
+
+
+class ConnectivityChannel(object):
+
+ def __init__(self, low_channel, mapping):
+ self._lock = threading.Lock()
+ self._low_channel = low_channel
+ self._mapping = mapping
+
+ self._polling = False
+ self._connectivity = None
+ self._try_to_connect = False
+ self._callbacks_and_connectivities = []
+ self._delivering = False
+
+ def _deliveries(self, connectivity):
+ callbacks_needing_update = []
+ for callback_and_connectivity in self._callbacks_and_connectivities:
+ callback, callback_connectivity = callback_and_connectivity
+ if callback_connectivity is not connectivity:
+ callbacks_needing_update.append(callback)
+ callback_and_connectivity[1] = connectivity
+ return callbacks_needing_update
+
+ def _deliver(self, initial_connectivity, initial_callbacks):
+ connectivity = initial_connectivity
+ callbacks = initial_callbacks
+ while True:
+ for callback in callbacks:
+ callable_util.call_logging_exceptions(
+ callback, _CHANNEL_SUBSCRIPTION_CALLBACK_ERROR_LOG_MESSAGE,
+ connectivity)
+ with self._lock:
+ callbacks = self._deliveries(self._connectivity)
+ if callbacks:
+ connectivity = self._connectivity
+ else:
+ self._delivering = False
+ return
+
+ def _spawn_delivery(self, connectivity, callbacks):
+ delivering_thread = threading.Thread(
+ target=self._deliver, args=(connectivity, callbacks,))
+ delivering_thread.start()
+ self._delivering = True
+
+ # TODO(issue 3064): Don't poll.
+ def _poll_connectivity(self, low_channel, initial_try_to_connect):
+ try_to_connect = initial_try_to_connect
+ low_connectivity = low_channel.check_connectivity_state(try_to_connect)
+ with self._lock:
+ self._connectivity = self._mapping[low_connectivity]
+ callbacks = tuple(
+ callback for callback, unused_but_known_to_be_none_connectivity
+ in self._callbacks_and_connectivities)
+ for callback_and_connectivity in self._callbacks_and_connectivities:
+ callback_and_connectivity[1] = self._connectivity
+ if callbacks:
+ self._spawn_delivery(self._connectivity, callbacks)
+ completion_queue = _low.CompletionQueue()
+ while True:
+ low_channel.watch_connectivity_state(
+ low_connectivity, time.time() + 0.2, completion_queue, None)
+ event = completion_queue.next()
+ with self._lock:
+ if not self._callbacks_and_connectivities and not self._try_to_connect:
+ self._polling = False
+ self._connectivity = None
+ completion_queue.shutdown()
+ break
+ try_to_connect = self._try_to_connect
+ self._try_to_connect = False
+ if event.success or try_to_connect:
+ low_connectivity = low_channel.check_connectivity_state(try_to_connect)
+ with self._lock:
+ self._connectivity = self._mapping[low_connectivity]
+ if not self._delivering:
+ callbacks = self._deliveries(self._connectivity)
+ if callbacks:
+ self._spawn_delivery(self._connectivity, callbacks)
+
+ def subscribe(self, callback, try_to_connect):
+ with self._lock:
+ if not self._callbacks_and_connectivities and not self._polling:
+ polling_thread = threading.Thread(
+ target=self._poll_connectivity,
+ args=(self._low_channel, bool(try_to_connect)))
+ polling_thread.start()
+ self._polling = True
+ self._callbacks_and_connectivities.append([callback, None])
+ elif not self._delivering and self._connectivity is not None:
+ self._spawn_delivery(self._connectivity, (callback,))
+ self._try_to_connect |= bool(try_to_connect)
+ self._callbacks_and_connectivities.append(
+ [callback, self._connectivity])
+ else:
+ self._try_to_connect |= bool(try_to_connect)
+ self._callbacks_and_connectivities.append([callback, None])
+
+ def unsubscribe(self, callback):
+ with self._lock:
+ for index, (subscribed_callback, unused_connectivity) in enumerate(
+ self._callbacks_and_connectivities):
+ if callback == subscribed_callback:
+ self._callbacks_and_connectivities.pop(index)
+ break
+
+ def low_channel(self):
+ return self._low_channel
diff --git a/src/python/grpcio/grpc/beta/beta.py b/src/python/grpcio/grpc/beta/beta.py
new file mode 100644
index 0000000000..40cad5e486
--- /dev/null
+++ b/src/python/grpcio/grpc/beta/beta.py
@@ -0,0 +1,114 @@
+# 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 gRPC Python Beta."""
+
+import enum
+
+from grpc._adapter import _low
+from grpc._adapter import _types
+from grpc.beta import _connectivity_channel
+
+_CHANNEL_SUBSCRIPTION_CALLBACK_ERROR_LOG_MESSAGE = (
+ 'Exception calling channel subscription callback!')
+
+
+@enum.unique
+class ChannelConnectivity(enum.Enum):
+ """Mirrors grpc_connectivity_state in the gRPC Core.
+
+ Attributes:
+ IDLE: The channel is idle.
+ CONNECTING: The channel is connecting.
+ READY: The channel is ready to conduct RPCs.
+ TRANSIENT_FAILURE: The channel has seen a failure from which it expects to
+ recover.
+ FATAL_FAILURE: The channel has seen a failure from which it cannot recover.
+ """
+
+ IDLE = (_types.ConnectivityState.IDLE, 'idle',)
+ CONNECTING = (_types.ConnectivityState.CONNECTING, 'connecting',)
+ READY = (_types.ConnectivityState.READY, 'ready',)
+ TRANSIENT_FAILURE = (
+ _types.ConnectivityState.TRANSIENT_FAILURE, 'transient failure',)
+ FATAL_FAILURE = (_types.ConnectivityState.FATAL_FAILURE, 'fatal failure',)
+
+_LOW_CONNECTIVITY_STATE_TO_CHANNEL_CONNECTIVITY = {
+ state: connectivity for state, connectivity in zip(
+ _types.ConnectivityState, ChannelConnectivity)
+}
+
+
+class Channel(object):
+ """A channel to a remote host through which RPCs may be conducted.
+
+ Only the "subscribe" and "unsubscribe" methods are supported for application
+ use. This class' instance constructor and all other attributes are
+ unsupported.
+ """
+
+ def __init__(self, low_channel):
+ self._connectivity_channel = _connectivity_channel.ConnectivityChannel(
+ low_channel, _LOW_CONNECTIVITY_STATE_TO_CHANNEL_CONNECTIVITY)
+
+ def subscribe(self, callback, try_to_connect=None):
+ """Subscribes to this Channel's connectivity.
+
+ Args:
+ callback: A callable to be invoked and passed this Channel's connectivity.
+ The callable will be invoked immediately upon subscription and again for
+ every change to this Channel's connectivity thereafter until it is
+ unsubscribed.
+ try_to_connect: A boolean indicating whether or not this Channel should
+ attempt to connect if it is not already connected and ready to conduct
+ RPCs.
+ """
+ self._connectivity_channel.subscribe(callback, try_to_connect)
+
+ def unsubscribe(self, callback):
+ """Unsubscribes a callback from this Channel's connectivity.
+
+ Args:
+ callback: A callable previously registered with this Channel from having
+ been passed to its "subscribe" method.
+ """
+ self._connectivity_channel.unsubscribe(callback)
+
+
+def create_insecure_channel(host, port):
+ """Creates an insecure Channel to a remote host.
+
+ Args:
+ host: The name of the remote host to which to connect.
+ port: The port of the remote host to which to connect.
+
+ Returns:
+ A Channel to the remote host through which RPCs may be conducted.
+ """
+ return Channel(_low.Channel('%s:%d' % (host, port), ()))
diff --git a/src/python/grpcio/grpc/beta/utilities.py b/src/python/grpcio/grpc/beta/utilities.py
new file mode 100644
index 0000000000..1b5356e3ad
--- /dev/null
+++ b/src/python/grpcio/grpc/beta/utilities.py
@@ -0,0 +1,161 @@
+# 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 for the gRPC Python Beta API."""
+
+import threading
+import time
+
+from grpc.beta import beta
+from grpc.framework.foundation import callable_util
+from grpc.framework.foundation import future
+
+_DONE_CALLBACK_EXCEPTION_LOG_MESSAGE = (
+ 'Exception calling connectivity future "done" callback!')
+
+
+class _ChannelReadyFuture(future.Future):
+
+ def __init__(self, channel):
+ self._condition = threading.Condition()
+ self._channel = channel
+
+ self._matured = False
+ self._cancelled = False
+ self._done_callbacks = []
+
+ def _block(self, timeout):
+ until = None if timeout is None else time.time() + timeout
+ with self._condition:
+ while True:
+ if self._cancelled:
+ raise future.CancelledError()
+ elif self._matured:
+ return
+ else:
+ if until is None:
+ self._condition.wait()
+ else:
+ remaining = until - time.time()
+ if remaining < 0:
+ raise future.TimeoutError()
+ else:
+ self._condition.wait(timeout=remaining)
+
+ def _update(self, connectivity):
+ with self._condition:
+ if not self._cancelled and connectivity is beta.ChannelConnectivity.READY:
+ self._matured = True
+ self._channel.unsubscribe(self._update)
+ self._condition.notify_all()
+ done_callbacks = tuple(self._done_callbacks)
+ self._done_callbacks = None
+ else:
+ return
+
+ for done_callback in done_callbacks:
+ callable_util.call_logging_exceptions(
+ done_callback, _DONE_CALLBACK_EXCEPTION_LOG_MESSAGE, self)
+
+ def cancel(self):
+ with self._condition:
+ if not self._matured:
+ self._cancelled = True
+ self._channel.unsubscribe(self._update)
+ self._condition.notify_all()
+ done_callbacks = tuple(self._done_callbacks)
+ self._done_callbacks = None
+ else:
+ return False
+
+ for done_callback in done_callbacks:
+ callable_util.call_logging_exceptions(
+ done_callback, _DONE_CALLBACK_EXCEPTION_LOG_MESSAGE, self)
+
+ def cancelled(self):
+ with self._condition:
+ return self._cancelled
+
+ def running(self):
+ with self._condition:
+ return not self._cancelled and not self._matured
+
+ def done(self):
+ with self._condition:
+ return self._cancelled or self._matured
+
+ def result(self, timeout=None):
+ self._block(timeout)
+ return None
+
+ def exception(self, timeout=None):
+ self._block(timeout)
+ return None
+
+ def traceback(self, timeout=None):
+ self._block(timeout)
+ return None
+
+ def add_done_callback(self, fn):
+ with self._condition:
+ if not self._cancelled and not self._matured:
+ self._done_callbacks.append(fn)
+ return
+
+ fn(self)
+
+ def start(self):
+ with self._condition:
+ self._channel.subscribe(self._update, try_to_connect=True)
+
+ def __del__(self):
+ with self._condition:
+ if not self._cancelled and not self._matured:
+ self._channel.unsubscribe(self._update)
+
+
+def channel_ready_future(channel):
+ """Creates a future.Future that matures when a beta.Channel is ready.
+
+ Cancelling the returned future.Future does not tell the given beta.Channel to
+ abandon attempts it may have been making to connect; cancelling merely
+ deactivates the return future.Future's subscription to the given
+ beta.Channel's connectivity.
+
+ Args:
+ channel: A beta.Channel.
+
+ Returns:
+ A future.Future that matures when the given Channel has connectivity
+ beta.ChannelConnectivity.READY.
+ """
+ ready_future = _ChannelReadyFuture(channel)
+ ready_future.start()
+ return ready_future
+
diff --git a/src/python/grpcio/grpc/framework/core/_context.py b/src/python/grpcio/grpc/framework/core/_context.py
index 24a12b612e..76b3534530 100644
--- a/src/python/grpcio/grpc/framework/core/_context.py
+++ b/src/python/grpcio/grpc/framework/core/_context.py
@@ -60,7 +60,7 @@ class OperationContext(base.OperationContext):
with self._lock:
if self._termination_manager.outcome is None:
self._termination_manager.abort(outcome)
- self._transmission_manager.abort(outcome)
+ self._transmission_manager.abort(outcome, None, None)
self._expiration_manager.terminate()
def outcome(self):
diff --git a/src/python/grpcio/grpc/framework/core/_emission.py b/src/python/grpcio/grpc/framework/core/_emission.py
index 7c702ab2ce..2d7b2e2f10 100644
--- a/src/python/grpcio/grpc/framework/core/_emission.py
+++ b/src/python/grpcio/grpc/framework/core/_emission.py
@@ -82,7 +82,8 @@ class EmissionManager(_interfaces.EmissionManager):
completion_present and self._completion_seen or
allowance_present and allowance <= 0):
self._termination_manager.abort(base.Outcome.LOCAL_FAILURE)
- self._transmission_manager.abort(base.Outcome.LOCAL_FAILURE)
+ self._transmission_manager.abort(
+ base.Outcome.LOCAL_FAILURE, None, None)
self._expiration_manager.terminate()
else:
self._initial_metadata_seen |= initial_metadata_present
diff --git a/src/python/grpcio/grpc/framework/core/_expiration.py b/src/python/grpcio/grpc/framework/core/_expiration.py
index d94bdf2d2b..d8690b3a02 100644
--- a/src/python/grpcio/grpc/framework/core/_expiration.py
+++ b/src/python/grpcio/grpc/framework/core/_expiration.py
@@ -73,7 +73,7 @@ class _ExpirationManager(_interfaces.ExpirationManager):
if self._future is not None and index == self._index:
self._future = None
self._termination_manager.expire()
- self._transmission_manager.abort(base.Outcome.EXPIRED)
+ self._transmission_manager.abort(base.Outcome.EXPIRED, None, None)
return expire
def start(self):
diff --git a/src/python/grpcio/grpc/framework/core/_ingestion.py b/src/python/grpcio/grpc/framework/core/_ingestion.py
index 59f7f8adc8..7b8127f3fc 100644
--- a/src/python/grpcio/grpc/framework/core/_ingestion.py
+++ b/src/python/grpcio/grpc/framework/core/_ingestion.py
@@ -31,6 +31,7 @@
import abc
import collections
+import enum
from grpc.framework.core import _constants
from grpc.framework.core import _interfaces
@@ -42,21 +43,31 @@ _CREATE_SUBSCRIPTION_EXCEPTION_LOG_MESSAGE = 'Exception initializing ingestion!'
_INGESTION_EXCEPTION_LOG_MESSAGE = 'Exception during ingestion!'
-class _SubscriptionCreation(collections.namedtuple(
- '_SubscriptionCreation', ('subscription', 'remote_error', 'abandoned'))):
+class _SubscriptionCreation(
+ collections.namedtuple(
+ '_SubscriptionCreation',
+ ('kind', 'subscription', 'code', 'message',))):
"""A sum type for the outcome of ingestion initialization.
- Either subscription will be non-None, remote_error will be True, or abandoned
- will be True.
-
Attributes:
- subscription: A base.Subscription describing the customer's interest in
- operation values from the other side.
- remote_error: A boolean indicating that the subscription could not be
- created due to an error on the remote side of the operation.
- abandoned: A boolean indicating that subscription creation was abandoned.
+ 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.
+ message: A message 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(object):
"""Common specification of subscription-creating behavior."""
@@ -101,12 +112,15 @@ class _ServiceSubscriptionCreator(_SubscriptionCreator):
try:
subscription = self._servicer.service(
group, method, self._operation_context, self._output_operator)
- except base.NoSuchMethodError:
- return _SubscriptionCreation(None, True, False)
+ except base.NoSuchMethodError as e:
+ return _SubscriptionCreation(
+ _SubscriptionCreation.Kind.REMOTE_ERROR, None, e.code, e.message)
except abandonment.Abandoned:
- return _SubscriptionCreation(None, False, True)
+ return _SubscriptionCreation(
+ _SubscriptionCreation.Kind.ABANDONED, None, None, None)
else:
- return _SubscriptionCreation(subscription, False, False)
+ return _SubscriptionCreation(
+ _SubscriptionCreation.Kind.SUBSCRIPTION, subscription, None, None)
def _wrap(behavior):
@@ -176,10 +190,10 @@ class _IngestionManager(_interfaces.IngestionManager):
self._pending_payloads = None
self._pending_completion = None
- def _abort_and_notify(self, outcome):
+ def _abort_and_notify(self, outcome, code, message):
self._abort_internal_only()
self._termination_manager.abort(outcome)
- self._transmission_manager.abort(outcome)
+ self._transmission_manager.abort(outcome, code, message)
self._expiration_manager.terminate()
def _operator_next(self):
@@ -236,12 +250,12 @@ class _IngestionManager(_interfaces.IngestionManager):
else:
with self._lock:
if self._termination_manager.outcome is None:
- self._abort_and_notify(base.Outcome.LOCAL_FAILURE)
+ self._abort_and_notify(base.Outcome.LOCAL_FAILURE, None, None)
return
else:
with self._lock:
if self._termination_manager.outcome is None:
- self._abort_and_notify(base.Outcome.LOCAL_FAILURE)
+ self._abort_and_notify(base.Outcome.LOCAL_FAILURE, None, None)
return
def _operator_post_create(self, subscription):
@@ -260,20 +274,22 @@ class _IngestionManager(_interfaces.IngestionManager):
def _create(self, subscription_creator, group, name):
outcome = callable_util.call_logging_exceptions(
- subscription_creator.create, _CREATE_SUBSCRIPTION_EXCEPTION_LOG_MESSAGE,
- group, name)
+ 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.LOCAL_FAILURE)
- elif outcome.return_value.abandoned:
+ self._abort_and_notify(base.Outcome.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.LOCAL_FAILURE)
- elif outcome.return_value.remote_error:
+ self._abort_and_notify(base.Outcome.LOCAL_FAILURE, None, None)
+ elif outcome.return_value.kind is _SubscriptionCreation.Kind.REMOTE_ERROR:
+ code = outcome.return_value.code
+ message = outcome.return_value.message
with self._lock:
if self._termination_manager.outcome is None:
- self._abort_and_notify(base.Outcome.REMOTE_FAILURE)
+ self._abort_and_notify(base.Outcome.REMOTE_FAILURE, code, message)
elif outcome.return_value.subscription.kind is base.Subscription.Kind.FULL:
self._operator_post_create(outcome.return_value.subscription)
else:
diff --git a/src/python/grpcio/grpc/framework/core/_interfaces.py b/src/python/grpcio/grpc/framework/core/_interfaces.py
index a626b9f767..deb5f34f9b 100644
--- a/src/python/grpcio/grpc/framework/core/_interfaces.py
+++ b/src/python/grpcio/grpc/framework/core/_interfaces.py
@@ -155,13 +155,19 @@ class TransmissionManager(object):
raise NotImplementedError()
@abc.abstractmethod
- def abort(self, outcome):
+ def abort(self, outcome, code, message):
"""Indicates that the operation has aborted.
Args:
outcome: An interfaces.Outcome for the operation. If None, indicates that
the operation abortion should not be communicated to the other side of
the operation.
+ code: A code value to communicate to the other side of the operation
+ along with indication of operation abortion. May be None, and has no
+ effect if outcome is None.
+ message: A message value to communicate to the other side of the
+ operation along with indication of operation abortion. May be None, and
+ has no effect if outcome is None.
"""
raise NotImplementedError()
diff --git a/src/python/grpcio/grpc/framework/core/_operation.py b/src/python/grpcio/grpc/framework/core/_operation.py
index d20e40a53d..cc873c03f9 100644
--- a/src/python/grpcio/grpc/framework/core/_operation.py
+++ b/src/python/grpcio/grpc/framework/core/_operation.py
@@ -79,7 +79,7 @@ class _EasyOperation(_interfaces.Operation):
with self._lock:
if self._termination_manager.outcome is None:
self._termination_manager.abort(outcome)
- self._transmission_manager.abort(outcome)
+ self._transmission_manager.abort(outcome, None, None)
self._expiration_manager.terminate()
diff --git a/src/python/grpcio/grpc/framework/core/_reception.py b/src/python/grpcio/grpc/framework/core/_reception.py
index 0858f64ff6..1cebe3874b 100644
--- a/src/python/grpcio/grpc/framework/core/_reception.py
+++ b/src/python/grpcio/grpc/framework/core/_reception.py
@@ -73,7 +73,7 @@ class ReceptionManager(_interfaces.ReceptionManager):
self._aborted = True
if self._termination_manager.outcome is None:
self._termination_manager.abort(outcome)
- self._transmission_manager.abort(None)
+ self._transmission_manager.abort(None, None, None)
self._expiration_manager.terminate()
def _sequence_failure(self, ticket):
diff --git a/src/python/grpcio/grpc/framework/core/_transmission.py b/src/python/grpcio/grpc/framework/core/_transmission.py
index 03644f4d49..efef87dd4c 100644
--- a/src/python/grpcio/grpc/framework/core/_transmission.py
+++ b/src/python/grpcio/grpc/framework/core/_transmission.py
@@ -104,9 +104,13 @@ class TransmissionManager(_interfaces.TransmissionManager):
return None
else:
self._abortion_outcome = None
+ if self._completion is None:
+ code, message = None, None
+ else:
+ code, message = self._completion.code, self._completion.message
return links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None,
- None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, code, message,
termination, None)
action = False
@@ -277,7 +281,7 @@ class TransmissionManager(_interfaces.TransmissionManager):
self._remote_complete = True
self._local_allowance = 0
- def abort(self, outcome):
+ def abort(self, outcome, code, message):
"""See _interfaces.TransmissionManager.abort for specification."""
if self._transmitting:
self._aborted, self._abortion_outcome = True, outcome
@@ -287,8 +291,12 @@ class TransmissionManager(_interfaces.TransmissionManager):
termination = _constants.ABORTION_OUTCOME_TO_TICKET_TERMINATION[
outcome]
if termination is not None:
+ if self._completion is None:
+ code, message = None, None
+ else:
+ code, message = self._completion.code, self._completion.message
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None,
- None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, code, message,
termination, None)
self._transmit(ticket)
diff --git a/src/python/grpcio/grpc/framework/crust/__init__.py b/src/python/grpcio/grpc/framework/crust/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/crust/__init__.py
@@ -0,0 +1,30 @@
+# 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.
+
+
diff --git a/src/python/grpcio/grpc/framework/crust/_calls.py b/src/python/grpcio/grpc/framework/crust/_calls.py
new file mode 100644
index 0000000000..f9077bedfe
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/crust/_calls.py
@@ -0,0 +1,204 @@
+# 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, initial_metadata, payload, complete):
+ rendezvous = _control.Rendezvous(None, None)
+ operation_context, operator = end.operate(
+ group, method, utilities.full_subscription(rendezvous), timeout,
+ 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, initial_metadata, payload):
+ """Services in a blocking fashion a unary-unary servicer method."""
+ rendezvous, unused_operation_context, unused_outcome = _invoke(
+ end, group, method, timeout, initial_metadata, payload, True)
+ if with_call:
+ return next(rendezvous, rendezvous)
+ else:
+ return next(rendezvous)
+
+
+def future_unary_unary(end, group, method, timeout, 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, initial_metadata, payload, True)
+ return rendezvous
+
+
+def inline_unary_stream(end, group, method, timeout, initial_metadata, payload):
+ """Services a value-in stream-out servicer method."""
+ rendezvous, unused_operation_context, unused_outcome = _invoke(
+ end, group, method, timeout, initial_metadata, payload, True)
+ return rendezvous
+
+
+def blocking_stream_unary(
+ end, group, method, timeout, with_call, 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, 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, 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, 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, initial_metadata, payload_iterator, pool):
+ """Services a stream-in stream-out servicer method."""
+ rendezvous, operation_context, outcome = _invoke(
+ end, group, method, timeout, 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, initial_metadata, payload, receiver,
+ abortion_callback, pool):
+ rendezvous, operation_context, outcome = _invoke(
+ end, group, method, timeout, initial_metadata, payload, True)
+ return _event_return_unary(
+ receiver, abortion_callback, rendezvous, operation_context, outcome, pool)
+
+
+def event_unary_stream(
+ end, group, method, timeout, initial_metadata, payload,
+ receiver, abortion_callback, pool):
+ rendezvous, operation_context, outcome = _invoke(
+ end, group, method, timeout, initial_metadata, payload, True)
+ return _event_return_stream(
+ receiver, abortion_callback, rendezvous, operation_context, outcome, pool)
+
+
+def event_stream_unary(
+ end, group, method, timeout, initial_metadata, receiver, abortion_callback,
+ pool):
+ rendezvous, operation_context, outcome = _invoke(
+ end, group, method, timeout, initial_metadata, None, False)
+ return _event_return_unary(
+ receiver, abortion_callback, rendezvous, operation_context, outcome, pool)
+
+
+def event_stream_stream(
+ end, group, method, timeout, initial_metadata, receiver, abortion_callback,
+ pool):
+ rendezvous, operation_context, outcome = _invoke(
+ end, group, method, timeout, initial_metadata, None, False)
+ return _event_return_stream(
+ receiver, abortion_callback, rendezvous, operation_context, outcome, pool)
diff --git a/src/python/grpcio/grpc/framework/crust/_control.py b/src/python/grpcio/grpc/framework/crust/_control.py
new file mode 100644
index 0000000000..01de3c15bd
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/crust/_control.py
@@ -0,0 +1,545 @@
+# 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_TO_TERMINATION_CONSTRUCTOR = {
+ base.Outcome.COMPLETED: lambda *unused_args: _Termination(True, None, None),
+ base.Outcome.CANCELLED: lambda *args: _Termination(
+ True, face.Abortion(face.Abortion.Kind.CANCELLED, *args),
+ face.CancellationError(*args)),
+ base.Outcome.EXPIRED: lambda *args: _Termination(
+ True, face.Abortion(face.Abortion.Kind.EXPIRED, *args),
+ face.ExpirationError(*args)),
+ base.Outcome.LOCAL_SHUTDOWN: lambda *args: _Termination(
+ True, face.Abortion(face.Abortion.Kind.LOCAL_SHUTDOWN, *args),
+ face.LocalShutdownError(*args)),
+ base.Outcome.REMOTE_SHUTDOWN: lambda *args: _Termination(
+ True, face.Abortion(face.Abortion.Kind.REMOTE_SHUTDOWN, *args),
+ face.RemoteShutdownError(*args)),
+ base.Outcome.RECEPTION_FAILURE: lambda *args: _Termination(
+ True, face.Abortion(face.Abortion.Kind.NETWORK_FAILURE, *args),
+ face.NetworkError(*args)),
+ base.Outcome.TRANSMISSION_FAILURE: lambda *args: _Termination(
+ True, face.Abortion(face.Abortion.Kind.NETWORK_FAILURE, *args),
+ face.NetworkError(*args)),
+ base.Outcome.LOCAL_FAILURE: lambda *args: _Termination(
+ True, face.Abortion(face.Abortion.Kind.LOCAL_FAILURE, *args),
+ face.LocalError(*args)),
+ base.Outcome.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._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, code, details = None, None, None
+ else:
+ terminal_metadata = self._up_completion.value.terminal_metadata
+ code = self._up_completion.value.code
+ details = self._up_completion.value.message
+ self._termination = _OPERATION_OUTCOME_TO_TERMINATION_CONSTRUCTOR[
+ outcome](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):
+ 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 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_outcome(self, outcome):
+ with self._condition:
+ return self._set_outcome(outcome)
+
+
+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)
diff --git a/src/python/grpcio/grpc/framework/crust/_service.py b/src/python/grpcio/grpc/framework/crust/_service.py
new file mode 100644
index 0000000000..2455a58f59
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/crust/_service.py
@@ -0,0 +1,166 @@
+# 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 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)
+ outcome = operation_context.add_termination_callback(rendezvous.set_outcome)
+ if outcome is None:
+ pool.submit(_control.pool_wrap(in_pool, operation_context), rendezvous)
+ return utilities.full_subscription(rendezvous)
+ 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)
+ outcome = operation_context.add_termination_callback(rendezvous.set_outcome)
+ if outcome is None:
+ def in_pool():
+ request_consumer = multi_method(
+ 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 utilities.full_subscription(rendezvous)
+ else:
+ raise abandonment.Abandoned()
+ return adaptation
diff --git a/src/python/grpcio/grpc/framework/crust/implementations.py b/src/python/grpcio/grpc/framework/crust/implementations.py
new file mode 100644
index 0000000000..12f7e79641
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/crust/implementations.py
@@ -0,0 +1,352 @@
+# 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."""
+
+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.service(
+ group, method, output_operator, context)
+ except face.NoSuchMethodError:
+ raise base.NoSuchMethodError()
+ else:
+ raise base.NoSuchMethodError()
+
+
+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):
+ return _calls.blocking_unary_unary(
+ self._end, self._group, self._method, timeout, with_call,
+ metadata, request)
+
+ def future(self, request, timeout, metadata=None):
+ return _calls.future_unary_unary(
+ self._end, self._group, self._method, timeout, metadata,
+ request)
+
+ def event(
+ self, request, receiver, abortion_callback, timeout,
+ metadata=None):
+ return _calls.event_unary_unary(
+ self._end, self._group, self._method, timeout, 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):
+ return _calls.inline_unary_stream(
+ self._end, self._group, self._method, timeout, metadata,
+ request)
+
+ def event(
+ self, request, receiver, abortion_callback, timeout,
+ metadata=None):
+ return _calls.event_unary_stream(
+ self._end, self._group, self._method, timeout, 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):
+ return _calls.blocking_stream_unary(
+ self._end, self._group, self._method, timeout, with_call,
+ metadata, request_iterator, self._pool)
+
+ def future(self, request_iterator, timeout, metadata=None):
+ return _calls.future_stream_unary(
+ self._end, self._group, self._method, timeout, metadata,
+ request_iterator, self._pool)
+
+ def event(
+ self, receiver, abortion_callback, timeout, metadata=None):
+ return _calls.event_stream_unary(
+ self._end, self._group, self._method, timeout, 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):
+ return _calls.inline_stream_stream(
+ self._end, self._group, self._method, timeout, metadata,
+ request_iterator, self._pool)
+
+ def event(
+ self, receiver, abortion_callback, timeout, metadata=None):
+ return _calls.event_stream_stream(
+ self._end, self._group, self._method, timeout, 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):
+ return _calls.blocking_unary_unary(
+ self._end, group, method, timeout, with_call, metadata,
+ request)
+
+ def future_unary_unary(
+ self, group, method, request, timeout, metadata=None):
+ return _calls.future_unary_unary(
+ self._end, group, method, timeout, metadata, request)
+
+ def inline_unary_stream(
+ self, group, method, request, timeout, metadata=None):
+ return _calls.inline_unary_stream(
+ self._end, group, method, timeout, metadata, request)
+
+ def blocking_stream_unary(
+ self, group, method, request_iterator, timeout, metadata=None,
+ with_call=None):
+ return _calls.blocking_stream_unary(
+ self._end, group, method, timeout, with_call, metadata,
+ request_iterator, self._pool)
+
+ def future_stream_unary(
+ self, group, method, request_iterator, timeout, metadata=None):
+ return _calls.future_stream_unary(
+ self._end, group, method, timeout, metadata,
+ request_iterator, self._pool)
+
+ def inline_stream_stream(
+ self, group, method, request_iterator, timeout, metadata=None):
+ return _calls.inline_stream_stream(
+ self._end, group, method, timeout, metadata,
+ request_iterator, self._pool)
+
+ def event_unary_unary(
+ self, group, method, request, receiver, abortion_callback, timeout,
+ metadata=None):
+ return _calls.event_unary_unary(
+ self._end, group, method, timeout, metadata, request,
+ receiver, abortion_callback, self._pool)
+
+ def event_unary_stream(
+ self, group, method, request, receiver, abortion_callback, timeout,
+ metadata=None):
+ return _calls.event_unary_stream(
+ self._end, group, method, timeout, metadata, request,
+ receiver, abortion_callback, self._pool)
+
+ def event_stream_unary(
+ self, group, method, receiver, abortion_callback, timeout,
+ metadata=None):
+ return _calls.event_stream_unary(
+ self._end, group, method, timeout, metadata, receiver,
+ abortion_callback, self._pool)
+
+ def event_stream_stream(
+ self, group, method, receiver, abortion_callback, timeout,
+ metadata=None):
+ return _calls.event_stream_stream(
+ self._end, group, method, timeout, 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 method_implementations.iteritems():
+ 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)
+ 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)
diff --git a/src/python/grpcio/grpc/framework/interfaces/base/base.py b/src/python/grpcio/grpc/framework/interfaces/base/base.py
index 76e0a5bdae..bc52efb4c5 100644
--- a/src/python/grpcio/grpc/framework/interfaces/base/base.py
+++ b/src/python/grpcio/grpc/framework/interfaces/base/base.py
@@ -47,7 +47,26 @@ from grpc.framework.foundation import abandonment # pylint: disable=unused-impo
class NoSuchMethodError(Exception):
- """Indicates that an unrecognized operation has been called."""
+ """Indicates that an unrecognized operation has been called.
+
+ Attributes:
+ code: A code value to communicate to the other side of the operation along
+ with indication of operation termination. May be None.
+ details: A details value to communicate to the other side of the operation
+ along with indication of operation termination. May be None.
+ """
+
+ def __init__(self, code, details):
+ """Constructor.
+
+ Args:
+ code: A code value to communicate to the other side of the operation
+ along with indication of operation termination. May be None.
+ details: A details value to communicate to the other side of the
+ operation along with indication of operation termination. May be None.
+ """
+ self.code = code
+ self.details = details
@enum.unique