def test_env_util_smoke(protocol): ca.get_environment_variables() try: ca.get_netifaces_addresses() except RuntimeError: # Netifaces may be unavailable ... ca.get_address_list(protocol=protocol) ca.get_beacon_address_list(protocol=protocol) ca._utils.get_manually_specified_beacon_addresses(protocol=protocol) ca._utils.get_manually_specified_client_addresses(protocol=protocol) ca.get_server_address_list(protocol=protocol)
async def run(self, *, log_pv_names=False): 'Start the server' self.log.info('Curio server starting up...') try: for address in ca.get_beacon_address_list(): sock = ca.bcast_socket(socket) await sock.connect(address) interface, _ = sock.getsockname() self.beacon_socks[address] = (interface, sock) async def make_socket(interface, port): return curio.network.tcp_server_socket(interface, port) self.port, self.tcp_sockets = await self._bind_tcp_sockets_with_consistent_port_number( make_socket) async with curio.TaskGroup() as self._task_group: g = self._task_group for interface, sock in self.tcp_sockets.items(): # Use run_server instead of tcp_server so we can hand in a # socket that is already bound, avoiding a race between the # moment we check for port availability and the moment the # TCP server binds. self.log.info("Listening on %s:%d", interface, self.port) await g.spawn(curio.network.run_server, sock, self.tcp_handler) await g.spawn(self._await_stop) await g.spawn(self.broadcaster_udp_server_loop) await g.spawn(self.broadcaster_queue_loop) await g.spawn(self.subscription_queue_loop) await g.spawn(self.broadcast_beacon_loop) async_lib = CurioAsyncLayer() for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) await g.spawn(method, async_lib) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) self._log_task_group_exceptions(self._task_group) except curio.TaskCancelled as ex: self.log.info('Server task cancelled. Must shut down.') raise ServerExit() from ex finally: self.log.info('Server exiting....') async_lib = CurioAsyncLayer() async with curio.TaskGroup() as task_group: for name, method in self.shutdown_methods.items(): self.log.debug('Calling shutdown method %r', name) await task_group.spawn(method, async_lib) self._log_task_group_exceptions(task_group) for sock in self.tcp_sockets.values(): await sock.close() for sock in self.udp_socks.values(): await sock.close() for _interface, sock in self.beacon_socks.values(): await sock.close() self._task_group = None
async def broadcast_beacon_loop(self): beacon_period = self.environ['EPICS_CAS_BEACON_PERIOD'] addresses = get_beacon_address_list() while True: beacon = ca.RsrvIsUpResponse(13, self.port, self.beacon_count, self.host) bytes_to_send = self.broadcaster.send(beacon) for addr_port in addresses: await self.udp_sock.sendto(bytes_to_send, addr_port) self.beacon_count += 1 await curio.sleep(beacon_period)
def test_beacon_addresses(monkeypatch, protocol, default_port, env_auto, env_addr, expected): env = ca.get_environment_variables() key = ca.Protocol(protocol).server_env_key env[f'EPICS_{key}_BEACON_ADDR_LIST'] = env_addr env[f'EPICS_{key}_AUTO_BEACON_ADDR_LIST'] = env_auto if protocol == ca.Protocol.ChannelAccess: env['EPICS_CAS_BEACON_PORT'] = int(default_port) else: env['EPICS_PVAS_BROADCAST_PORT'] = int(default_port) patch_env(monkeypatch, env) assert set(ca.get_beacon_address_list(protocol=protocol)) == set(expected)
async def run(self, *, log_pv_names=False): 'Start the server' self.log.info('Asyncio server starting up...') async def make_socket(interface, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setblocking(False) s.bind((interface, port)) return s self.port, self.tcp_sockets = await self._bind_tcp_sockets_with_consistent_port_number( make_socket) tasks = [] for interface, sock in self.tcp_sockets.items(): self.log.info("Listening on %s:%d", interface, self.port) tasks.append(self.loop.create_task(self.server_accept_loop(sock))) class BcastLoop(asyncio.Protocol): parent = self loop = self.loop def __init__(self, *args, **kwargs): self.transport = None self._tasks = () def connection_made(self, transport): self.transport = transport def datagram_received(self, data, addr): if data: tsk = self.loop.create_task( self.parent._broadcaster_recv_datagram(data, addr)) self._tasks = tuple(t for t in self._tasks + (tsk, ) if not t.done()) def error_received(self, exc): self.parent.log.error('BcastLoop received error', exc_info=exc) class TransportWrapper: """Make an asyncio transport something you can call sendto on.""" def __init__(self, transport): self.transport = transport async def sendto(self, bytes_to_send, addr_port): try: self.transport.sendto(bytes_to_send, addr_port) except OSError as exc: host, port = addr_port raise ca.CaprotoNetworkError( f"Failed to send to {host}:{port}") from exc def close(self): return self.transport.close() class ConnectedTransportWrapper: """Make an asyncio transport something you can call send on.""" def __init__(self, transport, address): self.transport = transport self.address = address async def send(self, bytes_to_send): try: self.transport.sendto(bytes_to_send, self.address) except OSError as exc: host, port = self.address raise ca.CaprotoNetworkError( f"Failed to send to {host}:{port}") from exc def close(self): return self.transport.close() reuse_port = sys.platform not in ('win32', ) and hasattr( socket, 'SO_REUSEPORT') for address in ca.get_beacon_address_list(): transport, _ = await self.loop.create_datagram_endpoint( BcastLoop, remote_addr=address, allow_broadcast=True, reuse_address=True, reuse_port=reuse_port) wrapped_transport = ConnectedTransportWrapper(transport, address) self.beacon_socks[address] = (interface, wrapped_transport) for interface in self.interfaces: transport, self.p = await self.loop.create_datagram_endpoint( BcastLoop, local_addr=(interface, self.ca_server_port), allow_broadcast=True, reuse_address=True, reuse_port=reuse_port) self.udp_socks[interface] = TransportWrapper(transport) self.log.debug('UDP socket bound on %s:%d', interface, self.ca_server_port) tasks.append(self.loop.create_task(self.broadcaster_queue_loop())) tasks.append(self.loop.create_task(self.subscription_queue_loop())) tasks.append(self.loop.create_task(self.broadcast_beacon_loop())) async_lib = AsyncioAsyncLayer(self.loop) for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) tasks.append(self.loop.create_task(method(async_lib))) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) try: await asyncio.gather(*tasks) except asyncio.CancelledError: self.log.info('Server task cancelled. Will shut down.') all_tasks = ( tasks + self._server_tasks + [c._cq_task for c in self.circuits if c._cq_task is not None] + [c._sq_task for c in self.circuits if c._sq_task is not None] + [t for c in self.circuits for t in c._write_tasks] + list(self.p._tasks)) for t in all_tasks: t.cancel() await asyncio.wait(all_tasks) return except Exception: self.log.exception('Server error. Will shut down') raise finally: self.log.info('Server exiting....') shutdown_tasks = [] async_lib = AsyncioAsyncLayer(self.loop) for name, method in self.shutdown_methods.items(): self.log.debug('Calling shutdown method %r', name) task = self.loop.create_task(method(async_lib)) shutdown_tasks.append(task) await asyncio.gather(*shutdown_tasks) for sock in self.tcp_sockets.values(): sock.close() for sock in self.udp_socks.values(): sock.close() for interface, sock in self.beacon_socks.values(): sock.close()
async def run(self, *, log_pv_names=False): 'Start the server' self.log.info('Server starting up...') try: async with trio.open_nursery() as self.nursery: for address in ca.get_beacon_address_list(): sock = ca.bcast_socket(socket) await sock.connect(address) interface, _ = sock.getsockname() self.beacon_socks[address] = (interface, sock) # This reproduces the common with # self._bind_tcp_sockets_with_consistent_port_number because # trio makes socket binding async where asyncio and curio make # it synchronous. # Find a random port number that is free on all interfaces, # and get a bound TCP socket with that port number on each # interface. tcp_sockets = {} # maps interface to bound socket stashed_ex = None for port in ca.random_ports(100): try: for interface in self.interfaces: s = trio.socket.socket() await s.bind((interface, port)) tcp_sockets[interface] = s except IOError as ex: stashed_ex = ex for s in tcp_sockets.values(): s.close() else: self.port = port break else: raise RuntimeError('No available ports and/or bind failed' ) from stashed_ex # (End of reproduced code) for interface, listen_sock in tcp_sockets.items(): self.log.info("Listening on %s:%d", interface, self.port) await self.nursery.start(self.server_accept_loop, listen_sock) await self.nursery.start(self.broadcaster_udp_server_loop) await self.nursery.start(self.broadcaster_queue_loop) await self.nursery.start(self.subscription_queue_loop) await self.nursery.start(self.broadcast_beacon_loop) async_lib = TrioAsyncLayer() for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) async def startup(task_status): task_status.started() await method(async_lib) await self.nursery.start(startup) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) except trio.Cancelled: self.log.info('Server task cancelled. Will shut down.') finally: self.log.info('Server exiting....')
async def run(self, *, log_pv_names=False): 'Start the server' self.log.info('Trio server starting up...') try: async with trio.open_nursery() as self.nursery: for address in ca.get_beacon_address_list(): sock = ca.bcast_socket(socket) await sock.connect(address) interface, _ = sock.getsockname() self.beacon_socks[address] = (interface, sock) async def make_socket(interface, port): s = trio.socket.socket() await s.bind((interface, port)) return s res = await self._bind_tcp_sockets_with_consistent_port_number( make_socket) self.port, self.tcp_sockets = res await self.nursery.start(self.broadcaster_udp_server_loop) await self.nursery.start(self.broadcaster_queue_loop) await self.nursery.start(self.subscription_queue_loop) await self.nursery.start(self.broadcast_beacon_loop) # Only after all loops have been started, begin listening: for interface, listen_sock in self.tcp_sockets.items(): self.log.info("Listening on %s:%d", interface, self.port) await self.nursery.start(self.server_accept_loop, listen_sock) async_lib = TrioAsyncLayer() for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) async def startup(task_status): task_status.started() await method(async_lib) await self.nursery.start(startup) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) except trio.Cancelled: self.log.info('Server task cancelled. Will shut down.') finally: self.log.info('Server exiting....') async_lib = TrioAsyncLayer() async with trio.open_nursery() as nursery: for name, method in self.shutdown_methods.items(): self.log.debug('Calling shutdown method %r', name) async def shutdown(task_status): task_status.started() await method(async_lib) await nursery.start(shutdown) for sock in self.tcp_sockets.values(): sock.close() for sock in self.udp_socks.values(): sock.close() for interface, sock in self.beacon_socks.values(): sock.close()
async def run(self, *, log_pv_names=False, startup_hook=None): 'Start the server' self.log.info('Asyncio server starting up...') self.port, self.tcp_sockets = await self._bind_tcp_sockets_with_consistent_port_number( _create_bound_tcp_socket) tasks = _TaskHandler() for interface, sock in self.tcp_sockets.items(): self.log.info("Listening on %s:%d", interface, self.port) self.broadcaster.server_addresses.append((interface, self.port)) tasks.create(self.server_accept_loop(sock)) for address in ca.get_beacon_address_list(): sock = _create_udp_socket() try: sock.connect(address) except Exception as ex: self.log.error( 'Beacon (%s:%d) socket setup failed: %s', *address, ex, ) continue wrapped_transport = _UdpTransportWrapper(sock, address, loop=self.loop) self.beacon_socks[address] = ( interface, # TODO; this is incorrect wrapped_transport) for interface in self.interfaces: sock = _create_udp_socket() sock.bind((interface, self.ca_server_port)) transport, _ = await self.loop.create_datagram_endpoint( functools.partial(_DatagramProtocol, parent=self, identifier=interface, queue=self.broadcaster_datagram_queue), sock=sock, ) self.udp_socks[interface] = _UdpTransportWrapper(transport, loop=self.loop) self.log.debug('UDP socket bound on %s:%d', interface, self.ca_server_port) tasks.create(self.broadcaster_receive_loop()) tasks.create(self.broadcaster_queue_loop()) tasks.create(self.subscription_queue_loop()) tasks.create(self.broadcast_beacon_loop()) async_lib = AsyncioAsyncLayer() if startup_hook is not None: self.log.debug('Calling startup hook %r', startup_hook.__name__) tasks.create(startup_hook(async_lib)) for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) tasks.create(method(async_lib)) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) try: await asyncio.gather(*tasks.tasks) except asyncio.CancelledError: self.log.info('Server task cancelled. Will shut down.') await tasks.cancel_all() await self.server_tasks.cancel_all() for circuit in self.circuits: await circuit.tasks.cancel_all() return except Exception: self.log.exception('Server error. Will shut down') raise finally: self.log.info('Server exiting....') shutdown_tasks = [] async_lib = AsyncioAsyncLayer() for name, method in self.shutdown_methods.items(): self.log.debug('Calling shutdown method %r', name) task = self.loop.create_task(method(async_lib)) shutdown_tasks.append(task) await asyncio.gather(*shutdown_tasks) for sock in self.tcp_sockets.values(): sock.close() for sock in self.udp_socks.values(): sock.close() for _, sock in self.beacon_socks.values(): sock.close()
async def run(self, *, log_pv_names=False): 'Start the server' self.log.info('Server starting up...') def make_socket(interface, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setblocking(False) s.bind((interface, port)) return s port, tcp_sockets = self._bind_tcp_sockets_with_consistent_port_number( make_socket) self.port = port tasks = [] for interface, sock in tcp_sockets.items(): self.log.info("Listening on %s:%d", interface, self.port) tasks.append(self.loop.create_task(self.server_accept_loop(sock))) class BcastLoop(asyncio.Protocol): parent = self loop = self.loop def __init__(self, *args, **kwargs): self.transport = None self._tasks = () def connection_made(self, transport): self.transport = transport def datagram_received(self, data, addr): tsk = self.loop.create_task(self.parent._broadcaster_recv_datagram( data, addr)) self._tasks = tuple(t for t in self._tasks + (tsk,) if not t.done()) class TransportWrapper: """Make an asyncio transport something you can call sendto on.""" def __init__(self, transport): self.transport = transport async def sendto(self, bytes_to_send, addr_port): self.transport.sendto(bytes_to_send, addr_port) class ConnectedTransportWrapper: """Make an asyncio transport something you can call send on.""" def __init__(self, transport, address): self.transport = transport self.address = address async def send(self, bytes_to_send): self.transport.sendto(bytes_to_send, self.address) for address in ca.get_beacon_address_list(): # Connected sockets do not play well with asyncio, so connect to # one and then discard it. temp_sock = ca.bcast_socket(socket) temp_sock.connect(address) interface, _ = temp_sock.getsockname() temp_sock.close() sock = ca.bcast_socket(socket) transport, _ = await self.loop.create_datagram_endpoint( BcastLoop, sock=sock) wrapped_transport = ConnectedTransportWrapper(transport, address) self.beacon_socks[address] = (interface, wrapped_transport) for interface in self.interfaces: udp_sock = bcast_socket() try: udp_sock.bind((interface, ca.EPICS_CA1_PORT)) except Exception: self.log.exception('UDP bind failure on interface %r', interface) raise transport, self.p = await self.loop.create_datagram_endpoint( BcastLoop, sock=udp_sock) self.udp_socks[interface] = TransportWrapper(transport) self.log.debug('Broadcasting on %s:%d', interface, ca.EPICS_CA1_PORT) tasks.append(self.loop.create_task(self.broadcaster_queue_loop())) tasks.append(self.loop.create_task(self.subscription_queue_loop())) tasks.append(self.loop.create_task(self.broadcast_beacon_loop())) async_lib = AsyncioAsyncLayer(self.loop) for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) tasks.append(self.loop.create_task(method(async_lib))) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) try: await asyncio.gather(*tasks) except asyncio.CancelledError: self.log.info('Server task cancelled. Will shut down.') udp_sock.close() all_tasks = (tasks + self._server_tasks + [c._cq_task for c in self.circuits if c._cq_task is not None] + [t for c in self.circuits for t in c._write_tasks] + list(self.p._tasks)) for t in all_tasks: t.cancel() await asyncio.wait(all_tasks) return except Exception as ex: self.log.exception('Server error. Will shut down') udp_sock.close() raise finally: self.log.info('Server exiting....')
async def run(self, *, log_pv_names=False): 'Start the server' self.log.info('Asyncio server starting up...') async def make_socket(interface, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setblocking(False) s.bind((interface, port)) return s self.port, self.tcp_sockets = await self._bind_tcp_sockets_with_consistent_port_number( make_socket) tasks = _TaskHandler() for interface, sock in self.tcp_sockets.items(): self.log.info("Listening on %s:%d", interface, self.port) self.broadcaster.server_addresses.append((interface, self.port)) tasks.create(self.server_accept_loop(sock)) class ConnectedTransportWrapper: """Make an asyncio transport something you can call send on.""" def __init__(self, transport, address): self.transport = transport self.address = address async def send(self, bytes_to_send): try: self.transport.sendto(bytes_to_send, self.address) except OSError as exc: host, port = self.address raise ca.CaprotoNetworkError( f"Failed to send to {host}:{port}") from exc def close(self): return self.transport.close() reuse_port = sys.platform not in ('win32', ) and hasattr( socket, 'SO_REUSEPORT') for address in ca.get_beacon_address_list(): transport, _ = await self.loop.create_datagram_endpoint( functools.partial(_DatagramProtocol, parent=self, recv_func=self._datagram_received), remote_addr=address, allow_broadcast=True, reuse_port=reuse_port) wrapped_transport = ConnectedTransportWrapper(transport, address) self.beacon_socks[address] = ( interface, # TODO; this is incorrect wrapped_transport) for interface in self.interfaces: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # Python says this is unsafe, but we need it to have # multiple servers live on the same host. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if reuse_port: sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) sock.setblocking(False) sock.bind((interface, self.ca_server_port)) transport, _ = await self.loop.create_datagram_endpoint( functools.partial(_DatagramProtocol, parent=self, recv_func=self._datagram_received), sock=sock) self.udp_socks[interface] = _TransportWrapper(transport) self.log.debug('UDP socket bound on %s:%d', interface, self.ca_server_port) tasks.create(self.broadcaster_queue_loop()) tasks.create(self.subscription_queue_loop()) tasks.create(self.broadcast_beacon_loop()) async_lib = AsyncioAsyncLayer() for name, method in self.startup_methods.items(): self.log.debug('Calling startup method %r', name) tasks.create(method(async_lib)) self.log.info('Server startup complete.') if log_pv_names: self.log.info('PVs available:\n%s', '\n'.join(self.pvdb)) try: await asyncio.gather(*tasks.tasks) except asyncio.CancelledError: self.log.info('Server task cancelled. Will shut down.') await tasks.cancel_all() await self.server_tasks.cancel_all() for circuit in self.circuits: await circuit.tasks.cancel_all() return except Exception: self.log.exception('Server error. Will shut down') raise finally: self.log.info('Server exiting....') shutdown_tasks = [] async_lib = AsyncioAsyncLayer() for name, method in self.shutdown_methods.items(): self.log.debug('Calling shutdown method %r', name) task = self.loop.create_task(method(async_lib)) shutdown_tasks.append(task) await asyncio.gather(*shutdown_tasks) for sock in self.tcp_sockets.values(): sock.close() for sock in self.udp_socks.values(): sock.close() for _interface, sock in self.beacon_socks.values(): sock.close()