jiegec / usbip

A Rust library to run a USB/IP server
MIT License
278 stars 26 forks source link

Create an asynchronous server loop #34

Open h7x4 opened 11 months ago

h7x4 commented 11 months ago

Asynchronous server loop

[!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.

Breaking Changes

Some remaining TODOs of varying importance: