Skip to content

Stronger Shutdown Semantics in Database #90

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

Merged
merged 4 commits into from
Oct 20, 2017
Merged

Conversation

hiranya911
Copy link
Contributor

@hiranya911 hiranya911 commented Oct 16, 2017

There are several limitations in the existing shutdown/destroy logic of FirebaseDatabase:

  1. ctx.stop() is called conditionally, and this doesn't always seem to happen.
  2. Some low-level websocket events like onClosed() fire late, usually after RunLoop.shutdown() has been called. In this case, the RunLoop is silently re-initialized to handle the late event.
  3. Some tasks like idle time checker are not cleaned up.

Users have observed dangling background threads left by the SDK due to the above issues.

To resolve these problems, this PR:

  1. Implements a new, stronger RepoManager.destroy() method that always calls ctx.stop().
  2. Additional check for handling late onClosed() events, thus preventing the RunLoop from re-initializing after shutdown.
  3. Cancels pending idle time check tasks on shutdown.
  4. Enables removeOnCancelPolicy() on the RevivingScheduledExecutor, which removes cancelled tasks from the job queue thus facilitating early termination of threads (the underlying ScheduledThreadPoolExecutor won't shutdown threads, as long as there are jobs in the queue, even if they are cancelled).

I have tested this PR locally, and verified that all database-related threads are shut down upon calling FirebaseApp.delete().

Copy link

@mikelehen mikelehen left a comment

Choose a reason for hiding this comment

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

Looks good, but a couple suggestions.

@@ -134,6 +142,26 @@ public void run() {
}
}

private void destroyInternal(final Context ctx) {

Choose a reason for hiding this comment

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

This seems to be a subset of interruptInternal() [it interrupts all the repos but never calls ctx.stop()]. Could we just use interruptInternal()?

Copy link
Contributor Author

@hiranya911 hiranya911 Oct 20, 2017

Choose a reason for hiding this comment

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

We were effectively calling interruptInternal() before this PR. But it only calls ctx.stop() conditionally. Specifically, it won't close the context when there are active event listeners. The new destroy() method has stronger semantics in the sense it will always stop the context.

Choose a reason for hiding this comment

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

Yeah. My point was that destroy() could call interrupt() and then call ctx.stop() instead of duplicating this code. I don't care that strongly though.

if (!isClosed) {
executorService.execute(new Runnable() {
@Override
public void run() {

Choose a reason for hiding this comment

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

I think you should probably duplicate the isClosed check here, since we're now on the correct thread (the one that mutates isClosed).

Copy link
Contributor Author

@hiranya911 hiranya911 Oct 20, 2017

Choose a reason for hiding this comment

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

onClosed() method already has the check:

private void onClosed() {
    if (!isClosed) {
      if (logger.logsDebug()) {
        logger.debug("closing itself");
      }
      shutdown();
    }
    conn = null;
    if (keepAlive != null) {
      keepAlive.cancel(false);
      keepAlive = null;
    }
  }

eventTarget.shutdown();
runLoop.shutdown();
if (eventTarget != null) {
eventTarget.shutdown();

Choose a reason for hiding this comment

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

should you set them to null too?

Copy link
Contributor Author

@hiranya911 hiranya911 Oct 20, 2017

Choose a reason for hiding this comment

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

I think we need them to remain non-null for Context.restartServices() to work. In case of a destroy() it won't really matter either way.

@hiranya911 hiranya911 assigned hiranya911 and unassigned mikelehen Oct 20, 2017
@hiranya911 hiranya911 merged commit e35216d into master Oct 20, 2017
@hiranya911 hiranya911 deleted the hkj-runloop-exit branch October 20, 2017 21:48
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