Skip to content

Conversation

@DanielG
Copy link
Contributor

@DanielG DanielG commented Apr 2, 2020

In developing a custom USB->DMX widget for OLA I've noticed that packet latency isn't as consistent as possible when only using a single libusb transfer at a time. Hence here I add support for more than one and also fix a nasty use after free hazard while I'm at it.

Commit messages copied below:

usbdmx: Add support for having multiple inflight transfers

Currently we only use a single active transfer for both sending and
reveiving usb packets.

This is suboptimal in terms of latency, USB actually guarantees certain
turnaround times for interrupt transfers for example but that only really
works if there is always an IRP ready to go in the USB host controller.

According to my measurments interrupt transfers with bInterval=1 can be
delayed by some tens of milliseconds instead of happening evey millisecond
on the dot if there are periods with no transfers ready to go.

In order to support multiple transfers being inflight I replace the
transfer state machinery in AsyncUsbTransceiverBase with a set of inflight
transfers and a queue of idle transfers.

The bookkeeping happens in a new TransferCompleteInternal method which
wraps the TransferComplete() method provided by subclasses. This split also
allowed moving some duplicated logic for LIBUSB_TRANSFER_NO_DEVICE to the
superclass.

The number of transfers to allow is passed to AsyncUsb{Sender,Receiver} as
a constructor argument since supporting more than one needs some
cooperation from the derived classes. Currently everything just defaults to
one transfer which shouldn't change the behaviour one bit.


usbdmx: Remove redundant CancelTransfer() calls:

The superclass destructor already calls CancelTransfer, no need to do it in
the derived classes also. To prevent this in the future this also make
CancelTransfer private.


usbdmx: Fix use after free hazard in destructor

(see diff for commentary)

@DanielG
Copy link
Contributor Author

DanielG commented Apr 3, 2020

Changes:

  • Actually {fire off,allow} up to the maximum number of transfers in the {receiver,sender}. Since PerformTransfer() is handled in the AsyncUsb{Sender,Receiver} classes we have to do the transfer count handling there.

In theory this should mean widget specific classes shouldn't even be able to notice what the transfer queue depth actually is, so maybe we can just turn this on across all widgets.

@DanielG DanielG force-pushed the usbdmx-inflight branch 2 times, most recently from 009b767 to 4d32528 Compare April 6, 2020 11:45
@DanielG
Copy link
Contributor Author

DanielG commented Apr 6, 2020

Changes:

  • Fix the use-after-free fix :)
    AsyncUsb{Sender,Receiver} have to call CancelTransfer, not the base class, because they close the device handle and we must also not free the device before the transfers complete!

Copy link
Member

@peternewman peternewman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial comments

Comment on lines 50 to 57
inflight_set::iterator i = m_inflight.find(transfer);
if (i == m_inflight.end()) { // not found
OLA_WARN << "Mismatched libusb transfer: "
<< transfer << " not in inflight set";
return;
}

m_inflight.erase(i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be easier replacing with our ola::STLLookupAndRemove? http://docs.openlighting.org/ola/doc/latest/group__stl.html#ga7a896801adfcea56e630bd5007c135e1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't seem to work for sets? Even if it would it's a bit akward. Since STLLookupAndRemove doesn't check if the value arg is NULL I'd have to provide a dummy.

./include/ola/stl/STLUtils.h:393:6: note: candidate: ‘template<class T1> bool ola::STLLookupAndRemove(T1*, const typename T1::key_type&, typename T1::mapped_type*)’
 bool STLLookupAndRemove(T1 *container,
      ^~~~~~~~~~~~~~~~~~
./include/ola/stl/STLUtils.h:393:6: note:   template argument deduction/substitution failed:
plugins/usbdmx/AsyncUsbTransceiverBase.cpp:52:59: note:   mismatched types ‘T1*’ and ‘std::set<libusb_transfer*>’
     if (STLLookupAndRemove(m_inflight, transfer, transfer_)) { // not found
                                                           

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, wrong command, after looking more carefully ola::STLRemove will do what you want:
http://docs.openlighting.org/ola/doc/latest/group__stl.html#ga6bab5568a6d6ef14d4faccea9eddd41b

Or is there a risk of multiple equal transfers in the set (I assume they have a unique ID, but maybe not)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is there a risk of multiple equal transfers in the set (I assume they have a unique ID, but maybe not)?

Reading more carefully, they have to be unique, so no risk there.

Copy link
Contributor Author

@DanielG DanielG Nov 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to push back on this, but honestly I don't see STLRemove having any advantage over calling .erase() here. All this change would do is invalidate my testing and perform another unnecessary traversal to find the right element again. A combined lookup and remove would have cleaned up the code a bit but for sure, but just with just the removal I don't see the point.

@DanielG DanielG force-pushed the usbdmx-inflight branch 2 times, most recently from e7dab7f to 39198c4 Compare February 22, 2021 08:23
@DanielG
Copy link
Contributor Author

DanielG commented Feb 22, 2021

Changes:

  • Fix typo emtpy -> empty

@DanielG
Copy link
Contributor Author

DanielG commented Feb 22, 2021

Changes:

  • Fix cpplint complaint about two spaces after code, before comment.

@DanielG DanielG requested a review from peternewman February 28, 2021 08:06
Comment on lines 50 to 57
inflight_set::iterator i = m_inflight.find(transfer);
if (i == m_inflight.end()) { // not found
OLA_WARN << "Mismatched libusb transfer: "
<< transfer << " not in inflight set";
return;
}

m_inflight.erase(i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, wrong command, after looking more carefully ola::STLRemove will do what you want:
http://docs.openlighting.org/ola/doc/latest/group__stl.html#ga6bab5568a6d6ef14d4faccea9eddd41b

Or is there a risk of multiple equal transfers in the set (I assume they have a unique ID, but maybe not)?

Copy link
Member

@peternewman peternewman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still can't get use to C++'s pop not being like this:
https://perldoc.perl.org/functions/pop

A few more, mostly minor, comments.

Currently we only use a single active transfer for both sending and
reveiving usb packets.

This is suboptimal in terms of latency, USB actually guarantees certain
turnaround times for interrupt transfers for example but that only really
works if there is always an IRP ready to go in the USB host controller.

According to my measurments interrupt transfers with bInterval=1 can be
delayed by some tens of milliseconds instead of happening evey millisecond
on the dot if there are periods with no transfers ready to go.

In order to support multiple transfers being inflight I replace the
transfer state machinery in AsyncUsbTransceiverBase with a set of inflight
transfers and a queue of idle transfers.

The bookkeeping happens in a new TransferCompleteInternal method which
wraps the TransferComplete() method provided by subclasses. This split also
allowed moving some duplicated logic for LIBUSB_TRANSFER_NO_DEVICE to the
superclass.

The number of transfers to allow is passed to AsyncUsb{Sender,Receiver} as
a constructor argument since supporting more than one needs some
cooperation from the derived classes. Currently everything just defaults to
one transfer which shouldn't change the behaviour one bit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants