Skip to content

Commit 4e067e5

Browse files
authored
gRPC: use SCNetworkReachability to quickly end connections when reachability changes (#1843)
The reachability state is not (yet) used to determine whether it's possible to reconnect; rather, the its only use is to quickly end any existing connections. The benefit is potentially quicker reaction to network being disconnected than it would take for the connection to time out.
1 parent 3f60f8d commit 4e067e5

12 files changed

+388
-10
lines changed

FirebaseFirestore.podspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling,
4141
'Firestore/third_party/Immutable/Tests/**',
4242

4343
# Exclude alternate implementations for other platforms
44+
'Firestore/core/src/firebase/firestore/remote/connectivity_monitor_noop.cc',
4445
'Firestore/core/src/firebase/firestore/util/filesystem_win.cc',
4546
'Firestore/core/src/firebase/firestore/util/hard_assert_stdio.cc',
4647
'Firestore/core/src/firebase/firestore/util/log_stdio.cc',
@@ -56,7 +57,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling,
5657
s.dependency 'Protobuf', '~> 3.1'
5758
s.dependency 'nanopb', '~> 0.3.8'
5859

59-
s.frameworks = 'MobileCoreServices'
60+
s.frameworks = 'MobileCoreServices', 'SystemConfiguration'
6061
s.library = 'c++'
6162
s.pod_target_xcconfig = {
6263
'GCC_PREPROCESSOR_DEFINITIONS' =>

Firestore/core/src/firebase/firestore/remote/CMakeLists.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,36 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
cc_library(
16+
firebase_firestore_remote_connectivity_monitor_apple
17+
SOURCES
18+
connectivity_monitor.cc
19+
connectivity_monitor.h
20+
connectivity_monitor_apple.mm
21+
DEPENDS
22+
absl_base
23+
firebase_firestore_util
24+
EXCLUDE_FROM_ALL
25+
)
26+
27+
cc_library(
28+
firebase_firestore_remote_connectivity_monitor_noop
29+
SOURCES
30+
connectivity_monitor.cc
31+
connectivity_monitor.h
32+
connectivity_monitor_noop.cc
33+
DEPENDS
34+
absl_base
35+
firebase_firestore_util
36+
EXCLUDE_FROM_ALL
37+
)
38+
39+
cc_select(
40+
firebase_firestore_remote_connectivity_monitor
41+
APPLE firebase_firestore_remote_connectivity_monitor_apple
42+
DEFAULT firebase_firestore_remote_connectivity_monitor_noop
43+
)
44+
1545
cc_library(
1646
firebase_firestore_remote
1747
SOURCES
@@ -28,6 +58,7 @@ cc_library(
2858
firebase_firestore_model
2959
firebase_firestore_nanopb
3060
firebase_firestore_protos_nanopb
61+
firebase_firestore_remote_connectivity_monitor
3162
firebase_firestore_util
3263
grpc
3364
)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2018 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+
#include "Firestore/core/src/firebase/firestore/remote/connectivity_monitor.h"
18+
19+
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
20+
21+
namespace firebase {
22+
namespace firestore {
23+
namespace remote {
24+
25+
void ConnectivityMonitor::SetInitialStatus(NetworkStatus new_status) {
26+
HARD_ASSERT(!status_.has_value(),
27+
"SetInitialStatus should only be called once");
28+
status_ = new_status;
29+
}
30+
31+
void ConnectivityMonitor::MaybeInvokeCallbacks(NetworkStatus new_status) {
32+
if (new_status == status_) {
33+
return;
34+
}
35+
status_ = new_status;
36+
37+
for (auto& callback : callbacks_) {
38+
callback(status_.value());
39+
}
40+
}
41+
42+
} // namespace remote
43+
} // namespace firestore
44+
} // namespace firebase
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2018 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_REMOTE_CONNECTIVITY_MONITOR_H_
18+
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_CONNECTIVITY_MONITOR_H_
19+
20+
#include <functional>
21+
#include <memory>
22+
#include <utility>
23+
#include <vector>
24+
25+
#include "Firestore/core/src/firebase/firestore/util/async_queue.h"
26+
#include "absl/types/optional.h"
27+
28+
namespace firebase {
29+
namespace firestore {
30+
namespace remote {
31+
32+
/**
33+
* A base class for monitoring changes in network connectivity; it is expected
34+
* that each platform will have its own system-dependent implementation.
35+
*/
36+
class ConnectivityMonitor {
37+
public:
38+
/**
39+
* The set of network states is deliberately simplified -- we only care about
40+
* states such that transition between them should break currently
41+
* established connections.
42+
*/
43+
enum class NetworkStatus {
44+
Unavailable,
45+
Available,
46+
AvailableViaCellular,
47+
};
48+
49+
using CallbackT = std::function<void(NetworkStatus)>;
50+
51+
/** Creates a platform-specific connectivity monitor. */
52+
static std::unique_ptr<ConnectivityMonitor> Create(
53+
util::AsyncQueue* worker_queue);
54+
55+
explicit ConnectivityMonitor(util::AsyncQueue* worker_queue)
56+
: worker_queue_{worker_queue} {
57+
}
58+
59+
virtual ~ConnectivityMonitor() {
60+
}
61+
62+
void AddCallback(CallbackT&& callback) {
63+
callbacks_.push_back(std::move(callback));
64+
}
65+
// TODO(varconst): RemoveCallback.
66+
67+
protected:
68+
// The status may be retrieved asynchronously.
69+
void SetInitialStatus(NetworkStatus new_status);
70+
71+
// Invokes callbacks only if the status changed.
72+
void MaybeInvokeCallbacks(NetworkStatus new_status);
73+
74+
util::AsyncQueue* queue() {
75+
return worker_queue_;
76+
}
77+
78+
private:
79+
util::AsyncQueue* worker_queue_ = nullptr;
80+
std::vector<CallbackT> callbacks_;
81+
absl::optional<NetworkStatus> status_;
82+
};
83+
84+
} // namespace remote
85+
} // namespace firestore
86+
} // namespace firebase
87+
88+
#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_CONNECTIVITY_MONITOR_H_
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2018 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+
#include "Firestore/core/src/firebase/firestore/remote/connectivity_monitor.h"
18+
19+
#if defined(__APPLE__)
20+
21+
#include <SystemConfiguration/SystemConfiguration.h>
22+
#include <dispatch/dispatch.h>
23+
#include <netinet/in.h>
24+
25+
#include <memory>
26+
27+
#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h"
28+
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
29+
#include "Firestore/core/src/firebase/firestore/util/log.h"
30+
#include "absl/memory/memory.h"
31+
32+
namespace firebase {
33+
namespace firestore {
34+
namespace remote {
35+
36+
namespace {
37+
38+
using NetworkStatus = ConnectivityMonitor::NetworkStatus;
39+
using util::AsyncQueue;
40+
using util::internal::ExecutorLibdispatch;
41+
42+
NetworkStatus ToNetworkStatus(SCNetworkReachabilityFlags flags) {
43+
if (!(flags & kSCNetworkReachabilityFlagsReachable)) {
44+
return NetworkStatus::Unavailable;
45+
}
46+
if (flags & kSCNetworkReachabilityFlagsConnectionRequired) {
47+
return NetworkStatus::Unavailable;
48+
}
49+
50+
#if TARGET_OS_IPHONE
51+
if (flags & kSCNetworkReachabilityFlagsIsWWAN) {
52+
return NetworkStatus::AvailableViaCellular;
53+
}
54+
#endif
55+
return NetworkStatus::Available;
56+
}
57+
58+
SCNetworkReachabilityRef CreateReachability() {
59+
// Pseudoaddress that monitors internet reachability in general.
60+
sockaddr_in any_connection_addr{};
61+
any_connection_addr.sin_len = sizeof(any_connection_addr);
62+
any_connection_addr.sin_family = AF_INET;
63+
return SCNetworkReachabilityCreateWithAddress(
64+
nullptr, reinterpret_cast<sockaddr*>(&any_connection_addr));
65+
}
66+
67+
void OnReachabilityChangedCallback(SCNetworkReachabilityRef /*unused*/,
68+
SCNetworkReachabilityFlags flags,
69+
void* raw_this);
70+
71+
} // namespace
72+
73+
/**
74+
* Implementation of `ConnectivityMonitor` based on `SCNetworkReachability`
75+
* (iOS/MacOS).
76+
*/
77+
class ConnectivityMonitorApple : public ConnectivityMonitor {
78+
public:
79+
explicit ConnectivityMonitorApple(AsyncQueue* worker_queue)
80+
: ConnectivityMonitor{worker_queue}, reachability_{CreateReachability()} {
81+
SCNetworkReachabilityFlags flags;
82+
if (SCNetworkReachabilityGetFlags(reachability_, &flags)) {
83+
SetInitialStatus(ToNetworkStatus(flags));
84+
}
85+
86+
SCNetworkReachabilityContext context{};
87+
context.info = this;
88+
bool success = SCNetworkReachabilitySetCallback(
89+
reachability_, OnReachabilityChangedCallback, &context);
90+
if (!success) {
91+
LOG_DEBUG("Couldn't set reachability callback");
92+
return;
93+
}
94+
95+
// TODO(varconst): 1. Make this at least more robust by adding an enum to
96+
// `Executor` that allows asserting on the actual type before casting.
97+
// 2. This is an unfortunate, brittle mechanism, see if better alternatives
98+
// come up.
99+
// On Apple platforms, the executor implementation must be the
100+
// libdispatch-based one.
101+
auto executor = static_cast<ExecutorLibdispatch*>(queue()->executor());
102+
success = SCNetworkReachabilitySetDispatchQueue(reachability_,
103+
executor->dispatch_queue());
104+
if (!success) {
105+
LOG_DEBUG("Couldn't set reachability queue");
106+
return;
107+
}
108+
}
109+
110+
~ConnectivityMonitorApple() {
111+
bool success =
112+
SCNetworkReachabilitySetDispatchQueue(reachability_, nullptr);
113+
if (!success) {
114+
LOG_DEBUG("Couldn't unset reachability queue");
115+
}
116+
}
117+
118+
void OnReachabilityChanged(SCNetworkReachabilityFlags flags) {
119+
queue()->ExecuteBlocking(
120+
[this, flags] { MaybeInvokeCallbacks(ToNetworkStatus(flags)); });
121+
}
122+
123+
private:
124+
SCNetworkReachabilityRef reachability_;
125+
};
126+
127+
namespace {
128+
129+
void OnReachabilityChangedCallback(SCNetworkReachabilityRef /*unused*/,
130+
SCNetworkReachabilityFlags flags,
131+
void* raw_this) {
132+
HARD_ASSERT(raw_this, "Received a null pointer as context");
133+
static_cast<ConnectivityMonitorApple*>(raw_this)->OnReachabilityChanged(
134+
flags);
135+
}
136+
137+
} // namespace
138+
139+
std::unique_ptr<ConnectivityMonitor> ConnectivityMonitor::Create(
140+
AsyncQueue* worker_queue) {
141+
return absl::make_unique<ConnectivityMonitorApple>(worker_queue);
142+
}
143+
144+
} // namespace remote
145+
} // namespace firestore
146+
} // namespace firebase
147+
148+
#endif // defined(__APPLE__)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2018 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+
#include "Firestore/core/src/firebase/firestore/remote/connectivity_monitor.h"
18+
19+
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
20+
#include "absl/memory/memory.h"
21+
22+
namespace firebase {
23+
namespace firestore {
24+
namespace remote {
25+
26+
using util::AsyncQueue;
27+
28+
// Returns the default monitor that does nothing. This is to ensure that
29+
// build doesn't break on platforms which don't yet implement
30+
// `ConnectivityMonitor`.
31+
std::unique_ptr<ConnectivityMonitor> ConnectivityMonitor::Create(
32+
AsyncQueue* worker_queue) {
33+
return absl::make_unique<ConnectivityMonitor>(worker_queue);
34+
}
35+
36+
} // namespace remote
37+
} // namespace firestore
38+
} // namespace firebase

0 commit comments

Comments
 (0)