mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 13:49:53 +01:00
Return early when starting/stopping if not in expected state.
This commit is contained in:
@@ -37,8 +37,14 @@ public interface LifecycleManager {
|
|||||||
*/
|
*/
|
||||||
enum LifecycleState {
|
enum LifecycleState {
|
||||||
|
|
||||||
STARTING, MIGRATING_DATABASE, COMPACTING_DATABASE, STARTING_SERVICES,
|
CREATED,
|
||||||
RUNNING, STOPPING;
|
STARTING,
|
||||||
|
MIGRATING_DATABASE,
|
||||||
|
COMPACTING_DATABASE,
|
||||||
|
STARTING_SERVICES,
|
||||||
|
RUNNING,
|
||||||
|
STOPPING,
|
||||||
|
STOPPED;
|
||||||
|
|
||||||
public boolean isAfter(LifecycleState state) {
|
public boolean isAfter(LifecycleState state) {
|
||||||
return ordinal() > state.ordinal();
|
return ordinal() > state.ordinal();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import java.util.List;
|
|||||||
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.concurrent.atomic.AtomicReference;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
@@ -29,10 +29,12 @@ import static java.util.logging.Level.INFO;
|
|||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.CREATED;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPED;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
||||||
@@ -60,12 +62,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
private final List<Service> services;
|
private final List<Service> services;
|
||||||
private final List<OpenDatabaseHook> openDatabaseHooks;
|
private final List<OpenDatabaseHook> openDatabaseHooks;
|
||||||
private final List<ExecutorService> executors;
|
private final List<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);
|
||||||
|
private final AtomicReference<LifecycleState> state =
|
||||||
private volatile LifecycleState state = STARTING;
|
new AtomicReference<>(CREATED);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||||
@@ -102,8 +103,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StartResult startServices(SecretKey dbKey) {
|
public StartResult startServices(SecretKey dbKey) {
|
||||||
if (!startStopSemaphore.tryAcquire()) {
|
if (!state.compareAndSet(CREATED, STARTING)) {
|
||||||
LOG.info("Already starting or stopping");
|
LOG.warning("Already running");
|
||||||
return ALREADY_RUNNING;
|
return ALREADY_RUNNING;
|
||||||
}
|
}
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
@@ -135,7 +136,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
});
|
});
|
||||||
|
|
||||||
LOG.info("Starting services");
|
LOG.info("Starting services");
|
||||||
state = STARTING_SERVICES;
|
state.set(STARTING_SERVICES);
|
||||||
dbLatch.countDown();
|
dbLatch.countDown();
|
||||||
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state = RUNNING;
|
state.set(RUNNING);
|
||||||
startupLatch.countDown();
|
startupLatch.countDown();
|
||||||
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
@@ -164,69 +165,58 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
} catch (ServiceException e) {
|
} catch (ServiceException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
return SERVICE_ERROR;
|
return SERVICE_ERROR;
|
||||||
} finally {
|
|
||||||
startStopSemaphore.release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDatabaseMigration() {
|
public void onDatabaseMigration() {
|
||||||
state = MIGRATING_DATABASE;
|
state.set(MIGRATING_DATABASE);
|
||||||
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDatabaseCompaction() {
|
public void onDatabaseCompaction() {
|
||||||
state = COMPACTING_DATABASE;
|
state.set(COMPACTING_DATABASE);
|
||||||
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
|
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopServices() {
|
public void stopServices() {
|
||||||
try {
|
if (!state.compareAndSet(RUNNING, STOPPING)) {
|
||||||
startStopSemaphore.acquire();
|
LOG.warning("Not running");
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.warning("Interrupted while waiting to stop services");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
LOG.info("Stopping services");
|
||||||
if (state == STOPPING) {
|
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||||
LOG.info("Already stopped");
|
for (Service s : services) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOG.info("Stopping services");
|
|
||||||
state = STOPPING;
|
|
||||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
|
||||||
for (Service s : services) {
|
|
||||||
try {
|
|
||||||
long start = now();
|
|
||||||
s.stopService();
|
|
||||||
if (LOG.isLoggable(FINE)) {
|
|
||||||
logDuration(LOG, "Stopping service "
|
|
||||||
+ s.getClass().getSimpleName(), start);
|
|
||||||
}
|
|
||||||
} catch (ServiceException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (ExecutorService e : executors) {
|
|
||||||
if (LOG.isLoggable(FINE)) {
|
|
||||||
LOG.fine("Stopping executor "
|
|
||||||
+ e.getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
e.shutdownNow();
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
long start = now();
|
long start = now();
|
||||||
db.close();
|
s.stopService();
|
||||||
logDuration(LOG, "Closing database", start);
|
if (LOG.isLoggable(FINE)) {
|
||||||
} catch (DbException e) {
|
logDuration(LOG, "Stopping service "
|
||||||
|
+ s.getClass().getSimpleName(), start);
|
||||||
|
}
|
||||||
|
} catch (ServiceException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
shutdownLatch.countDown();
|
|
||||||
} finally {
|
|
||||||
startStopSemaphore.release();
|
|
||||||
}
|
}
|
||||||
|
for (ExecutorService e : executors) {
|
||||||
|
if (LOG.isLoggable(FINE)) {
|
||||||
|
LOG.fine("Stopping executor "
|
||||||
|
+ e.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
e.shutdownNow();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
long start = now();
|
||||||
|
db.close();
|
||||||
|
logDuration(LOG, "Closing database", start);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
state.set(STOPPED);
|
||||||
|
shutdownLatch.countDown();
|
||||||
|
eventBus.broadcast(new LifecycleEvent(STOPPED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -246,6 +236,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LifecycleState getLifecycleState() {
|
public LifecycleState getLifecycleState() {
|
||||||
return state;
|
return state.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
@@ -13,12 +14,10 @@ import org.jmock.Expectations;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertTrue;
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPED;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
||||||
import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS;
|
import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS;
|
||||||
@@ -31,6 +30,8 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
private final EventBus eventBus = context.mock(EventBus.class);
|
private final EventBus eventBus = context.mock(EventBus.class);
|
||||||
private final Clock clock = context.mock(Clock.class);
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
private final OpenDatabaseHook hook = context.mock(OpenDatabaseHook.class);
|
||||||
|
private final Service service = context.mock(Service.class);
|
||||||
|
|
||||||
private final SecretKey dbKey = getSecretKey();
|
private final SecretKey dbKey = getSecretKey();
|
||||||
|
|
||||||
@@ -45,8 +46,6 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
|
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
AtomicBoolean called = new AtomicBoolean(false);
|
|
||||||
OpenDatabaseHook hook = transaction -> called.set(true);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
@@ -55,6 +54,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(db).removeTemporaryMessages(txn);
|
oneOf(db).removeTemporaryMessages(txn);
|
||||||
|
oneOf(hook).onDatabaseOpened(txn);
|
||||||
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -62,7 +62,38 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||||
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
||||||
assertTrue(called.get());
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServicesAreStartedAndStopped() throws Exception {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(db).open(dbKey, lifecycleManager);
|
||||||
|
will(returnValue(false));
|
||||||
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
|
oneOf(db).removeTemporaryMessages(txn);
|
||||||
|
oneOf(service).startService();
|
||||||
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
lifecycleManager.registerService(service);
|
||||||
|
|
||||||
|
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||||
|
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(db).close();
|
||||||
|
oneOf(service).stopService();
|
||||||
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
lifecycleManager.stopServices();
|
||||||
|
assertEquals(STOPPED, lifecycleManager.getLifecycleState());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -89,6 +120,31 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
assertEquals(STARTING, lifecycleManager.getLifecycleState());
|
assertEquals(STARTING, lifecycleManager.getLifecycleState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecondCallToStartServicesReturnsEarly() throws Exception {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(db).open(dbKey, lifecycleManager);
|
||||||
|
will(returnValue(false));
|
||||||
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
|
oneOf(db).removeTemporaryMessages(txn);
|
||||||
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||||
|
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
|
// Calling startServices() again should not try to open the DB or
|
||||||
|
// start the services again
|
||||||
|
assertEquals(ALREADY_RUNNING, lifecycleManager.startServices(dbKey));
|
||||||
|
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSecondCallToStopServicesReturnsEarly() throws Exception {
|
public void testSecondCallToStopServicesReturnsEarly() throws Exception {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
@@ -101,7 +157,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(db).removeTemporaryMessages(txn);
|
oneOf(db).removeTemporaryMessages(txn);
|
||||||
exactly(2).of(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||||
@@ -109,17 +165,17 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
|
||||||
oneOf(db).close();
|
oneOf(db).close();
|
||||||
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
lifecycleManager.stopServices();
|
lifecycleManager.stopServices();
|
||||||
assertEquals(STOPPING, lifecycleManager.getLifecycleState());
|
assertEquals(STOPPED, lifecycleManager.getLifecycleState());
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
// Calling stopServices() again should not broadcast another event or
|
// Calling stopServices() again should not broadcast another event or
|
||||||
// try to close the DB again
|
// try to close the DB again
|
||||||
lifecycleManager.stopServices();
|
lifecycleManager.stopServices();
|
||||||
assertEquals(STOPPING, lifecycleManager.getLifecycleState());
|
assertEquals(STOPPED, lifecycleManager.getLifecycleState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user