/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.firebase.database.utilities;
import com.google.firebase.database.DatabaseException;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.annotations.Nullable;
import com.google.firebase.database.core.Context;
import com.google.firebase.database.core.RepoManager;
import com.google.firebase.database.core.RunLoop;
import com.google.firebase.internal.RevivingScheduledExecutor;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
public abstract class DefaultRunLoop implements RunLoop {
private ScheduledThreadPoolExecutor executor;
private UncaughtExceptionHandler exceptionHandler;
/** Creates a DefaultRunLoop that does not periodically restart its threads. */
public DefaultRunLoop() {
this(Executors.defaultThreadFactory(), false, null);
}
/**
* Creates a DefaultRunLoop that optionally restarts its threads periodically. If 'context' is
* provided, these restarts will automatically interrupt and resume all Repo connections.
*/
public DefaultRunLoop(
final ThreadFactory threadFactory,
final boolean periodicRestart,
@Nullable final Context context) {
executor =
new RevivingScheduledExecutor(threadFactory, "FirebaseDatabaseWorker", periodicRestart) {
@Override
protected void handleException(Throwable throwable) {
DefaultRunLoop.this.handleExceptionInternal(throwable);
}
@Override
protected void beforeRestart() {
if (context != null) {
RepoManager.interrupt(context);
}
}
@Override
protected void afterRestart() {
if (context != null) {
RepoManager.resume(context);
}
}
};
// Core threads don't time out, this only takes effect when we drop the number of required
// core threads
executor.setKeepAliveTime(3, TimeUnit.SECONDS);
}
public static String messageForException(Throwable t) {
if (t instanceof OutOfMemoryError) {
return "Firebase Database encountered an OutOfMemoryError. You may need to reduce the"
+ " amount of data you are syncing to the client (e.g. by using queries or syncing"
+ " a deeper path). See "
+ "https://firebase.google"
+ ".com/docs/database/ios/structure-data#best_practices_for_data_structure"
+ " and "
+ "https://firebase.google.com/docs/database/android/retrieve-data#filtering_data";
} else if (t instanceof DatabaseException) {
// Exception should be self-explanatory and they shouldn't contact support.
return "";
} else {
return "Uncaught exception in Firebase Database runloop ("
+ FirebaseDatabase.getSdkVersion()
+ "). Please report to firebase-database-client@google.com";
}
}
private void handleExceptionInternal(Throwable e) {
UncaughtExceptionHandler exceptionHandler;
exceptionHandler = getExceptionHandler();
try {
if (exceptionHandler != null) {
exceptionHandler.uncaughtException(Thread.currentThread(), e);
}
} finally {
handleException(e);
}
}
public abstract void handleException(Throwable e);
public ScheduledExecutorService getExecutorService() {
return this.executor;
}
@Override
public void scheduleNow(final Runnable runnable) {
executor.execute(runnable);
}
@Override
@SuppressWarnings("rawtypes")
public ScheduledFuture schedule(final Runnable runnable, long milliseconds) {
return executor.schedule(runnable, milliseconds, TimeUnit.MILLISECONDS);
}
@Override
public void shutdown() {
executor.setCorePoolSize(0);
}
@Override
public void restart() {
executor.setCorePoolSize(1);
}
/**
* Returns the exception handler currently set on this run loop. This is to be
* used during integration testing.
*/
public synchronized UncaughtExceptionHandler getExceptionHandler() {
return exceptionHandler;
}
/**
* Sets the specified exception handler for intercepting run loop errors. This is to be
* used during integration testing for handling errors that may occur in the run loop's
* worker thread.
*/
public synchronized void setExceptionHandler(UncaughtExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
}