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 Deque<String> pathDeque = new ArrayDeque<>();
protected Routes routes; 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; private Object embeddedServerIdentifier = null;


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


try { try {
latch.await(); initLatch.await();
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.info("Interrupted by another thread"); LOG.info("Interrupted by another thread");
Thread.currentThread().interrupt(); 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() { 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) { if (server != null) {
server.extinguish(); server.extinguish();
latch = new CountDownLatch(1); initLatch = new CountDownLatch(1);
} }


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


/** /**
Expand Down Expand Up @@ -557,7 +581,7 @@ public synchronized void init() {
initExceptionHandler.accept(e); initExceptionHandler.accept(e);
} }
try { try {
latch.countDown(); initLatch.countDown();
server.join(); server.join();
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.error("server interrupted", 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() { public static void stop() {
getInstance().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 // // 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.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import org.powermock.reflect.Whitebox; import org.powermock.reflect.Whitebox;


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


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


Expand Down Expand Up @@ -239,6 +243,40 @@ public void testWebSocket_whenHandlerNull_thenThrowNullPointerException() {
service.webSocket("/", null); 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 @WebSocket
protected static class DummyWebSocketListener { protected static class DummyWebSocketListener {
} }
Expand Down

0 comments on commit 8022555

Please sign in to comment.