Skip to content

Commit 902d9fe

Browse files
author
Hui-Wu
authored
Port public shutdown (#3467)
* async_queue shutdown ready * shutdown feature complete. need to fix shared_ptr for credprovider and stop executor leakage. * stop executor leakage. * Improved comments without shared_from_this * shared_ptr everywhere! * undo scheme change * addressing comments #1 * addressing comments * addressing comments #3
1 parent c67c32a commit 902d9fe

29 files changed

+315
-114
lines changed

Firestore/Example/Tests/API/FSTAPIHelpers.mm

+2-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@
6565
persistenceKey:"db123"
6666
credentialsProvider:nullptr
6767
workerQueue:nullptr
68-
firebaseApp:nil];
68+
firebaseApp:nil
69+
instanceRegistry:nil];
6970
});
7071
#pragma clang diagnostic pop
7172
return sharedInstance;

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

+66
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
2323
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
2424
#import "Firestore/Source/API/FIRFirestore+Internal.h"
25+
#import "Firestore/Source/Core/FSTFirestoreClient.h"
26+
#include "Firestore/core/test/firebase/firestore/testutil/app_testing.h"
27+
28+
namespace testutil = firebase::firestore::testutil;
2529

2630
using firebase::firestore::util::TimerId;
2731

@@ -1315,4 +1319,66 @@ - (void)testClearPersistenceWhileRunningFails {
13151319
[self awaitExpectations];
13161320
}
13171321

1322+
- (void)testRestartFirestoreLeadsToNewInstance {
1323+
FIRApp *app = testutil::AppForUnitTesting(util::MakeString([FSTIntegrationTestCase projectID]));
1324+
FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
1325+
FIRFirestore *sameInstance = [FIRFirestore firestoreForApp:app];
1326+
firestore.settings = [FSTIntegrationTestCase settings];
1327+
1328+
XCTAssertEqual(firestore, sameInstance);
1329+
1330+
NSDictionary<NSString *, id> *data =
1331+
@{@"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
1332+
[self writeDocumentRef:[firestore documentWithPath:@"abc/123"] data:data];
1333+
1334+
[self shutdownFirestore:firestore];
1335+
1336+
// Create a new instance, check it's a different instance.
1337+
FIRFirestore *newInstance = [FIRFirestore firestoreForApp:app];
1338+
newInstance.settings = [FSTIntegrationTestCase settings];
1339+
XCTAssertNotEqual(firestore, newInstance);
1340+
1341+
// New instance still functions.
1342+
FIRDocumentSnapshot *snapshot =
1343+
[self readDocumentForRef:[newInstance documentWithPath:@"abc/123"]];
1344+
XCTAssertTrue([data isEqualToDictionary:[snapshot data]]);
1345+
}
1346+
1347+
- (void)testAppDeleteLeadsToFirestoreShutdown {
1348+
FIRApp *app = testutil::AppForUnitTesting(util::MakeString([FSTIntegrationTestCase projectID]));
1349+
FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
1350+
firestore.settings = [FSTIntegrationTestCase settings];
1351+
NSDictionary<NSString *, id> *data =
1352+
@{@"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
1353+
[self writeDocumentRef:[firestore documentWithPath:@"abc/123"] data:data];
1354+
1355+
[self deleteApp:app];
1356+
1357+
FSTFirestoreClient *client = firestore.wrapped->client();
1358+
XCTAssertTrue([client isShutdown]);
1359+
}
1360+
1361+
- (void)testShutdownCanBeCalledMultipleTimes {
1362+
FIRApp *app = testutil::AppForUnitTesting(util::MakeString([FSTIntegrationTestCase projectID]));
1363+
FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
1364+
1365+
[firestore shutdownWithCompletion:[self completionForExpectationWithName:@"Shutdown1"]];
1366+
[self awaitExpectations];
1367+
XCTAssertThrowsSpecific(
1368+
{
1369+
[firestore disableNetworkWithCompletion:^(NSError *error){
1370+
}];
1371+
},
1372+
NSException, @"The client has already been shutdown.");
1373+
1374+
[firestore shutdownWithCompletion:[self completionForExpectationWithName:@"Shutdown2"]];
1375+
[self awaitExpectations];
1376+
XCTAssertThrowsSpecific(
1377+
{
1378+
[firestore enableNetworkWithCompletion:^(NSError *error){
1379+
}];
1380+
},
1381+
NSException, @"The client has already been shutdown.");
1382+
}
1383+
13181384
@end

Firestore/Example/Tests/Integration/FSTDatastoreTests.mm

+2-2
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ @interface FSTDatastoreTests : XCTestCase
159159
@implementation FSTDatastoreTests {
160160
std::shared_ptr<AsyncQueue> _testWorkerQueue;
161161
FSTLocalStore *_localStore;
162-
EmptyCredentialsProvider _credentials;
163162

164163
DatabaseInfo _databaseInfo;
165164
std::shared_ptr<Datastore> _datastore;
@@ -183,7 +182,8 @@ - (void)setUp {
183182
dispatch_queue_t queue = dispatch_queue_create(
184183
"com.google.firestore.FSTDatastoreTestsWorkerQueue", DISPATCH_QUEUE_SERIAL);
185184
_testWorkerQueue = std::make_shared<AsyncQueue>(absl::make_unique<ExecutorLibdispatch>(queue));
186-
_datastore = std::make_shared<Datastore>(_databaseInfo, _testWorkerQueue, &_credentials);
185+
_datastore = std::make_shared<Datastore>(_databaseInfo, _testWorkerQueue,
186+
std::make_shared<EmptyCredentialsProvider>());
187187

188188
_remoteStore =
189189
absl::make_unique<RemoteStore>(_localStore, _datastore, _testWorkerQueue, [](OnlineState) {});

Firestore/Example/Tests/SpecTests/FSTMockDatastore.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class MockDatastore : public Datastore {
3838
public:
3939
MockDatastore(const core::DatabaseInfo& database_info,
4040
const std::shared_ptr<util::AsyncQueue>& worker_queue,
41-
auth::CredentialsProvider* credentials);
41+
std::shared_ptr<auth::CredentialsProvider> credentials);
4242

4343
std::shared_ptr<WatchStream> CreateWatchStream(WatchStreamCallback* callback) override;
4444
std::shared_ptr<WriteStream> CreateWriteStream(WriteStreamCallback* callback) override;
@@ -93,7 +93,7 @@ class MockDatastore : public Datastore {
9393
// reduces the number of test-only methods in `Datastore`.
9494
const core::DatabaseInfo* database_info_ = nullptr;
9595
std::shared_ptr<util::AsyncQueue> worker_queue_;
96-
auth::CredentialsProvider* credentials_ = nullptr;
96+
std::shared_ptr<auth::CredentialsProvider> credentials_;
9797

9898
std::shared_ptr<MockWatchStream> watch_stream_;
9999
std::shared_ptr<MockWriteStream> write_stream_;

Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm

+3-3
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
class MockWatchStream : public WatchStream {
6666
public:
6767
MockWatchStream(const std::shared_ptr<AsyncQueue>& worker_queue,
68-
CredentialsProvider* credentials_provider,
68+
std::shared_ptr<CredentialsProvider> credentials_provider,
6969
FSTSerializerBeta* serializer,
7070
GrpcConnection* grpc_connection,
7171
WatchStreamCallback* callback,
@@ -156,7 +156,7 @@ void WriteWatchChange(const WatchChange& change, SnapshotVersion snap) {
156156
class MockWriteStream : public WriteStream {
157157
public:
158158
MockWriteStream(const std::shared_ptr<AsyncQueue>& worker_queue,
159-
CredentialsProvider* credentials_provider,
159+
std::shared_ptr<CredentialsProvider> credentials_provider,
160160
FSTSerializerBeta* serializer,
161161
GrpcConnection* grpc_connection,
162162
WriteStreamCallback* callback,
@@ -239,7 +239,7 @@ int sent_mutations_count() const {
239239

240240
MockDatastore::MockDatastore(const core::DatabaseInfo& database_info,
241241
const std::shared_ptr<util::AsyncQueue>& worker_queue,
242-
auth::CredentialsProvider* credentials)
242+
std::shared_ptr<auth::CredentialsProvider> credentials)
243243
: Datastore{database_info, worker_queue, credentials, CreateNoOpConnectivityMonitor()},
244244
database_info_{&database_info},
245245
worker_queue_{worker_queue},

Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm

+2-2
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ @implementation FSTSyncEngineTestDriver {
148148

149149
DatabaseInfo _databaseInfo;
150150
User _currentUser;
151-
EmptyCredentialsProvider _credentialProvider;
152151

153152
std::shared_ptr<MockDatastore> _datastore;
154153
}
@@ -179,7 +178,8 @@ - (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
179178
_persistence = persistence;
180179
_localStore = [[FSTLocalStore alloc] initWithPersistence:persistence initialUser:initialUser];
181180

182-
_datastore = std::make_shared<MockDatastore>(_databaseInfo, _workerQueue, &_credentialProvider);
181+
_datastore = std::make_shared<MockDatastore>(_databaseInfo, _workerQueue,
182+
std::make_shared<EmptyCredentialsProvider>());
183183
_remoteStore = absl::make_unique<RemoteStore>(
184184
_localStore, _datastore, _workerQueue, [self](OnlineState onlineState) {
185185
[self.syncEngine applyChangedOnlineState:onlineState];

Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm

+2-1
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,8 @@ - (FIRFirestore *)firestoreWithApp:(FIRApp *)app {
234234
persistenceKey:util::MakeString(persistenceKey)
235235
credentialsProvider:std::move(credentials_provider)
236236
workerQueue:std::move(workerQueue)
237-
firebaseApp:app];
237+
firebaseApp:app
238+
instanceRegistry:nil];
238239

239240
firestore.settings = [FSTIntegrationTestCase settings];
240241
[_firestores addObject:firestore];

Firestore/Source/API/FIRFirestore+Internal.h

+30-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ namespace util = firebase::firestore::util;
3434

3535
NS_ASSUME_NONNULL_BEGIN
3636

37+
/** Provides a registry management interface for FIRFirestore instances. */
38+
@protocol FSTFirestoreInstanceRegistry
39+
40+
/** Removes the FIRFirestore instance with given database name from registry. */
41+
- (void)removeInstanceWithDatabase:(NSString *)database;
42+
43+
@end
44+
3745
@interface FIRFirestore (/* Init */)
3846

3947
/**
@@ -42,9 +50,10 @@ NS_ASSUME_NONNULL_BEGIN
4250
*/
4351
- (instancetype)initWithDatabaseID:(model::DatabaseId)databaseID
4452
persistenceKey:(std::string)persistenceKey
45-
credentialsProvider:(std::unique_ptr<auth::CredentialsProvider>)credentialsProvider
53+
credentialsProvider:(std::shared_ptr<auth::CredentialsProvider>)credentialsProvider
4654
workerQueue:(std::shared_ptr<util::AsyncQueue>)workerQueue
47-
firebaseApp:(FIRApp *)app;
55+
firebaseApp:(FIRApp *)app
56+
instanceRegistry:(nullable id<FSTFirestoreInstanceRegistry>)registry;
4857
@end
4958

5059
/** Internal FIRFirestore API we don't want exposed in our public header files. */
@@ -56,14 +65,31 @@ NS_ASSUME_NONNULL_BEGIN
5665
+ (FIRFirestore *)recoverFromFirestore:(std::shared_ptr<api::Firestore>)firestore;
5766

5867
/**
59-
* Shutdown this `FIRFirestore`, releasing all resources (abandoning any outstanding writes,
60-
* removing all listens, closing all network connections, etc.).
68+
* Shuts down this `FIRFirestore` instance.
69+
*
70+
* After shutdown only the `clearPersistence` method may be used. Any other method
71+
* will throw an error.
72+
*
73+
* To restart after shutdown, simply create a new instance of FIRFirestore with
74+
* `firestore` or `firestoreForApp` methods.
75+
*
76+
* Shutdown does not cancel any pending writes and any tasks that are awaiting a response from
77+
* the server will not be resolved. The next time you start this instance, it will resume
78+
* attempting to send these writes to the server.
79+
*
80+
* Note: Under normal circumstances, calling this method is not required. This
81+
* method is useful only when you want to force this instance to release all of its resources or
82+
* in combination with `clearPersistence` to ensure that all local state is destroyed
83+
* between test runs.
6184
*
6285
* @param completion A block to execute once everything has shut down.
6386
*/
87+
// TODO(b/135755126): Make this public.
6488
- (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion
6589
NS_SWIFT_NAME(shutdown(completion:));
6690

91+
- (void)shutdownInternalWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
92+
6793
- (const std::shared_ptr<util::AsyncQueue> &)workerQueue;
6894

6995
@property(nonatomic, assign, readonly) std::shared_ptr<api::Firestore> wrapped;

Firestore/Source/API/FIRFirestore.mm

+15-3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ @interface FIRFirestore ()
7272
@implementation FIRFirestore {
7373
std::shared_ptr<Firestore> _firestore;
7474
FIRFirestoreSettings *_settings;
75+
__weak id<FSTFirestoreInstanceRegistry> _registry;
7576
}
7677

7778
+ (instancetype)firestore {
@@ -106,15 +107,17 @@ + (instancetype)firestoreForApp:(FIRApp *)app database:(NSString *)database {
106107

107108
- (instancetype)initWithDatabaseID:(model::DatabaseId)databaseID
108109
persistenceKey:(std::string)persistenceKey
109-
credentialsProvider:(std::unique_ptr<CredentialsProvider>)credentialsProvider
110+
credentialsProvider:(std::shared_ptr<CredentialsProvider>)credentialsProvider
110111
workerQueue:(std::shared_ptr<AsyncQueue>)workerQueue
111-
firebaseApp:(FIRApp *)app {
112+
firebaseApp:(FIRApp *)app
113+
instanceRegistry:(nullable id<FSTFirestoreInstanceRegistry>)registry {
112114
if (self = [super init]) {
113115
_firestore = std::make_shared<Firestore>(std::move(databaseID), std::move(persistenceKey),
114116
std::move(credentialsProvider), std::move(workerQueue),
115117
(__bridge void *)self);
116118

117119
_app = app;
120+
_registry = registry;
118121

119122
FSTPreConverterBlock block = ^id _Nullable(id _Nullable input) {
120123
if ([input isKindOfClass:[FIRDocumentReference class]]) {
@@ -298,10 +301,19 @@ + (FIRFirestore *)recoverFromFirestore:(std::shared_ptr<Firestore>)firestore {
298301
return (__bridge FIRFirestore *)firestore->extension();
299302
}
300303

301-
- (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
304+
- (void)shutdownInternalWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
302305
_firestore->Shutdown(util::MakeCallback(completion));
303306
}
304307

308+
- (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
309+
id<FSTFirestoreInstanceRegistry> strongRegistry = _registry;
310+
if (strongRegistry) {
311+
[strongRegistry
312+
removeInstanceWithDatabase:util::MakeNSString(_firestore->database_id().database_id())];
313+
}
314+
[self shutdownInternalWithCompletion:completion];
315+
}
316+
305317
@end
306318

307319
NS_ASSUME_NONNULL_END

Firestore/Source/API/FSTFirestoreComponent.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#import <Foundation/Foundation.h>
1818

19+
#import "Firestore/Source/API/FIRFirestore+Internal.h"
20+
1921
@class FIRApp;
2022
@class FIRFirestore;
2123

@@ -35,7 +37,8 @@ NS_ASSUME_NONNULL_BEGIN
3537

3638
/// A concrete implementation for FSTInstanceProvider to create Firestore instances and register
3739
/// with Core's component system.
38-
@interface FSTFirestoreComponent : NSObject <FSTFirestoreMultiDBProvider>
40+
@interface FSTFirestoreComponent
41+
: NSObject <FSTFirestoreInstanceRegistry, FSTFirestoreMultiDBProvider>
3942

4043
/// The FIRApp that instances will be set up with.
4144
@property(nonatomic, weak, readonly) FIRApp *app;
@@ -46,6 +49,8 @@ NS_ASSUME_NONNULL_BEGIN
4649
/// Default method for retrieving a Firestore instance, or creating one if it doesn't exist.
4750
- (FIRFirestore *)firestoreForDatabase:(NSString *)database;
4851

52+
- (void)removeInstanceWithDatabase:(NSString *)database;
53+
4954
/// Default initializer.
5055
- (instancetype)initWithApp:(FIRApp *)app NS_DESIGNATED_INITIALIZER;
5156

Firestore/Source/API/FSTFirestoreComponent.mm

+23-6
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,18 @@ - (instancetype)initWithApp:(FIRApp *)app {
7070
return self;
7171
}
7272

73+
- (NSString *)keyForDatabase:(NSString *)database {
74+
return [NSString stringWithFormat:@"%@|%@", self.app.name, database];
75+
}
76+
7377
#pragma mark - FSTInstanceProvider Conformance
7478

7579
- (FIRFirestore *)firestoreForDatabase:(NSString *)database {
7680
if (!database) {
7781
ThrowInvalidArgument("Database identifier may not be nil.");
7882
}
7983

80-
NSString *key = [NSString stringWithFormat:@"%@|%@", self.app.name, database];
84+
NSString *key = [self keyForDatabase:database];
8185

8286
// Get the component from the container.
8387
@synchronized(self.instances) {
@@ -93,7 +97,7 @@ - (FIRFirestore *)firestoreForDatabase:(NSString *)database {
9397
auto workerQueue = absl::make_unique<AsyncQueue>(std::move(executor));
9498

9599
id<FIRAuthInterop> auth = FIR_COMPONENT(FIRAuthInterop, self.app.container);
96-
auto credentialsProvider = absl::make_unique<FirebaseCredentialsProvider>(self.app, auth);
100+
auto credentialsProvider = std::make_shared<FirebaseCredentialsProvider>(self.app, auth);
97101

98102
model::DatabaseId databaseID{util::MakeString(self.app.options.projectID),
99103
util::MakeString(database)};
@@ -102,19 +106,32 @@ - (FIRFirestore *)firestoreForDatabase:(NSString *)database {
102106
persistenceKey:std::move(persistenceKey)
103107
credentialsProvider:std::move(credentialsProvider)
104108
workerQueue:std::move(workerQueue)
105-
firebaseApp:self.app];
109+
firebaseApp:self.app
110+
instanceRegistry:self];
106111
_instances[key] = firestore;
107112
}
108-
109113
return firestore;
110114
}
111115
}
112116

117+
- (void)removeInstanceWithDatabase:(NSString *)database {
118+
@synchronized(_instances) {
119+
NSString *key = [self keyForDatabase:database];
120+
[_instances removeObjectForKey:key];
121+
}
122+
}
123+
113124
#pragma mark - FIRComponentLifecycleMaintainer
114125

115126
- (void)appWillBeDeleted:(FIRApp *)app {
116-
// Stop any actions and clean up resources since instances of Firestore associated with this app
117-
// will be removed. Currently does not do anything.
127+
NSDictionary<NSString *, FIRFirestore *> *instances;
128+
@synchronized(_instances) {
129+
instances = [_instances copy];
130+
[_instances removeAllObjects];
131+
}
132+
for (NSString *key in instances) {
133+
[instances[key] shutdownInternalWithCompletion:nil];
134+
}
118135
}
119136

120137
#pragma mark - Object Lifecycle

Firestore/Source/Core/FSTFirestoreClient.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ NS_ASSUME_NONNULL_BEGIN
6969
+ (instancetype)clientWithDatabaseInfo:(const core::DatabaseInfo &)databaseInfo
7070
settings:(const api::Settings &)settings
7171
credentialsProvider:
72-
(auth::CredentialsProvider *)credentialsProvider // no passing ownership
72+
(std::shared_ptr<auth::CredentialsProvider>)credentialsProvider
7373
userExecutor:(std::shared_ptr<util::Executor>)userExecutor
7474
workerQueue:(std::shared_ptr<util::AsyncQueue>)workerQueue;
7575

0 commit comments

Comments
 (0)