def test_circuit_properties(): host = '127.0.0.1' port = 5555 prio = 1 circuit = ca.VirtualCircuit(ca.CLIENT, (host, port), prio) circuit.host == host circuit.port == port circuit.key == ((host, port), prio) # CLIENT circuit needs to know its prio at init time with pytest.raises(ca.CaprotoRuntimeError): ca.VirtualCircuit(ca.CLIENT, host, None) # SERVER circuit does not srv_circuit = ca.VirtualCircuit(ca.SERVER, (host, port), None) # 'key' is not defined until prio is defined with pytest.raises(ca.CaprotoRuntimeError): srv_circuit.key srv_circuit.priority = prio srv_circuit.key # VersionRequest priority must match prio set above. with pytest.raises(ca.LocalProtocolError): circuit.send( ca.VersionRequest(version=ca.DEFAULT_PROTOCOL_VERSION, priority=2))
def test_broadcaster_checks(): b = ca.Broadcaster(ca.CLIENT) with pytest.raises(ca.LocalProtocolError): b.send( ca.SearchRequest(name='LIRR', cid=0, version=ca.DEFAULT_PROTOCOL_VERSION)) b.send(ca.RepeaterRegisterRequest('1.2.3.4')) res = ca.RepeaterConfirmResponse('5.6.7.8') commands = b.recv(bytes(res), ('5.6.7.8', 6666)) assert commands[0] == res b.process_commands(commands) req = ca.SearchRequest(name='LIRR', cid=0, version=ca.DEFAULT_PROTOCOL_VERSION) with pytest.raises(ca.LocalProtocolError): b.send(req) b.send(ca.VersionRequest(priority=0, version=ca.DEFAULT_PROTOCOL_VERSION), req) res = ca.SearchResponse(port=6666, ip='1.2.3.4', cid=0, version=ca.DEFAULT_PROTOCOL_VERSION) addr = ('1.2.3.4', 6666) commands = b.recv(bytes(res), addr) # see changes to _broadcaster.py. Apparently rsrv does not always conform # to the protocol and include a version response before search responsesq # with pytest.raises(ca.RemoteProtocolError): # b.process_commands(commands) # commands = b.recv(bytes(ca.VersionResponse(version=ca.DEFAULT_PROTOCOL_VERSION)) + bytes(res), addr) b.process_commands(commands) # this gets both
def make_channel(pv_name, udp_sock, priority, timeout): log = logging.LoggerAdapter(logging.getLogger('caproto.ch'), {'pv': pv_name}) address = search(pv_name, udp_sock, timeout) try: circuit = global_circuits[(address, priority)] except KeyError: circuit = global_circuits[(address, priority)] = ca.VirtualCircuit( our_role=ca.CLIENT, address=address, priority=priority) chan = ca.ClientChannel(pv_name, circuit) new = False if chan.circuit not in sockets: new = True sockets[chan.circuit] = socket.create_connection( chan.circuit.address, timeout) circuit.our_address = sockets[chan.circuit].getsockname() try: if new: # Initialize our new TCP-based CA connection with a VersionRequest. send( chan.circuit, ca.VersionRequest(priority=priority, version=ca.DEFAULT_PROTOCOL_VERSION), pv_name) send(chan.circuit, chan.host_name(socket.gethostname())) send(chan.circuit, chan.client_name(getpass.getuser())) send(chan.circuit, chan.create(), pv_name) t = time.monotonic() while True: try: commands = recv(chan.circuit) if time.monotonic() - t > timeout: raise socket.timeout except socket.timeout: raise CaprotoTimeoutError("Timeout while awaiting channel " "creation.") tags = { 'direction': '<<<---', 'our_address': chan.circuit.our_address, 'their_address': chan.circuit.address } for command in commands: if isinstance(command, ca.Message): tags['bytesize'] = len(command) logger.debug("%r", command, extra=tags) elif command is ca.DISCONNECTED: raise CaprotoError('Disconnected during initialization') if chan.states[ca.CLIENT] is ca.CONNECTED: log.info("Channel connected.") break except BaseException: sockets[chan.circuit].close() del sockets[chan.circuit] del global_circuits[(chan.circuit.address, chan.circuit.priority)] raise return chan
async def connect(self): async with self._socket_lock: self.socket = await socket.create_connection(self.circuit.address) # Kick off background loops that read from the socket # and process the commands read from it. await curio.spawn(self._receive_loop(), daemon=True) await curio.spawn(self._command_queue_loop(), daemon=True) # Send commands that initialize the Circuit. await self.send( ca.VersionRequest(version=13, priority=self.circuit.priority)) host_name = await socket.gethostname() await self.send(ca.HostNameRequest(name=host_name)) client_name = getpass.getuser() await self.send(ca.ClientNameRequest(name=client_name))
async def connect(self): async with self._socket_lock: self.socket = await trio.open_tcp_stream(*self.circuit.address) # Kick off background loops that read from the socket # and process the commands read from it. self.nursery.start_soon(self._receive_loop) self.nursery.start_soon(self._command_queue_loop) # Send commands that initialize the Circuit. await self.send(ca.VersionRequest(version=ca.DEFAULT_PROTOCOL_VERSION, priority=self.circuit.priority)) host_name = socket.gethostname() await self.send(ca.HostNameRequest(name=host_name)) client_name = getpass.getuser() await self.send(ca.ClientNameRequest(name=client_name))
async def search_many(self, *names): "Generate, process, and transport search request(s)" # We have have already searched for these names recently. # Filter `pv_names` down to a subset, `needs_search`. needs_search = [] use_cached_search = defaultdict(list) for name in names: try: address = self.get_cached_search_result(name) except KeyError: needs_search.append((self.new_id(), name)) else: use_cached_search[address].append(name) for addr, names in use_cached_search.items(): yield (address, names) use_cached_search.clear() # Generate search_ids and stash them on Context state so they can # be used to match SearchResponses with SearchRequests. for search_id, name in needs_search: self.unanswered_searches[search_id] = name results = defaultdict(list) while needs_search: self.log.debug('Searching for %r PVs....', len(needs_search)) requests = (ca.SearchRequest(name, search_id, ca.DEFAULT_PROTOCOL_VERSION) for search_id, name in needs_search) for batch in batch_requests(requests, SEARCH_MAX_DATAGRAM_BYTES): await self.send(self.ca_server_port, ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION), *batch) with trio.move_on_after(1): await self.wait_on_new_command() results.clear() found = [(search_id, name) for search_id, name in needs_search if search_id not in self.unanswered_searches] needs_search = [key for key in needs_search if key not in found] for search_id, name in found: address, timestamp = self.search_results[name] results[address].append(name) for addr, names in results.items(): yield (address, names)
def make_channel(pv_name, udp_sock, priority, timeout): log = logging.getLogger(f'caproto.ch.{pv_name}.{priority}') address = search(pv_name, udp_sock, timeout) try: circuit = global_circuits[(address, priority)] except KeyError: circuit = global_circuits[(address, priority)] = ca.VirtualCircuit( our_role=ca.CLIENT, address=address, priority=priority) chan = ca.ClientChannel(pv_name, circuit) new = False if chan.circuit not in sockets: new = True sockets[chan.circuit] = socket.create_connection( chan.circuit.address, timeout) try: if new: # Initialize our new TCP-based CA connection with a VersionRequest. send( chan.circuit, ca.VersionRequest(priority=priority, version=ca.DEFAULT_PROTOCOL_VERSION)) send(chan.circuit, chan.host_name(socket.gethostname())) send(chan.circuit, chan.client_name(getpass.getuser())) send(chan.circuit, chan.create()) t = time.monotonic() while True: try: commands = recv(chan.circuit) if time.monotonic() - t > timeout: raise socket.timeout except socket.timeout: raise CaprotoTimeoutError("Timeout while awaiting channel " "creation.") if chan.states[ca.CLIENT] is ca.CONNECTED: log.info('%s connected' % pv_name) break for command in commands: if command is ca.DISCONNECTED: raise CaprotoError('Disconnected during initialization') except BaseException: sockets[chan.circuit].close() del sockets[chan.circuit] del global_circuits[(chan.circuit.address, chan.circuit.priority)] raise return chan
def circuit_pair(request): host = '127.0.0.1' port = 5555 priority = 1 version = 13 cli_circuit = ca.VirtualCircuit(ca.CLIENT, (host, port), priority) buffers_to_send = cli_circuit.send( ca.VersionRequest(version=version, priority=priority)) srv_circuit = ca.VirtualCircuit(ca.SERVER, (host, port), None) commands, _ = srv_circuit.recv(*buffers_to_send) for command in commands: srv_circuit.process_command(command) buffers_to_send = srv_circuit.send(ca.VersionResponse(version=version)) commands, _ = cli_circuit.recv(*buffers_to_send) for command in commands: cli_circuit.process_command(command) return cli_circuit, srv_circuit
def test_broadcaster_checks(): b = ca.Broadcaster(ca.CLIENT) with pytest.raises(ca.LocalProtocolError): b.send(ca.SearchRequest(name='LIRR', cid=0, version=13)) b.send(ca.RepeaterRegisterRequest('1.2.3.4')) res = ca.RepeaterConfirmResponse('5.6.7.8') commands = b.recv(bytes(res), ('5.6.7.8', 6666)) assert commands[0] == res b.process_commands(commands) req = ca.SearchRequest(name='LIRR', cid=0, version=13) with pytest.raises(ca.LocalProtocolError): b.send(req) b.send(ca.VersionRequest(priority=0, version=13), req) res = ca.SearchResponse(port=6666, ip='1.2.3.4', cid=0, version=13) addr = ('1.2.3.4', 6666) commands = b.recv(bytes(res), addr) with pytest.raises(ca.RemoteProtocolError): b.process_commands(commands) commands = b.recv(bytes(ca.VersionResponse(version=13)) + bytes(res), addr) b.process_commands(commands) # this gets both
def test_nonet(): # Register with the repeater. assert not cli_b._registered bytes_to_send = cli_b.send(ca.RepeaterRegisterRequest('0.0.0.0')) assert not cli_b._registered # Receive response data = bytes(ca.RepeaterConfirmResponse('127.0.0.1')) commands = cli_b.recv(data, cli_addr) cli_b.process_commands(commands) assert cli_b._registered # Search for pv1. # CA requires us to send a VersionRequest and a SearchRequest bundled into # one datagram. bytes_to_send = cli_b.send(ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION), ca.SearchRequest(pv1, 0, ca.DEFAULT_PROTOCOL_VERSION)) commands = srv_b.recv(bytes_to_send, cli_addr) srv_b.process_commands(commands) ver_req, search_req = commands bytes_to_send = srv_b.send( ca.VersionResponse(ca.DEFAULT_PROTOCOL_VERSION), ca.SearchResponse(5064, None, search_req.cid, ca.DEFAULT_PROTOCOL_VERSION)) # Receive a VersionResponse and SearchResponse. commands = iter(cli_b.recv(bytes_to_send, cli_addr)) command = next(commands) assert type(command) is ca.VersionResponse command = next(commands) assert type(command) is ca.SearchResponse address = ca.extract_address(command) circuit = ca.VirtualCircuit(our_role=ca.CLIENT, address=address, priority=0) circuit.log.setLevel('DEBUG') chan1 = ca.ClientChannel(pv1, circuit) assert chan1.states[ca.CLIENT] is ca.SEND_CREATE_CHAN_REQUEST assert chan1.states[ca.SERVER] is ca.IDLE srv_circuit = ca.VirtualCircuit(our_role=ca.SERVER, address=address, priority=None) cli_send(chan1.circuit, ca.VersionRequest(priority=0, version=ca.DEFAULT_PROTOCOL_VERSION)) srv_recv(srv_circuit) srv_send(srv_circuit, ca.VersionResponse(version=ca.DEFAULT_PROTOCOL_VERSION)) cli_recv(chan1.circuit) cli_send(chan1.circuit, ca.HostNameRequest('localhost')) cli_send(chan1.circuit, ca.ClientNameRequest('username')) cli_send(chan1.circuit, ca.CreateChanRequest(name=pv1, cid=chan1.cid, version=ca.DEFAULT_PROTOCOL_VERSION)) assert chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE assert chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE srv_recv(srv_circuit) assert chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE assert chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE srv_chan1, = srv_circuit.channels.values() assert srv_chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE assert srv_chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE srv_send(srv_circuit, ca.CreateChanResponse(cid=chan1.cid, sid=1, data_type=5, data_count=1)) assert srv_chan1.states[ca.CLIENT] is ca.CONNECTED assert srv_chan1.states[ca.SERVER] is ca.CONNECTED # At this point the CLIENT is not aware that we are CONNECTED because it # has not yet received the CreateChanResponse. It should not be allowed to # read or write. assert chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE assert chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE # Try sending a premature read request. read_req = ca.ReadNotifyRequest(sid=srv_chan1.sid, data_type=srv_chan1.native_data_type, data_count=srv_chan1.native_data_count, ioid=0) with pytest.raises(ca.LocalProtocolError): cli_send(chan1.circuit, read_req) # The above failed because the sid is not recognized. Remove that failure # by editing the sid cache, and check that it *still* fails, this time # because of the state machine prohibiting this command before the channel # is in a CONNECTED state. chan1.circuit.channels_sid[1] = chan1 with pytest.raises(ca.LocalProtocolError): cli_send(chan1.circuit, read_req) cli_recv(chan1.circuit) assert chan1.states[ca.CLIENT] is ca.CONNECTED assert chan1.states[ca.SERVER] is ca.CONNECTED # Test subscriptions. assert chan1.native_data_type and chan1.native_data_count add_req = ca.EventAddRequest(data_type=chan1.native_data_type, data_count=chan1.native_data_count, sid=chan1.sid, subscriptionid=0, low=0, high=0, to=0, mask=1) cli_send(chan1.circuit, add_req) srv_recv(srv_circuit) add_res = ca.EventAddResponse(data=(3,), data_type=chan1.native_data_type, data_count=chan1.native_data_count, subscriptionid=0, status=1) srv_send(srv_circuit, add_res) cli_recv(chan1.circuit) cancel_req = ca.EventCancelRequest(data_type=add_req.data_type, sid=add_req.sid, subscriptionid=add_req.subscriptionid) cli_send(chan1.circuit, cancel_req) srv_recv(srv_circuit) # Test reading. cli_send(chan1.circuit, ca.ReadNotifyRequest(data_type=5, data_count=1, sid=chan1.sid, ioid=12)) srv_recv(srv_circuit) srv_send(srv_circuit, ca.ReadNotifyResponse(data=(3,), data_type=5, data_count=1, ioid=12, status=1)) cli_recv(chan1.circuit) # Test writing. request = ca.WriteNotifyRequest(data_type=2, data_count=1, sid=chan1.sid, ioid=13, data=(4,)) cli_send(chan1.circuit, request) srv_recv(srv_circuit) srv_send(srv_circuit, ca.WriteNotifyResponse(data_type=5, data_count=1, ioid=13, status=1)) cli_recv(chan1.circuit) # Test "clearing" (closing) the channel. cli_send(chan1.circuit, ca.ClearChannelRequest(sid=chan1.sid, cid=chan1.cid)) assert chan1.states[ca.CLIENT] is ca.MUST_CLOSE assert chan1.states[ca.SERVER] is ca.MUST_CLOSE srv_recv(srv_circuit) assert srv_chan1.states[ca.CLIENT] is ca.MUST_CLOSE assert srv_chan1.states[ca.SERVER] is ca.MUST_CLOSE srv_send(srv_circuit, ca.ClearChannelResponse(sid=chan1.sid, cid=chan1.cid)) assert srv_chan1.states[ca.CLIENT] is ca.CLOSED assert srv_chan1.states[ca.SERVER] is ca.CLOSED
def search(pv_name, udp_sock, timeout, *, max_retries=2): # Set Broadcaster log level to match our logger. b = ca.Broadcaster(our_role=ca.CLIENT) b.client_address = safe_getsockname(udp_sock) # Send registration request to the repeater logger.debug('Registering with the Channel Access repeater.') bytes_to_send = b.send(ca.RepeaterRegisterRequest()) env = get_environment_variables() repeater_port = env['EPICS_CA_REPEATER_PORT'] client_address_list = ca.get_client_address_list() local_address = ca.get_local_address() try: udp_sock.sendto(bytes_to_send, (local_address, repeater_port)) except OSError as exc: raise ca.CaprotoNetworkError( f"Failed to send to {local_address}:{repeater_port}") from exc logger.debug("Searching for %r....", pv_name) commands = (ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION), ca.SearchRequest(pv_name, 0, ca.DEFAULT_PROTOCOL_VERSION)) bytes_to_send = b.send(*commands) tags = { 'role': 'CLIENT', 'our_address': b.client_address, 'direction': '--->>>' } def send_search(): for dest in client_address_list: tags['their_address'] = dest b.log.debug('%d commands %dB', len(commands), len(bytes_to_send), extra=tags) try: udp_sock.sendto(bytes_to_send, dest) except OSError as exc: host, port = dest raise ca.CaprotoNetworkError( f"Failed to send to {host}:{port}") from exc def check_timeout(): nonlocal retry_at if time.monotonic() >= retry_at: send_search() retry_at = time.monotonic() + retry_timeout if time.monotonic() - t > timeout: raise CaprotoTimeoutError( f"Timed out while awaiting a response " f"from the search for {pv_name!r}. Search " f"requests were sent to this address list: " f"{ca.get_address_list()}.") # Initial search attempt send_search() # Await a search response, and keep track of registration status retry_timeout = timeout / max((max_retries, 1)) t = time.monotonic() retry_at = t + retry_timeout try: orig_timeout = udp_sock.gettimeout() udp_sock.settimeout(retry_timeout) while True: try: bytes_received, address = udp_sock.recvfrom(ca.MAX_UDP_RECV) except socket.timeout: check_timeout() continue check_timeout() commands = b.recv(bytes_received, address) b.process_commands(commands) for command in commands: if isinstance(command, ca.SearchResponse) and command.cid == 0: address = ca.extract_address(command) logger.debug('Found %r at %s:%d', pv_name, *address) return address else: # None of the commands we have seen are a reply to our request. # Receive more data. continue finally: udp_sock.settimeout(orig_timeout)
def search(pv_name, udp_sock, timeout, *, max_retries=2): # Set Broadcaster log level to match our logger. b = ca.Broadcaster(our_role=ca.CLIENT) # Send registration request to the repeater logger.debug('Registering with the Channel Access repeater.') bytes_to_send = b.send(ca.RepeaterRegisterRequest()) repeater_port = os.environ.get('EPICS_CA_REPEATER_PORT', 5065) for host in ca.get_address_list(): udp_sock.sendto(bytes_to_send, (host, repeater_port)) logger.debug("Searching for '%s'....", pv_name) bytes_to_send = b.send( ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION), ca.SearchRequest(pv_name, 0, ca.DEFAULT_PROTOCOL_VERSION)) def send_search(): for host in ca.get_address_list(): if ':' in host: host, _, specified_port = host.partition(':') dest = (host, int(specified_port)) else: dest = (host, CA_SERVER_PORT) udp_sock.sendto(bytes_to_send, dest) logger.debug('Search request sent to %r.', dest) def check_timeout(): nonlocal retry_at if time.monotonic() >= retry_at: send_search() retry_at = time.monotonic() + retry_timeout if time.monotonic() - t > timeout: raise TimeoutError(f"Timed out while awaiting a response " f"from the search for {pv_name!r}. Search " f"requests were sent to this address list: " f"{ca.get_address_list()}.") # Initial search attempt send_search() # Await a search response, and keep track of registration status retry_timeout = timeout / max((max_retries, 1)) t = time.monotonic() retry_at = t + retry_timeout try: orig_timeout = udp_sock.gettimeout() udp_sock.settimeout(retry_timeout) while True: try: bytes_received, address = udp_sock.recvfrom(ca.MAX_UDP_RECV) except socket.timeout: check_timeout() continue check_timeout() commands = b.recv(bytes_received, address) b.process_commands(commands) for command in commands: if isinstance(command, ca.SearchResponse) and command.cid == 0: address = ca.extract_address(command) logger.debug('Found %s at %s', pv_name, address) return address else: # None of the commands we have seen are a reply to our request. # Receive more data. continue finally: udp_sock.settimeout(orig_timeout)
def main(*, skip_monitor_section=False): # A broadcast socket udp_sock = ca.bcast_socket() # Register with the repeater. bytes_to_send = b.send(ca.RepeaterRegisterRequest('0.0.0.0')) # TODO: for test environment with specific hosts listed in # EPICS_CA_ADDR_LIST if False: fake_reg = (('127.0.0.1', ca.EPICS_CA1_PORT), [ca.RepeaterConfirmResponse(repeater_address='127.0.0.1')]) b.command_queue.put(fake_reg) else: udp_sock.sendto(bytes_to_send, ('', CA_REPEATER_PORT)) # Receive response data, address = udp_sock.recvfrom(1024) commands = b.recv(data, address) b.process_commands(commands) # Search for pv1. # CA requires us to send a VersionRequest and a SearchRequest bundled into # one datagram. bytes_to_send = b.send(ca.VersionRequest(0, 13), ca.SearchRequest(pv1, 0, 13)) for host in ca.get_address_list(): if ':' in host: host, _, specified_port = host.partition(':') udp_sock.sendto(bytes_to_send, (host, int(specified_port))) else: udp_sock.sendto(bytes_to_send, (host, CA_SERVER_PORT)) print('searching for %s' % pv1) # Receive a VersionResponse and SearchResponse. bytes_received, address = udp_sock.recvfrom(1024) commands = b.recv(bytes_received, address) b.process_commands(commands) c1, c2 = commands assert type(c1) is ca.VersionResponse assert type(c2) is ca.SearchResponse address = ca.extract_address(c2) circuit = ca.VirtualCircuit(our_role=ca.CLIENT, address=address, priority=0) circuit.log.setLevel('DEBUG') chan1 = ca.ClientChannel(pv1, circuit) sockets[chan1.circuit] = socket.create_connection(chan1.circuit.address) # Initialize our new TCP-based CA connection with a VersionRequest. send(chan1.circuit, ca.VersionRequest(priority=0, version=13)) recv(chan1.circuit) # Send info about us. send(chan1.circuit, ca.HostNameRequest('localhost')) send(chan1.circuit, ca.ClientNameRequest('username')) send(chan1.circuit, ca.CreateChanRequest(name=pv1, cid=chan1.cid, version=13)) commands = recv(chan1.circuit) # Test subscriptions. assert chan1.native_data_type and chan1.native_data_count add_req = ca.EventAddRequest(data_type=chan1.native_data_type, data_count=chan1.native_data_count, sid=chan1.sid, subscriptionid=0, low=0, high=0, to=0, mask=1) send(chan1.circuit, add_req) commands = recv(chan1.circuit) if not skip_monitor_section: try: print('Monitoring until Ctrl-C is hit. Meanwhile, use caput to ' 'change the value and watch for commands to arrive here.') while True: commands = recv(chan1.circuit) if commands: print(commands) except KeyboardInterrupt: pass cancel_req = ca.EventCancelRequest(data_type=add_req.data_type, sid=add_req.sid, subscriptionid=add_req.subscriptionid) send(chan1.circuit, cancel_req) commands, = recv(chan1.circuit) # Test reading. send( chan1.circuit, ca.ReadNotifyRequest(data_type=2, data_count=1, sid=chan1.sid, ioid=12)) commands, = recv(chan1.circuit) # Test writing. request = ca.WriteNotifyRequest(data_type=2, data_count=1, sid=chan1.sid, ioid=13, data=(4, )) send(chan1.circuit, request) recv(chan1.circuit) time.sleep(2) send( chan1.circuit, ca.ReadNotifyRequest(data_type=2, data_count=1, sid=chan1.sid, ioid=14)) recv(chan1.circuit) # Test "clearing" (closing) the channel. send(chan1.circuit, ca.ClearChannelRequest(chan1.sid, chan1.cid)) recv(chan1.circuit) sockets.pop(chan1.circuit).close() udp_sock.close()