Skip to content

Make FirebaseApp.delete() Public - #5

Merged
hiranya911 merged 70 commits into
masterfrom
hkj-delete-apps
Apr 19, 2017
Merged

Make FirebaseApp.delete() Public#5
hiranya911 merged 70 commits into
masterfrom
hkj-delete-apps

Conversation

@hiranya911

Copy link
Copy Markdown
Contributor

This PR fixes and exposes the delete() method in FirebaseApp class.

  • Introduced a new abstraction called FirebaseService

  • FirebaseAuth and FirebaseDatabase are wrapped as instances of FirebaseService, and attached to the FirebaseApp. 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)

Hiranya Jayathilaka and others added 30 commits April 5, 2017 19:12
* 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.

@mikelehen mikelehen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments on the class and methods please.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return;
public void delete() {
List<FirebaseService> servicesCopy;
synchronized (this) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we lock and make an immutable copy?

@hiranya911 hiranya911 Apr 18, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

firebaseApp.delete();
verify(listener, never()).onDeleted(appName, OPTIONS);
try {
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not nest these?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// ignore
}

FirebaseApp firebaseApp2 = FirebaseApp.initializeApp(OPTIONS, name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment explaining intention of this test? E.g.:

// Verify we can re-use the same name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


@Test
public void testInvokeAfterAppDelete() throws ExecutionException, InterruptedException {
if (!isCertCredential) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some log statements.

@AfterClass
public static void tearDownClass() {
public static void tearDownClass() throws InterruptedException {
TestHelpers.interruptConfig(config);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment? It looks like the intention is that all the integration tests use this method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@mikelehen mikelehen assigned hiranya911 and unassigned mikelehen Apr 18, 2017
@hiranya911

Copy link
Copy Markdown
Contributor Author

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.

@hiranya911 hiranya911 assigned mikelehen and unassigned hiranya911 Apr 18, 2017
Hiranya Jayathilaka added 2 commits April 18, 2017 16:26
…he contention on the database-object lock by making ensureRepo() thread safe and returning the Repo from the method.

@mikelehen mikelehen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple FYI comments, but I think this looks good. Thanks for the changes!

}
synchronized (appsLock) {
instances.remove(this.name);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood.

@hiranya911
hiranya911 merged commit 333a795 into master Apr 19, 2017
@hiranya911
hiranya911 deleted the hkj-delete-apps branch April 19, 2017 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants