Skip to content

Commit 6fc90d3

Browse files
authored
Fix sanitizer builds (#2940)
* Make GrpcConnection safe to use with thread sanitizer One additional lock acquisition per connection attempt isn't going to kill us. * Make tests pass with tsan and ubsan
1 parent 9f93a53 commit 6fc90d3

File tree

3 files changed

+113
-25
lines changed

3 files changed

+113
-25
lines changed

Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.mm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
2626

2727
#include "Firestore/core/src/firebase/firestore/util/autoid.h"
28+
#include "Firestore/core/src/firebase/firestore/util/sanitizers.h"
2829
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
2930

3031
using firebase::firestore::util::CreateAutoId;
@@ -404,9 +405,15 @@ - (void)testReasonableMemoryUsageForLotsOfMutations {
404405
const int64_t memoryUsedAfterCommitMb = GetCurrentMemoryUsedInMb();
405406
XCTAssertNotEqual(memoryUsedAfterCommitMb, -1);
406407
const int64_t memoryDeltaMb = memoryUsedAfterCommitMb - memoryUsedBeforeCommitMb;
408+
409+
#if !defined(THREAD_SANITIZER) && !defined(ADDRESS_SANITIZER)
407410
// This by its nature cannot be a precise value. Runs on simulator seem to give an increase of
408411
// 10MB in debug mode pretty consistently. A regression would be on the scale of 500Mb.
412+
//
413+
// This check is disabled under the thread sanitizer because it introduces an overhead of
414+
// 5x-10x.
409415
XCTAssertLessThan(memoryDeltaMb, 20);
416+
#endif
410417

411418
[expectation fulfill];
412419
}];

Firestore/core/src/firebase/firestore/remote/grpc_connection.cc

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "Firestore/core/src/firebase/firestore/remote/grpc_connection.h"
1818

1919
#include <algorithm>
20+
#include <mutex> // NOLINT(build/c++11)
2021
#include <string>
2122
#include <utility>
2223

@@ -66,15 +67,51 @@ struct HostConfig {
6667
bool use_insecure_channel = false;
6768
};
6869

69-
using ConfigByHost = std::unordered_map<std::string, HostConfig>;
70+
class HostConfigMap {
71+
using ConfigByHost = std::unordered_map<std::string, HostConfig>;
72+
using Guard = std::lock_guard<std::mutex>;
73+
74+
public:
75+
const HostConfig* _Nullable find(const std::string& host) const {
76+
Guard guard{mutex_};
77+
auto iter = map_.find(host);
78+
if (iter == map_.end()) {
79+
return nullptr;
80+
} else {
81+
return &(iter->second);
82+
}
83+
}
7084

71-
ConfigByHost& Config() {
72-
static ConfigByHost config_by_host_;
73-
return config_by_host_;
74-
}
85+
void UseTestCertificate(const std::string& host,
86+
const Path& certificate_path,
87+
const std::string& target_name) {
88+
HARD_ASSERT(!host.empty(), "Empty host name");
89+
HARD_ASSERT(!certificate_path.native_value().empty(),
90+
"Empty path to test certificate");
91+
HARD_ASSERT(!target_name.empty(), "Empty SSL target name");
92+
93+
Guard guard(mutex_);
94+
HostConfig& host_config = map_[host];
95+
host_config.certificate_path = certificate_path;
96+
host_config.target_name = target_name;
97+
}
98+
99+
void UseInsecureChannel(const std::string& host) {
100+
HARD_ASSERT(!host.empty(), "Empty host name");
101+
102+
Guard guard(mutex_);
103+
HostConfig& host_config = map_[host];
104+
host_config.use_insecure_channel = true;
105+
}
106+
107+
private:
108+
ConfigByHost map_;
109+
mutable std::mutex mutex_;
110+
};
75111

76-
bool HasSpecialConfig(const std::string& host) {
77-
return Config().find(host) != Config().end();
112+
HostConfigMap& Config() {
113+
static HostConfigMap config_by_host_;
114+
return config_by_host_;
78115
}
79116

80117
} // namespace
@@ -144,22 +181,21 @@ void GrpcConnection::EnsureActiveStub() {
144181
std::shared_ptr<grpc::Channel> GrpcConnection::CreateChannel() const {
145182
const std::string& host = database_info_->host();
146183

147-
if (!HasSpecialConfig(host)) {
184+
const HostConfig* host_config = Config().find(host);
185+
if (!host_config) {
148186
std::string root_certificate = LoadGrpcRootCertificate();
149187
return grpc::CreateChannel(host, CreateSslCredentials(root_certificate));
150188
}
151189

152-
const HostConfig& host_config = Config()[host];
153-
154190
// For the case when `Settings.sslEnabled == false`.
155-
if (host_config.use_insecure_channel) {
191+
if (host_config->use_insecure_channel) {
156192
return grpc::CreateChannel(host, grpc::InsecureChannelCredentials());
157193
}
158194

159195
// For tests only
160196
grpc::ChannelArguments args;
161-
args.SetSslTargetNameOverride(host_config.target_name);
162-
Path path = host_config.certificate_path;
197+
args.SetSslTargetNameOverride(host_config->target_name);
198+
Path path = host_config->certificate_path;
163199
StatusOr<std::string> test_certificate = ReadFile(path);
164200
HARD_ASSERT(test_certificate.ok(),
165201
StringFormat("Unable to open root certificates at file path %s",
@@ -241,21 +277,11 @@ void GrpcConnection::Unregister(GrpcCall* call) {
241277
const std::string& host,
242278
const Path& certificate_path,
243279
const std::string& target_name) {
244-
HARD_ASSERT(!host.empty(), "Empty host name");
245-
HARD_ASSERT(!certificate_path.native_value().empty(),
246-
"Empty path to test certificate");
247-
HARD_ASSERT(!target_name.empty(), "Empty SSL target name");
248-
249-
HostConfig& host_config = Config()[host];
250-
host_config.certificate_path = certificate_path;
251-
host_config.target_name = target_name;
280+
Config().UseTestCertificate(host, certificate_path, target_name);
252281
}
253282

254283
/*static*/ void GrpcConnection::UseInsecureChannel(const std::string& host) {
255-
HARD_ASSERT(!host.empty(), "Empty host name");
256-
257-
HostConfig& host_config = Config()[host];
258-
host_config.use_insecure_channel = true;
284+
Config().UseInsecureChannel(host);
259285
}
260286

261287
} // namespace remote
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_SANITIZERS_H_
18+
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_SANITIZERS_H_
19+
20+
// Clang feature detection compiler extension
21+
#if defined(__has_feature)
22+
#define HAS_FEATURE(FEATURE) __has_feature(FEATURE)
23+
#else
24+
#define HAS_FEATURE(FEATURE) 0
25+
#endif
26+
27+
// Auto-detect the presence of sanitizers. Abseil does not do this yet, and
28+
// instead relies on users to pass e.g. -DADDRESS_SANITIZER on the command-line.
29+
30+
// Clang and GCC 4.8
31+
#if !defined(ADDRESS_SANITIZER)
32+
#if HAS_FEATURE(address_sanitizer) || \
33+
(defined(__GNUC__) && defined(__SANITIZE_ADDRESS__))
34+
#define ADDRESS_SANITIZER 1
35+
#endif
36+
#endif // !defined(ADDRESS_SANITIZER)
37+
38+
// Clang-only
39+
#if !defined(MEMORY_SANITIZER)
40+
#if HAS_FEATURE(memory_sanitizer)
41+
#define MEMORY_SANITIZER 1
42+
#endif
43+
#endif // !defined(MEMORY_SANITIZER)
44+
45+
// Clang and GCC 4.8+
46+
#if !defined(THREAD_SANITIZER)
47+
#if HAS_FEATURE(thread_sanitizer) || \
48+
(defined(__GNUC__) && defined(__SANITIZE_THREAD__))
49+
#define THREAD_SANITIZER 1
50+
#endif
51+
#endif // !defined(THREAD_SANITIZER)
52+
53+
// There doesn't appear to be a __has_feature check for ubsan.
54+
55+
#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_SANITIZERS_H_

0 commit comments

Comments
 (0)