From 2cb578462b77e96028e0b089ea39097511873ac6 Mon Sep 17 00:00:00 2001 From: Serhii Plyhun Date: Fri, 31 Jul 2026 17:49:57 +0200 Subject: [PATCH 1/3] SUP-20109: Better API message for the liveness check failed --- .../resources/i18n/translations_de.properties | 1 + .../resources/i18n/translations_en.properties | 1 + .../resources/i18n/translations_zh.properties | 1 + .../handler/MonitoringCrudHandler.java | 38 +++++++++---------- .../MonitoringServerEndpointTest.java | 2 +- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/common/src/main/resources/i18n/translations_de.properties b/common/src/main/resources/i18n/translations_de.properties index a3e4950c557..fb63548bc37 100644 --- a/common/src/main/resources/i18n/translations_de.properties +++ b/common/src/main/resources/i18n/translations_de.properties @@ -1,5 +1,6 @@ error=Fehler error_internal=Interner Fehler aufgetreten. +error_service_unavailable=Das Service ist derzeit nicht verfügbar. Bitte versuchen Sie es später erneut. error_invalid_parameter=Falscher Inhalt "{1}" des Parameters "{0}". error_not_authorized=Sie sind nicht berechtigt um auf die angefragte Resource zuzugreifen. error_admin_permission_required=Es werden Administrator Rechte benötigt. diff --git a/common/src/main/resources/i18n/translations_en.properties b/common/src/main/resources/i18n/translations_en.properties index 784e3f577d3..37cb37f06c3 100644 --- a/common/src/main/resources/i18n/translations_en.properties +++ b/common/src/main/resources/i18n/translations_en.properties @@ -1,5 +1,6 @@ error=Error error_internal=Internal error occurred. +error_service_unavailable=Service is unavailable at the moment. Please try again later. error_invalid_parameter=Invalid "{0}" parameter value "{1}". error_not_authorized=You are not authorized to access the requested resource. error_admin_permission_required=Administration permissions are required. diff --git a/common/src/main/resources/i18n/translations_zh.properties b/common/src/main/resources/i18n/translations_zh.properties index a78c3f5a217..85e56763345 100644 --- a/common/src/main/resources/i18n/translations_zh.properties +++ b/common/src/main/resources/i18n/translations_zh.properties @@ -1,5 +1,6 @@ error=错误 error_internal=发生内部错误。 +error_service_unavailable=该服务目前无法使用。请稍后再试。 error_invalid_parameter=“{0}”参数值“{1}”无效。 error_not_authorized=你无权访问所请求的资源。 error_admin_permission_required=需要管理权限。 diff --git a/core/src/main/java/com/gentics/mesh/core/endpoint/handler/MonitoringCrudHandler.java b/core/src/main/java/com/gentics/mesh/core/endpoint/handler/MonitoringCrudHandler.java index be95d56564b..fc393b51eb0 100644 --- a/core/src/main/java/com/gentics/mesh/core/endpoint/handler/MonitoringCrudHandler.java +++ b/core/src/main/java/com/gentics/mesh/core/endpoint/handler/MonitoringCrudHandler.java @@ -60,7 +60,7 @@ public void handleLive(RoutingContext rc) { PluginStatus status = pluginManager.getStatus(id); if (status == PluginStatus.FAILED) { log.warn("Plugin {" + id + "} is in status failed."); - throw error(SERVICE_UNAVAILABLE, "error_internal").setLogStackTrace(false); + throw error(SERVICE_UNAVAILABLE, "error_service_unavailable").setLogStackTrace(false); } } if (!liveness.isLive()) { @@ -72,7 +72,7 @@ public void handleLive(RoutingContext rc) { break; default: log.warn("Liveness was set to false due to {}", liveness.getError()); - throw error(SERVICE_UNAVAILABLE, "error_internal").setLogStackTrace(false); + throw error(SERVICE_UNAVAILABLE, "error_service_unavailable").setLogStackTrace(false); } } rc.response().setStatusCode(200).end(); @@ -87,7 +87,7 @@ public void handleReady(RoutingContext rc) { MeshStatus status = boot.mesh().getStatus(); if (!status.equals(MeshStatus.READY)) { log.warn("Status is {" + status.name() + "} - Failing readiness probe"); - throw error(SERVICE_UNAVAILABLE, "error_internal").setLogStackTrace(false); + throw error(SERVICE_UNAVAILABLE, "error_service_unavailable").setLogStackTrace(false); } for (String id : pluginManager.getPluginIds()) { PluginStatus pluginStatus = pluginManager.getStatus(id); @@ -99,16 +99,16 @@ public void handleReady(RoutingContext rc) { // be reached. if (pluginStatus == PluginStatus.FAILED) { log.error("Plugin {" + id + "} is in status failed."); - throw error(SERVICE_UNAVAILABLE, "error_internal").setLogStackTrace(false); + throw error(SERVICE_UNAVAILABLE, "error_service_unavailable").setLogStackTrace(false); } } if (!liveness.isLive()) { log.warn("Liveness was set to false due to {}", liveness.getError()); - throw error(SERVICE_UNAVAILABLE, "error_internal").setLogStackTrace(false); + throw error(SERVICE_UNAVAILABLE, "error_service_unavailable").setLogStackTrace(false); } if (!db.isHealthy()) { log.warn("Failing DB health check"); - throw error(SERVICE_UNAVAILABLE, "error_internal").setLogStackTrace(false); + throw error(SERVICE_UNAVAILABLE, "error_service_unavailable").setLogStackTrace(false); } rc.response().end(); } @@ -123,18 +123,18 @@ public void handleReady(RoutingContext rc) { */ public void handleWritable(RoutingContext rc) { localConfigApi.getActiveConfig() - .map(LocalConfigModel::isReadOnly) - .map(Boolean::booleanValue) - .subscribe(isReadOnly -> { - if (isReadOnly) { - log.warn("Local node cannot write - read only mode set"); - rc.fail(error(SERVICE_UNAVAILABLE, "error_internal").setLogStackTrace(false)); - } else if (db.isReadOnly(false)) { - log.warn("Local node cannot write - read only database"); - rc.fail(error(SERVICE_UNAVAILABLE, "error_internal").setLogStackTrace(false)); - } else { - rc.response().setStatusCode(200).end(); - } - }); + .map(LocalConfigModel::isReadOnly) + .map(Boolean::booleanValue) + .subscribe(isReadOnly -> { + if (isReadOnly) { + log.warn("Local node cannot write - read only mode set"); + rc.fail(error(SERVICE_UNAVAILABLE, "error_service_unavailable").setLogStackTrace(false)); + } else if (db.isReadOnly(false)) { + log.warn("Local node cannot write - read only database"); + rc.fail(error(SERVICE_UNAVAILABLE, "error_service_unavailable").setLogStackTrace(false)); + } else { + rc.response().setStatusCode(200).end(); + } + }); } } diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/monitoring/MonitoringServerEndpointTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/monitoring/MonitoringServerEndpointTest.java index 8d37aceeb2e..5031c7853d8 100644 --- a/tests/tests-core/src/main/java/com/gentics/mesh/core/monitoring/MonitoringServerEndpointTest.java +++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/monitoring/MonitoringServerEndpointTest.java @@ -121,7 +121,7 @@ public void testWritableReturns200() { @Test public void testWritableReturns503WhenReadOnlyMode() { call(() -> client().updateLocalConfig(buildLocalConfigModel(true))); - call(() -> monClient().writable(), SERVICE_UNAVAILABLE, "error_internal"); + call(() -> monClient().writable(), SERVICE_UNAVAILABLE, "error_service_unavailable"); } private LocalConfigModel buildLocalConfigModel(boolean readOnly) { From bbe0be49b884e07cec27323afd37d0282f364cac Mon Sep 17 00:00:00 2001 From: Serhii Plyhun Date: Wed, 5 Aug 2026 14:25:26 +0200 Subject: [PATCH 2/3] Add responsiveness tests --- .../mesh/test/MeshCoreOptionChanger.java | 2 + .../gentics/mesh/core/ResponsivenessTest.java | 30 ++++++++++++ .../PluginConnectivityResponsivenessTest.java | 49 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 tests/tests-core/src/main/java/com/gentics/mesh/core/ResponsivenessTest.java create mode 100644 tests/tests-core/src/main/java/com/gentics/mesh/core/admin/PluginConnectivityResponsivenessTest.java diff --git a/tests/context-api/src/main/java/com/gentics/mesh/test/MeshCoreOptionChanger.java b/tests/context-api/src/main/java/com/gentics/mesh/test/MeshCoreOptionChanger.java index 93dda498556..63b91ee665d 100644 --- a/tests/context-api/src/main/java/com/gentics/mesh/test/MeshCoreOptionChanger.java +++ b/tests/context-api/src/main/java/com/gentics/mesh/test/MeshCoreOptionChanger.java @@ -47,6 +47,8 @@ public enum MeshCoreOptionChanger implements MeshOptionChanger { options.getUploadOptions().setCheckInterval(5_000); }), SHORT_MIGRATION_BATCH(options -> { options.setMigrationMaxBatchSize(2); + }), SMALL_EVENT_LOOP_POOL(options -> { + options.getVertxOptions().setEventPoolSize(1); }); private final Consumer changer; diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/ResponsivenessTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/ResponsivenessTest.java new file mode 100644 index 00000000000..3c073193ae6 --- /dev/null +++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/ResponsivenessTest.java @@ -0,0 +1,30 @@ +package com.gentics.mesh.core; + +import java.util.concurrent.CountDownLatch; + +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.gentics.mesh.test.MeshCoreOptionChanger; +import com.gentics.mesh.test.MeshTestSetting; +import com.gentics.mesh.test.TestSize; +import com.gentics.mesh.test.category.FailingTests; +import com.gentics.mesh.test.context.AbstractMeshTest; + +@MeshTestSetting(testSize = TestSize.PROJECT, startServer = true, optionChanger = MeshCoreOptionChanger.SMALL_EVENT_LOOP_POOL) +@Category(FailingTests.class) +public class ResponsivenessTest extends AbstractMeshTest { + + @Test + public void testBlockingGets() throws InterruptedException { + int numThreads = 100; + CountDownLatch latch = new CountDownLatch(numThreads); + for (int i = 0; i < numThreads; i++) { + new Thread(() -> { + client().me().blockingGet(); + latch.countDown(); + }).start(); + } + latch.await(); + } +} diff --git a/tests/tests-core/src/main/java/com/gentics/mesh/core/admin/PluginConnectivityResponsivenessTest.java b/tests/tests-core/src/main/java/com/gentics/mesh/core/admin/PluginConnectivityResponsivenessTest.java new file mode 100644 index 00000000000..70de9c64a48 --- /dev/null +++ b/tests/tests-core/src/main/java/com/gentics/mesh/core/admin/PluginConnectivityResponsivenessTest.java @@ -0,0 +1,49 @@ +package com.gentics.mesh.core.admin; + +import static com.gentics.mesh.MeshVersion.CURRENT_API_BASE_PATH; +import static com.gentics.mesh.test.TestSize.PROJECT; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.gentics.mesh.core.rest.error.GenericRestException; +import com.gentics.mesh.core.rest.user.UserResponse; +import com.gentics.mesh.json.JsonUtil; +import com.gentics.mesh.plugin.AbstractPluginTest; +import com.gentics.mesh.test.MeshCoreOptionChanger; +import com.gentics.mesh.test.MeshTestSetting; +import com.gentics.mesh.test.category.PluginTests; + +@Category(PluginTests.class) +@MeshTestSetting(testSize = PROJECT, startServer = true, inMemoryDB = true, optionChanger = MeshCoreOptionChanger.SMALL_EVENT_LOOP_POOL) +public class PluginConnectivityResponsivenessTest extends AbstractPluginTest { + + @Test + public void testClientPlugin() throws IOException, InterruptedException { + grantAdmin(); + + copyAndDeploy(CLIENT_PATH, "client.jar"); + assertEquals(1, pluginManager().getPluginIds().size()); + + int numRequests = 50; + CountDownLatch latch = new CountDownLatch(numRequests); + for (int i = 0; i < numRequests; i++) { + new Thread(() -> { + try { + assertNotNull("Should find a default John Doe user", JsonUtil.readValue(httpGetNow(CURRENT_API_BASE_PATH + "/plugins/client/me"), UserResponse.class)); + latch.countDown(); + } catch (GenericRestException | IOException e) { + throw new IllegalStateException(e); + } + }).start(); + } + latch.await(10, TimeUnit.SECONDS); + } +} From 517481fa0ccd30deffa7342eed634abb94eef2e2 Mon Sep 17 00:00:00 2001 From: Serhii Plyhun Date: Wed, 5 Aug 2026 14:30:53 +0200 Subject: [PATCH 3/3] Changelog --- changelog/src/changelog/entries/2026/08/8859.SUP-20109.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/src/changelog/entries/2026/08/8859.SUP-20109.bugfix diff --git a/changelog/src/changelog/entries/2026/08/8859.SUP-20109.bugfix b/changelog/src/changelog/entries/2026/08/8859.SUP-20109.bugfix new file mode 100644 index 00000000000..0a833839eb5 --- /dev/null +++ b/changelog/src/changelog/entries/2026/08/8859.SUP-20109.bugfix @@ -0,0 +1 @@ +Liveness: The misleading `Internal error` description for suspended liveness has been replaced with the proper `Service unavailable` message. \ No newline at end of file