libusb_bulk_transfer() 阻塞长达60s的原因

以下内容,纯属记录。

最近调查在通过libusb与Android手机进行AOA通信,当手机拔掉,在write线程中会去调用libusb_bulk_transfer()进行相关的动作。但是会出现调用这个函数block长达60s时间才退出返回,即使设置参数timeout也没用。

截取到的log如下:
write线程第 00:00:49.532 开始写,但是直到 00:01:49.728 才返回。但是read线程每200ms timeout都能够正常返回。

01-01 00:39:53.244 D/xxx   (   40): read bulk ret = -7, transferred is 0
01-01 00:39:53.444 D/xxx   (   40): read bulk ret = -7, transferred is 0
01-01 00:39:53.648 D/xxx   (   40): read bulk ret = -7, transferred is 0
01-01 00:39:53.848 D/xxx   (   40): read bulk ret = -7, transferred is 0
01-01 00:00:49.532 D/xxx   (   40): starting write bulk, len = 10
01-01 00:00:49.728 D/xxx   (   40): read bulk ret = -7(LIBUSB_ERROR_TIMEOUT), transferred = 0


01-01 00:01:49.728 D/xxx   (   40): write ret = -7(LIBUSB_ERROR_TIMEOUT), transferred = 0
[  110.944172] usb 2-1: USB disconnect, device number 3
01-01 00:01:49.728 D/xxx   (   40): read bulk ret = -7(LIBUSB_ERROR_TIMEOUT), transferred = 0

主要原因是在read和write线程是异步执行的,在read的过程中又来了一次write,在 00:00:49.728 read返回之前就开始starting write了。我的理解是:两个线程同时调用libusb_bulk_transfer()会出现竞争关系。在libusb中为了解决这种竞争关系,引入了libusb_handle_events_completed() 函数。

关于libusb多线程应用和竞争关系,可以参照:[Multi-threaded applications and asynchronous I/O(翻译)]。
这篇文章我直接通过Google翻译出来,为了以后好理解。

个人理解,解决这个问题的方法主要是在read和write线程之间加锁,保证串行执行。因为在当前的实际情况中,write的时候比较少,串行工作不影响整体性能。

函数调用关系如下:

/** \ingroup syncio
 * Perform a USB bulk transfer. The direction of the transfer is inferred from
 * the direction bits of the endpoint address.
 *
 * For bulk reads, the length field indicates the maximum length of
 * data you are expecting to receive. If less data arrives than expected,
 * this function will return that data, so be sure to check the
 * transferred output parameter.
 *
 * You should also check the transferred parameter for bulk writes.
 * Not all of the data may have been written.
 *
 * Also check transferred when dealing with a timeout error code.
 * libusb may have to split your transfer into a number of chunks to satisfy
 * underlying O/S requirements, meaning that the timeout may expire after
 * the first few chunks have completed. libusb is careful not to lose any data
 * that may have been transferred; do not assume that timeout conditions
 * indicate a complete lack of I/O.
 *
 * \param dev_handle a handle for the device to communicate with
 * \param endpoint the address of a valid endpoint to communicate with
 * \param data a suitably-sized data buffer for either input or output
 * (depending on endpoint)
 * \param length for bulk writes, the number of bytes from data to be sent. for
 * bulk reads, the maximum number of bytes to receive into the data buffer.
 * \param transferred output location for the number of bytes actually
 * transferred.
 * \param timeout timeout (in millseconds) that this function should wait
 * before giving up due to no response being received. For an unlimited
 * timeout, use value 0.
 *
 * \returns 0 on success (and populates transferred)
 * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out (and populates
 * transferred)
 * \returns LIBUSB_ERROR_PIPE if the endpoint halted
 * \returns LIBUSB_ERROR_OVERFLOW if the device offered more data, see
 * \ref packetoverflow
 * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
 * \returns another LIBUSB_ERROR code on other failures
 */
int API_EXPORTED libusb_bulk_transfer(struct libusb_device_handle *dev_handle,
    unsigned char endpoint, unsigned char *data, int length, int *transferred,
    unsigned int timeout)
{
    return do_sync_bulk_transfer(dev_handle, endpoint, data, length,
        transferred, timeout, LIBUSB_TRANSFER_TYPE_BULK);
}
static int do_sync_bulk_transfer(struct libusb_device_handle *dev_handle,
    unsigned char endpoint, unsigned char *buffer, int length,
    int *transferred, unsigned int timeout, unsigned char type)
{
    struct libusb_transfer *transfer = libusb_alloc_transfer(0);
    int completed = 0;
    int r;

    if (!transfer)
        return LIBUSB_ERROR_NO_MEM;

    libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, length,
        bulk_transfer_cb, &completed, timeout);
    transfer->type = type;

    r = libusb_submit_transfer(transfer);
    if (r < 0) {
        libusb_free_transfer(transfer);
        return r;
    }

    while (!completed) {
        r = libusb_handle_events_completed(HANDLE_CTX(dev_handle), &completed);
        if (r < 0) {
            if (r == LIBUSB_ERROR_INTERRUPTED)
                continue;
            libusb_cancel_transfer(transfer);
            while (!completed)
                if (libusb_handle_events_completed(HANDLE_CTX(dev_handle), &completed) < 0)
                    break;
            libusb_free_transfer(transfer);
            return r;
        }
    }

    *transferred = transfer->actual_length;
    switch (transfer->status) {
    case LIBUSB_TRANSFER_COMPLETED:
        r = 0;
        break;
    case LIBUSB_TRANSFER_TIMED_OUT:
        r = LIBUSB_ERROR_TIMEOUT;
        break;
    case LIBUSB_TRANSFER_STALL:
        r = LIBUSB_ERROR_PIPE;
        break;
    case LIBUSB_TRANSFER_OVERFLOW:
        r = LIBUSB_ERROR_OVERFLOW;
        break;
    case LIBUSB_TRANSFER_NO_DEVICE:
        r = LIBUSB_ERROR_NO_DEVICE;
        break;
    default:
        usbi_warn(HANDLE_CTX(dev_handle),
            "unrecognised status code %d", transfer->status);
        r = LIBUSB_ERROR_OTHER;
    }

    libusb_free_transfer(transfer);
    return r;
}
/** \ingroup poll
 * Handle any pending events in blocking mode.
 *
 * Like libusb_handle_events(), with the addition of a completed parameter
 * to allow for race free waiting for the completion of a specific transfer.
 *
 * See libusb_handle_events_timeout_completed() for details on the completed
 * parameter.
 *
 * \param ctx the context to operate on, or NULL for the default context
 * \param completed pointer to completion integer to check, or NULL
 * \returns 0 on success, or a LIBUSB_ERROR code on failure
 * \see \ref mtasync
 */
int API_EXPORTED libusb_handle_events_completed(libusb_context *ctx,
    int *completed)
{
    struct timeval tv;
    tv.tv_sec = 60;
    tv.tv_usec = 0;
    return libusb_handle_events_timeout_completed(ctx, &tv, completed);
}
/** \ingroup poll
 * Handle any pending events.
 *
 * libusb determines "pending events" by checking if any timeouts have expired
 * and by checking the set of file descriptors for activity.
 *
 * If a zero timeval is passed, this function will handle any already-pending
 * events and then immediately return in non-blocking style.
 *
 * If a non-zero timeval is passed and no events are currently pending, this
 * function will block waiting for events to handle up until the specified
 * timeout. If an event arrives or a signal is raised, this function will
 * return early.
 *
 * If the parameter completed is not NULL then after obtaining the event
 * handling lock this function will return immediately if the integer
 * pointed to is not 0. This allows for race free waiting for the completion
 * of a specific transfer.
 *
 * \param ctx the context to operate on, or NULL for the default context
 * \param tv the maximum time to block waiting for events, or an all zero
 * timeval struct for non-blocking mode
 * \param completed pointer to completion integer to check, or NULL
 * \returns 0 on success, or a LIBUSB_ERROR code on failure
 * \see \ref mtasync
 */
int API_EXPORTED libusb_handle_events_timeout_completed(libusb_context *ctx,
    struct timeval *tv, int *completed)

你可能感兴趣的:(USB)