以下内容,纯属记录。
最近调查在通过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)