GATT operation in progress - how to handle it? #188
|
Testing this on Android (Chrome Dev build) and soon in Opera internal build. |
|
I believe this issue has been fixed recently (https://codereview.chromium.org/1465863003/). |
|
Tested the latest Chromium and the crash when "GATT operation in progress" occurs has been fixed - so that is good. But my main question is still open: is it the responsibility of the page author to handle "GATT operation in progress" or should this be handled by the implementation? There seems to be pros and cons with both approaches. |
|
The implementation's supposed to handle it, but we don't yet (https://crbug.com/560862). @scheib has argued that we should make the web page handle it instead, in which case the rule would be that you wait for the |
|
We should keep the implementation flexible, and not force queuing if the application doesn't desire it. 12:35 AM Hi, short question about web-bluetooth: I have a page that writes to a characteristic whenever a button is pressed (or actually a touch event), but if I click to fast I get the "GATT operation in progress"-error. Is it so that the page author is supposed to handle this with a queue or similar, or will the implementation do the queing? |
|
Truly low power devices will be setting connection intervals of +100ms, and it is highly likely that only one GATT operation may be handled per connection interval, arguably the page author does not need to know the GATT operations have entered a queue, but it will need to know which ones have been executed. Imagine a case where the web page has written to a characteristic expecting the device behaviour to change, if that operation is 4th in the queue, the device may still send notification indicating it is still performing with the previous old behaviour, what is the web page to do given that it thinks the GATT operations have already been sent? |
|
When the web page wants to know which writes have completed, it needs to attach a handler to the Promise returned by |
|
I think we should keep the implementation as simple as possible for now. Writing a library/polyfill to queue GATT operations is fairly trivial and would allow us to iterate faster if unforeseen problems arise. |
|
Agree with @scheib about keeping the implementation flexible. It is easy enough to wait for fast write operations and not all apps need to have queuing. |
|
SG. In order to write the queuing library, we'd need to guarantee that two separate tabs using Bluetooth concurrently, or a tab and an extension/app, or a tab and the OS, couldn't cause the tab to see an |
|
We don't get any signals from the system that the there is an operation in progress. Which also means our implementation of queuing couldn't guarantee no |
|
I'd strongly suggest implementing the queue directly inside the UA. That's what CoreBluetooth does (and I think Windows as well if I'm not mistaken). Just see Of course it's easier with promises to implement a queue yourself rather than having a global complete event as in Android, but you still would have the problem that you need to wrap all kind of GATT operations in an additional layer. The problem with if a user enqueues requests faster than the peripheral can consume is pretty small I think. Normally you send so small amounts of data that upper bound is not a problem and the problem would exist regardless if the queue implementation is in the UA or the js app. The js app programmer could still implement flow-prevention mechanisms by waiting for the promise to finish before issuing the next request if he is worried about sending data too fast. |
|
I'm convinced that at a minimum we should ensure a queuing library is available that is easy to use. And, I agree that long term this is best to build into the UA. I see & agree that my concern that an app may not want queueing is resolved by the app waiting for each promise to resolve before proceeding. How many times should an operation be retried, or how long to wait? I see the Nordic puck central sample uses a timer to abort all pending 'bundled' operations. I'm a bit concerned that these are opinionated decisions that either: need to be spec.ed, UA has to make a call for, or options need to be exposed. |
|
How do you mean that an operation is retried? The request is sent to the remote device and then it responds either with success (promise resolves) or some error code (promise rejects). The only problems that can happen are that either the link gets disconnected or the GATT timer specified by the BLE standard of 30 seconds times out (in that case the link is disconnected as well). Or that the underlying bluetooth stack bails out and crashes but then you're probably out of luck anyway. Haven't seen that however as of yet regarding GATT operations on the common platforms. On the GATT level you can't abort a request. Even if you for some reason want a shorter timeout than 30 seconds you are not allowed to send a new request until the previous one is complete. So in practice a custom timeout doesn't mean anything at all than basically notifying that the operation has still not yet completed. Nordic's example seems kind of strange that they give the ability to cancel enqueued requests not yet sent. That is really something you would like to do on the application layer, if you want to do that at all. I'd like to see a use case here. I think that if you must decide what to send next depends on the result of some operation, then you should wait until the operation completes and then resume rather than enqueuing lots of operations and then remove them from the queue if some operation didn't return what you wanted... |
|
Retries: I would presume that a queue should re-try an operation upon receiving a "GATT operation in progress" error. e.g.:
It is possible multiple retries may be needed. And, that a delay before retry is warranted (likely with exponential backoff). An alternative would be that if operation A fails due to an in progress error, then B would also fail. But then we don't need a library - we just use a chain of promises. |
|
At least on Android, as long as you keep track if there is a pending GATT operation or not, and make sure you never execute a GATT operation if there already is one pending, it does not fail. Are you talking about BlueZ in mind now? Does it have a restriction that pending GATT operations are global for all clients, meaning that you can't know if there is a pending operation and not, and it fails when there is? |
|
We should test how Android deals with multiple separate applications trying to do multiple operations at once. If it doesn't return InProgress, then we're home free, since the UA can serialize all of its operations. (@Emill, have you already tested this?) We could fix BlueZ if it's more global than Android. |
|
Yes it works fine on Android. It has an internal queue of operations. |
|
Actually the only bug I've found on Android so far when the callback never is called is this one https://code.google.com/p/android/issues/detail?id=223558 which I should have submitted a long time ago... Anyway, have you found out how BlueZ behaves when another app is executing a GATT operation? |
|
For the record, here are not supported parallel operations and their respective errors I've found on current Dev Channel (55.0.2883.17): Android
Chrome OS / Linux
Mac OS
As you can see, parallel connects works fine on Android and Mac OS while they're not on Chrome OS and Linux (BlueZ). |
|
I'm planning to add a statement that UAs MAY return a |
|
#316 includes |
|
I think they are somewhat different. When you call writeValue() multiple times you expect each write to be sent to the device whereas when you call startNotifications() multiple times you don't expect each call to write to the descriptor so we can queue on the first write to the descriptor succeeding or failing. |
|
If you're writing one characteristic, and you concurrently start notifications on another one, does that work? |
|
FYI, with BlueZ, I get the error below: characteristic.readValue().catch(err => console.log('readValue', err));
characteristic2.startNotifications().catch(err => console.log('startNotifications', err));
|
I have a web page that writes to a characteristic, but if I do this too fast, I will get the error "GATT operation in progress". Is this something the page author should handle or should the implementation take care of queuing the requests?
If it is the page author, it will be good to include some recommendations for how to handle this. AFAIK there is no way to query the state of the implementation other than responding to the errors.