Make FirebaseApp.delete() Public - #5
Conversation
* Reformatted all source files * Separated unit and integration tests * Added maven checkstyle plugin * Got the basic build and unit tests working * Scrubbed the codebase for private keys, certificates etc.
…ance across all integration tests
mikelehen
left a comment
There was a problem hiding this comment.
Mostly looks good, but I have some questions / comments about threading and the behavior of FirebaseDatabase after a delete() call.
|
|
||
| import com.google.common.base.Strings; | ||
|
|
||
| public abstract class FirebaseService<T> { |
There was a problem hiding this comment.
Comments on the class and methods please.
| return; | ||
| public void delete() { | ||
| List<FirebaseService> servicesCopy; | ||
| synchronized (this) { |
There was a problem hiding this comment.
FYI- some say you should avoid "synchronized(this)" since it could potentially interact badly with outside code (if a developer synchronizes on your object externally). And so classes will often create an internal object to lock on instead. Some discussion here: http://stackoverflow.com/questions/442564/avoid-synchronizedthis-in-java
There was a problem hiding this comment.
Makes sense. Using a per Firebase App lock object instead of "this"
| static void clearInstancesForTest() { | ||
| // TODO(arondeak): also delete, once functionality is implemented. | ||
| synchronized (sLock) { | ||
| for (FirebaseApp app : ImmutableList.copyOf(instances.values())) { |
There was a problem hiding this comment.
Why do we lock and make an immutable copy?
There was a problem hiding this comment.
We need to iterate over a copy, as delete() would try to remove from the original map while iterating. Added a comment to explain.
| return; | ||
| } | ||
|
|
||
| servicesCopy = ImmutableList.copyOf(services.values()); |
There was a problem hiding this comment.
I'm guessing you are intentionally calling destroy() outside the lock to avoid calling external code inside the lock, which could potentially result in deadlocks. But I'd prefer not guess. :-) Can you add a comment?
| } | ||
|
|
||
| void removeAuthStateListener(@NonNull AuthStateListener listener) { | ||
| synchronized void removeAuthStateListener(@NonNull AuthStateListener listener) { |
There was a problem hiding this comment.
FYI- This "synchronized" will likely show up in our javadoc-generated reference docs (if we have them for the admin SDK), which is kind of annoying (some methods will show up as "synchronized" and others won't, which could confuse developers). I'd avoid using synchronized methods (and per my other comment, it's probably better to use sLock than this anyway).
| firebaseApp.delete(); | ||
| verify(listener, never()).onDeleted(appName, OPTIONS); | ||
| try { | ||
| try { |
| // ignore | ||
| } | ||
|
|
||
| FirebaseApp firebaseApp2 = FirebaseApp.initializeApp(OPTIONS, name); |
There was a problem hiding this comment.
Add a comment explaining intention of this test? E.g.:
// Verify we can re-use the same name.
|
|
||
| @Test | ||
| public void testInvokeAfterAppDelete() throws ExecutionException, InterruptedException { | ||
| if (!isCertCredential) { |
There was a problem hiding this comment.
I wonder if we should log a message or something when skipping tests so that we don't accidentally skip them without realizing it? Your call.
There was a problem hiding this comment.
Added some log statements.
| @AfterClass | ||
| public static void tearDownClass() { | ||
| public static void tearDownClass() throws InterruptedException { | ||
| TestHelpers.interruptConfig(config); |
There was a problem hiding this comment.
I'm not 100% sure what's going on here. Would it be possible to encapsulate these in nice self-describing TestHelper methods. TestHelpers.startAppForUnitTest() / TestHelpers.stopAppForUnitTest() or something? Or is DataSnapshotTest special for some reason and needs different logic? If so, please add a comment.
There was a problem hiding this comment.
We just need to use a mock/stand-in DatabaseConfig here since we are not connecting to an actual database. Added a comment to explain.
| .setCredential(FirebaseCredentials.fromCertificate(getServiceAccountCertificate())) | ||
| .build(); | ||
| return FirebaseApp.initializeApp(options); | ||
| public static synchronized FirebaseApp ensureDefaultApp() { |
There was a problem hiding this comment.
Can you add a comment? It looks like the intention is that all the integration tests use this method?
|
I've implemented all the suggested changes. Few additional changes were made to FirebaseDatabase, Repo and Query to make sure they cannot be used after application delete. |
…he contention on the database-object lock by making ensureRepo() thread safe and returning the Repo from the method.
mikelehen
left a comment
There was a problem hiding this comment.
Left a couple FYI comments, but I think this looks good. Thanks for the changes!
| } | ||
| synchronized (appsLock) { | ||
| instances.remove(this.name); | ||
| } |
There was a problem hiding this comment.
FYI- Acquiring multiple locks is a step towards deadlock potential (if another piece of code acquires both locks but in the opposite order, you have a deadlock). I'm not too concerned in this case, but if this were a larger code base with less clear component interactions, I would avoid having multiple locks or else set very clear rules about how the locks must be used (a common strategy is to declare a locking order A > B, saying that if you want to acquire lock B you must first acquire A).
I'm fine with leaving this as-is though.
There was a problem hiding this comment.
Good point. We have at least one place where we take these 2 locks in the reverse order (in clearInstancesForTest()). That shouldn't be an issue since it's only a test method, but I think it's better to implement this correct from the onset. Therefore I'm changing this back to how it was. That is I'm moving the nested synch block out. I think it makes sense for lock to guard only the instance members, and appsLock to guard the instances map. No real need for the nested synch block here.
| } | ||
|
|
||
| checkNotNull(url, | ||
| "Can't pass null for argument 'url' in FirebaseDatabase.getReferenceFromUrl()"); |
There was a problem hiding this comment.
As a minor thing, it's generally preferable not to make tangential refactorings while responding to code review feedback as it makes it harder to review and increases the potential for needing extra code review iterations. Basically, every code review iteration should make the minimal set of changes to get the change ready for submission. Anything tangential should probably go in a separate PR.
On a related note, it's generally better to have many small PRs (each with a single purpose) than single large PRs (that do a bunch of different things). It makes reviewing easier and keeps the change history cleaner.
This PR fixes and exposes the
delete()method inFirebaseAppclass.Introduced a new abstraction called
FirebaseServiceFirebaseAuthandFirebaseDatabaseare wrapped as instances ofFirebaseService, and attached to theFirebaseApp. Also we no longer keep static caches of auth and database instances.This enables
FirebaseApp.delete()method to gracefully tear down the "services" attached to it. Also all services attached to an app instances get cleaned/GC'd along with the app.Updated integration test suite to use a single masterApp instances across all tests (saves about 30 seconds caused by connection start and tear down)