# 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.')