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

View File

@@ -2,8 +2,17 @@ package org.briarproject.api.lifecycle;
import java.util.concurrent.ExecutorService; 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 { 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. */ /** Registers a {@link Service} to be started and stopped. */
public void register(Service s); public void register(Service s);
@@ -14,27 +23,37 @@ public interface LifecycleManager {
public void registerForShutdown(ExecutorService e); public void registerForShutdown(ExecutorService e);
/** /**
* Starts any registered {@link Service}s and returns true if all services * Starts any registered {@link Service Services} and opens the {@link
* started successfully. * org.briarproject.api.db.DatabaseComponent DatabaseComponent}.
*/ */
public boolean startServices(); public StartResult startServices();
/** /**
* Stops any registered {@link Service}s and shuts down any registered * Stops any registered {@link Service Services}, shuts down any
* {@link java.util.concurrent.ExecutorService ExecutorService}s. * registered {@link java.util.concurrent.ExecutorService ExecutorServices},
* and closes the {@link org.briarproject.api.db.DatabaseComponent
* DatabaseComponent}.
*/ */
public void stopServices(); 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; 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; public void waitForStartup() throws InterruptedException;
/** /**
* Waits for all registered {@link Service}s to stop and all registered * Waits for all registered {@link Service Services} to stop, all
* {@link java.util.concurrent.ExecutorService ExecutorService}s to shut * registered {@link java.util.concurrent.ExecutorService ExecutorServices}
* down before returning. * to shut down, and the {@link org.briarproject.api.db.DatabaseComponent
* DatabaseComponent} to be closed before returning.
*/ */
public void waitForShutdown() throws InterruptedException; 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.INFO;
import static java.util.logging.Level.WARNING; 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.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@@ -27,6 +32,7 @@ class LifecycleManagerImpl implements LifecycleManager {
private final DatabaseComponent db; private final DatabaseComponent db;
private final Collection<Service> services; private final Collection<Service> services;
private final Collection<ExecutorService> executors; private final Collection<ExecutorService> executors;
private final Semaphore startStopSemaphore = new Semaphore(1);
private final CountDownLatch dbLatch = new CountDownLatch(1); private final CountDownLatch dbLatch = new CountDownLatch(1);
private final CountDownLatch startupLatch = new CountDownLatch(1); private final CountDownLatch startupLatch = new CountDownLatch(1);
private final CountDownLatch shutdownLatch = new CountDownLatch(1); private final CountDownLatch shutdownLatch = new CountDownLatch(1);
@@ -51,12 +57,16 @@ class LifecycleManagerImpl implements LifecycleManager {
executors.add(e); executors.add(e);
} }
public boolean startServices() { public StartResult startServices() {
if(!startStopSemaphore.tryAcquire()) {
LOG.info("Already starting or stopping");
return ALREADY_RUNNING;
}
try { try {
LOG.info("Starting"); LOG.info("Starting services");
long start = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
boolean reopened = db.open(); boolean reopened = db.open();
long duration = clock.currentTimeMillis() - start; long duration = clock.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) { if(LOG.isLoggable(INFO)) {
if(reopened) if(reopened)
LOG.info("Reopening database took " + duration + " ms"); LOG.info("Reopening database took " + duration + " ms");
@@ -64,15 +74,15 @@ class LifecycleManagerImpl implements LifecycleManager {
} }
dbLatch.countDown(); dbLatch.countDown();
for(Service s : services) { for(Service s : services) {
start = clock.currentTimeMillis(); now = clock.currentTimeMillis();
boolean started = s.start(); boolean started = s.start();
duration = clock.currentTimeMillis() - start; duration = clock.currentTimeMillis() - now;
if(!started) { if(!started) {
if(LOG.isLoggable(WARNING)) { if(LOG.isLoggable(WARNING)) {
String name = s.getClass().getName(); String name = s.getClass().getName();
LOG.warning(name + " did not start"); LOG.warning(name + " did not start");
} }
return false; return SERVICE_ERROR;
} }
if(LOG.isLoggable(INFO)) { if(LOG.isLoggable(INFO)) {
String name = s.getClass().getName(); String name = s.getClass().getName();
@@ -80,19 +90,27 @@ class LifecycleManagerImpl implements LifecycleManager {
} }
} }
startupLatch.countDown(); startupLatch.countDown();
return true; return SUCCESS;
} catch(DbException e) { } catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false; return DB_ERROR;
} catch(IOException e) { } catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false; return DB_ERROR;
} finally {
startStopSemaphore.release();
} }
} }
public void stopServices() { public void stopServices() {
try { 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) { for(Service s : services) {
boolean stopped = s.stop(); boolean stopped = s.stop();
if(LOG.isLoggable(INFO)) { if(LOG.isLoggable(INFO)) {
@@ -111,6 +129,8 @@ class LifecycleManagerImpl implements LifecycleManager {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch(IOException e) { } catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), 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 void registerForShutdown(ExecutorService e) {}
public boolean startServices() { return true; } public StartResult startServices() { return StartResult.SUCCESS; }
public void stopServices() {} public void stopServices() {}