Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mila/MultiDB #738

Merged
merged 19 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 80 additions & 15 deletions firestore/src/FirebaseFirestore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,24 @@ public sealed class FirebaseFirestore {
private readonly FirebaseFirestoreSettings _settings;
private readonly TransactionManager _transactionManager;

private static readonly IDictionary<FirebaseApp, FirebaseFirestore> _instanceCache =
new Dictionary<FirebaseApp, FirebaseFirestore>();
private static readonly IDictionary<FirestoreInstanceCacheKey, FirebaseFirestore> _instanceCache =
new Dictionary<FirestoreInstanceCacheKey, FirebaseFirestore>();

private const string kDefaultDatabase = "(default)";
tom-andersen marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Returns the <c>database name</c> of this <c>FirebaseFirestore</c> .
/// </summary>
private string DatabaseName { get; set; }

// //TODO(Mila)->update
// We rely on e.g. firestore.Document("a/b").Firestore returning the original Firestore
// instance so it's important the constructor remains private and we only create one
// FirebaseFirestore instance per FirebaseApp instance.
private FirebaseFirestore(FirestoreProxy proxy, FirebaseApp app) {
private FirebaseFirestore(FirestoreProxy proxy, FirebaseApp app, string database) {
_proxy = Util.NotNull(proxy);
App = app;
DatabaseName = database;
app.AppDisposed += OnAppDisposed;

// This call to InitializeConverterCache() exists to make sure that AOT works.
Expand Down Expand Up @@ -99,9 +108,11 @@ private void Dispose() {
_isInCppInstanceCache = false;
RemoveSelfFromInstanceCache();
}

_proxy = null;
App = null;
DatabaseName = null;

} finally {
_disposeLock.ReleaseWriterLock();
}
Expand All @@ -115,41 +126,67 @@ private void Dispose() {
public FirebaseApp App { get; private set; }

/// <summary>
/// Gets the instance of <c>FirebaseFirestore</c> for the default <c>FirebaseApp</c>.
/// Gets the instance of <c>FirebaseFirestore</c> for the default <c>FirebaseApp</c> with the default <c>database</c>.
/// </summary>
/// <value>A <c>FirebaseFirestore</c> instance.</value>
public static FirebaseFirestore DefaultInstance {
get {
FirebaseApp app = Util.NotNull(FirebaseApp.DefaultInstance);
return GetInstance(app);
return GetInstance(app,kDefaultDatabase);
}
}

/// <summary>
/// Gets an instance of <c>FirebaseFirestore</c> for a specific <c>FirebaseApp</c>.
/// Gets an instance of <c>FirebaseFirestore</c> for a specific <c>FirebaseApp</c> with the default <c>database</c>.
/// </summary>
/// <param name="app">The <c>FirebaseApp</c> for which to get a <c>FirebaseFirestore</c>
/// instance.</param>
/// <returns>A <c>FirebaseFirestore</c> instance.</returns>
public static FirebaseFirestore GetInstance(FirebaseApp app) {
Preconditions.CheckNotNull(app, nameof(app));
return GetInstance(app, kDefaultDatabase);
}


/// <summary>
/// Gets an instance of <c>FirebaseFirestore</c> for the default <c>FirebaseApp</c> with a spesific <c>database</c>.
/// </summary>
/// <param name="database">The named <c>database</c>.
/// instance.</param>
/// <returns>A <c>FirebaseFirestore</c> instance.</returns>
public static FirebaseFirestore GetInstance(string database) {
Preconditions.CheckNotNull(database, nameof(database));
FirebaseApp app = Util.NotNull(FirebaseApp.DefaultInstance);
return GetInstance(app, database);
}

/// <summary>
/// Gets an instance of <c>FirebaseFirestore</c> for a specific <c>FirebaseApp</c> with a spesific <c>database</c>.
/// </summary>
/// <param name="app">The <c>FirebaseApp</c> for which to get a <c>FirebaseFirestore</c>
/// <param name="database">The named <c>database</c>.
/// instance.</param>
/// <returns>A <c>FirebaseFirestore</c> instance.</returns>
public static FirebaseFirestore GetInstance(FirebaseApp app, string database) {
Preconditions.CheckNotNull(app, nameof(app));
Preconditions.CheckNotNull(database, nameof(database));

while (true) {
FirebaseFirestore firestore;

FirestoreInstanceCacheKey key = new FirestoreInstanceCacheKey{App = app, DatabaseName = database};
// Acquire the lock on `_instanceCache` to see if the given `FirebaseApp` is in
// `_instanceCache`; if it isn't then create the `FirebaseFirestore` instance, put it in the
// cache, and return it.
lock (_instanceCache) {
if (!_instanceCache.TryGetValue(app, out firestore)) {
if (!_instanceCache.TryGetValue(key, out firestore)) {
// NOTE: FirestoreProxy.GetInstance() returns an *owning* reference (see the %newobject
// directive in firestore.SWIG); therefore, we must make sure that we *never* call
// FirestoreProxy.GetInstance() when it would return a proxy for a C++ object that it
// previously returned. Otherwise, if it did, then that C++ object would be deleted
// twice, causing a crash.
FirestoreProxy firestoreProxy = Util.NotNull(FirestoreProxy.GetInstance(app));
firestore = new FirebaseFirestore(firestoreProxy, app);
_instanceCache[app] = firestore;
FirestoreProxy firestoreProxy = Util.NotNull(FirestoreProxy.GetInstance(app, database));
firestore = new FirebaseFirestore(firestoreProxy, app, database);
_instanceCache[key] = firestore;
return firestore;
}
}
Expand Down Expand Up @@ -557,7 +594,7 @@ public Task WaitForPendingWritesAsync() {
/// used. Calling any other method will result in an error.
///
/// To restart after termination, simply create a new instance of <c>FirebaseFirestore</c> with
/// <c>GetInstance()</c> or <c>GetInstance(FirebaseApp)</c>.
/// <c>GetInstance</c> methods.
///
/// <c>Terminate()</c> does not cancel any pending writes, and any tasks that are awaiting a
/// response from the server will not be resolved. The next time you start this instance, it
Expand Down Expand Up @@ -663,10 +700,38 @@ private void WithFirestoreProxy(Action<FirestoreProxy> action) {
private void RemoveSelfFromInstanceCache() {
lock (_instanceCache) {
FirebaseFirestore cachedFirestore;
if (_instanceCache.TryGetValue(App, out cachedFirestore) && cachedFirestore == this) {
_instanceCache.Remove(App);
FirestoreInstanceCacheKey key = new FirestoreInstanceCacheKey{App = App, DatabaseName = DatabaseName};
if (_instanceCache.TryGetValue(key, out cachedFirestore) && cachedFirestore == this) {
_instanceCache.Remove(key);
}
}
}

private struct FirestoreInstanceCacheKey : IEquatable<FirestoreInstanceCacheKey> {
tom-andersen marked this conversation as resolved.
Show resolved Hide resolved
public FirebaseApp App { get; set; }
public string DatabaseName { get; set; }

public override int GetHashCode() {
return App.GetHashCode() + DatabaseName.GetHashCode();
}
public override bool Equals(object obj) {
return obj is FirestoreInstanceCacheKey && Equals((FirestoreInstanceCacheKey)obj);
}
public bool Equals(FirestoreInstanceCacheKey other) {
return App == other.App && DatabaseName == other.DatabaseName;
}

public static bool operator ==(FirestoreInstanceCacheKey lhs, FirestoreInstanceCacheKey rhs) {
return lhs.Equals(rhs);
}
public static bool operator !=(FirestoreInstanceCacheKey lhs, FirestoreInstanceCacheKey rhs) {
return !lhs.Equals(rhs);
}
public override string ToString() {
return String.Format("FirestoreInstanceKey: {0}", new { App, DatabaseName });
tom-andersen marked this conversation as resolved.
Show resolved Hide resolved
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,14 @@ public static InvalidArgumentsTestCase[] TestCases {
action = FieldValue_ArrayRemove_NullArray },
new InvalidArgumentsTestCase { name = "FieldValue_ArrayUnion_NullArray",
action = FieldValue_ArrayUnion_NullArray },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null",
action = FirebaseFirestore_GetInstance_Null },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null_App",
action = FirebaseFirestore_GetInstance_Null_App },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null_Database_Name",
action = FirebaseFirestore_GetInstance_Null_Database_Name },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_App_With_Null_Database_Name",
action = FirebaseFirestore_GetInstance_App_With_Null_Database_Name},
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_Null_App_With_Database_Name",
action = FirebaseFirestore_GetInstance_Null_App_With_Database_Name },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_GetInstance_DisposedApp",
action = FirebaseFirestore_GetInstance_DisposedApp },
new InvalidArgumentsTestCase { name = "FirebaseFirestore_Collection_NullStringPath",
Expand Down Expand Up @@ -628,11 +634,26 @@ private static void FieldValue_ArrayUnion_NullArray(UIHandlerAutomated handler)
handler.AssertException(typeof(ArgumentNullException), () => FieldValue.ArrayUnion(null));
}

private static void FirebaseFirestore_GetInstance_Null(UIHandlerAutomated handler) {
private static void FirebaseFirestore_GetInstance_Null_App(UIHandlerAutomated handler) {
handler.AssertException(typeof(ArgumentNullException),
() => FirebaseFirestore.GetInstance(null));
() => FirebaseFirestore.GetInstance((FirebaseApp)null));
}

private static void FirebaseFirestore_GetInstance_Null_Database_Name(UIHandlerAutomated handler) {
handler.AssertException(typeof(ArgumentNullException),
() => FirebaseFirestore.GetInstance((string)null));
}
private static void FirebaseFirestore_GetInstance_Null_App_With_Database_Name(UIHandlerAutomated handler) {
handler.AssertException(typeof(ArgumentNullException),
() => FirebaseFirestore.GetInstance((FirebaseApp)null, "foo"));
}
private static void FirebaseFirestore_GetInstance_App_With_Null_Database_Name(UIHandlerAutomated handler)
{
FirebaseApp app = FirebaseApp.DefaultInstance;
handler.AssertException(typeof(ArgumentNullException),
() => FirebaseFirestore.GetInstance(app, (string)null));
}

private static void FirebaseFirestore_GetInstance_DisposedApp(UIHandlerAutomated handler) {
FirebaseApp disposedApp =
FirebaseApp.Create(handler.db.App.Options, "test-getinstance-disposedapp");
Expand Down
Loading