[!NOTE]
Marking this as draft for now, due to severe test failures
If anyone comes by and sees this PR, and wants the functionality it provides, feel free to pick up development.
I don't think I'll be able to work on this for quite a while.
This depends on #31
This PR creates an alternative server loop, in which the incoming URB requests are handled in parallel. There are some kinds of devices which rely on this feature, where it might send several requests to different interfaces, or even just different endpoints on the same interface before the device will send the URB responses.
The flow would look something like this:
sequenceDiagram
HOST->>IN EP: BULK IN REQ
HOST->>OUT EP: BULK OUT REQ
OUT EP->>HOST: BULK OUT RES
IN EP->>HOST: BULK IN RES
In order to solve this, I've made use of the async-scoped library, to allow us to keep API with the socket trait object from UsbIpServer::handler. If I've understood it correctly, this is because the trait object is not static and lives on the stack, while normal multithreaded async tokio requires everything to have a static lifetime, as it cannot provide any guarantees about how long the threads are going to live. The library solves this by providing unsafe async functions that assumes you've ensured the threads will be shut down before closing the scope.
This library also seem to be part of the reason why the tests are failing. I am not exactly sure what is going on, but the MockSocket tests are flaky and the TCP Socket tests seem to crash or deadlock. I believe there is either one or more race conditions, which leads to the wakers potentially getting dropped. However, this only seems to be the test driver, as the implementation has been working with some basic interaction testing.
The current design shares the socket between two threads, where one thread (the rx thread) is responsible for handling the incoming packages, and transferring responses to the other thread (the tx thread). The tx thread is responsible for sending all incoming messages. In the event that an USBIP_CMD_SUBMIT is requested, the rx thread will spin up a new thread for handling this request and then sending the response to the tx thread. This ensures the rx thread is ready to receive new requests, and can then spin up multiple USBIP_CMD_SUBMIT handler threads simultaneously.
[ ] Write some more tests, especially ones verifying that the code can handle requests in parallel.
[ ] Clean up some .unwrap()s
[ ] Deduplicate some of the code, especially the tests.
[ ] Add some more rigid and human readable documentation than the API docs,
explaining the difference between the implementations, and when to use which one.
[ ] Make the traces, warnings, etc. more consistent accross the implementations.
[ ] Handle graceful shutdown of USBIP_CMD_SUBMIT handler threads in a cleaner way (there's nothing aborting them when rx/tx thread dies, they'll currently just die off on their own whenever the device is finished doing whatever it needs to)
[ ] See if the final implementation will allow for dropping some trait requirements for the socket in UsbIpServer::handler
Asynchronous server loop
If anyone comes by and sees this PR, and wants the functionality it provides, feel free to pick up development. I don't think I'll be able to work on this for quite a while.
This depends on #31
This PR creates an alternative server loop, in which the incoming URB requests are handled in parallel. There are some kinds of devices which rely on this feature, where it might send several requests to different interfaces, or even just different endpoints on the same interface before the device will send the URB responses.
The flow would look something like this:
In order to solve this, I've made use of the async-scoped library, to allow us to keep API with the socket trait object from
UsbIpServer::handler
. If I've understood it correctly, this is because the trait object is not static and lives on the stack, while normal multithreaded async tokio requires everything to have a static lifetime, as it cannot provide any guarantees about how long the threads are going to live. The library solves this by providing unsafe async functions that assumes you've ensured the threads will be shut down before closing the scope.This library also seem to be part of the reason why the tests are failing. I am not exactly sure what is going on, but the
MockSocket
tests are flaky and the TCP Socket tests seem to crash or deadlock. I believe there is either one or more race conditions, which leads to the wakers potentially getting dropped. However, this only seems to be the test driver, as the implementation has been working with some basic interaction testing.The current design shares the socket between two threads, where one thread (the rx thread) is responsible for handling the incoming packages, and transferring responses to the other thread (the tx thread). The tx thread is responsible for sending all incoming messages. In the event that an
USBIP_CMD_SUBMIT
is requested, the rx thread will spin up a new thread for handling this request and then sending the response to the tx thread. This ensures the rx thread is ready to receive new requests, and can then spin up multipleUSBIP_CMD_SUBMIT
handler threads simultaneously.Breaking Changes
async-trait
andasync-scoped
UsbIpServer
has been remade into a traitUsbIpServer
has been renamed toSyncUsbIpServer
and is now namespaced beneathusbip::server
AsyncUsbIpServer
struct which implementsUsbIpServer
, also located atusbip::server
usbip
namespace (in earlierlib.rs
) has been moved into theUsbIpServer
traitSend
trait restriction to handlersocket
.Clone
restriction toUsbIpServer
, meaning theArc
is no longer required when working with it.Some remaining TODOs of varying importance:
async-trait
to upcoming rust async traits.unwrap()
sUSBIP_CMD_SUBMIT
handler threads in a cleaner way (there's nothing aborting them when rx/tx thread dies, they'll currently just die off on their own whenever the device is finished doing whatever it needs to)UsbIpServer::handler