| # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import socket |
| import threading |
| from multiprocessing import connection |
| |
| |
| def connect(address, timeout=30): |
| """Creates an AsyncListener client for testing purposes. |
| |
| @param address: Address of the socket to connect to. |
| @param timeout: Connection timeout, in seconds. Default is 30. |
| |
| @raises socket.timeout: If a connection is not made before the timeout |
| expires. |
| """ |
| return _ClientFactory(address).connect(timeout) |
| |
| |
| class _ClientFactory(threading.Thread): |
| """Factory class for making client connections with a timeout. |
| |
| Instantiate this with an address, and call connect. The factory will take |
| care of polling for a connection. If a connection is not established within |
| a set period of time, the make_connction call will raise a socket.timeout |
| exception instead of hanging indefinitely. |
| """ |
| def __init__(self, address): |
| super(_ClientFactory, self).__init__() |
| # Use a daemon thread, so that if this thread hangs, it doesn't keep the |
| # parent thread alive. All daemon threads die when the parent process |
| # dies. |
| self.daemon = True |
| self._address = address |
| self._client = None |
| |
| |
| def run(self): |
| """Instantiates a connection.Client.""" |
| self._client = connection.Client(self._address) |
| |
| |
| def connect(self, timeout): |
| """Attempts to create a connection.Client with a timeout. |
| |
| Every 5 seconds a warning will be logged for debugging purposes. After |
| the timeout expires, the function will raise a socket.timout error. |
| |
| @param timeout: A connection timeout, in seconds. |
| |
| @return: A connection.Client connected using the address that was |
| specified when this factory was created. |
| |
| @raises socket.timeout: If the connection is not established before the |
| given timeout expires. |
| """ |
| # Start the thread, which attempts to open the connection. |
| self.start() |
| # Poll approximately once a second, so clients don't wait forever. |
| for i in range(1, timeout + 1): |
| self.join(1) |
| if self._client is not None: |
| return self._client |
| |
| # Log a warning when we first detect a potential problem, then every |
| # 5 seconds after that. |
| if i < 3 or i % 5 == 0: |
| logging.warning( |
| 'Test client failed to connect after %s seconds', i) |
| # Still no connection - time out. |
| raise socket.timeout('Test client timed out waiting for connection.') |