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 error_msg = (
57 "Interceptor must be ServerInterceptor,"
58 "the following are invalid: {invalid_interceptors}"
59 )
60 raise ValueError(error_msg)
61 self._server = cygrpc.AioServer(
62 self._loop,
63 thread_pool,
64 generic_handlers,
65 interceptors,
66 _augment_channel_arguments(options, compression),
67 maximum_concurrent_rpcs,
68 )
69
70 def add_generic_rpc_handlers(
71 self, generic_rpc_handlers: Sequence[grpc.GenericRpcHandler]
72 ) -> None:
73 """Registers GenericRpcHandlers with this Server.
74
75 This method is only safe to call before the server is started.
76
77 Args:
78 generic_rpc_handlers: A sequence of GenericRpcHandlers that will be
79 used to service RPCs.
80 """
81 self._server.add_generic_rpc_handlers(generic_rpc_handlers)
82
83 def add_registered_method_handlers(
84 self,
85 service_name: str,
86 method_handlers: Dict[str, grpc.RpcMethodHandler],
87 ) -> None:
88 # TODO(xuanwn): Implement this for AsyncIO.
89 pass
90
91 def add_insecure_port(self, address: str) -> int:
92 """Opens an insecure port for accepting RPCs.
93
94 This method may only be called before starting the server.
95
96 Args:
97 address: The address for which to open a port. If the port is 0,
98 or not specified in the address, then the gRPC runtime will choose a port.
99
100 Returns:
101 An integer port on which the server will accept RPC requests.
102 """
103 return _common.validate_port_binding_result(
104 address, self._server.add_insecure_port(_common.encode(address))
105 )
106
107 def add_secure_port(
108 self, address: str, server_credentials: grpc.ServerCredentials
109 ) -> int:
110 """Opens a secure port for accepting RPCs.
111
112 This method may only be called before starting the server.
113
114 Args:
115 address: The address for which to open a port.
116 if the port is 0, or not specified in the address, then the gRPC
117 runtime will choose a port.
118 server_credentials: A ServerCredentials object.
119
120 Returns:
121 An integer port on which the server will accept RPC requests.
122 """
123 return _common.validate_port_binding_result(
124 address,
125 self._server.add_secure_port(
126 _common.encode(address), server_credentials
127 ),
128 )
129
130 async def start(self) -> None:
131 """Starts this Server.
132
133 This method may only be called once. (i.e. it is not idempotent).
134 """
135 await self._server.start()
136
137 async def stop(self, grace: Optional[float]) -> None:
138 """Stops this Server.
139
140 This method immediately stops the server from servicing new RPCs in
141 all cases.
142
143 If a grace period is specified, this method waits until all active
144 RPCs are finished or until the grace period is reached. RPCs that haven't
145 been terminated within the grace period are aborted.
146 If a grace period is not specified (by passing None for grace), all
147 existing RPCs are aborted immediately and this method blocks until
148 the last RPC handler terminates.
149
150 This method is idempotent and may be called at any time. Passing a
151 smaller grace value in a subsequent call will have the effect of
152 stopping the Server sooner (passing None will have the effect of
153 stopping the server immediately). Passing a larger grace value in a
154 subsequent call will not have the effect of stopping the server later
155 (i.e. the most restrictive grace value is used).
156
157 Args:
158 grace: A duration of time in seconds or None.
159 """
160 await self._server.shutdown(grace)
161
162 async def wait_for_termination(
163 self, timeout: Optional[float] = None
164 ) -> bool:
165 """Block current coroutine until the server stops.
166
167 This is an EXPERIMENTAL API.
168
169 The wait will not consume computational resources during blocking, and
170 it will block until one of the two following conditions are met:
171
172 1) The server is stopped or terminated;
173 2) A timeout occurs if timeout is not `None`.
174
175 The timeout argument works in the same way as `threading.Event.wait()`.
176 https://docs.python.org/3/library/threading.html#threading.Event.wait
177
178 Args:
179 timeout: A floating point number specifying a timeout for the
180 operation in seconds.
181
182 Returns:
183 A bool indicates if the operation times out.
184 """
185 return await self._server.wait_for_termination(timeout)
186
187 def __del__(self):
188 """Schedules a graceful shutdown in current event loop.
189
190 The Cython AioServer doesn't hold a ref-count to this class. It should
191 be safe to slightly extend the underlying Cython object's life span.
192 """
193 if hasattr(self, "_server"):
194 if self._server.is_running():
195 cygrpc.schedule_coro_threadsafe(
196 self._server.shutdown(None),
197 self._loop,
198 )
199
200
201def server(
202 migration_thread_pool: Optional[Executor] = None,
203 handlers: Optional[Sequence[grpc.GenericRpcHandler]] = None,
204 interceptors: Optional[Sequence[Any]] = None,
205 options: Optional[ChannelArgumentType] = None,
206 maximum_concurrent_rpcs: Optional[int] = None,
207 compression: Optional[grpc.Compression] = None,
208):
209 """Creates a Server with which RPCs can be serviced.
210
211 Args:
212 migration_thread_pool: A futures.ThreadPoolExecutor to be used by the
213 Server to execute non-AsyncIO RPC handlers for migration purpose.
214 handlers: An optional list of GenericRpcHandlers used for executing RPCs.
215 More handlers may be added by calling add_generic_rpc_handlers any time
216 before the server is started.
217 interceptors: An optional list of ServerInterceptor objects that observe
218 and optionally manipulate the incoming RPCs before handing them over to
219 handlers. The interceptors are given control in the order they are
220 specified. This is an EXPERIMENTAL API.
221 options: An optional list of key-value pairs (:term:`channel_arguments` in gRPC runtime)
222 to configure the channel.
223 maximum_concurrent_rpcs: The maximum number of concurrent RPCs this server
224 will service before returning RESOURCE_EXHAUSTED status, or None to
225 indicate no limit.
226 compression: An element of grpc.compression, e.g.
227 grpc.compression.Gzip. This compression algorithm will be used for the
228 lifetime of the server unless overridden by set_compression.
229
230 Returns:
231 A Server object.
232 """
233 return Server(
234 migration_thread_pool,
235 () if handlers is None else handlers,
236 () if interceptors is None else interceptors,
237 () if options is None else options,
238 maximum_concurrent_rpcs,
239 compression,
240 )