Execute checks asynchronously with timeout
Do not block during the execution of the health-checks. Wait up to
500 msec and fail for timeout if not completed yet.
Change-Id: I1b282e9de339ace631262bd16b6e13cc1ffa35e4
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckModule.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckModule.java
index 6971282..0fa02f2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckModule.java
@@ -14,14 +14,21 @@
package com.googlesource.gerrit.plugins.healthcheck;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.inject.AbstractModule;
import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck;
+import java.util.concurrent.Executors;
public class HealthCheckModule extends AbstractModule {
+ public static final int CHECK_THREADS_DEFAULT = 10;
@Override
protected void configure() {
+ bind(ListeningExecutorService.class)
+ .toInstance(
+ MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(CHECK_THREADS_DEFAULT)));
DynamicSet.setOf(binder(), HealthCheck.class);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AbstractHealthCheck.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AbstractHealthCheck.java
index f186092..5c8d410 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AbstractHealthCheck.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AbstractHealthCheck.java
@@ -14,14 +14,22 @@
package com.googlesource.gerrit.plugins.healthcheck.check;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractHealthCheck implements HealthCheck {
private static final Logger log = LoggerFactory.getLogger(AbstractHealthCheck.class);
+ public static final long CHECK_TIMEOUT = 500L;
private final String name;
+ private final ListeningExecutorService executor;
- protected AbstractHealthCheck(String name) {
+ protected AbstractHealthCheck(ListeningExecutorService executor, String name) {
+ this.executor = executor;
this.name = name;
}
@@ -32,15 +40,29 @@
@Override
public Status run() {
- Result healthy;
- long ts = System.currentTimeMillis();
+ final long ts = System.currentTimeMillis();
+ ListenableFuture<Status> resultFuture =
+ executor.submit(
+ () -> {
+ Result healthy;
+ try {
+ healthy = doCheck();
+ } catch (Exception e) {
+ log.warn("Check {} failed", name, e);
+ healthy = Result.FAILED;
+ }
+ return new Status(healthy, ts, System.currentTimeMillis() - ts);
+ });
+
try {
- healthy = doCheck();
- } catch (Exception e) {
- log.warn("Check {} failed", name, e);
- healthy = Result.FAILED;
+ return resultFuture.get(CHECK_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ log.warn("Check {} timed out", name, e);
+ return new Status(Result.TIMEOUT, ts, System.currentTimeMillis() - ts);
+ } catch (InterruptedException | ExecutionException e) {
+ log.warn("Check {} failed while waiting for its future result", name, e);
+ return new Status(Result.FAILED, ts, System.currentTimeMillis() - ts);
}
- return new Status(healthy, ts, System.currentTimeMillis() - ts);
}
protected abstract Result doCheck() throws Exception;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/JGitHealthCheck.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/JGitHealthCheck.java
index be0a373..a23e0d1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/JGitHealthCheck.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/JGitHealthCheck.java
@@ -16,6 +16,7 @@
import static com.googlesource.gerrit.plugins.healthcheck.check.HealthCheckNames.JGIT;
+import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
@@ -27,8 +28,11 @@
private final AllProjectsName allProjectsName;
@Inject
- public JGitHealthCheck(GitRepositoryManager repositoryManager, AllProjectsName allProjectsName) {
- super(JGIT);
+ public JGitHealthCheck(
+ ListeningExecutorService executor,
+ GitRepositoryManager repositoryManager,
+ AllProjectsName allProjectsName) {
+ super(executor, JGIT);
this.repositoryManager = repositoryManager;
this.allProjectsName = allProjectsName;
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ProjectsListHealthCheck.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ProjectsListHealthCheck.java
index 669ae62..580f3a0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ProjectsListHealthCheck.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ProjectsListHealthCheck.java
@@ -16,6 +16,7 @@
import static com.googlesource.gerrit.plugins.healthcheck.check.HealthCheckNames.PROJECTSLIST;
+import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.server.project.ListProjects;
import com.google.inject.Inject;
@@ -29,8 +30,8 @@
private final ListProjects listProjects;
@Inject
- public ProjectsListHealthCheck(ListProjects listProjects) {
- super(PROJECTSLIST);
+ public ProjectsListHealthCheck(ListeningExecutorService executor, ListProjects listProjects) {
+ super(executor, PROJECTSLIST);
this.listProjects = listProjects;
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ReviewDbHealthCheck.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ReviewDbHealthCheck.java
index 10aad07..138997b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ReviewDbHealthCheck.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ReviewDbHealthCheck.java
@@ -16,27 +16,24 @@
import static com.googlesource.gerrit.plugins.healthcheck.check.HealthCheckNames.REVIEWDB;
+import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
-import com.google.inject.Provider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
public class ReviewDbHealthCheck extends AbstractHealthCheck {
- private static final Logger log = LoggerFactory.getLogger(ReviewDbHealthCheck.class);
-
- private final Provider<ReviewDb> reviewDb;
+ private final SchemaFactory<ReviewDb> reviewDb;
@Inject
- public ReviewDbHealthCheck(Provider<ReviewDb> reviewDb) {
- super(REVIEWDB);
+ public ReviewDbHealthCheck(ListeningExecutorService executor, SchemaFactory<ReviewDb> reviewDb) {
+ super(executor, REVIEWDB);
this.reviewDb = reviewDb;
}
@Override
protected Result doCheck() throws Exception {
- try (ReviewDb db = reviewDb.get()) {
+ try (ReviewDb db = reviewDb.open()) {
db.schemaVersion().get(new CurrentSchemaVersion.Key());
return Result.PASSED;
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckIT.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckIT.java
index f5c6036..86a0965 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckIT.java
@@ -21,6 +21,7 @@
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
@@ -28,6 +29,7 @@
import org.junit.Test;
@TestPlugin(name = "healthcheck", sysModule = "com.googlesource.gerrit.plugins.healthcheck.Module")
+@Sandboxed
public class HealthCheckIT extends LightweightPluginDaemonTest {
Gson gson = new Gson();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/JGitHealthCheckTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/JGitHealthCheckTest.java
index 677d22a..990b4cb 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/JGitHealthCheckTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/JGitHealthCheckTest.java
@@ -17,11 +17,14 @@
import static com.google.common.truth.Truth.assertThat;
import static org.eclipse.jgit.lib.RefUpdate.Result.NEW;
+import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.RepositoryCaseMismatchException;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck.Result;
import com.googlesource.gerrit.plugins.healthcheck.check.JGitHealthCheck;
import java.io.IOException;
@@ -41,10 +44,14 @@
public class JGitHealthCheckTest {
private AllProjectsName allProjectsName = new AllProjectsName("All-Projects");
private InMemoryRepositoryManager inMemoryRepositoryManager = new InMemoryRepositoryManager();
- PersonIdent personIdent = new PersonIdent("Gerrit Rietveld", "gerrit@rietveld.nl");
+ private PersonIdent personIdent = new PersonIdent("Gerrit Rietveld", "gerrit@rietveld.nl");
+
+ @Inject private ListeningExecutorService executor;
@Before
public void setupAllProjects() throws Exception {
+ Guice.createInjector(new HealthCheckModule()).injectMembers(this);
+
InMemoryRepositoryManager.Repo repo =
inMemoryRepositoryManager.createRepository(allProjectsName);
createCommit(repo, "refs/meta/config");
@@ -53,14 +60,14 @@
@Test
public void shouldBeHealthyWhenJGitIsWorking() {
JGitHealthCheck reviewDbCheck =
- new JGitHealthCheck(getWorkingRepositoryManager(), allProjectsName);
+ new JGitHealthCheck(executor, getWorkingRepositoryManager(), allProjectsName);
assertThat(reviewDbCheck.run().result).isEqualTo(Result.PASSED);
}
@Test
public void shouldBeUnhealthyWhenJGitIsFailing() {
JGitHealthCheck jGitHealthCheck =
- new JGitHealthCheck(getFailingGitRepositoryManager(), allProjectsName);
+ new JGitHealthCheck(executor, getFailingGitRepositoryManager(), allProjectsName);
assertThat(jGitHealthCheck.run().result).isEqualTo(Result.FAILED);
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ProjectsListHealthCheckTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ProjectsListHealthCheckTest.java
index 668dbdf..a45ea32 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ProjectsListHealthCheckTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ProjectsListHealthCheckTest.java
@@ -16,29 +16,50 @@
import static com.google.common.truth.Truth.assertThat;
+import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.server.project.ListProjects;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.healthcheck.check.AbstractHealthCheck;
import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck.Result;
import com.googlesource.gerrit.plugins.healthcheck.check.ProjectsListHealthCheck;
import java.util.SortedMap;
import java.util.TreeMap;
+import org.junit.Before;
import org.junit.Test;
public class ProjectsListHealthCheckTest {
+ @Inject private ListeningExecutorService executor;
+
+ @Before
+ public void setUp() {
+ Guice.createInjector(new HealthCheckModule()).injectMembers(this);
+ }
@Test
public void shouldBeHealthyWhenListProjectsWorks() {
- ProjectsListHealthCheck jGitHealthCheck = new ProjectsListHealthCheck(getWorkingProjectList());
+ ProjectsListHealthCheck jGitHealthCheck =
+ new ProjectsListHealthCheck(executor, getWorkingProjectList(0));
assertThat(jGitHealthCheck.run().result).isEqualTo(Result.PASSED);
}
@Test
public void shouldBeUnhealthyWhenListProjectsIsFailing() {
- ProjectsListHealthCheck jGitHealthCheck = new ProjectsListHealthCheck(getFailingProjectList());
+ ProjectsListHealthCheck jGitHealthCheck =
+ new ProjectsListHealthCheck(executor, getFailingProjectList());
assertThat(jGitHealthCheck.run().result).isEqualTo(Result.FAILED);
}
+ @Test
+ public void shouldBeUnhealthyWhenListProjectsIsTimingOut() {
+ ProjectsListHealthCheck jGitHealthCheck =
+ new ProjectsListHealthCheck(
+ executor, getWorkingProjectList(AbstractHealthCheck.CHECK_TIMEOUT * 2));
+ assertThat(jGitHealthCheck.run().result).isEqualTo(Result.TIMEOUT);
+ }
+
private ListProjects getFailingProjectList() {
return new ListProjects(null, null, null, null, null, null, null, null) {
@Override
@@ -48,12 +69,17 @@
};
}
- private ListProjects getWorkingProjectList() {
+ private ListProjects getWorkingProjectList(long execTime) {
return new ListProjects(null, null, null, null, null, null, null, null) {
@Override
public SortedMap<String, ProjectInfo> apply() throws BadRequestException {
SortedMap<String, ProjectInfo> projects = new TreeMap<>();
projects.put("testproject", new ProjectInfo());
+ try {
+ Thread.sleep(execTime);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
return projects;
}
};
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ReviewDbHealthCheckTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ReviewDbHealthCheckTest.java
index aae2082..1e5d712 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ReviewDbHealthCheckTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ReviewDbHealthCheckTest.java
@@ -16,52 +16,49 @@
import static com.google.common.truth.Truth.assertThat;
+import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.testutil.DisabledReviewDb;
import com.google.gerrit.testutil.InMemoryDatabase;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck;
import com.googlesource.gerrit.plugins.healthcheck.check.ReviewDbHealthCheck;
+import org.junit.Before;
import org.junit.Test;
public class ReviewDbHealthCheckTest {
+ private SchemaFactory<ReviewDb> workingReviewDbFactory;
+
+ @Inject private ListeningExecutorService executor;
+
+ @Before
+ public void setUp() throws Exception {
+ Guice.createInjector(new HealthCheckModule()).injectMembers(this);
+ workingReviewDbFactory = InMemoryDatabase.newDatabase(new LifecycleManager()).create();
+ }
@Test
- public void shouldBeHealthyWhenReviewDbIsWorking() throws OrmException {
- ReviewDbHealthCheck reviewDbCheck = new ReviewDbHealthCheck(getWorkingReviewDbProvider());
+ public void shouldBeHealthyWhenReviewDbIsWorking() {
+ ReviewDbHealthCheck reviewDbCheck = new ReviewDbHealthCheck(executor, workingReviewDbFactory);
assertThat(reviewDbCheck.run().result).isEqualTo(HealthCheck.Result.PASSED);
}
@Test
public void shouldBeUnhealthyWhenReviewDbIsFailing() {
- ReviewDbHealthCheck reviewDbCheck = new ReviewDbHealthCheck(getFailingReviewDbProvider());
+ ReviewDbHealthCheck reviewDbCheck =
+ new ReviewDbHealthCheck(executor, getFailingReviewDbProvider());
assertThat(reviewDbCheck.run().result).isEqualTo(HealthCheck.Result.FAILED);
}
- private Provider<ReviewDb> getFailingReviewDbProvider() {
- return new Provider<ReviewDb>() {
+ private SchemaFactory<ReviewDb> getFailingReviewDbProvider() {
+ return new SchemaFactory<ReviewDb>() {
@Override
- public ReviewDb get() {
+ public ReviewDb open() {
return new DisabledReviewDb();
}
};
}
-
- private Provider<ReviewDb> getWorkingReviewDbProvider() throws OrmException {
- InMemoryDatabase inMemoryDatabase =
- InMemoryDatabase.newDatabase(new LifecycleManager()).create();
- return new Provider<ReviewDb>() {
- @Override
- public ReviewDb get() {
- try {
- return inMemoryDatabase.open();
- } catch (OrmException e) {
- e.printStackTrace();
- return null;
- }
- }
- };
- }
}