Don't allow LifecycleManager to start and stop concurrently. Bug #68.

This commit is contained in:
akwizgran
2014-05-02 15:15:47 +01:00
parent adf9adf1af
commit e1d099903d
4 changed files with 71 additions and 24 deletions

View File

@@ -4,6 +4,8 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -24,6 +26,7 @@ import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.lifecycle.LifecycleManager.StartResult;
import org.briarproject.api.messaging.GroupId;
import roboguice.service.RoboService;
@@ -87,11 +90,16 @@ public class BriarService extends RoboService implements EventListener {
new Thread() {
@Override
public void run() {
if(lifecycleManager.startServices()) {
StartResult result = lifecycleManager.startServices();
if(result == SUCCESS) {
db.addListener(BriarService.this);
started = true;
} else if(result == ALREADY_RUNNING) {
LOG.info("Already running");
stopSelf();
} else {
LOG.info("Startup failed");
if(LOG.isLoggable(WARNING))
LOG.warning("Startup failed: " + result);
showStartupFailureNotification();
stopSelf();
}

View File

@@ -2,8 +2,17 @@ package org.briarproject.api.lifecycle;
import java.util.concurrent.ExecutorService;
/**
* Manages the lifecycle of the app, starting and stopping {@link Service
* Services}, shutting down {@link java.util.concurrent.ExecutorService
* ExecutorServices}, and opening and closing the {@link
* org.briarproject.api.db.DatabaseComponent DatabaseComponent}.
*/
public interface LifecycleManager {
/** The result of calling {@link LifecycleManager#startServices()}. */
enum StartResult { ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS }
/** Registers a {@link Service} to be started and stopped. */
public void register(Service s);
@@ -14,27 +23,37 @@ public interface LifecycleManager {
public void registerForShutdown(ExecutorService e);
/**
* Starts any registered {@link Service}s and returns true if all services
* started successfully.
* Starts any registered {@link Service Services} and opens the {@link
* org.briarproject.api.db.DatabaseComponent DatabaseComponent}.
*/
public boolean startServices();
public StartResult startServices();
/**
* Stops any registered {@link Service}s and shuts down any registered
* {@link java.util.concurrent.ExecutorService ExecutorService}s.
* Stops any registered {@link Service Services}, shuts down any
* registered {@link java.util.concurrent.ExecutorService ExecutorServices},
* and closes the {@link org.briarproject.api.db.DatabaseComponent
* DatabaseComponent}.
*/
public void stopServices();
/** Waits for the database to be opened before returning. */
/**
* Waits for the {@link org.briarproject.api.db.DatabaseComponent
* DatabaseComponent} to be opened before returning.
*/
public void waitForDatabase() throws InterruptedException;
/** Waits for all registered {@link Service}s to start before returning. */
/**
* Waits for the {@link org.briarproject.api.db.DatabaseComponent
* DatabaseComponent} to be opened and all registered {@link Service
* Services} to start before returning.
*/
public void waitForStartup() throws InterruptedException;
/**
* Waits for all registered {@link Service}s to stop and all registered
* {@link java.util.concurrent.ExecutorService ExecutorService}s to shut
* down before returning.
* Waits for all registered {@link Service Services} to stop, all
* registered {@link java.util.concurrent.ExecutorService ExecutorServices}
* to shut down, and the {@link org.briarproject.api.db.DatabaseComponent
* DatabaseComponent} to be closed before returning.
*/
public void waitForShutdown() throws InterruptedException;
}

View File

@@ -2,12 +2,17 @@ package org.briarproject.lifecycle;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -27,6 +32,7 @@ class LifecycleManagerImpl implements LifecycleManager {
private final DatabaseComponent db;
private final Collection<Service> services;
private final Collection<ExecutorService> executors;
private final Semaphore startStopSemaphore = new Semaphore(1);
private final CountDownLatch dbLatch = new CountDownLatch(1);
private final CountDownLatch startupLatch = new CountDownLatch(1);
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
@@ -51,12 +57,16 @@ class LifecycleManagerImpl implements LifecycleManager {
executors.add(e);
}
public boolean startServices() {
public StartResult startServices() {
if(!startStopSemaphore.tryAcquire()) {
LOG.info("Already starting or stopping");
return ALREADY_RUNNING;
}
try {
LOG.info("Starting");
long start = clock.currentTimeMillis();
LOG.info("Starting services");
long now = clock.currentTimeMillis();
boolean reopened = db.open();
long duration = clock.currentTimeMillis() - start;
long duration = clock.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) {
if(reopened)
LOG.info("Reopening database took " + duration + " ms");
@@ -64,15 +74,15 @@ class LifecycleManagerImpl implements LifecycleManager {
}
dbLatch.countDown();
for(Service s : services) {
start = clock.currentTimeMillis();
now = clock.currentTimeMillis();
boolean started = s.start();
duration = clock.currentTimeMillis() - start;
duration = clock.currentTimeMillis() - now;
if(!started) {
if(LOG.isLoggable(WARNING)) {
String name = s.getClass().getName();
LOG.warning(name + " did not start");
}
return false;
return SERVICE_ERROR;
}
if(LOG.isLoggable(INFO)) {
String name = s.getClass().getName();
@@ -80,19 +90,27 @@ class LifecycleManagerImpl implements LifecycleManager {
}
}
startupLatch.countDown();
return true;
return SUCCESS;
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false;
return DB_ERROR;
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false;
return DB_ERROR;
} finally {
startStopSemaphore.release();
}
}
public void stopServices() {
try {
LOG.info("Shutting down");
startStopSemaphore.acquire();
} catch(InterruptedException e) {
LOG.warning("Interrupted while waiting to stop services");
return;
}
try {
LOG.info("Stopping services");
for(Service s : services) {
boolean stopped = s.stop();
if(LOG.isLoggable(INFO)) {
@@ -111,6 +129,8 @@ class LifecycleManagerImpl implements LifecycleManager {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
startStopSemaphore.release();
}
}

View File

@@ -17,7 +17,7 @@ public class TestLifecycleModule extends AbstractModule {
public void registerForShutdown(ExecutorService e) {}
public boolean startServices() { return true; }
public StartResult startServices() { return StartResult.SUCCESS; }
public void stopServices() {}