1# Copyright 2019 The gRPC Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Server-side implementation of gRPC Asyncio Python."""
15
16from concurrent.futures import Executor
17from typing import Any, Dict, Optional, Sequence
18
19import grpc
20from grpc import _common
21from grpc import _compression
22from grpc._cython import cygrpc
23
24from . import _base_server
25from ._interceptor import ServerInterceptor
26from ._typing import ChannelArgumentType
27
28
29def _augment_channel_arguments(
30 base_options: ChannelArgumentType, compression: Optional[grpc.Compression]
31):
32 compression_option = _compression.create_channel_option(compression)
33 return tuple(base_options) + compression_option
34
35
36class Server(_base_server.Server):
37 """Serves RPCs."""
38
39 def __init__(
40 self,
41 thread_pool: Optional[Executor],
42 generic_handlers: Optional[Sequence[grpc.GenericRpcHandler]],
43 interceptors: Optional[Sequence[Any]],
44 options: ChannelArgumentType,
45 maximum_concurrent_rpcs: Optional[int],
46 compression: Optional[grpc.Compression],
47 ):
48 self._loop = cygrpc.get_working_loop()
49 if interceptors:
50 invalid_interceptors = [
51 interceptor
52 for interceptor in interceptors
53 if not isinstance(interceptor, ServerInterceptor)
54 ]
55 if invalid_interceptors:
56 raise ValueError(
57 "Interceptor must be ServerInterceptor, the "
58 f"following are invalid: {invalid_interceptors}"
59 )
60 self._server = cygrpc.AioServer(
61 self._loop,
62 thread_pool,
63 generic_handlers,
64 interceptors,
65 _augment_channel_arguments(options, compression),
66 maximum_concurrent_rpcs,
67 )
68
69 def add_generic_rpc_handlers(
70 self, generic_rpc_handlers: Sequence[grpc.GenericRpcHandler]
71 ) -> None:
72 """Registers GenericRpcHandlers with this Server.
73
74 This method is only safe to call before the server is started.
75
76 Args:
77 generic_rpc_handlers: A sequence of GenericRpcHandlers that will be
78 used to service RPCs.
79 """
80 self._server.add_generic_rpc_handlers(generic_rpc_handlers)
81
82 def add_registered_method_handlers(
83 self,
84 service_name: str,
85 method_handlers: Dict[str, grpc.RpcMethodHandler],
86 ) -> None:
87 # TODO(xuanwn): Implement this for AsyncIO.
88 pass
89
90 def add_insecure_port(self, address: str) -> int:
91 """Opens an insecure port for accepting RPCs.
92
93 This method may only be called before starting the server.
94
95 Args:
96 address: The address for which to open a port. If the port is 0,
97 or not specified in the address, then the gRPC runtime will choose a port.
98
99 Returns:
100 An integer port on which the server will accept RPC requests.
101 """
102 return _common.validate_port_binding_result(
103 address, self._server.add_insecure_port(_common.encode(address))
104 )
105
106 def add_secure_port(
107 self, address: str, server_credentials: grpc.ServerCredentials
108 ) -> int:
109 """Opens a secure port for accepting RPCs.
110
111 This method may only be called before starting the server.
112
113 Args:
114 address: The address for which to open a port.
115 if the port is 0, or not specified in the address, then the gRPC
116 runtime will choose a port.
117 server_credentials: A ServerCredentials object.
118
119 Returns:
120 An integer port on which the server will accept RPC requests.
121 """
122 return _common.validate_port_binding_result(
123 address,
124 self._server.add_secure_port(
125 _common.encode(address), server_credentials
126 ),
127 )
128
129 async def start(self) -> None:
130 """Starts this Server.
131
132 This method may only be called once. (i.e. it is not idempotent).
133 """
134 await self._server.start()
135
136 async def stop(self, grace: Optional[float]) -> None:
137 """Stops this Server.
138
139 This method immediately stops the server from servicing new RPCs in
140 all cases.
141
142 If a grace period is specified, this method waits until all active
143 RPCs are finished or until the grace period is reached. RPCs that haven't
144 been terminated within the grace period are aborted.
145 If a grace period is not specified (by passing None for grace), all
146 existing RPCs are aborted immediately and this method blocks until
147 the last RPC handler terminates.
148
149 This method is idempotent and may be called at any time. Passing a
150 smaller grace value in a subsequent call will have the effect of
151 stopping the Server sooner (passing None will have the effect of
152 stopping the server immediately). Passing a larger grace value in a
153 subsequent call will not have the effect of stopping the server later
154 (i.e. the most restrictive grace value is used).
155
156 Args:
157 grace: A duration of time in seconds or None.
158 """
159 await self._server.shutdown(grace)
160
161 async def wait_for_termination(
162 self, timeout: Optional[float] = None
163 ) -> bool:
164 """Block current coroutine until the server stops.
165
166 This is an EXPERIMENTAL API.
167
168 The wait will not consume computational resources during blocking, and
169 it will block until one of the two following conditions are met:
170
171 1) The server is stopped or terminated;
172 2) A timeout occurs if timeout is not `None`.
173
174 The timeout argument works in the same way as `threading.Event.wait()`.
175 https://docs.python.org/3/library/threading.html#threading.Event.wait
176
177 Args:
178 timeout: A floating point number specifying a timeout for the
179 operation in seconds.
180
181 Returns:
182 A bool indicates if the operation times out.
183 """
184 return await self._server.wait_for_termination(timeout)
185
186 def __del__(self):
187 """Schedules a graceful shutdown in current event loop.
188
189 The Cython AioServer doesn't hold a ref-count to this class. It should
190 be safe to slightly extend the underlying Cython object's life span.
191 """
192 if hasattr(self, "_server"):
193 if self._server.is_running():
194 cygrpc.schedule_coro_threadsafe(
195 self._server.shutdown(None),
196 self._loop,
197 )
198
199
200def server(
201 migration_thread_pool: Optional[Executor] = None,
202 handlers: Optional[Sequence[grpc.GenericRpcHandler]] = None,
203 interceptors: Optional[Sequence[Any]] = None,
204 options: Optional[ChannelArgumentType] = None,
205 maximum_concurrent_rpcs: Optional[int] = None,
206 compression: Optional[grpc.Compression] = None,
207):
208 """Creates a Server with which RPCs can be serviced.
209
210 Args:
211 migration_thread_pool: A futures.ThreadPoolExecutor to be used by the
212 Server to execute non-AsyncIO RPC handlers for migration purpose.
213 handlers: An optional list of GenericRpcHandlers used for executing RPCs.
214 More handlers may be added by calling add_generic_rpc_handlers any time
215 before the server is started.
216 interceptors: An optional list of ServerInterceptor objects that observe
217 and optionally manipulate the incoming RPCs before handing them over to
218 handlers. The interceptors are given control in the order they are
219 specified. This is an EXPERIMENTAL API.
220 options: An optional list of key-value pairs (:term:`channel_arguments` in gRPC runtime)
221 to configure the channel.
222 maximum_concurrent_rpcs: The maximum number of concurrent RPCs this server
223 will service before returning RESOURCE_EXHAUSTED status, or None to
224 indicate no limit.
225 compression: An element of grpc.compression, e.g.
226 grpc.compression.Gzip. This compression algorithm will be used for the
227 lifetime of the server unless overridden by set_compression.
228
229 Returns:
230 A Server object.
231 """
232 return Server(
233 migration_thread_pool,
234 () if handlers is None else handlers,
235 () if interceptors is None else interceptors,
236 () if options is None else options,
237 maximum_concurrent_rpcs,
238 compression,
239 )