Skip to content

Commit

Permalink
Added Service.awaitStop(). (#730)
Browse files Browse the repository at this point in the history
* Added Service.stopAndWait().

* Doc comments, small test improvements, log when interrupted.

* Renamed stopAndWait() -> awaitStop(), refactored.

Changed it to be more like init() and awaitInitialization().

* Expose `awaitStop()` via static method in `Spark`.
  • Loading branch information
jakaarl authored and perwendel committed Jun 11, 2018
1 parent e198dff commit 8022555
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 7 deletions.
38 changes: 31 additions & 7 deletions src/main/java/spark/Service.java
Expand Up @@ -76,7 +76,8 @@ public final class Service extends Routable {
protected Deque<String> pathDeque = new ArrayDeque<>();
protected Routes routes;

private CountDownLatch latch = new CountDownLatch(1);
private CountDownLatch initLatch = new CountDownLatch(1);
private CountDownLatch stopLatch = new CountDownLatch(0);

private Object embeddedServerIdentifier = null;

Expand Down Expand Up @@ -440,7 +441,7 @@ public void awaitInitialization() {
}

try {
latch.await();
initLatch.await();
} catch (InterruptedException e) {
LOG.info("Interrupted by another thread");
Thread.currentThread().interrupt();
Expand All @@ -458,20 +459,43 @@ private boolean hasMultipleHandlers() {


/**
* Stops the Spark server and clears all routes
* Stops the Spark server and clears all routes.
*/
public synchronized void stop() {
new Thread(() -> {
if (!initialized) {
return;
}
initiateStop();
}

/**
* Waits for the Spark server to stop.
* <b>Warning:</b> this method should not be called from a request handler.
*/
public void awaitStop() {
try {
stopLatch.await();
} catch (InterruptedException e) {
LOG.warn("Interrupted by another thread");
Thread.currentThread().interrupt();
}
}

private void initiateStop() {
stopLatch = new CountDownLatch(1);
Thread stopThread = new Thread(() -> {
if (server != null) {
server.extinguish();
latch = new CountDownLatch(1);
initLatch = new CountDownLatch(1);
}

routes.clear();
exceptionMapper.clear();
staticFilesConfiguration.clear();
initialized = false;
}).start();
stopLatch.countDown();
});
stopThread.start();
}

/**
Expand Down Expand Up @@ -557,7 +581,7 @@ public synchronized void init() {
initExceptionHandler.accept(e);
}
try {
latch.countDown();
initLatch.countDown();
server.join();
} catch (InterruptedException e) {
LOG.error("server interrupted", e);
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/spark/Spark.java
Expand Up @@ -1173,6 +1173,14 @@ public static void awaitInitialization() {
public static void stop() {
getInstance().stop();
}

/**
* Waits for the Spark server to be stopped.
* If it's already stopped, will return immediately.
*/
public static void awaitStop() {
getInstance().awaitStop();
}

////////////////
// Websockets //
Expand Down
38 changes: 38 additions & 0 deletions src/test/java/spark/ServiceTest.java
Expand Up @@ -7,11 +7,15 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import org.powermock.reflect.Whitebox;

import spark.embeddedserver.EmbeddedServer;
import spark.route.Routes;
import spark.ssl.SslStores;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static spark.Service.ignite;

Expand Down Expand Up @@ -239,6 +243,40 @@ public void testWebSocket_whenHandlerNull_thenThrowNullPointerException() {
service.webSocket("/", null);
}

@Test(timeout = 300)
public void stopExtinguishesServer() {
Service service = Service.ignite();
Routes routes = Mockito.mock(Routes.class);
EmbeddedServer server = Mockito.mock(EmbeddedServer.class);
service.routes = routes;
service.server = server;
service.initialized = true;
service.stop();
try {
// yes, this is ugly and forces to set a test timeout as a precaution :(
while (service.initialized) {
Thread.sleep(20);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Mockito.verify(server).extinguish();
}

@Test
public void awaitStopBlocksUntilExtinguished() {
Service service = Service.ignite();
Routes routes = Mockito.mock(Routes.class);
EmbeddedServer server = Mockito.mock(EmbeddedServer.class);
service.routes = routes;
service.server = server;
service.initialized = true;
service.stop();
service.awaitStop();
Mockito.verify(server).extinguish();
assertFalse(service.initialized);
}

@WebSocket
protected static class DummyWebSocketListener {
}
Expand Down

0 comments on commit 8022555

Please sign in to comment.