Fixed a go/mobile_tsan error (data_race) found while running //third_party/nearby/internal/platform/implementation/apple/Tests:PlatformTest

PiperOrigin-RevId: 915715369
This commit is contained in:
Edwin Wu
2026-05-14 18:17:28 -07:00
committed by Copybara-Service
parent 80d209c76b
commit e234002750
11 changed files with 124 additions and 66 deletions
@@ -64,6 +64,11 @@ static const int kMaxAdvertisementLengthOnIOS = 23;
self = [super init];
if (self) {
_queue = queue ?: dispatch_queue_create(kGNCBLEGATTServerQueueLabel, DISPATCH_QUEUE_SERIAL);
_services = [[NSMutableDictionary alloc] init];
_pendingCharacteristics = [[NSMutableDictionary alloc] init];
_characteristicValues = [[NSMutableDictionary alloc] init];
_advertisementData = nil;
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
if (!peripheralManager) {
// In shared mode, the peripheral manager must be injected.
@@ -76,7 +81,7 @@ static const int kMaxAdvertisementLengthOnIOS = 23;
// Legacy mode: Create a new manager if one isn't provided.
if (!peripheralManager) {
peripheralManager = [[CBPeripheralManager alloc]
initWithDelegate:self
initWithDelegate:nil
queue:_queue
options:@{CBPeripheralManagerOptionShowPowerAlertKey : @NO}];
}
@@ -85,11 +90,6 @@ static const int kMaxAdvertisementLengthOnIOS = 23;
// delegate.
_peripheralManager.peripheralDelegate = self;
}
_services = [[NSMutableDictionary alloc] init];
_pendingCharacteristics = [[NSMutableDictionary alloc] init];
_characteristicValues = [[NSMutableDictionary alloc] init];
_advertisementData = nil;
}
return self;
}
@@ -93,7 +93,7 @@ using MultiThreadExecutor = ::nearby::api::SubmittableExecutor;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0);
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"];
const int kRunnableCount = 1000;
const int kRunnableCount = 100;
for (int i = 0; i < kRunnableCount; i++) {
executor->Execute([self]() { self.counter++; });
}
@@ -76,20 +76,14 @@ using SingleThreadExecutor = ::nearby::api::SubmittableExecutor;
// Tests that shutting down an existing task allows to complete.
- (void)testShutdownToAllowExistingTaskComplete {
std::unique_ptr<SingleThreadExecutor> executor([self executor]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0);
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"];
executor->Execute([self]() { self.counter++; });
executor->Shutdown();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), queue, ^{
XCTAssertEqual(self.counter, 1);
executor->Execute([self, expectation]() {
self.counter++;
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:0.5 handler:nil];
executor->Shutdown();
[self waitForExpectationsWithTimeout:1.0 handler:nil];
XCTAssertEqual(self.counter, 1);
}
@end
@@ -65,7 +65,7 @@
auto timer = std::make_unique<nearby::apple::Timer>();
std::atomic<int> fireCount = 0;
XCTAssertTrue(timer->Create(10, 10, [&]() {
XCTAssertTrue(timer->Create(100, 100, [&]() {
if (fireCount.fetch_add(1) == 1) {
dispatch_async(dispatch_get_main_queue(), ^{
[expectation fulfill];
@@ -73,9 +73,9 @@
}
}));
[self waitForExpectationsWithTimeout:1.0 handler:nil];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
XCTAssertTrue(timer->Stop());
XCTAssertEqual(fireCount.load(), 2);
XCTAssertGreaterThanOrEqual(fireCount.load(), 2);
}
- (void)testRestart {
@@ -56,8 +56,9 @@ using ::nearby::ObjCStringFromCppString;
- (void)testUUIDStringFromNSUUID {
NSString *uuidString = @"E621E1F8-C36C-495A-93FC-0C247A3E6E5F";
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
std::string expectedCppString = [uuidString UTF8String];
XCTAssertEqual(nearby::UUIDStringFromNSUUID(uuid), expectedCppString);
XCTAssert(nearby::UUIDStringFromNSUUID(uuid) ==
std::string([uuidString UTF8String],
[uuidString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]));
}
- (void)testBluetoothUUIDConversions {
@@ -50,8 +50,9 @@
- (void)testBleL2capServerSocketAccept {
XCTestExpectation *expectation = [self expectationWithDescription:@"accept"];
nearby::apple::BleL2capServerSocket *serverSocket = _serverSocket.get();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
std::unique_ptr<nearby::api::ble::BleL2capSocket> clientSocket = _serverSocket->Accept();
std::unique_ptr<nearby::api::ble::BleL2capSocket> clientSocket = serverSocket->Accept();
XCTAssertNotEqual(clientSocket.get(), nullptr);
[expectation fulfill];
});
@@ -76,8 +77,9 @@
- (void)testBleL2capServerSocketClose {
XCTestExpectation *expectation = [self expectationWithDescription:@"close"];
nearby::apple::BleL2capServerSocket *serverSocket = _serverSocket.get();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
std::unique_ptr<nearby::api::ble::BleL2capSocket> clientSocket = _serverSocket->Accept();
std::unique_ptr<nearby::api::ble::BleL2capSocket> clientSocket = serverSocket->Accept();
XCTAssertEqual(clientSocket.get(), nullptr);
[expectation fulfill];
});
@@ -776,18 +776,20 @@ static const char *const kTestServiceID = "TestServiceID";
NSDictionary<CBUUID *, NSData *> *serviceData =
@{[CBUUID UUIDWithString:kTestServiceUUIDString] : [NSData dataWithBytes:"test" length:4]};
__block XCTestExpectation *expectation1 = [self expectationWithDescription:@"Callback 1"];
XCTestExpectation *expectation1 = [self expectationWithDescription:@"Callback 1"];
XCTestExpectation *expectation2 = [self expectationWithDescription:@"Callback 2"];
expectation2.inverted = YES; // Should NOT be called.
auto callback1_fulfilled = std::make_shared<std::atomic<bool>>(false);
nearby::api::ble::BleMedium::ScanCallback callback = {
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
nearby::api::ble::BleAdvertisementData)>(
^(nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
const nearby::api::ble::BleAdvertisementData &advertisement) {
if ([expectation1.description isEqualToString:@"Callback 1"]) {
[callback1_fulfilled, expectation1, expectation2](
nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
const nearby::api::ble::BleAdvertisementData &advertisement) {
if (!callback1_fulfilled->exchange(true)) {
[expectation1 fulfill];
expectation1 = nil; // Prevent double fulfillment
} else {
[expectation2 fulfill];
}
@@ -42,8 +42,9 @@
- (void)testBleServerSocketAccept {
XCTestExpectation *expectation = [self expectationWithDescription:@"accept"];
nearby::apple::BleServerSocket *serverSocket = _serverSocket.get();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
std::unique_ptr<nearby::api::ble::BleSocket> clientSocket = _serverSocket->Accept();
std::unique_ptr<nearby::api::ble::BleSocket> clientSocket = serverSocket->Accept();
XCTAssertNotEqual(clientSocket.get(), nullptr);
[expectation fulfill];
});
@@ -57,8 +58,9 @@
- (void)testBleServerSocketClose {
XCTestExpectation *expectation = [self expectationWithDescription:@"close"];
nearby::apple::BleServerSocket *serverSocket = _serverSocket.get();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
std::unique_ptr<nearby::api::ble::BleSocket> clientSocket = _serverSocket->Accept();
std::unique_ptr<nearby::api::ble::BleSocket> clientSocket = serverSocket->Accept();
XCTAssertEqual(clientSocket.get(), nullptr);
[expectation fulfill];
});
@@ -245,13 +245,16 @@ class BleMedium : public api::ble::BleMedium {
GNSPeripheralServiceManager *socketPeripheralServiceManager_;
GNSPeripheralManager *socketPeripheralManager_;
GNSCentralManager *socketCentralManager_;
absl::Mutex scanning_mutex_;
GNSCentralManager *socketCentralManager_ ABSL_GUARDED_BY(scanning_mutex_);
// Used for the blocking version of StartAdvertising and only has an advertisement found callback.
api::ble::BleMedium::ScanCallback scan_cb_;
std::shared_ptr<api::ble::BleMedium::ScanCallback> scan_cb_ ABSL_GUARDED_BY(scanning_mutex_);
// Used for the async version of StartAdvertising and has both an advertisement found and result
// callback.
api::ble::BleMedium::ScanningCallback scanning_cb_;
std::shared_ptr<api::ble::BleMedium::ScanningCallback> scanning_cb_
ABSL_GUARDED_BY(scanning_mutex_);
// Used for the BleServerSocket.
absl::Mutex server_socket_mutex_;
@@ -173,11 +173,19 @@ void BleMedium::HandleAdvertisementFound(id<GNCPeripheral> peripheral,
}
#endif
if (scanning_cb_.advertisement_found_cb) {
scanning_cb_.advertisement_found_cb(unique_id, data);
std::shared_ptr<api::ble::BleMedium::ScanningCallback> scanning_cb;
std::shared_ptr<api::ble::BleMedium::ScanCallback> scan_cb;
{
absl::MutexLock lock(&scanning_mutex_);
scanning_cb = scanning_cb_;
scan_cb = scan_cb_;
}
if (scan_cb_.advertisement_found_cb) {
scan_cb_.advertisement_found_cb(unique_id, data);
if (scanning_cb && scanning_cb->advertisement_found_cb) {
scanning_cb->advertisement_found_cb(unique_id, data);
}
if (scan_cb && scan_cb->advertisement_found_cb) {
scan_cb->advertisement_found_cb(unique_id, data);
}
}
@@ -185,7 +193,17 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
const Uuid &service_uuid, api::ble::TxPowerLevel tx_power_level,
api::ble::BleMedium::ScanningCallback callback) {
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
scanning_cb_ = std::move(callback);
{
absl::MutexLock lock(&scanning_mutex_);
scanning_cb_ = std::make_shared<api::ble::BleMedium::ScanningCallback>(std::move(callback));
if (central_manager_factory_) {
socketCentralManager_ = central_manager_factory_(serviceUUID);
} else {
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
}
}
// Clear the map of discovered peripherals only when we are starting a new scan. If we cleared the
// map every time we stopped a scan, we would not be able to connect to peripherals that we
@@ -193,12 +211,10 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
peripherals_.Clear();
ClearAdvertisementPacketsMap();
if (central_manager_factory_) {
socketCentralManager_ = central_manager_factory_(serviceUUID);
} else {
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
{
absl::MutexLock lock(&scanning_mutex_);
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
}
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
@@ -211,8 +227,13 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
}
completionHandler:^(NSError *error) {
blockError = error;
if (scanning_cb_.start_scanning_result) {
scanning_cb_.start_scanning_result(
std::shared_ptr<api::ble::BleMedium::ScanningCallback> scanning_cb;
{
absl::MutexLock lock(&scanning_mutex_);
scanning_cb = scanning_cb_;
}
if (scanning_cb && scanning_cb->start_scanning_result) {
scanning_cb->start_scanning_result(
error == nil ? absl::OkStatus()
: absl::InternalError(error.localizedDescription.UTF8String));
}
@@ -222,8 +243,13 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, kApiTimeoutInSeconds * NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, timeout) != 0) {
GNCLoggerError(@"Start scanning operation timed out.");
if (scanning_cb_.start_scanning_result) {
scanning_cb_.start_scanning_result(absl::DeadlineExceededError("Start scanning timed out"));
std::shared_ptr<api::ble::BleMedium::ScanningCallback> scanning_cb;
{
absl::MutexLock lock(&scanning_mutex_);
scanning_cb = scanning_cb_;
}
if (scanning_cb && scanning_cb->start_scanning_result) {
scanning_cb->start_scanning_result(absl::DeadlineExceededError("Start scanning timed out"));
}
return nullptr;
}
@@ -243,7 +269,17 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
bool BleMedium::StartScanning(const Uuid &service_uuid, api::ble::TxPowerLevel tx_power_level,
api::ble::BleMedium::ScanCallback callback) {
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
scan_cb_ = std::move(callback);
{
absl::MutexLock lock(&scanning_mutex_);
scan_cb_ = std::make_shared<api::ble::BleMedium::ScanCallback>(std::move(callback));
if (central_manager_factory_) {
socketCentralManager_ = central_manager_factory_(serviceUUID);
} else {
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
}
}
// Clear the map of discovered peripherals only when we are starting a new scan. If we cleared the
// map every time we stopped a scan, we would not be able to connect to peripherals that we
@@ -251,12 +287,10 @@ bool BleMedium::StartScanning(const Uuid &service_uuid, api::ble::TxPowerLevel t
peripherals_.Clear();
ClearAdvertisementPacketsMap();
if (central_manager_factory_) {
socketCentralManager_ = central_manager_factory_(serviceUUID);
} else {
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
{
absl::MutexLock lock(&scanning_mutex_);
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
}
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
@@ -294,7 +328,16 @@ bool BleMedium::StartMultipleServicesScanning(const std::vector<Uuid> &service_u
[serviceUUIDs addObject:CBUUID128FromCPP(service_uuid)];
}
scan_cb_ = std::move(callback);
{
absl::MutexLock lock(&scanning_mutex_);
scan_cb_ = std::make_shared<api::ble::BleMedium::ScanCallback>(std::move(callback));
if (central_manager_factory_) {
socketCentralManager_ = central_manager_factory_(serviceUUIDs[0]);
} else {
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUIDs[0]];
}
}
// Clear the map of discovered peripherals only when we are starting a new scan. If we cleared the
// map every time we stopped a scan, we would not be able to connect to peripherals that we
@@ -302,12 +345,10 @@ bool BleMedium::StartMultipleServicesScanning(const std::vector<Uuid> &service_u
peripherals_.Clear();
ClearAdvertisementPacketsMap();
if (central_manager_factory_) {
socketCentralManager_ = central_manager_factory_(serviceUUIDs[0]);
} else {
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUIDs[0]];
{
absl::MutexLock lock(&scanning_mutex_);
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUIDs[0] ]];
}
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUIDs[0] ]];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
@@ -333,7 +374,12 @@ bool BleMedium::StartMultipleServicesScanning(const std::vector<Uuid> &service_u
}
bool BleMedium::StopScanning() {
[socketCentralManager_ stopNoScanMode];
{
absl::MutexLock lock(&scanning_mutex_);
[socketCentralManager_ stopNoScanMode];
scan_cb_ = nullptr;
scanning_cb_ = nullptr;
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
@@ -694,8 +740,12 @@ std::unique_ptr<api::ble::BleSocket> BleMedium::Connect(
return nullptr;
}
GNSCentralPeerManager *updatedCentralPeerManager =
[socketCentralManager_ retrieveCentralPeerWithIdentifier:peripheral.identifier];
GNSCentralPeerManager *updatedCentralPeerManager;
{
absl::MutexLock lock(&scanning_mutex_);
updatedCentralPeerManager =
[socketCentralManager_ retrieveCentralPeerWithIdentifier:peripheral.identifier];
}
if (!updatedCentralPeerManager) {
return nullptr;
}
@@ -27,11 +27,15 @@ bool CppBoolFromObjCBool(BOOL b) { return b ? true : false; }
char CharFromNSNumber(NSNumber* n) { return n.charValue; }
NSString* ObjCStringFromCppString(absl::string_view s) {
return [NSString stringWithUTF8String:s.data()];
return [[NSString alloc] initWithBytes:s.data() length:s.size() encoding:NSUTF8StringEncoding];
}
std::string CppStringFromObjCString(NSString* s) {
return std::string([s UTF8String], [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
if (!s) return std::string();
const char* cstr = [s UTF8String];
if (!cstr) return std::string();
NSUInteger len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
return std::string(cstr, len);
}
NSData* NSDataFromByteArray(ByteArray byteArray) {