mirror of https://github.com/grpc/grpc.git
commit
d243f3dbe3
82 changed files with 2200 additions and 1511 deletions
@ -0,0 +1,32 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
load("@grpc_python_dependencies//:requirements.bzl", "requirement") |
||||
|
||||
py_library( |
||||
name = "wait_for_ready_example", |
||||
testonly = 1, |
||||
srcs = ["wait_for_ready_example.py"], |
||||
deps = [ |
||||
"//src/python/grpcio/grpc:grpcio", |
||||
"//examples:py_helloworld", |
||||
], |
||||
) |
||||
|
||||
py_test( |
||||
name = "test/_wait_for_ready_example_test", |
||||
srcs = ["test/_wait_for_ready_example_test.py"], |
||||
deps = [":wait_for_ready_example",], |
||||
size = "small", |
||||
) |
@ -0,0 +1,32 @@ |
||||
# gRPC Python Example for Wait-for-ready |
||||
|
||||
The default behavior of an RPC is to fail instantly if the server is not ready yet. This example demonstrates how to change that behavior. |
||||
|
||||
|
||||
### Definition of 'wait-for-ready' semantics |
||||
> If an RPC is issued but the channel is in TRANSIENT_FAILURE or SHUTDOWN states, the RPC is unable to be transmitted promptly. By default, gRPC implementations SHOULD fail such RPCs immediately. This is known as "fail fast," but the usage of the term is historical. RPCs SHOULD NOT fail as a result of the channel being in other states (CONNECTING, READY, or IDLE). |
||||
> |
||||
> gRPC implementations MAY provide a per-RPC option to not fail RPCs as a result of the channel being in TRANSIENT_FAILURE state. Instead, the implementation queues the RPCs until the channel is READY. This is known as "wait for ready." The RPCs SHOULD still fail before READY if there are unrelated reasons, such as the channel is SHUTDOWN or the RPC's deadline is reached. |
||||
> |
||||
> From https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md |
||||
|
||||
|
||||
### Use cases for 'wait-for-ready' |
||||
|
||||
When developers spin up gRPC clients and servers at the same time, it is very like to fail first couple RPC calls due to unavailability of the server. If developers failed to prepare for this situation, the result can be catastrophic. But with 'wait-for-ready' semantics, developers can initialize the client and server in any order, especially useful in testing. |
||||
|
||||
Also, developers may ensure the server is up before starting client. But in some cases like transient network failure may result in a temporary unavailability of the server. With 'wait-for-ready' semantics, those RPC calls will automatically wait until the server is ready to accept incoming requests. |
||||
|
||||
|
||||
### DEMO Snippets |
||||
|
||||
```Python |
||||
# Per RPC level |
||||
stub = ...Stub(...) |
||||
|
||||
stub.important_transaction_1(..., wait_for_ready=True) |
||||
stub.unimportant_transaction_2(...) |
||||
stub.important_transaction_3(..., wait_for_ready=True) |
||||
stub.unimportant_transaction_4(...) |
||||
# The unimportant transactions can be status report, or health check, etc. |
||||
``` |
@ -0,0 +1,31 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
"""Tests of the wait-for-ready example.""" |
||||
|
||||
import unittest |
||||
import logging |
||||
|
||||
from examples.python.wait_for_ready import wait_for_ready_example |
||||
|
||||
|
||||
class WaitForReadyExampleTest(unittest.TestCase): |
||||
|
||||
def test_wait_for_ready_example(self): |
||||
wait_for_ready_example.main() |
||||
# No unhandled exception raised, no deadlock, test passed! |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig() |
||||
unittest.main(verbosity=2) |
@ -0,0 +1,114 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
"""The Python example of utilizing wait-for-ready flag.""" |
||||
|
||||
from __future__ import print_function |
||||
import logging |
||||
from concurrent import futures |
||||
from contextlib import contextmanager |
||||
import socket |
||||
import threading |
||||
|
||||
import grpc |
||||
|
||||
from examples.protos import helloworld_pb2 |
||||
from examples.protos import helloworld_pb2_grpc |
||||
|
||||
_LOGGER = logging.getLogger(__name__) |
||||
_LOGGER.setLevel(logging.INFO) |
||||
|
||||
_ONE_DAY_IN_SECONDS = 60 * 60 * 24 |
||||
|
||||
|
||||
@contextmanager |
||||
def get_free_loopback_tcp_port(): |
||||
tcp_socket = socket.socket(socket.AF_INET6) |
||||
tcp_socket.bind(('', 0)) |
||||
address_tuple = tcp_socket.getsockname() |
||||
yield "[::1]:%s" % (address_tuple[1]) |
||||
tcp_socket.close() |
||||
|
||||
|
||||
class Greeter(helloworld_pb2_grpc.GreeterServicer): |
||||
|
||||
def SayHello(self, request, unused_context): |
||||
return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name) |
||||
|
||||
|
||||
def create_server(server_address): |
||||
server = grpc.server(futures.ThreadPoolExecutor()) |
||||
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server) |
||||
bound_port = server.add_insecure_port(server_address) |
||||
assert bound_port == int(server_address.split(':')[-1]) |
||||
return server |
||||
|
||||
|
||||
def process(stub, wait_for_ready=None): |
||||
try: |
||||
response = stub.SayHello( |
||||
helloworld_pb2.HelloRequest(name='you'), |
||||
wait_for_ready=wait_for_ready) |
||||
message = response.message |
||||
except grpc.RpcError as rpc_error: |
||||
assert rpc_error.code() == grpc.StatusCode.UNAVAILABLE |
||||
assert not wait_for_ready |
||||
message = rpc_error |
||||
else: |
||||
assert wait_for_ready |
||||
_LOGGER.info("Wait-for-ready %s, client received: %s", "enabled" |
||||
if wait_for_ready else "disabled", message) |
||||
|
||||
|
||||
def main(): |
||||
# Pick a random free port |
||||
with get_free_loopback_tcp_port() as server_address: |
||||
|
||||
# Register connectivity event to notify main thread |
||||
transient_failure_event = threading.Event() |
||||
|
||||
def wait_for_transient_failure(channel_connectivity): |
||||
if channel_connectivity == grpc.ChannelConnectivity.TRANSIENT_FAILURE: |
||||
transient_failure_event.set() |
||||
|
||||
# Create gRPC channel |
||||
channel = grpc.insecure_channel(server_address) |
||||
channel.subscribe(wait_for_transient_failure) |
||||
stub = helloworld_pb2_grpc.GreeterStub(channel) |
||||
|
||||
# Fire an RPC without wait_for_ready |
||||
thread_disabled_wait_for_ready = threading.Thread( |
||||
target=process, args=(stub, False)) |
||||
thread_disabled_wait_for_ready.start() |
||||
# Fire an RPC with wait_for_ready |
||||
thread_enabled_wait_for_ready = threading.Thread( |
||||
target=process, args=(stub, True)) |
||||
thread_enabled_wait_for_ready.start() |
||||
|
||||
# Wait for the channel entering TRANSIENT FAILURE state. |
||||
transient_failure_event.wait() |
||||
server = create_server(server_address) |
||||
server.start() |
||||
|
||||
# Expected to fail with StatusCode.UNAVAILABLE. |
||||
thread_disabled_wait_for_ready.join() |
||||
# Expected to success. |
||||
thread_enabled_wait_for_ready.join() |
||||
|
||||
server.stop(None) |
||||
channel.close() |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig(level=logging.INFO) |
||||
main() |
@ -0,0 +1,151 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2019 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef GRPCPP_IMPL_CODEGEN_SYNC_H |
||||
#define GRPCPP_IMPL_CODEGEN_SYNC_H |
||||
|
||||
#include <grpc/impl/codegen/port_platform.h> |
||||
|
||||
#ifdef GPR_HAS_PTHREAD_H |
||||
#include <pthread.h> |
||||
#endif |
||||
|
||||
#include <mutex> |
||||
|
||||
#include <grpc/impl/codegen/log.h> |
||||
#include <grpc/impl/codegen/sync.h> |
||||
|
||||
#include <grpcpp/impl/codegen/core_codegen_interface.h> |
||||
|
||||
// The core library is not accessible in C++ codegen headers, and vice versa.
|
||||
// Thus, we need to have duplicate headers with similar functionality.
|
||||
// Make sure any change to this file is also reflected in
|
||||
// src/core/lib/gprpp/sync.h too.
|
||||
//
|
||||
// Whenever possible, prefer "src/core/lib/gprpp/sync.h" over this file,
|
||||
// since in core we do not rely on g_core_codegen_interface and hence do not
|
||||
// pay the costs of virtual function calls.
|
||||
|
||||
namespace grpc { |
||||
namespace internal { |
||||
|
||||
class Mutex { |
||||
public: |
||||
Mutex() { g_core_codegen_interface->gpr_mu_init(&mu_); } |
||||
~Mutex() { g_core_codegen_interface->gpr_mu_destroy(&mu_); } |
||||
|
||||
Mutex(const Mutex&) = delete; |
||||
Mutex& operator=(const Mutex&) = delete; |
||||
|
||||
gpr_mu* get() { return &mu_; } |
||||
const gpr_mu* get() const { return &mu_; } |
||||
|
||||
private: |
||||
union { |
||||
gpr_mu mu_; |
||||
std::mutex do_not_use_sth_; |
||||
#ifdef GPR_HAS_PTHREAD_H |
||||
pthread_mutex_t do_not_use_pth_; |
||||
#endif |
||||
}; |
||||
}; |
||||
|
||||
// MutexLock is a std::
|
||||
class MutexLock { |
||||
public: |
||||
explicit MutexLock(Mutex* mu) : mu_(mu->get()) { |
||||
g_core_codegen_interface->gpr_mu_lock(mu_); |
||||
} |
||||
explicit MutexLock(gpr_mu* mu) : mu_(mu) { |
||||
g_core_codegen_interface->gpr_mu_lock(mu_); |
||||
} |
||||
~MutexLock() { g_core_codegen_interface->gpr_mu_unlock(mu_); } |
||||
|
||||
MutexLock(const MutexLock&) = delete; |
||||
MutexLock& operator=(const MutexLock&) = delete; |
||||
|
||||
private: |
||||
gpr_mu* const mu_; |
||||
}; |
||||
|
||||
class ReleasableMutexLock { |
||||
public: |
||||
explicit ReleasableMutexLock(Mutex* mu) : mu_(mu->get()) { |
||||
g_core_codegen_interface->gpr_mu_lock(mu_); |
||||
} |
||||
explicit ReleasableMutexLock(gpr_mu* mu) : mu_(mu) { |
||||
g_core_codegen_interface->gpr_mu_lock(mu_); |
||||
} |
||||
~ReleasableMutexLock() { |
||||
if (!released_) g_core_codegen_interface->gpr_mu_unlock(mu_); |
||||
} |
||||
|
||||
ReleasableMutexLock(const ReleasableMutexLock&) = delete; |
||||
ReleasableMutexLock& operator=(const ReleasableMutexLock&) = delete; |
||||
|
||||
void Lock() { |
||||
GPR_DEBUG_ASSERT(released_); |
||||
g_core_codegen_interface->gpr_mu_lock(mu_); |
||||
released_ = false; |
||||
} |
||||
|
||||
void Unlock() { |
||||
GPR_DEBUG_ASSERT(!released_); |
||||
released_ = true; |
||||
g_core_codegen_interface->gpr_mu_unlock(mu_); |
||||
} |
||||
|
||||
private: |
||||
gpr_mu* const mu_; |
||||
bool released_ = false; |
||||
}; |
||||
|
||||
class CondVar { |
||||
public: |
||||
CondVar() { g_core_codegen_interface->gpr_cv_init(&cv_); } |
||||
~CondVar() { g_core_codegen_interface->gpr_cv_destroy(&cv_); } |
||||
|
||||
CondVar(const CondVar&) = delete; |
||||
CondVar& operator=(const CondVar&) = delete; |
||||
|
||||
void Signal() { g_core_codegen_interface->gpr_cv_signal(&cv_); } |
||||
void Broadcast() { g_core_codegen_interface->gpr_cv_broadcast(&cv_); } |
||||
|
||||
int Wait(Mutex* mu) { |
||||
return Wait(mu, |
||||
g_core_codegen_interface->gpr_inf_future(GPR_CLOCK_REALTIME)); |
||||
} |
||||
int Wait(Mutex* mu, const gpr_timespec& deadline) { |
||||
return g_core_codegen_interface->gpr_cv_wait(&cv_, mu->get(), deadline); |
||||
} |
||||
|
||||
template <typename Predicate> |
||||
void WaitUntil(Mutex* mu, Predicate pred) { |
||||
while (!pred()) { |
||||
Wait(mu, g_core_codegen_interface->gpr_inf_future(GPR_CLOCK_REALTIME)); |
||||
} |
||||
} |
||||
|
||||
private: |
||||
gpr_cv cv_; |
||||
}; |
||||
|
||||
} // namespace internal
|
||||
} // namespace grpc
|
||||
|
||||
#endif // GRPCPP_IMPL_CODEGEN_SYNC_H
|
File diff suppressed because it is too large
Load Diff
@ -1,42 +0,0 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef GRPC_CORE_LIB_GPRPP_MUTEX_LOCK_H |
||||
#define GRPC_CORE_LIB_GPRPP_MUTEX_LOCK_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <grpc/support/sync.h> |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class MutexLock { |
||||
public: |
||||
explicit MutexLock(gpr_mu* mu) : mu_(mu) { gpr_mu_lock(mu); } |
||||
~MutexLock() { gpr_mu_unlock(mu_); } |
||||
|
||||
MutexLock(const MutexLock&) = delete; |
||||
MutexLock& operator=(const MutexLock&) = delete; |
||||
|
||||
private: |
||||
gpr_mu* const mu_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif /* GRPC_CORE_LIB_GPRPP_MUTEX_LOCK_H */ |
@ -0,0 +1,126 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2019 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef GRPC_CORE_LIB_GPRPP_SYNC_H |
||||
#define GRPC_CORE_LIB_GPRPP_SYNC_H |
||||
|
||||
#include <grpc/impl/codegen/port_platform.h> |
||||
|
||||
#include <grpc/impl/codegen/log.h> |
||||
#include <grpc/impl/codegen/sync.h> |
||||
#include <grpc/support/sync.h> |
||||
#include <grpc/support/time.h> |
||||
|
||||
// The core library is not accessible in C++ codegen headers, and vice versa.
|
||||
// Thus, we need to have duplicate headers with similar functionality.
|
||||
// Make sure any change to this file is also reflected in
|
||||
// include/grpcpp/impl/codegen/sync.h.
|
||||
//
|
||||
// Whenever possible, prefer using this file over <grpcpp/impl/codegen/sync.h>
|
||||
// since this file doesn't rely on g_core_codegen_interface and hence does not
|
||||
// pay the costs of virtual function calls.
|
||||
|
||||
namespace grpc_core { |
||||
|
||||
class Mutex { |
||||
public: |
||||
Mutex() { gpr_mu_init(&mu_); } |
||||
~Mutex() { gpr_mu_destroy(&mu_); } |
||||
|
||||
Mutex(const Mutex&) = delete; |
||||
Mutex& operator=(const Mutex&) = delete; |
||||
|
||||
gpr_mu* get() { return &mu_; } |
||||
const gpr_mu* get() const { return &mu_; } |
||||
|
||||
private: |
||||
gpr_mu mu_; |
||||
}; |
||||
|
||||
// MutexLock is a std::
|
||||
class MutexLock { |
||||
public: |
||||
explicit MutexLock(Mutex* mu) : mu_(mu->get()) { gpr_mu_lock(mu_); } |
||||
explicit MutexLock(gpr_mu* mu) : mu_(mu) { gpr_mu_lock(mu_); } |
||||
~MutexLock() { gpr_mu_unlock(mu_); } |
||||
|
||||
MutexLock(const MutexLock&) = delete; |
||||
MutexLock& operator=(const MutexLock&) = delete; |
||||
|
||||
private: |
||||
gpr_mu* const mu_; |
||||
}; |
||||
|
||||
class ReleasableMutexLock { |
||||
public: |
||||
explicit ReleasableMutexLock(Mutex* mu) : mu_(mu->get()) { gpr_mu_lock(mu_); } |
||||
explicit ReleasableMutexLock(gpr_mu* mu) : mu_(mu) { gpr_mu_lock(mu_); } |
||||
~ReleasableMutexLock() { |
||||
if (!released_) gpr_mu_unlock(mu_); |
||||
} |
||||
|
||||
ReleasableMutexLock(const ReleasableMutexLock&) = delete; |
||||
ReleasableMutexLock& operator=(const ReleasableMutexLock&) = delete; |
||||
|
||||
void Lock() { |
||||
GPR_DEBUG_ASSERT(released_); |
||||
gpr_mu_lock(mu_); |
||||
released_ = false; |
||||
} |
||||
|
||||
void Unlock() { |
||||
GPR_DEBUG_ASSERT(!released_); |
||||
released_ = true; |
||||
gpr_mu_unlock(mu_); |
||||
} |
||||
|
||||
private: |
||||
gpr_mu* const mu_; |
||||
bool released_ = false; |
||||
}; |
||||
|
||||
class CondVar { |
||||
public: |
||||
CondVar() { gpr_cv_init(&cv_); } |
||||
~CondVar() { gpr_cv_destroy(&cv_); } |
||||
|
||||
CondVar(const CondVar&) = delete; |
||||
CondVar& operator=(const CondVar&) = delete; |
||||
|
||||
void Signal() { gpr_cv_signal(&cv_); } |
||||
void Broadcast() { gpr_cv_broadcast(&cv_); } |
||||
|
||||
int Wait(Mutex* mu) { return Wait(mu, gpr_inf_future(GPR_CLOCK_REALTIME)); } |
||||
int Wait(Mutex* mu, const gpr_timespec& deadline) { |
||||
return gpr_cv_wait(&cv_, mu->get(), deadline); |
||||
} |
||||
|
||||
template <typename Predicate> |
||||
void WaitUntil(Mutex* mu, Predicate pred) { |
||||
while (!pred()) { |
||||
Wait(mu, gpr_inf_future(GPR_CLOCK_REALTIME)); |
||||
} |
||||
} |
||||
|
||||
private: |
||||
gpr_cv cv_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif /* GRPC_CORE_LIB_GPRPP_SYNC_H */ |
@ -0,0 +1,63 @@ |
||||
# Copyright 2019 The gRPC Authors |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
"""Tests for an actual dns resolution.""" |
||||
|
||||
import unittest |
||||
import logging |
||||
import six |
||||
|
||||
import grpc |
||||
from tests.unit import test_common |
||||
from tests.unit.framework.common import test_constants |
||||
|
||||
_METHOD = '/ANY/METHOD' |
||||
_REQUEST = b'\x00\x00\x00' |
||||
_RESPONSE = _REQUEST |
||||
|
||||
|
||||
class GenericHandler(grpc.GenericRpcHandler): |
||||
|
||||
def service(self, unused_handler_details): |
||||
return grpc.unary_unary_rpc_method_handler( |
||||
lambda request, unused_context: request, |
||||
) |
||||
|
||||
|
||||
class DNSResolverTest(unittest.TestCase): |
||||
|
||||
def setUp(self): |
||||
self._server = test_common.test_server() |
||||
self._server.add_generic_rpc_handlers((GenericHandler(),)) |
||||
self._port = self._server.add_insecure_port('[::]:0') |
||||
self._server.start() |
||||
|
||||
def tearDown(self): |
||||
self._server.stop(None) |
||||
|
||||
def test_connect_loopback(self): |
||||
# NOTE(https://github.com/grpc/grpc/issues/18422) |
||||
# In short, Gevent + C-Ares = Segfault. The C-Ares driver is not |
||||
# supported by custom io manager like "gevent" or "libuv". |
||||
with grpc.insecure_channel( |
||||
'loopback4.unittest.grpc.io:%d' % self._port) as channel: |
||||
self.assertEqual( |
||||
channel.unary_unary(_METHOD)( |
||||
_REQUEST, |
||||
timeout=test_constants.SHORT_TIMEOUT, |
||||
), _RESPONSE) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig() |
||||
unittest.main(verbosity=2) |
Loading…
Reference in new issue