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