mirror of https://github.com/grpc/grpc.git
Add retry example for gRPC Python (#26829)
* Add retry example for gRPC Python * Make sanity test happy && simplfy proto definition * Fix a grammer issuepull/26837/head
parent
ca482bdbc7
commit
6c6463e1cd
5 changed files with 253 additions and 0 deletions
@ -0,0 +1,48 @@ |
||||
# Retry Example in gRPC Python |
||||
|
||||
## Prerequisite |
||||
|
||||
* grpcio >= 1.39.0 |
||||
* grpcio-tools >= 1.39.0 |
||||
|
||||
## Running the example |
||||
|
||||
In terminal 1, start the flaky server: |
||||
|
||||
```sh |
||||
python3 flaky_server.py |
||||
``` |
||||
|
||||
In terminal 2, start the retry clients: |
||||
|
||||
```sh |
||||
python3 retry_client.py |
||||
# Or |
||||
python3 async_retry_client.py |
||||
``` |
||||
|
||||
## Expect results |
||||
|
||||
The client RPC will succeed, even with server injecting multiple errors. Here is an example server log: |
||||
|
||||
```sh |
||||
$ python3 flaky_server.py |
||||
INFO:root:Starting flaky server on [::]:50051 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:54471 |
||||
INFO:root:Successfully responding to RPC from ipv6:[::1]:54473 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:54491 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:54581 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:54581 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:54581 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:54581 |
||||
INFO:root:Successfully responding to RPC from ipv6:[::1]:54581 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:55474 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:55474 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:55474 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:55474 |
||||
INFO:root:Successfully responding to RPC from ipv6:[::1]:55474 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:55533 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:55533 |
||||
INFO:root:Injecting error to RPC from ipv6:[::1]:55533 |
||||
INFO:root:Successfully responding to RPC from ipv6:[::1]:55533 |
||||
``` |
@ -0,0 +1,58 @@ |
||||
# Copyright 2021 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 AsyncIO implementation of the gRPC client-side retry example.""" |
||||
|
||||
import asyncio |
||||
import json |
||||
import logging |
||||
|
||||
import grpc |
||||
|
||||
helloworld_pb2, helloworld_pb2_grpc = grpc.protos_and_services( |
||||
"helloworld.proto") |
||||
|
||||
|
||||
async def run() -> None: |
||||
# The ServiceConfig proto definition can be found: |
||||
# https://github.com/grpc/grpc-proto/blob/ec886024c2f7b7f597ba89d5b7d60c3f94627b17/grpc/service_config/service_config.proto#L377 |
||||
service_config_json = json.dumps({ |
||||
"methodConfig": [{ |
||||
# To apply retry to all methods, put [{}] in the "name" field |
||||
"name": [{ |
||||
"service": "helloworld.Greeter", |
||||
"method": "SayHello" |
||||
}], |
||||
"retryPolicy": { |
||||
"maxAttempts": 5, |
||||
"initialBackoff": "0.1s", |
||||
"maxBackoff": "1s", |
||||
"backoffMultiplier": 2, |
||||
"retryableStatusCodes": ["UNAVAILABLE"], |
||||
}, |
||||
}] |
||||
}) |
||||
options = [] |
||||
# NOTE: the retry feature will be enabled by default >=v1.40.0 |
||||
options.append(("grpc.enable_retries", 1)) |
||||
options.append(("grpc.service_config", service_config_json)) |
||||
async with grpc.aio.insecure_channel('localhost:50051', |
||||
options=options) as channel: |
||||
stub = helloworld_pb2_grpc.GreeterStub(channel) |
||||
response = await stub.SayHello(helloworld_pb2.HelloRequest(name='you')) |
||||
print("Greeter client received: " + response.message) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig() |
||||
asyncio.run(run()) |
@ -0,0 +1,58 @@ |
||||
# Copyright 2021 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. |
||||
"""A flaky backend for the gRPC Python retry example.""" |
||||
|
||||
import asyncio |
||||
import collections |
||||
import logging |
||||
import random |
||||
|
||||
import grpc |
||||
|
||||
helloworld_pb2, helloworld_pb2_grpc = grpc.protos_and_services( |
||||
"helloworld.proto") |
||||
|
||||
|
||||
class ErrorInjectingGreeter(helloworld_pb2_grpc.GreeterServicer): |
||||
|
||||
def __init__(self): |
||||
self._counter = collections.defaultdict(int) |
||||
|
||||
async def SayHello( |
||||
self, request: helloworld_pb2.HelloRequest, |
||||
context: grpc.aio.ServicerContext) -> helloworld_pb2.HelloReply: |
||||
self._counter[context.peer()] += 1 |
||||
if self._counter[context.peer()] < 5: |
||||
if random.random() < 0.75: |
||||
logging.info('Injecting error to RPC from %s', context.peer()) |
||||
await context.abort(grpc.StatusCode.UNAVAILABLE, |
||||
'injected error') |
||||
logging.info('Successfully responding to RPC from %s', context.peer()) |
||||
return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name) |
||||
|
||||
|
||||
async def serve() -> None: |
||||
server = grpc.aio.server() |
||||
helloworld_pb2_grpc.add_GreeterServicer_to_server(ErrorInjectingGreeter(), |
||||
server) |
||||
listen_addr = '[::]:50051' |
||||
server.add_insecure_port(listen_addr) |
||||
logging.info("Starting flaky server on %s", listen_addr) |
||||
await server.start() |
||||
await server.wait_for_termination() |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig(level=logging.INFO) |
||||
asyncio.run(serve()) |
@ -0,0 +1,33 @@ |
||||
// Copyright 2021 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. |
||||
|
||||
syntax = "proto3"; |
||||
|
||||
package helloworld; |
||||
|
||||
// The greeting service definition. |
||||
service Greeter { |
||||
// Sends a greeting |
||||
rpc SayHello (HelloRequest) returns (HelloReply) {} |
||||
} |
||||
|
||||
// The request message containing the user's name. |
||||
message HelloRequest { |
||||
string name = 1; |
||||
} |
||||
|
||||
// The response message containing the greetings |
||||
message HelloReply { |
||||
string message = 1; |
||||
} |
@ -0,0 +1,56 @@ |
||||
# Copyright 2021 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 implementation of the gRPC client-side retry example.""" |
||||
|
||||
import json |
||||
import logging |
||||
|
||||
import grpc |
||||
|
||||
helloworld_pb2, helloworld_pb2_grpc = grpc.protos_and_services( |
||||
"helloworld.proto") |
||||
|
||||
|
||||
def run(): |
||||
# The ServiceConfig proto definition can be found: |
||||
# https://github.com/grpc/grpc-proto/blob/ec886024c2f7b7f597ba89d5b7d60c3f94627b17/grpc/service_config/service_config.proto#L377 |
||||
service_config_json = json.dumps({ |
||||
"methodConfig": [{ |
||||
# To apply retry to all methods, put [{}] in the "name" field |
||||
"name": [{ |
||||
"service": "helloworld.Greeter", |
||||
"method": "SayHello" |
||||
}], |
||||
"retryPolicy": { |
||||
"maxAttempts": 5, |
||||
"initialBackoff": "0.1s", |
||||
"maxBackoff": "1s", |
||||
"backoffMultiplier": 2, |
||||
"retryableStatusCodes": ["UNAVAILABLE"], |
||||
}, |
||||
}] |
||||
}) |
||||
options = [] |
||||
# NOTE: the retry feature will be enabled by default >=v1.40.0 |
||||
options.append(("grpc.enable_retries", 1)) |
||||
options.append(("grpc.service_config", service_config_json)) |
||||
with grpc.insecure_channel('localhost:50051', options=options) as channel: |
||||
stub = helloworld_pb2_grpc.GreeterStub(channel) |
||||
response = stub.SayHello(helloworld_pb2.HelloRequest(name='you')) |
||||
print("Greeter client received: " + response.message) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
logging.basicConfig() |
||||
run() |
Loading…
Reference in new issue