Commit 2464034c authored by Tiansong Cui's avatar Tiansong Cui Committed by Commit Bot

[chromecast][BLE] Queue concurrent Connect requests

The Bluetooth stack will drop a Connect request while there is a
Connect request pending. The request's callback is never called.

This CL queues concurrent Connect requests to avoid this issue.

Bug: internal 111887604
Test: cast_bluetooth_unittests
      Try concurrent Connect requests, second request returned
      successfully.

Change-Id: I187330f94f75c643c896549d49f8e00a7ff3e195
Reviewed-on: https://chromium-review.googlesource.com/1155998Reviewed-by: default avatarBailey Forrest <bcf@chromium.org>
Commit-Queue: Tiansong Cui <tiansong@google.com>
Cr-Commit-Position: refs/heads/master@{#579436}
parent 205cd9c8
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/stl_util.h"
#include "chromecast/base/bind_to_task_runner.h" #include "chromecast/base/bind_to_task_runner.h"
#include "chromecast/device/bluetooth/le/remote_characteristic_impl.h" #include "chromecast/device/bluetooth/le/remote_characteristic_impl.h"
#include "chromecast/device/bluetooth/le/remote_descriptor_impl.h" #include "chromecast/device/bluetooth/le/remote_descriptor_impl.h"
...@@ -118,9 +119,21 @@ void GattClientManagerImpl::NotifyConnect( ...@@ -118,9 +119,21 @@ void GattClientManagerImpl::NotifyConnect(
observers_->Notify(FROM_HERE, &Observer::OnConnectInitated, addr); observers_->Notify(FROM_HERE, &Observer::OnConnectInitated, addr);
} }
void GattClientManagerImpl::EnqueueConnectRequest(
const bluetooth_v2_shlib::Addr& addr) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
pending_connect_requests_.push_back(addr);
// Run the request if this is the only request in the queue. Otherwise, it
// will be run when all previous requests complete.
if (pending_connect_requests_.size() == 1) {
RunQueuedConnectRequest();
}
}
void GattClientManagerImpl::EnqueueReadRemoteRssiRequest( void GattClientManagerImpl::EnqueueReadRemoteRssiRequest(
const bluetooth_v2_shlib::Addr& addr) { const bluetooth_v2_shlib::Addr& addr) {
MAKE_SURE_IO_THREAD(EnqueueReadRemoteRssiRequest, addr); DCHECK(io_task_runner_->BelongsToCurrentThread());
pending_read_remote_rssi_requests_.push_back(addr); pending_read_remote_rssi_requests_.push_back(addr);
// Run the request if this is the only request in the queue. Otherwise, it // Run the request if this is the only request in the queue. Otherwise, it
...@@ -152,10 +165,14 @@ void GattClientManagerImpl::OnConnectChanged( ...@@ -152,10 +165,14 @@ void GattClientManagerImpl::OnConnectChanged(
connected_devices_.insert(addr); connected_devices_.insert(addr);
} else { } else {
connected_devices_.erase(addr); connected_devices_.erase(addr);
pending_read_remote_rssi_requests_.erase( if (addr == pending_connect_requests_.front()) {
std::remove(pending_read_remote_rssi_requests_.begin(), pending_connect_requests_.pop_front();
pending_read_remote_rssi_requests_.end(), addr), RunQueuedConnectRequest();
pending_read_remote_rssi_requests_.end()); } else {
base::Erase(pending_connect_requests_, addr);
}
base::Erase(pending_read_remote_rssi_requests_, addr);
} }
// We won't declare the device connected until service discovery completes. // We won't declare the device connected until service discovery completes.
...@@ -236,16 +253,15 @@ void GattClientManagerImpl::OnReadRemoteRssi( ...@@ -236,16 +253,15 @@ void GattClientManagerImpl::OnReadRemoteRssi(
CHECK_DEVICE_EXISTS_IT(it); CHECK_DEVICE_EXISTS_IT(it);
it->second->OnReadRemoteRssiComplete(status, rssi); it->second->OnReadRemoteRssiComplete(status, rssi);
if (pending_read_remote_rssi_requests_.empty()) { if (pending_read_remote_rssi_requests_.empty() ||
addr != pending_read_remote_rssi_requests_.front()) {
NOTREACHED() << "Unexpected call to " << __func__; NOTREACHED() << "Unexpected call to " << __func__;
} else { return;
pending_read_remote_rssi_requests_.pop_front();
} }
// Run the next request if there is one in the queue. pending_read_remote_rssi_requests_.pop_front();
if (!pending_read_remote_rssi_requests_.empty()) { // Try to run the next ReadRemoteRssi request
RunQueuedReadRemoteRssiRequest(); RunQueuedReadRemoteRssiRequest();
}
} }
void GattClientManagerImpl::OnMtuChanged(const bluetooth_v2_shlib::Addr& addr, void GattClientManagerImpl::OnMtuChanged(const bluetooth_v2_shlib::Addr& addr,
...@@ -275,6 +291,16 @@ void GattClientManagerImpl::OnGetServices( ...@@ -275,6 +291,16 @@ void GattClientManagerImpl::OnGetServices(
observers_->Notify(FROM_HERE, &Observer::OnServicesUpdated, it->second, observers_->Notify(FROM_HERE, &Observer::OnServicesUpdated, it->second,
it->second->GetServicesSync()); it->second->GetServicesSync());
if (pending_connect_requests_.empty() ||
addr != pending_connect_requests_.front()) {
NOTREACHED() << "Unexpected call to " << __func__;
return;
}
pending_connect_requests_.pop_front();
// Try to run the next Connect request
RunQueuedConnectRequest();
} }
void GattClientManagerImpl::OnServicesRemoved( void GattClientManagerImpl::OnServicesRemoved(
...@@ -301,9 +327,37 @@ void GattClientManagerImpl::OnServicesAdded( ...@@ -301,9 +327,37 @@ void GattClientManagerImpl::OnServicesAdded(
it->second->GetServicesSync()); it->second->GetServicesSync());
} }
void GattClientManagerImpl::RunQueuedConnectRequest() {
DCHECK(io_task_runner_->BelongsToCurrentThread());
if (pending_connect_requests_.empty()) {
return;
}
auto addr = pending_connect_requests_.front();
while (!gatt_client_->Connect(addr)) {
// If current request fails, run the next request
LOG(ERROR) << "Connect failed";
auto it = addr_to_device_.find(addr);
if (it != addr_to_device_.end()) {
it->second->SetConnected(false);
}
pending_connect_requests_.pop_front();
if (pending_connect_requests_.empty()) {
return;
}
addr = pending_connect_requests_.front();
}
}
void GattClientManagerImpl::RunQueuedReadRemoteRssiRequest() { void GattClientManagerImpl::RunQueuedReadRemoteRssiRequest() {
DCHECK(io_task_runner_->BelongsToCurrentThread()); DCHECK(io_task_runner_->BelongsToCurrentThread());
DCHECK(!pending_read_remote_rssi_requests_.empty());
if (pending_read_remote_rssi_requests_.empty()) {
return;
}
auto addr = pending_read_remote_rssi_requests_.front(); auto addr = pending_read_remote_rssi_requests_.front();
while (!gatt_client_->ReadRemoteRssi(addr)) { while (!gatt_client_->ReadRemoteRssi(addr)) {
......
...@@ -43,6 +43,9 @@ class GattClientManagerImpl ...@@ -43,6 +43,9 @@ class GattClientManagerImpl
void NotifyConnect(const bluetooth_v2_shlib::Addr& addr) override; void NotifyConnect(const bluetooth_v2_shlib::Addr& addr) override;
scoped_refptr<base::SingleThreadTaskRunner> task_runner() override; scoped_refptr<base::SingleThreadTaskRunner> task_runner() override;
// Add a Connect request to the queue. They can only be executed serially.
void EnqueueConnectRequest(const bluetooth_v2_shlib::Addr& addr);
// Add a ReadRemoteRssi request to the queue. They can only be executed // Add a ReadRemoteRssi request to the queue. They can only be executed
// serially. // serially.
void EnqueueReadRemoteRssiRequest(const bluetooth_v2_shlib::Addr& addr); void EnqueueReadRemoteRssiRequest(const bluetooth_v2_shlib::Addr& addr);
...@@ -89,6 +92,7 @@ class GattClientManagerImpl ...@@ -89,6 +92,7 @@ class GattClientManagerImpl
const bluetooth_v2_shlib::Addr& addr, const bluetooth_v2_shlib::Addr& addr,
const std::vector<bluetooth_v2_shlib::Gatt::Service>& services) override; const std::vector<bluetooth_v2_shlib::Gatt::Service>& services) override;
void RunQueuedConnectRequest();
void RunQueuedReadRemoteRssiRequest(); void RunQueuedReadRemoteRssiRequest();
static void FinalizeOnIoThread( static void FinalizeOnIoThread(
...@@ -108,6 +112,9 @@ class GattClientManagerImpl ...@@ -108,6 +112,9 @@ class GattClientManagerImpl
addr_to_device_; addr_to_device_;
std::set<bluetooth_v2_shlib::Addr> connected_devices_; std::set<bluetooth_v2_shlib::Addr> connected_devices_;
// Queue for concurrent Connect requests.
std::deque<bluetooth_v2_shlib::Addr> pending_connect_requests_;
// Queue for concurrent ReadRemoteRssi requests. // Queue for concurrent ReadRemoteRssi requests.
std::deque<bluetooth_v2_shlib::Addr> pending_read_remote_rssi_requests_; std::deque<bluetooth_v2_shlib::Addr> pending_read_remote_rssi_requests_;
......
...@@ -33,6 +33,8 @@ const bluetooth_v2_shlib::Addr kTestAddr2 = { ...@@ -33,6 +33,8 @@ const bluetooth_v2_shlib::Addr kTestAddr2 = {
{0x10, 0x11, 0x12, 0x13, 0x14, 0x15}}; {0x10, 0x11, 0x12, 0x13, 0x14, 0x15}};
const bluetooth_v2_shlib::Addr kTestAddr3 = { const bluetooth_v2_shlib::Addr kTestAddr3 = {
{0x20, 0x21, 0x22, 0x23, 0x24, 0x25}}; {0x20, 0x21, 0x22, 0x23, 0x24, 0x25}};
const bluetooth_v2_shlib::Addr kTestAddr4 = {
{0x30, 0x31, 0x32, 0x33, 0x34, 0x35}};
class MockGattClientManagerObserver : public GattClientManager::Observer { class MockGattClientManagerObserver : public GattClientManager::Observer {
public: public:
...@@ -238,6 +240,66 @@ TEST_F(GattClientManagerTest, RemoteDeviceConnect) { ...@@ -238,6 +240,66 @@ TEST_F(GattClientManagerTest, RemoteDeviceConnect) {
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
} }
TEST_F(GattClientManagerTest, RemoteDeviceConnectConcurrent) {
bluetooth_v2_shlib::Gatt::Client::Delegate* delegate =
gatt_client_->delegate();
scoped_refptr<RemoteDevice> device1 = GetDevice(kTestAddr1);
scoped_refptr<RemoteDevice> device2 = GetDevice(kTestAddr2);
scoped_refptr<RemoteDevice> device3 = GetDevice(kTestAddr3);
scoped_refptr<RemoteDevice> device4 = GetDevice(kTestAddr4);
base::MockCallback<RemoteDevice::StatusCallback> cb1;
base::MockCallback<RemoteDevice::StatusCallback> cb2;
base::MockCallback<RemoteDevice::StatusCallback> cb3;
base::MockCallback<RemoteDevice::StatusCallback> cb4;
// Only the 1st Connect request will be executed immediately. The rest will be
// queued.
EXPECT_CALL(*gatt_client_, Connect(kTestAddr1)).WillOnce(Return(true));
device1->Connect(cb1.Get());
device2->Connect(cb2.Get());
device3->Connect(cb3.Get());
device4->Connect(cb4.Get());
EXPECT_CALL(*gatt_client_, GetServices(kTestAddr1)).WillOnce(Return(true));
delegate->OnConnectChanged(kTestAddr1, true /* status */,
true /* connected */);
// Queued Connect requests will not be called until we receive OnGetServices
// of the current Connect request if it is successful.
EXPECT_CALL(cb1, Run(true));
EXPECT_CALL(*gatt_client_, Connect(kTestAddr2)).WillOnce(Return(false));
EXPECT_CALL(cb2, Run(false));
// If the Connect request fails in the initial request (not in the callback),
// the next queued request will be executed immediately.
EXPECT_CALL(*gatt_client_, Connect(kTestAddr3)).WillOnce(Return(true));
delegate->OnGetServices(kTestAddr1, {});
EXPECT_CALL(cb3, Run(false));
EXPECT_CALL(*gatt_client_, Connect(kTestAddr4)).WillOnce(Return(true));
delegate->OnConnectChanged(kTestAddr3, true /* status */,
false /* connected */);
EXPECT_CALL(*gatt_client_, GetServices(kTestAddr4)).WillOnce(Return(true));
delegate->OnConnectChanged(kTestAddr4, true /* status */,
true /* connected */);
EXPECT_CALL(cb4, Run(true));
delegate->OnGetServices(kTestAddr4, {});
EXPECT_TRUE(device1->IsConnected());
EXPECT_FALSE(device2->IsConnected());
EXPECT_FALSE(device3->IsConnected());
EXPECT_TRUE(device4->IsConnected());
base::MockCallback<base::OnceCallback<void(size_t)>>
get_num_connected_callback;
EXPECT_CALL(get_num_connected_callback, Run(2));
gatt_client_manager_->GetNumConnected(get_num_connected_callback.Get());
base::RunLoop().RunUntilIdle();
}
TEST_F(GattClientManagerTest, RemoteDeviceReadRssi) { TEST_F(GattClientManagerTest, RemoteDeviceReadRssi) {
static const int kRssi = -34; static const int kRssi = -34;
...@@ -269,20 +331,25 @@ TEST_F(GattClientManagerTest, RemoteDeviceReadRssiConcurrent) { ...@@ -269,20 +331,25 @@ TEST_F(GattClientManagerTest, RemoteDeviceReadRssiConcurrent) {
base::MockCallback<RemoteDevice::RssiCallback> rssi_cb2; base::MockCallback<RemoteDevice::RssiCallback> rssi_cb2;
base::MockCallback<RemoteDevice::RssiCallback> rssi_cb3; base::MockCallback<RemoteDevice::RssiCallback> rssi_cb3;
// Only the 1st ReadRemoteRssi request will be executed immediately. The rest
// will be queued.
EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr1)).WillOnce(Return(true)); EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr1)).WillOnce(Return(true));
EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr2))
.WillOnce(Return(false));
EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr3)).WillOnce(Return(true));
device1->ReadRemoteRssi(rssi_cb1.Get()); device1->ReadRemoteRssi(rssi_cb1.Get());
device2->ReadRemoteRssi(rssi_cb2.Get()); device2->ReadRemoteRssi(rssi_cb2.Get());
device3->ReadRemoteRssi(rssi_cb3.Get()); device3->ReadRemoteRssi(rssi_cb3.Get());
// Queued ReadRemoteRssi requests will not be called until we receive
// OnGetServices of the current Connect request if it is successful.
EXPECT_CALL(rssi_cb1, Run(true, kRssi1)); EXPECT_CALL(rssi_cb1, Run(true, kRssi1));
EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr2))
.WillOnce(Return(false));
EXPECT_CALL(rssi_cb2, Run(false, _)); EXPECT_CALL(rssi_cb2, Run(false, _));
EXPECT_CALL(rssi_cb3, Run(true, kRssi3)); // If the ReadRemoteRssi request fails in the initial request (not in the
// callback), the next queued request will be executed immediately.
EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr3)).WillOnce(Return(true));
delegate->OnReadRemoteRssi(kTestAddr1, true, kRssi1); delegate->OnReadRemoteRssi(kTestAddr1, true, kRssi1);
EXPECT_CALL(rssi_cb3, Run(true, kRssi3));
delegate->OnReadRemoteRssi(kTestAddr3, true, kRssi3); delegate->OnReadRemoteRssi(kTestAddr3, true, kRssi3);
} }
......
...@@ -85,11 +85,10 @@ bool RemoteDeviceImpl::ConnectSync() { ...@@ -85,11 +85,10 @@ bool RemoteDeviceImpl::ConnectSync() {
} }
gatt_client_manager_->NotifyConnect(addr_); gatt_client_manager_->NotifyConnect(addr_);
if (!gatt_client_manager_->gatt_client()->Connect(addr_)) {
LOG(ERROR) << __func__ << " failed";
return false;
}
connect_pending_ = true; connect_pending_ = true;
gatt_client_manager_->EnqueueConnectRequest(addr_);
return true; return true;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment