aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-process
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2016-03-09 11:06:15 +0100
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2016-03-21 16:44:03 +0100
commiteb5e8f7774ee19a58c6796840ec9e71d67bb5a1d (patch)
treeda00f83b2d89cd607f23cb02b4e2c668abb46f13 /server/sonar-process
parent4d3d6b2504673128263eadcec82c8fd64f71e4c5 (diff)
downloadsonarqube-eb5e8f7774ee19a58c6796840ec9e71d67bb5a1d.tar.gz
sonarqube-eb5e8f7774ee19a58c6796840ec9e71d67bb5a1d.zip
SONAR-7435 rename state when process is done starting from ready to up
also clean up code of DefaultProcessCommands and fix it's Javadoc which was out of sync with implementation
Diffstat (limited to 'server/sonar-process')
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java111
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java8
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Monitored.java7
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java22
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java8
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java32
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java6
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java4
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java2
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java2
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java2
11 files changed, 111 insertions, 93 deletions
diff --git a/server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java b/server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java
index e174984a88b..52f5bcfd9fd 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java
@@ -32,46 +32,50 @@ import static org.sonar.process.ProcessCommands.MAX_PROCESSES;
* Process inter-communication to :
* <ul>
* <li>share status of child process</li>
- * <li>stop child process</li>
+ * <li>stop/restart child process</li>
* </ul>
*
- * <p/>
- * It relies on files shared by both processes. Following alternatives were considered but not selected :
+ * <p>
+ * It relies on a single file accessed by all processes through a {@link MappedByteBuffer}.<br/>
+ * Following alternatives were considered but not selected :
* <ul>
* <li>JMX beans over RMI: network issues (mostly because of Java reverse-DNS) + requires to configure and open a new port</li>
* <li>simple socket protocol: same drawbacks are RMI connection</li>
* <li>java.lang.Process#destroy(): shutdown hooks are not executed on some OS (mostly MSWindows)</li>
* <li>execute OS-specific commands (for instance kill on *nix): OS-specific, so hell to support. Moreover how to get identify a process ?</li>
* </ul>
+ * </p>
+ *
+ * <p>
+ * The file contains {@link ProcessCommands#MAX_PROCESSES} groups of {@link #BYTE_LENGTH_FOR_ONE_PROCESS} bits.
+ * Each group of byte is used as follow:
+ * <ul>
+ * <li>First byte contains {@link #EMPTY} until process is UP and writes {@link #UP}</li>
+ * <li>Second byte contains {@link #EMPTY} until any process requests current one to stop by writing value {@link #STOP}</li>
+ * <li>Third byte contains {@link #EMPTY} until any process requests current one to restart by writing value {@link #RESTART}. Process acknowledges restart by writing back {@link #EMPTY}</li>
+ * <li>The next 8 bytes contains a long (value of {@link System#currentTimeMillis()}) which represents the date of the last ping</li>
+ * </ul>
+ * </p>
*/
public class AllProcessesCommands {
+ private static final int UP_BYTE_OFFSET = 0;
+ private static final int STOP_BYTE_OFFSET = 1;
+ private static final int RESTART_BYTE_OFFSET = 2;
+ private static final int PING_BYTE_OFFSET = 3;
- /**
- * The ByteBuffer will contains :
- * <ul>
- * <li>First byte will contains 0x00 until stop command is issued = 0xFF</li>
- * <li>Then each 10 bytes will be reserved for each process</li>
- * </ul>
- *
- * Description of ten bytes of each process :
- * <ul>
- * <li>First byte will contains the state 0x00 until READY 0x01</li>
- * <li>The second byte will contains the request for stopping 0x00 or STOP (0xFF)</li>
- * <li>The second byte will contains the request for restarting 0x00 or RESTART (0xAA)</li>
- * <li>The next 8 bytes contains a long (System.currentTimeInMillis for ping)</li>
- * </ul>
- */
- final MappedByteBuffer mappedByteBuffer;
- private final RandomAccessFile sharedMemory;
private static final int BYTE_LENGTH_FOR_ONE_PROCESS = 1 + 1 + 1 + 8;
// With this shared memory we can handle up to MAX_PROCESSES processes
private static final int MAX_SHARED_MEMORY = BYTE_LENGTH_FOR_ONE_PROCESS * MAX_PROCESSES;
- public static final byte STOP = (byte) 0xFF;
- public static final byte RESTART = (byte) 0xAA;
- public static final byte READY = (byte) 0x01;
- public static final byte EMPTY = (byte) 0x00;
+ private static final byte STOP = (byte) 0xFF;
+ private static final byte RESTART = (byte) 0xAA;
+ private static final byte UP = (byte) 0x01;
+ private static final byte EMPTY = (byte) 0x00;
+
+ //VisibleForTesting
+ final MappedByteBuffer mappedByteBuffer;
+ private final RandomAccessFile sharedMemory;
public AllProcessesCommands(File directory) {
if (!directory.isDirectory() || !directory.exists()) {
@@ -103,56 +107,52 @@ public class AllProcessesCommands {
return processCommands;
}
- boolean isReady(int processNumber) {
- return mappedByteBuffer.get(offset(processNumber)) == READY;
+ boolean isUp(int processNumber) {
+ return readByte(processNumber, UP_BYTE_OFFSET) == UP;
}
/**
- * To be executed by child process to declare that it's ready
+ * To be executed by child process to declare that it is done starting
*/
- void setReady(int processNumber) {
- mappedByteBuffer.put(offset(processNumber), READY);
+ void setUp(int processNumber) {
+ writeByte(processNumber, UP_BYTE_OFFSET, UP);
}
void ping(int processNumber) {
- mappedByteBuffer.putLong(2 + offset(processNumber), System.currentTimeMillis());
+ writeLong(processNumber, PING_BYTE_OFFSET, System.currentTimeMillis());
}
long getLastPing(int processNumber) {
- return mappedByteBuffer.getLong(2 + offset(processNumber));
+ return readLong(processNumber, PING_BYTE_OFFSET);
}
/**
* To be executed by monitor process to ask for child process termination
*/
void askForStop(int processNumber) {
- mappedByteBuffer.put(offset(processNumber) + 1, STOP);
+ writeByte(processNumber, STOP_BYTE_OFFSET, STOP);
}
boolean askedForStop(int processNumber) {
- return mappedByteBuffer.get(offset(processNumber) + 1) == STOP;
+ return readByte(processNumber, STOP_BYTE_OFFSET) == STOP;
}
void askForRestart(int processNumber) {
- mappedByteBuffer.put(offset(processNumber) + 3, RESTART);
+ writeByte(processNumber, RESTART_BYTE_OFFSET, RESTART);
}
boolean askedForRestart(int processNumber) {
- return mappedByteBuffer.get(offset(processNumber) + 3) == RESTART;
+ return readByte(processNumber, RESTART_BYTE_OFFSET) == RESTART;
}
void acknowledgeAskForRestart(int processNumber) {
- mappedByteBuffer.put(offset(processNumber) + 3, EMPTY);
+ writeByte(processNumber, RESTART_BYTE_OFFSET, EMPTY);
}
public void close() {
IOUtils.closeQuietly(sharedMemory);
}
- int offset(int processNumber) {
- return BYTE_LENGTH_FOR_ONE_PROCESS * processNumber;
- }
-
public void checkProcessNumber(int processNumber) {
boolean result = processNumber >= 0 && processNumber < MAX_PROCESSES;
if (!result) {
@@ -162,10 +162,31 @@ public class AllProcessesCommands {
private void cleanData(int processNumber) {
for (int i = 0; i < BYTE_LENGTH_FOR_ONE_PROCESS; i++) {
- mappedByteBuffer.put(offset(processNumber) + i, EMPTY);
+ writeByte(processNumber, i, EMPTY);
}
}
+ private void writeByte(int processNumber, int offset, byte value) {
+ mappedByteBuffer.put(offset(processNumber) + offset, value);
+ }
+
+ private byte readByte(int processNumber, int offset) {
+ return mappedByteBuffer.get(offset(processNumber) + offset);
+ }
+
+ private void writeLong(int processNumber, int offset, long value) {
+ mappedByteBuffer.putLong(offset(processNumber) + offset, value);
+ }
+
+ private long readLong(int processNumber, int offset) {
+ return mappedByteBuffer.getLong(offset(processNumber) + offset);
+ }
+
+ // VisibleForTesting
+ int offset(int processNumber) {
+ return BYTE_LENGTH_FOR_ONE_PROCESS * processNumber;
+ }
+
private class ProcessCommandsImpl implements ProcessCommands {
private final int processNumber;
@@ -175,13 +196,13 @@ public class AllProcessesCommands {
}
@Override
- public boolean isReady() {
- return AllProcessesCommands.this.isReady(processNumber);
+ public boolean isUp() {
+ return AllProcessesCommands.this.isUp(processNumber);
}
@Override
- public void setReady() {
- AllProcessesCommands.this.setReady(processNumber);
+ public void setUp() {
+ AllProcessesCommands.this.setUp(processNumber);
}
@Override
diff --git a/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java b/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java
index 68ca4a98063..0ef4413aa75 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java
@@ -40,13 +40,13 @@ public class DefaultProcessCommands implements ProcessCommands {
}
@Override
- public boolean isReady() {
- return delegate.isReady();
+ public boolean isUp() {
+ return delegate.isUp();
}
@Override
- public void setReady() {
- delegate.setReady();
+ public void setUp() {
+ delegate.setUp();
}
@Override
diff --git a/server/sonar-process/src/main/java/org/sonar/process/Monitored.java b/server/sonar-process/src/main/java/org/sonar/process/Monitored.java
index f7015ac42d8..87a6b1f32c1 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/Monitored.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/Monitored.java
@@ -27,11 +27,10 @@ public interface Monitored {
void start();
/**
- * True if the process is started and operational (-> can accept requests), false if
- * it's still starting. An exception is thrown is process failed to start (not starting
- * nor started).
+ * True if the process is done starting, false otherwise.
+ * An exception may be thrown if process fails to start.
*/
- boolean isReady();
+ boolean isUp();
/**
* Blocks until the process is terminated
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java
index e1299f94092..f52f5b5f5df 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java
@@ -19,33 +19,27 @@
*/
package org.sonar.process;
+import java.io.File;
+
/**
* Process inter-communication to :
* <ul>
- * <li>share status of child process</li>
- * <li>stop child process</li>
- * <li>restart all child processes</li>
+ * <li>share status of specific process</li>
+ * <li>stop/restart a specific processes</li>
* </ul>
*
- * <p/>
- * It relies on files shared by both processes. Following alternatives were considered but not selected :
- * <ul>
- * <li>JMX beans over RMI: network issues (mostly because of Java reverse-DNS) + requires to configure and open a new port</li>
- * <li>simple socket protocol: same drawbacks are RMI connection</li>
- * <li>java.lang.Process#destroy(): shutdown hooks are not executed on some OS (mostly MSWindows)</li>
- * <li>execute OS-specific commands (for instance kill on *nix): OS-specific, so hell to support. Moreover how to get identify a process ?</li>
- * </ul>
+ * @see DefaultProcessCommands#DefaultProcessCommands(File, int)
*/
public interface ProcessCommands extends AutoCloseable {
int MAX_PROCESSES = 50;
- boolean isReady();
+ boolean isUp();
/**
- * To be executed by child process to declare that it's ready
+ * To be executed by child process to declare that it is done starting
*/
- void setReady();
+ void setUp();
void ping();
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java
index d932511ed73..8968210526d 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java
@@ -79,14 +79,14 @@ public class ProcessEntryPoint implements Stoppable {
stopWatcher.start();
monitored.start();
- boolean ready = false;
- while (!ready) {
- ready = monitored.isReady();
+ boolean up = false;
+ while (!up) {
+ up = monitored.isUp();
Thread.sleep(20L);
}
// notify monitor that process is ready
- commands.setReady();
+ commands.setUp();
if (lifecycle.tryToMoveTo(Lifecycle.State.STARTED)) {
monitored.awaitStop();
diff --git a/server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java b/server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java
index 625ab016e1d..ad92121b824 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java
@@ -33,6 +33,10 @@ import static org.sonar.process.ProcessCommands.MAX_PROCESSES;
public class AllProcessesCommandsTest {
private static final int PROCESS_NUMBER = 1;
+ private static final byte STOP = (byte) 0xFF;
+ private static final byte RESTART = (byte) 0xAA;
+ private static final byte UP = (byte) 0x01;
+ private static final byte EMPTY = (byte) 0x00;
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@@ -57,17 +61,17 @@ public class AllProcessesCommandsTest {
File dir = temp.newFolder();
AllProcessesCommands commands = new AllProcessesCommands(dir);
- assertThat(commands.isReady(PROCESS_NUMBER)).isFalse();
- assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER))).isEqualTo(AllProcessesCommands.EMPTY);
+ assertThat(commands.isUp(PROCESS_NUMBER)).isFalse();
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER))).isEqualTo(EMPTY);
assertThat(commands.mappedByteBuffer.getLong(2 + commands.offset(PROCESS_NUMBER))).isEqualTo(0L);
- commands.setReady(PROCESS_NUMBER);
- assertThat(commands.isReady(PROCESS_NUMBER)).isTrue();
- assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER))).isEqualTo(AllProcessesCommands.READY);
+ commands.setUp(PROCESS_NUMBER);
+ assertThat(commands.isUp(PROCESS_NUMBER)).isTrue();
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER))).isEqualTo(UP);
long currentTime = System.currentTimeMillis();
commands.ping(PROCESS_NUMBER);
- assertThat(commands.mappedByteBuffer.getLong(2 + commands.offset(PROCESS_NUMBER))).isGreaterThanOrEqualTo(currentTime);
+ assertThat(commands.mappedByteBuffer.getLong(3 + commands.offset(PROCESS_NUMBER))).isGreaterThanOrEqualTo(currentTime);
}
@Test
@@ -75,12 +79,12 @@ public class AllProcessesCommandsTest {
File dir = temp.newFolder();
AllProcessesCommands commands = new AllProcessesCommands(dir);
- assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + PROCESS_NUMBER)).isNotEqualTo(AllProcessesCommands.STOP);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 1)).isNotEqualTo(STOP);
assertThat(commands.askedForStop(PROCESS_NUMBER)).isFalse();
commands.askForStop(PROCESS_NUMBER);
assertThat(commands.askedForStop(PROCESS_NUMBER)).isTrue();
- assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + PROCESS_NUMBER)).isEqualTo(AllProcessesCommands.STOP);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 1)).isEqualTo(STOP);
}
@Test
@@ -88,12 +92,12 @@ public class AllProcessesCommandsTest {
File dir = temp.newFolder();
AllProcessesCommands commands = new AllProcessesCommands(dir);
- assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isNotEqualTo(AllProcessesCommands.RESTART);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) +2)).isNotEqualTo(RESTART);
assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
commands.askForRestart(PROCESS_NUMBER);
assertThat(commands.askedForRestart(PROCESS_NUMBER)).isTrue();
- assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isEqualTo(AllProcessesCommands.RESTART);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 2)).isEqualTo(RESTART);
}
@Test
@@ -101,11 +105,11 @@ public class AllProcessesCommandsTest {
File dir = temp.newFolder();
AllProcessesCommands commands = new AllProcessesCommands(dir);
- assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isNotEqualTo(AllProcessesCommands.RESTART);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 2)).isNotEqualTo(RESTART);
assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
commands.acknowledgeAskForRestart(PROCESS_NUMBER);
- assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isNotEqualTo(AllProcessesCommands.RESTART);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 2)).isNotEqualTo(RESTART);
assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
}
@@ -117,10 +121,10 @@ public class AllProcessesCommandsTest {
commands.askForRestart(PROCESS_NUMBER);
assertThat(commands.askedForRestart(PROCESS_NUMBER)).isTrue();
- assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isEqualTo(AllProcessesCommands.RESTART);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 2)).isEqualTo(RESTART);
commands.acknowledgeAskForRestart(PROCESS_NUMBER);
- assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isNotEqualTo(AllProcessesCommands.RESTART);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 2)).isNotEqualTo(RESTART);
assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java b/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java
index b641ddbba5c..89d07923427 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java
@@ -57,10 +57,10 @@ public class DefaultProcessCommandsTest {
File dir = temp.newFolder();
DefaultProcessCommands commands = new DefaultProcessCommands(dir, PROCESS_NUMBER);
- assertThat(commands.isReady()).isFalse();
+ assertThat(commands.isUp()).isFalse();
- commands.setReady();
- assertThat(commands.isReady()).isTrue();
+ commands.setUp();
+ assertThat(commands.isUp()).isTrue();
}
@Test
diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java
index e8a62fc58b0..8e107af3ee1 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java
@@ -165,7 +165,7 @@ public class ProcessEntryPointTest {
}
@Override
- public boolean isReady() {
+ public boolean isUp() {
return true;
}
@@ -188,7 +188,7 @@ public class ProcessEntryPointTest {
}
@Override
- public boolean isReady() {
+ public boolean isUp() {
return false;
}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
index 60cb57644f4..77e135163ef 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
@@ -88,7 +88,7 @@ public class HttpProcess implements Monitored {
}
@Override
- public boolean isReady() {
+ public boolean isUp() {
if (ready) {
return true;
}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java
index cb75808346f..3cf328eff4d 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java
@@ -51,7 +51,7 @@ public class InfiniteTerminationProcess implements Monitored {
}
@Override
- public boolean isReady() {
+ public boolean isUp() {
return state == State.STARTED;
}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java
index b3b3a5099cb..9e9ab0485e9 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java
@@ -51,7 +51,7 @@ public class StandardProcess implements Monitored {
}
@Override
- public boolean isReady() {
+ public boolean isUp() {
return state == State.STARTED;
}