xref: /aosp_15_r20/external/grpc-grpc/examples/python/cancellation/README.md (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1*cc02d7e2SAndroid Build Coastguard Worker### Cancellation
2*cc02d7e2SAndroid Build Coastguard Worker
3*cc02d7e2SAndroid Build Coastguard WorkerIn the example, we implement a silly algorithm. We search for bytestrings whose
4*cc02d7e2SAndroid Build Coastguard Workerhashes are similar to a given search string. For example, say we're looking for
5*cc02d7e2SAndroid Build Coastguard Workerthe string "doctor". Our algorithm may return `JrqhZVkTDoctYrUlXDbL6pfYQHU=` or
6*cc02d7e2SAndroid Build Coastguard Worker`RC9/7mlM3ldy4TdoctOc6WzYbO4=`. This is a brute force algorithm, so the server
7*cc02d7e2SAndroid Build Coastguard Workerperforming the search must be conscious of the resources it allows to each client
8*cc02d7e2SAndroid Build Coastguard Workerand each client must be conscientious of the resources it demands of the server.
9*cc02d7e2SAndroid Build Coastguard Worker
10*cc02d7e2SAndroid Build Coastguard WorkerIn particular, we ensure that client processes cancel the stream explicitly
11*cc02d7e2SAndroid Build Coastguard Workerbefore terminating and we ensure that server processes cancel RPCs that have gone on longer
12*cc02d7e2SAndroid Build Coastguard Workerthan a certain number of iterations.
13*cc02d7e2SAndroid Build Coastguard Worker
14*cc02d7e2SAndroid Build Coastguard Worker#### Cancellation on the Client Side
15*cc02d7e2SAndroid Build Coastguard Worker
16*cc02d7e2SAndroid Build Coastguard WorkerA client may cancel an RPC for several reasons. Perhaps the data it requested
17*cc02d7e2SAndroid Build Coastguard Workerhas been made irrelevant. Perhaps you, as the client, want to be a good citizen
18*cc02d7e2SAndroid Build Coastguard Workerof the server and are conserving compute resources.
19*cc02d7e2SAndroid Build Coastguard Worker
20*cc02d7e2SAndroid Build Coastguard Worker##### Cancelling a Server-Side Unary RPC from the Client
21*cc02d7e2SAndroid Build Coastguard Worker
22*cc02d7e2SAndroid Build Coastguard WorkerThe default RPC methods on a stub will simply return the result of an RPC.
23*cc02d7e2SAndroid Build Coastguard Worker
24*cc02d7e2SAndroid Build Coastguard Worker```python
25*cc02d7e2SAndroid Build Coastguard Worker>>> stub = hash_name_pb2_grpc.HashFinderStub(channel)
26*cc02d7e2SAndroid Build Coastguard Worker>>> stub.Find(hash_name_pb2.HashNameRequest(desired_name=name))
27*cc02d7e2SAndroid Build Coastguard Worker<hash_name_pb2.HashNameResponse object at 0x7fe2eb8ce2d0>
28*cc02d7e2SAndroid Build Coastguard Worker```
29*cc02d7e2SAndroid Build Coastguard Worker
30*cc02d7e2SAndroid Build Coastguard WorkerBut you may use the `future()` method to receive an instance of `grpc.Future`.
31*cc02d7e2SAndroid Build Coastguard WorkerThis interface allows you to wait on a response with a timeout, add a callback
32*cc02d7e2SAndroid Build Coastguard Workerto be executed when the RPC completes, or to cancel the RPC before it has
33*cc02d7e2SAndroid Build Coastguard Workercompleted.
34*cc02d7e2SAndroid Build Coastguard Worker
35*cc02d7e2SAndroid Build Coastguard WorkerIn the example, we use this interface to cancel our in-progress RPC when the
36*cc02d7e2SAndroid Build Coastguard Workeruser interrupts the process with ctrl-c.
37*cc02d7e2SAndroid Build Coastguard Worker
38*cc02d7e2SAndroid Build Coastguard Worker```python
39*cc02d7e2SAndroid Build Coastguard Workerstub = hash_name_pb2_grpc.HashFinderStub(channel)
40*cc02d7e2SAndroid Build Coastguard Workerfuture = stub.Find.future(hash_name_pb2.HashNameRequest(desired_name=name))
41*cc02d7e2SAndroid Build Coastguard Workerdef cancel_request(unused_signum, unused_frame):
42*cc02d7e2SAndroid Build Coastguard Worker    future.cancel()
43*cc02d7e2SAndroid Build Coastguard Worker    sys.exit(0)
44*cc02d7e2SAndroid Build Coastguard Workersignal.signal(signal.SIGINT, cancel_request)
45*cc02d7e2SAndroid Build Coastguard Worker
46*cc02d7e2SAndroid Build Coastguard Workerresult = future.result()
47*cc02d7e2SAndroid Build Coastguard Workerprint(result)
48*cc02d7e2SAndroid Build Coastguard Worker```
49*cc02d7e2SAndroid Build Coastguard Worker
50*cc02d7e2SAndroid Build Coastguard WorkerWe also call `sys.exit(0)` to terminate the process. If we do not do this, then
51*cc02d7e2SAndroid Build Coastguard Worker`future.result()` with throw an `RpcError`. Alternatively, you may catch this
52*cc02d7e2SAndroid Build Coastguard Workerexception.
53*cc02d7e2SAndroid Build Coastguard Worker
54*cc02d7e2SAndroid Build Coastguard Worker
55*cc02d7e2SAndroid Build Coastguard Worker##### Cancelling a Server-Side Streaming RPC from the Client
56*cc02d7e2SAndroid Build Coastguard Worker
57*cc02d7e2SAndroid Build Coastguard WorkerCancelling a Server-side streaming RPC is even simpler from the perspective of
58*cc02d7e2SAndroid Build Coastguard Workerthe gRPC API. The default stub method is already an instance of `grpc.Future`,
59*cc02d7e2SAndroid Build Coastguard Workerso the methods outlined above still apply. It is also a generator, so we may
60*cc02d7e2SAndroid Build Coastguard Workeriterate over it to yield the results of our RPC.
61*cc02d7e2SAndroid Build Coastguard Worker
62*cc02d7e2SAndroid Build Coastguard Worker```python
63*cc02d7e2SAndroid Build Coastguard Workerstub = hash_name_pb2_grpc.HashFinderStub(channel)
64*cc02d7e2SAndroid Build Coastguard Workerresult_generator = stub.FindRange(hash_name_pb2.HashNameRequest(desired_name=name))
65*cc02d7e2SAndroid Build Coastguard Workerdef cancel_request(unused_signum, unused_frame):
66*cc02d7e2SAndroid Build Coastguard Worker    result_generator.cancel()
67*cc02d7e2SAndroid Build Coastguard Worker    sys.exit(0)
68*cc02d7e2SAndroid Build Coastguard Workersignal.signal(signal.SIGINT, cancel_request)
69*cc02d7e2SAndroid Build Coastguard Workerfor result in result_generator:
70*cc02d7e2SAndroid Build Coastguard Worker    print(result)
71*cc02d7e2SAndroid Build Coastguard Worker```
72*cc02d7e2SAndroid Build Coastguard Worker
73*cc02d7e2SAndroid Build Coastguard WorkerWe also call `sys.exit(0)` here to terminate the process. Alternatively, you may
74*cc02d7e2SAndroid Build Coastguard Workercatch the `RpcError` raised by the for loop upon cancellation.
75*cc02d7e2SAndroid Build Coastguard Worker
76*cc02d7e2SAndroid Build Coastguard Worker
77*cc02d7e2SAndroid Build Coastguard Worker#### Cancellation on the Server Side
78*cc02d7e2SAndroid Build Coastguard Worker
79*cc02d7e2SAndroid Build Coastguard WorkerA server is responsible for cancellation in two ways. It must respond in some way
80*cc02d7e2SAndroid Build Coastguard Workerwhen a client initiates a cancellation, otherwise long-running computations
81*cc02d7e2SAndroid Build Coastguard Workercould continue indefinitely.
82*cc02d7e2SAndroid Build Coastguard Worker
83*cc02d7e2SAndroid Build Coastguard WorkerIt may also decide to cancel the RPC for its own reasons. In our example, the
84*cc02d7e2SAndroid Build Coastguard Workerserver can be configured to cancel an RPC after a certain number of hashes has
85*cc02d7e2SAndroid Build Coastguard Workerbeen computed in order to conserve compute resources.
86*cc02d7e2SAndroid Build Coastguard Worker
87*cc02d7e2SAndroid Build Coastguard Worker##### Responding to Cancellations from a Servicer Thread
88*cc02d7e2SAndroid Build Coastguard Worker
89*cc02d7e2SAndroid Build Coastguard WorkerIt's important to remember that a gRPC Python server is backed by a thread pool
90*cc02d7e2SAndroid Build Coastguard Workerwith a fixed size. When an RPC is cancelled, the library does *not* terminate
91*cc02d7e2SAndroid Build Coastguard Workeryour servicer thread. It is your responsibility as the application author to
92*cc02d7e2SAndroid Build Coastguard Workerensure that your servicer thread terminates soon after the RPC has been
93*cc02d7e2SAndroid Build Coastguard Workercancelled.
94*cc02d7e2SAndroid Build Coastguard Worker
95*cc02d7e2SAndroid Build Coastguard WorkerIn this example, we use the `ServicerContext.add_callback` method to set a
96*cc02d7e2SAndroid Build Coastguard Worker`threading.Event` object when the RPC is terminated. We pass this `Event` object
97*cc02d7e2SAndroid Build Coastguard Workerdown through our hashing algorithm and ensure to check that the RPC is still
98*cc02d7e2SAndroid Build Coastguard Workerongoing before each iteration.
99*cc02d7e2SAndroid Build Coastguard Worker
100*cc02d7e2SAndroid Build Coastguard Worker```python
101*cc02d7e2SAndroid Build Coastguard Workerstop_event = threading.Event()
102*cc02d7e2SAndroid Build Coastguard Workerdef on_rpc_done():
103*cc02d7e2SAndroid Build Coastguard Worker    # Regain servicer thread.
104*cc02d7e2SAndroid Build Coastguard Worker    stop_event.set()
105*cc02d7e2SAndroid Build Coastguard Workercontext.add_callback(on_rpc_done)
106*cc02d7e2SAndroid Build Coastguard Workersecret = _find_secret(stop_event)
107*cc02d7e2SAndroid Build Coastguard Worker```
108*cc02d7e2SAndroid Build Coastguard Worker
109*cc02d7e2SAndroid Build Coastguard Worker##### Initiating a Cancellation on the Server Side
110*cc02d7e2SAndroid Build Coastguard Worker
111*cc02d7e2SAndroid Build Coastguard WorkerInitiating a cancellation from the server side is simpler. Just call
112*cc02d7e2SAndroid Build Coastguard Worker`ServicerContext.cancel()`.
113*cc02d7e2SAndroid Build Coastguard Worker
114*cc02d7e2SAndroid Build Coastguard WorkerIn our example, we ensure that no single client is monopolizing the server by
115*cc02d7e2SAndroid Build Coastguard Workercancelling after a configurable number of hashes have been checked.
116*cc02d7e2SAndroid Build Coastguard Worker
117*cc02d7e2SAndroid Build Coastguard Worker```python
118*cc02d7e2SAndroid Build Coastguard Workertry:
119*cc02d7e2SAndroid Build Coastguard Worker    for candidate in secret_generator:
120*cc02d7e2SAndroid Build Coastguard Worker        yield candidate
121*cc02d7e2SAndroid Build Coastguard Workerexcept ResourceLimitExceededError:
122*cc02d7e2SAndroid Build Coastguard Worker    print("Cancelling RPC due to exhausted resources.")
123*cc02d7e2SAndroid Build Coastguard Worker    context.cancel()
124*cc02d7e2SAndroid Build Coastguard Worker```
125*cc02d7e2SAndroid Build Coastguard Worker
126*cc02d7e2SAndroid Build Coastguard WorkerIn this type of situation, you may also consider returning a more specific error
127*cc02d7e2SAndroid Build Coastguard Workerusing the [`grpcio-status`](https://pypi.org/project/grpcio-status/) package.
128