webusb icon indicating copy to clipboard operation
webusb copied to clipboard

Transfer calls need timeout/cancellation

Open krockot opened this issue 10 years ago • 22 comments

Callers should be able to cancel transfers, at least with a timeout argument if not a more general solution. Two ways I could see cancellation happening:

1. Introduce a new interface for return

interface PendingTransfer {
    readonly attribute Promise<ArrayBuffer> promise;
    void cancel();
};

All transfer calls would return this instead of a Promise<ArrayBuffer>. This seems undesirable to me because then usage looks like:

device.controlTransferIn(foo, bar, ...).promise.then(() => ...)

Pretty ugly.

2. Introduce a new Waiter interface that can be passed to transfer calls

interface TransferWaiter {
    void cancel();
};

The pattern then looks like this:

let waiter = new TransferWaiter;
device.controlTransferIn(foo, bar, ..., waiter).then(...).catch(...);
setTimeout(function() { waiter.cancel(); }, 100);

This would address the more general cancellation concern without a need to introduce an explicit timeout argument. Still not a common pattern, but it seems reasonable to me.

Note: "waiter" may be a misnomer.

krockot avatar Jul 21 '15 16:07 krockot

Oh, and of course the third option is to just introduce a millisecond timeout argument to transfer calls.

krockot avatar Jul 21 '15 16:07 krockot

Cancelable promises are coming soon... Might be OK to wait on those?

domenic avatar Jul 21 '15 16:07 domenic

SGTM. We can move forward without cancelable transfers for now, and switch to cancelable promises when they're ready.

krockot avatar Jul 21 '15 18:07 krockot

Timeouts and cancels are necessary for some USB devices. For instance, a common pattern is for a driver to maintain an outstanding read on a particular end point, to allow the device to send an asynchronous notification (i.e. a user button press). Implicit in this is the ability for several transfers to be outstanding (to different end points) from different threads.

gwgill avatar Apr 12 '16 00:04 gwgill

@gwgill This API is asynchronous so there's no need to maintain separate threads for reading from different endpoints.

Timeouts and cancels are currently unspecified because I am still investigating what behavior is possible to implement cross-platform. The USB specification itself has no concept of either of these. It is a function of the USB host controller interface whether or not it is possible to cancel a transfer and how destructive that operation is to other transfers.

reillyeon avatar Apr 12 '16 00:04 reillyeon

This API is asynchronous so there's no need to maintain separate threads for reading from different endpoints.

It may be possible without threads, but not natural or easy from a control flow point of view, nor from the point of view of translating existing driver code.

Timeouts/cancels are possible on MSWindows, Linux/Android and OS X (with some trouble for the latter), so I'm not sure what platforms you are thinking of that don't support it.

It's fine to say "we're not supporting USB devices that require timeouts and/or cancels because it makes our API and platform support too hard", as long as you are aware that there are some USB devices that won't work without this. It's certainly possible that such devices are too complex for anyone to want to provide any sort of Web based USB driver for them.

gwgill avatar Apr 12 '16 01:04 gwgill

The proposal for cancellable promises has been withdrawn. I believe we should follow the lead of the Fetch API in adding a mechanism to cancel a transfer.

reillyeon avatar Mar 04 '17 00:03 reillyeon

@reillyeon way to cancel the transfer would indeed be useful.

karelbilek avatar Mar 29 '18 11:03 karelbilek

Would a mechanism that cancelled all outstanding transfers on a given endpoint (control transfers would be uncancellable) be sufficient?

reillyeon avatar Mar 29 '18 18:03 reillyeon

did this ever go anywhere?

brandonros avatar Jul 21 '20 19:07 brandonros

This hasn't made progress due to the previously mentioned cross-platform concerns. Do you have a particular device that requires transfer cancelation? Is canceling all transfers on an endpoint sufficient?

reillyeon avatar Jul 21 '20 19:07 reillyeon

@reillyeon https://github.com/WICG/webusb/issues/25#issuecomment-208661215 seems to suggest that all major platforms support cancelation? Can we always do whatever libusb_cancel_transfer does?

RReverser avatar Jun 17 '21 13:06 RReverser

The dirty secret of libusb_cancel_transfer is that it doesn't actually support cancelling specific transfers on platforms other than Linux. On macOS and Windows it will cancel all pending transfers on the endpoint the provided transfer was submitted to. If we supported this operating in WebUSB we would probably implement this behavior across all platforms.

reillyeon avatar Jun 18 '21 01:06 reillyeon

The dirty secret of libusb_cancel_transfer is that it doesn't actually support cancelling specific transfers on platforms other than Linux.

Oh lol okay. I'd never guess this from the API...

RReverser avatar Jun 18 '21 13:06 RReverser

Is this going to be implemented? This is useful for some applications that need to do asynchronous transfers (without waiting for a result.)

anirudhb avatar Jan 13 '22 23:01 anirudhb

Is this going to be implemented? This is useful for some applications that need to do asynchronous transfers (without waiting for a result.)

Can you describe this use case in more detail? If you don't need to wait for the result of a USB transfer you can simply not await the Promise returned by the transfer function. Canceling transfers is only useful if you've submitted a transfer and no longer want the device to be given the opportunity to complete the transfer. That is, you want it removed from the USB host controller's transfer schedule.

reillyeon avatar Jan 14 '22 02:01 reillyeon

I want to request a transfer and cancel it after a specific timeout, or just have the ability to cancel it if I don't want it to be completed.

anirudhb avatar Jan 14 '22 02:01 anirudhb

I wonder if WebUSB trying to do best-effort thing on unsupported platforms would help: if it knows there's only one in-flight transfer, it can use the proper underlying cancellation mechanism to "cancel all transfers", and if there's more than one, then it would immediately reject the promise, and ignore the result of said transfer when it eventually comes back.

I imagine in most real-world scenarios there will be only one in-flight transfer anyway, so such optimisation would actually be helpful.

RReverser avatar Jan 15 '22 14:01 RReverser

Can you describe this use case in more detail? If you don't need to wait for the result of a USB transfer you can simply not await the Promise returned by the transfer function.

I actually recently ran into a corner case of a device's behavior that would make cancelling useful. I'm currently working on talking to Zebra thermal label printers, which use a text-based protocol to print labels. Occasionally I have managed to 'wedge' the printer's communication channel, such that transferring commands to the printer will hang indefinitely. In this situation the only remedy is power-cycling the printer. Being able to detect this scenario (timeout) and act upon it (cancel all transfers, or maybe in my case close the connection entirely and alert the user) would be useful.

I'm working around this problem by racing two promises for a timeout, closing the whole connection, swallowing the resulting exception on the send promise, and notifying the user to power-cycle the printer.

Cellivar avatar Dec 13 '22 09:12 Cellivar

Being able to detect this scenario (timeout) and act upon it (cancel all transfers, or maybe in my case close the connection entirely and alert the user) would be useful.

As much as I'd like to still see best-effort transfer cancellation available via WebUSB API, it sounds like your particular scenario is better covered by the existing .reset() method instead.

RReverser avatar Nov 18 '23 23:11 RReverser

As much as I'd like to still see best-effort transfer cancellation available via WebUSB API, it sounds like your particular scenario is better covered by the existing .reset() method instead.

Thanks for the suggestion! The last time I took a look this seemed very heavy-handed compared to what I actually need to do. The devices I'm working with seem to incorrectly refuse to process commands while in an error state, however if you stop sending regular commands and switch to sending error status request commands it will process them. The semantics are not a connection reset, rather, it's stop asking the wrong question and ask the right question. This behavior is much more closely modeled as a timeout/cancel followed by a new bulk out transfer with different contents.

It's been a while since I explored the reset option, I'll give it a spin and see if I can find a graceful method in there.

Cellivar avatar Nov 20 '23 19:11 Cellivar

The last time I took a look this seemed very heavy-handed compared to what I actually need to do.

Ah. Well, without knowing the specifics, you did mention that "maybe in my case close the connection entirely and alert the user" is an acceptable option, so I figured reset might be less heavy-handed than that, as it should cancel all transfers like you want & reset the device to a working state without completely disconnecting. It is more heavy-handed than just transfer cancellation though.

RReverser avatar Nov 20 '23 20:11 RReverser