From 9d36fe7e6f0c5ece4de1b29ec78a15c37c158b1d Mon Sep 17 00:00:00 2001 From: Vladimir Chtchetkine Date: Mon, 26 Mar 2012 10:29:20 -0700 Subject: Implements an asynchronous socket connector with retries The typical usage of the API is as such: 1. The client creates an async connector instance by calling async_socket_connector_new routine, supplying there address of the socket to connect, and a callback to invoke on connection events. 2. The client then proceeds with calling async_socket_connector_connect that would initiate connection attempts. The main job on the client side falls on the client's callback routine that serves the connection events. Once connection has been initiated, the connector will invoke that callback to report current connection status. In general, there are three connection events passed to the callback: 1. Success. 2. Failure. 3. Retry. Typically, when client's callback is called for successful connection, the client will pull connected socket's FD from the connector, and then this FD will be used by the client for I/O on the connected socket. If socket's FD is pulled by the client, it must return ASC_CB_KEEP from the callback. When client's callback is invoked with an error (ASC_CONNECTION_FAILED event), the client has an opportunity to review the error (available in 'errno'), and either abort the connection by returning ASC_CB_ABORT, or schedule a retry by returning ASC_CB_RETRY from the callback. If client returns ASC_CB_ABORT from the callback, the connector will stop connection attempts, and will self-destruct. If ASC_CB_RETRY is returned from the callback, the connector will retry connection attempt after timeout that was set by the caller in the call to async_socket_connector_new routine. When client's callback is invoked with ASC_CONNECTION_RETRY, the client has an opportunity to cancel further connection attempts by returning ASC_CB_ABORT, or it can allow another connection attempt by returning ASC_CB_RETRY. The client has no control over the lifespan of initialized connector instance. It always self-destructs after client's cllback returns with a status other than ASC_CB_RETRY. Change-Id: I39b0057013e45ee10d1ef98905b8a5210656a26c --- android/async-socket-connector.c | 322 +++++++++++++++++++++++++++++++++++++++ android/async-socket-connector.h | 158 +++++++++++++++++++ android/utils/debug.h | 1 + 3 files changed, 481 insertions(+) create mode 100644 android/async-socket-connector.c create mode 100644 android/async-socket-connector.h (limited to 'android') diff --git a/android/async-socket-connector.c b/android/async-socket-connector.c new file mode 100644 index 0000000..daf6a37 --- /dev/null +++ b/android/async-socket-connector.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Encapsulates exchange protocol between the emulator, and an Android device + * that is connected to the host via USB. The communication is established over + * a TCP port forwarding, enabled by ADB. + */ + +#include "qemu-common.h" +#include "android/async-utils.h" +#include "android/utils/debug.h" +#include "android/async-socket-connector.h" +#include "utils/panic.h" +#include "iolooper.h" + +#define E(...) derror(__VA_ARGS__) +#define W(...) dwarning(__VA_ARGS__) +#define D(...) VERBOSE_PRINT(asconnector,__VA_ARGS__) +#define D_ACTIVE VERBOSE_CHECK(asconnector) + +/******************************************************************************** + * Internals + *******************************************************************************/ + +struct AsyncSocketConnector { + /* TCP address for the connection. */ + SockAddress address; + /* I/O looper for asynchronous I/O. */ + Looper* looper; + /* I/O port for asynchronous connection. */ + LoopIo connector_io[1]; + /* Timer that is used to retry asynchronous connections. */ + LoopTimer connector_timer[1]; + /* Asynchronous connector to the socket. */ + AsyncConnector connector[1]; + /* Callback to invoke on connection / connection error. */ + asc_event_cb on_connected_cb; + /* An opaque parameter to pass to the connection callback. */ + void* on_connected_cb_opaque; + /* Retry timeout in milliseconds. */ + int retry_to; + /* Socket descriptor for the connection. */ + int fd; +}; + +/* Asynchronous I/O looper callback invoked by the connector. + * Param: + * opaque - AsyncSocketConnector instance. + * fd, events - Standard I/O callback parameters. + */ +static void _on_async_socket_connector_io(void* opaque, int fd, unsigned events); + +/* Gets socket's address string. */ +AINLINED const char* +_asc_socket_string(AsyncSocketConnector* connector) +{ + return sock_address_to_string(&connector->address); +} + +/* Destroys AsyncSocketConnector instance. + * Param: + * connector - Initialized AsyncSocketConnector instance. + */ +static void +_async_socket_connector_free(AsyncSocketConnector* connector) +{ + if (connector != NULL) { + if (connector->fd >= 0) { + socket_close(connector->fd); + } + + if (connector->looper != NULL) { + loopTimer_done(connector->connector_timer); + looper_free(connector->looper); + } + + sock_address_done(&connector->address); + + AFREE(connector); + } +} + +/* Opens connection socket. + * Param: + * connector - Initialized AsyncSocketConnector instance. + * Return: + * 0 on success, or -1 on failure. + */ +static int +_async_socket_connector_open_socket(AsyncSocketConnector* connector) +{ + /* Open socket. */ + connector->fd = socket_create_inet(SOCKET_STREAM); + if (connector->fd < 0) { + D("Unable to create connector socket for %s. Error: %s", + _asc_socket_string(connector), strerror(errno)); + return -1; + } + + /* Prepare for async I/O on the connector. */ + socket_set_nonblock(connector->fd); + loopIo_init(connector->connector_io, connector->looper, connector->fd, + _on_async_socket_connector_io, connector); + + return 0; +} + +/* Closes connection socket. + * Param: + * connector - Initialized AsyncSocketConnector instance. + * Return: + * 0 on success, or -1 on failure. + */ +static void +_async_socket_connector_close_socket(AsyncSocketConnector* connector) +{ + if (connector->fd >= 0) { + socket_close(connector->fd); + connector->fd = -1; + } +} + +/* Asynchronous connector (AsyncConnector instance) has completed connection + * attempt. + * + * NOTE: Upon exit from this routine AsyncSocketConnector instance might be + * destroyed. So, once this routine is called, there must be no further + * references to AsyncSocketConnector instance passed to this routine. + * Param: + * connector - Initialized AsyncSocketConnector instance. + * status - Status of the connection attempt. + */ +static void +_on_async_socket_connector_connecting(AsyncSocketConnector* connector, + AsyncStatus status) +{ + ASCCbRes action; + int do_retry = 0; + + switch (status) { + case ASYNC_COMPLETE: + loopIo_done(connector->connector_io); + D("Socket %s is connected", _asc_socket_string(connector)); + /* Invoke "on connected" callback */ + action = connector->on_connected_cb(connector->on_connected_cb_opaque, + connector, ASC_CONNECTION_SUCCEEDED); + if (action == ASC_CB_RETRY) { + do_retry = 1; + } else if (action == ASC_CB_ABORT) { + _async_socket_connector_close_socket(connector); + } + break; + + case ASYNC_ERROR: + loopIo_done(connector->connector_io); + D("Error %d while connecting to socket %s: %s", + errno, _asc_socket_string(connector), strerror(errno)); + /* Invoke "on connected" callback */ + action = connector->on_connected_cb(connector->on_connected_cb_opaque, + connector, ASC_CONNECTION_FAILED); + if (action == ASC_CB_RETRY) { + do_retry = 1; + } else if (action == ASC_CB_ABORT) { + _async_socket_connector_close_socket(connector); + } + break; + + case ASYNC_NEED_MORE: + return; + } + + if (do_retry) { + D("Retrying connection to socket %s", _asc_socket_string(connector)); + loopTimer_startRelative(connector->connector_timer, connector->retry_to); + } else { + _async_socket_connector_free(connector); + } +} + +static void +_on_async_socket_connector_io(void* opaque, int fd, unsigned events) +{ + AsyncSocketConnector* const connector = (AsyncSocketConnector*)opaque; + + /* Complete socket connection. */ + const AsyncStatus status = asyncConnector_run(connector->connector); + _on_async_socket_connector_connecting(connector, status); +} + +/* Retry connection timer callback. + * Param: + * opaque - AsyncSocketConnector instance. + */ +static void +_on_async_socket_connector_retry(void* opaque) +{ + AsyncSocketConnector* const connector = (AsyncSocketConnector*)opaque; + + /* Invoke the callback to notify about a connection retry attempt. */ + const ASCCbRes action = + connector->on_connected_cb(connector->on_connected_cb_opaque, + connector, ASC_CONNECTION_RETRY); + + if (action == ASC_CB_RETRY) { + AsyncStatus status; + + /* Close handle opened for the previous (failed) attempt. */ + _async_socket_connector_close_socket(connector); + + /* Retry connection attempt. */ + if (_async_socket_connector_open_socket(connector) == 0) { + status = asyncConnector_init(connector->connector, &connector->address, + connector->connector_io); + } else { + status = ASYNC_ERROR; + } + + _on_async_socket_connector_connecting(connector, status); + } else { + _async_socket_connector_free(connector); + } +} + +/******************************************************************************** + * Async connector implementation + *******************************************************************************/ + +AsyncSocketConnector* +async_socket_connector_new(const SockAddress* address, + int retry_to, + asc_event_cb cb, + void* cb_opaque) +{ + AsyncSocketConnector* connector; + + if (cb == NULL) { + W("No callback for AsyncSocketConnector for %s", + sock_address_to_string(address)); + errno = EINVAL; + return NULL; + } + + ANEW0(connector); + + connector->fd = -1; + connector->retry_to = retry_to; + connector->on_connected_cb = cb; + connector->on_connected_cb_opaque = cb_opaque; + + /* Copy socket address. */ + if (sock_address_get_family(address) == SOCKET_UNIX) { + sock_address_init_unix(&connector->address, sock_address_get_path(address)); + } else { + connector->address = *address; + } + + /* Create a looper for asynchronous I/O. */ + connector->looper = looper_newCore(); + if (connector->looper != NULL) { + /* Create a timer that will be used for connection retries. */ + loopTimer_init(connector->connector_timer, connector->looper, + _on_async_socket_connector_retry, connector); + } else { + E("Unable to create I/O looper for asynchronous connector to socket %s", + _asc_socket_string(connector)); + _async_socket_connector_free(connector); + return NULL; + } + + return connector; +} + +ASCConnectRes +async_socket_connector_connect(AsyncSocketConnector* connector) +{ + AsyncStatus status; + + if (_async_socket_connector_open_socket(connector) == 0) { + status = asyncConnector_init(connector->connector, &connector->address, + connector->connector_io); + } else { + status = ASYNC_ERROR; + } + + _on_async_socket_connector_connecting(connector, status); + + switch (status) { + case ASYNC_COMPLETE: + return ASC_CONNECT_SUCCEEDED; + + case ASYNC_ERROR: + return ASC_CONNECT_FAILED; + + case ASYNC_NEED_MORE: + default: + return ASC_CONNECT_IN_PROGRESS; + } +} + +int +async_socket_connector_pull_fd(AsyncSocketConnector* connector) +{ + const int fd = connector->fd; + if (fd >= 0) { + connector->fd = -1; + } + return fd; +} diff --git a/android/async-socket-connector.h b/android/async-socket-connector.h new file mode 100644 index 0000000..998d0c8 --- /dev/null +++ b/android/async-socket-connector.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_ASYNC_SOCKET_CONNECTOR_H_ +#define ANDROID_ASYNC_SOCKET_CONNECTOR_H_ + +/* + * Contains declaration of an API that allows asynchronous connection to a + * socket with retries. + * + * The typical usage of this API is as such: + * + * 1. The client creates an async connector instance by calling async_socket_connector_new + * routine, supplying there address of the socket to connect, and a callback + * to invoke on connection events. + * 2. The client then proceeds with calling async_socket_connector_connect that + * would initiate connection attempts. + * + * The main job on the client side falls on the client's callback routine that + * serves the connection events. Once connection has been initiated, the connector + * will invoke that callback to report current connection status. + * + * In general, there are three connection events passed to the callback: + * 1. Success. + * 2. Failure. + * 3. Retry. + * + * Typically, when client's callback is called for successful connection, the + * client will pull connected socket's FD from the connector, and then this FD + * will be used by the client for I/O on the connected socket. If socket's FD + * is pulled by the client, it must return ASC_CB_KEEP from the callback. + * + * When client's callback is invoked with an error (ASC_CONNECTION_FAILED event), + * the client has an opportunity to review the error (available in 'errno'), and + * either abort the connection by returning ASC_CB_ABORT, or schedule a retry + * by returning ASC_CB_RETRY from the callback. If client returns ASC_CB_ABORT + * from the callback, the connector will stop connection attempts, and will + * self-destruct. If ASC_CB_RETRY is returned from the callback, the connector + * will retry connection attempt after timeout that was set by the caller in the + * call to async_socket_connector_new routine. + * + * When client's callback is invoked with ASC_CONNECTION_RETRY, the client has an + * opportunity to cancel further connection attempts by returning ASC_CB_ABORT, + * or it can allow another connection attempt by returning ASC_CB_RETRY. + * + * The client has no control over the lifespan of initialized connector instance. + * It always self-destructs after client's cllback returns with a status other + * than ASC_CB_RETRY. + */ + +/* Declares async socket connector descriptor. */ +typedef struct AsyncSocketConnector AsyncSocketConnector; + +/* Enumerates connection events. + * Values from this enum are passed to the callback that connector's client uses + * to monitor connection status / progress. + */ +typedef enum ASCEvent { + /* Connection with the socket has been successfuly established. */ + ASC_CONNECTION_SUCCEEDED, + /* A failure has occured while establising connection, with errno containing + * the actual error. */ + ASC_CONNECTION_FAILED, + /* Async socket connector is about to retry the connection. */ + ASC_CONNECTION_RETRY, +} ASCEvent; + +/* Enumerates return values from the callback to the connector's client. + */ +typedef enum ASCCbRes { + /* Keep established connection. */ + ASC_CB_KEEP, + /* Abort connection attempts. */ + ASC_CB_ABORT, + /* Retry connection attempt. */ + ASC_CB_RETRY, +} ASCCbRes; + +/* Enumerates values returned from the connector routine. + */ +typedef enum ASCConnectRes { + /* Connection has succeeded in the connector routine. */ + ASC_CONNECT_SUCCEEDED, + /* Connection has failed in the connector routine. */ + ASC_CONNECT_FAILED, + /* Connection is in progress, and will be completed asynchronously. */ + ASC_CONNECT_IN_PROGRESS, +} ASCConnectRes; + +/* Declares callback that connector's client uses to monitor connection + * status / progress. + * Param: + * opaque - An opaque pointer associated with the client. + * connector - Connector instance for thecallback. + * event - Event that has occured. If event is set to ASC_CONNECTION_FAILED, + * errno contains connection error. + * Return: + * One of ASCCbRes values. + */ +typedef ASCCbRes (*asc_event_cb)(void* opaque, + AsyncSocketConnector* connector, + ASCEvent event); + +/* Creates and initializes AsyncSocketConnector instance. + * Param: + * address - Initialized socket address to connect to. + * retry_to - Retry timeout in milliseconds. + * cb, cb_opaque - Callback to invoke on connection events. This callback is + * required, and must not be NULL. + * Return: + * Initialized AsyncSocketConnector instance. Note that AsyncSocketConnector + * instance returned from this routine will be destroyed by the connector itself, + * when its work on connecting to the socket is completed. Typically, the + * connector wil destroy the descriptor after client's callback routine returns + * with the status other than ASC_CB_RETRY. + */ +extern AsyncSocketConnector* async_socket_connector_new(const SockAddress* address, + int retry_to, + asc_event_cb cb, + void* cb_opaque); + +/* Initiates asynchronous connection. + * Param: + * connector - Initialized AsyncSocketConnector instance. + * Return: + * Status indicating state of the connection: completed, failed, or in progress. + * Note that the connector will always invoke a callback passed to the + * async_socket_connector_new routine prior to exiting from this routine with + * statuses other ASC_CONNECT_IN_PROGRESS. + */ +extern ASCConnectRes async_socket_connector_connect(AsyncSocketConnector* connector); + +/* Pulls socket's file descriptor from the connector. + * This routine should be called from the connection callback on successful + * connection status. This will provide the connector's client with operational + * socket FD, and at the same time this will tell the connector not to close + * the FD when connector descriptor gets destroyed. + * Param: + * connector - Initialized AsyncSocketConnector instance. + * Return: + * File descriptor for the connected socket. + */ +extern int async_socket_connector_pull_fd(AsyncSocketConnector* connector); + +#endif /* ANDROID_ASYNC_SOCKET_CONNECTOR_H_ */ diff --git a/android/utils/debug.h b/android/utils/debug.h index e97ec43..d7de9da 100644 --- a/android/utils/debug.h +++ b/android/utils/debug.h @@ -44,6 +44,7 @@ _VERBOSE_TAG(adbserver, "ADB server") \ _VERBOSE_TAG(adbclient, "ADB QEMU client") \ _VERBOSE_TAG(adb, "ADB debugger") \ + _VERBOSE_TAG(asconnector, "Asynchronous socket connector") \ #define _VERBOSE_TAG(x,y) VERBOSE_##x, typedef enum { -- cgit v1.1