This is an automated email from the ASF dual-hosted git repository.
imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph.git
The following commit(s) were added to refs/heads/master by this push:
new 68dd29b29 refactor(server): add logs for load-based request rejection
(#2972)
68dd29b29 is described below
commit 68dd29b29b30215d47a595309d3d8f96ca4b010e
Author: contrueCT <[email protected]>
AuthorDate: Sat Apr 25 16:02:49 2026 +0800
refactor(server): add logs for load-based request rejection (#2972)
* fix(filter): enhance load detection logging and memory management
* fix(api): refine low-memory rejection handling
---
.../hugegraph/api/filter/LoadDetectFilter.java | 96 +++--
.../org/apache/hugegraph/unit/UnitTestSuite.java | 2 +
.../unit/api/filter/LoadDetectFilterTest.java | 399 +++++++++++++++++++++
3 files changed, 477 insertions(+), 20 deletions(-)
diff --git
a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/LoadDetectFilter.java
b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/LoadDetectFilter.java
index 7ee5e7c0f..1df19f5e5 100644
---
a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/LoadDetectFilter.java
+++
b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/LoadDetectFilter.java
@@ -19,12 +19,16 @@ package org.apache.hugegraph.api.filter;
import java.util.List;
import java.util.Set;
+import java.util.function.BooleanSupplier;
+import java.util.function.LongSupplier;
import org.apache.hugegraph.config.HugeConfig;
import org.apache.hugegraph.config.ServerOptions;
import org.apache.hugegraph.define.WorkLoad;
import org.apache.hugegraph.util.Bytes;
import org.apache.hugegraph.util.E;
+import org.apache.hugegraph.util.Log;
+import org.slf4j.Logger;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.RateLimiter;
@@ -43,6 +47,8 @@ import jakarta.ws.rs.ext.Provider;
@PreMatching
public class LoadDetectFilter implements ContainerRequestFilter {
+ private static final Logger LOG = Log.logger(LoadDetectFilter.class);
+
private static final Set<String> WHITE_API_LIST = ImmutableSet.of(
"",
"apis",
@@ -54,10 +60,44 @@ public class LoadDetectFilter implements
ContainerRequestFilter {
private static final RateLimiter GC_RATE_LIMITER =
RateLimiter.create(1.0 / 30);
+ // Log at most 1 request per second to avoid too many logs when server is
under heavy load
+ private static final RateLimiter BUSY_REJECT_LOG_RATE_LIMITER =
+ RateLimiter.create(1.0);
+ private static final RateLimiter MEMORY_REJECT_LOG_RATE_LIMITER =
+ RateLimiter.create(1.0);
+
@Context
private jakarta.inject.Provider<HugeConfig> configProvider;
@Context
private jakarta.inject.Provider<WorkLoad> loadProvider;
+ private BooleanSupplier gcTrigger = LoadDetectFilter::triggerGcIfNeeded;
+ private BooleanSupplier busyRejectLogPermit =
+ BUSY_REJECT_LOG_RATE_LIMITER::tryAcquire;
+ private BooleanSupplier memoryRejectLogPermit =
+ MEMORY_REJECT_LOG_RATE_LIMITER::tryAcquire;
+ private LongSupplier freeMemorySupplier =
LoadDetectFilter::currentFreeMemoryInMB;
+
+ public static boolean isWhiteAPI(ContainerRequestContext context) {
+ List<PathSegment> segments = context.getUriInfo().getPathSegments();
+ E.checkArgument(!segments.isEmpty(), "Invalid request uri '%s'",
+ context.getUriInfo().getPath());
+ String rootPath = segments.get(0).getPath();
+ return WHITE_API_LIST.contains(rootPath);
+ }
+
+ private static boolean triggerGcIfNeeded() {
+ if (GC_RATE_LIMITER.tryAcquire(1)) {
+ System.gc();
+ return true;
+ }
+ return false;
+ }
+
+ private static long currentFreeMemoryInMB() {
+ long allocatedMem = Runtime.getRuntime().totalMemory() -
+ Runtime.getRuntime().freeMemory();
+ return (Runtime.getRuntime().maxMemory() - allocatedMem) / Bytes.MB;
+ }
@Override
public void filter(ContainerRequestContext context) {
@@ -70,7 +110,14 @@ public class LoadDetectFilter implements
ContainerRequestFilter {
int maxWorkerThreads = config.get(ServerOptions.MAX_WORKER_THREADS);
WorkLoad load = this.loadProvider.get();
// There will be a thread doesn't work, dedicated to statistics
- if (load.incrementAndGet() >= maxWorkerThreads) {
+ int currentLoad = load.incrementAndGet();
+ if (currentLoad >= maxWorkerThreads) {
+ if (this.busyRejectLogPermit.getAsBoolean()) {
+ LOG.warn("Rejected request due to high worker load, method={},
path={}, " +
+ "currentLoad={}, maxWorkerThreads={}",
+ context.getMethod(), context.getUriInfo().getPath(),
+ currentLoad, maxWorkerThreads);
+ }
throw new ServiceUnavailableException(String.format(
"The server is too busy to process the request, " +
"you can config %s to adjust it or try again later",
@@ -78,12 +125,35 @@ public class LoadDetectFilter implements
ContainerRequestFilter {
}
long minFreeMemory = config.get(ServerOptions.MIN_FREE_MEMORY);
- long allocatedMem = Runtime.getRuntime().totalMemory() -
- Runtime.getRuntime().freeMemory();
- long presumableFreeMem = (Runtime.getRuntime().maxMemory() -
- allocatedMem) / Bytes.MB;
+ long presumableFreeMem = this.freeMemorySupplier.getAsLong();
if (presumableFreeMem < minFreeMemory) {
- gcIfNeeded();
+ boolean gcTriggered = this.gcTrigger.getAsBoolean();
+ if (gcTriggered) {
+ long recheckedFreeMem = this.freeMemorySupplier.getAsLong();
+ if (recheckedFreeMem >= minFreeMemory) {
+ if (this.memoryRejectLogPermit.getAsBoolean()) {
+ LOG.warn("Low free memory recovered after GC,
method={}, path={}, " +
+ "presumableFreeMemMB={},
recheckedFreeMemMB={}, " +
+ "minFreeMemoryMB={}",
+ context.getMethod(),
context.getUriInfo().getPath(),
+ presumableFreeMem, recheckedFreeMem,
minFreeMemory);
+ }
+ return;
+ }
+ if (this.memoryRejectLogPermit.getAsBoolean()) {
+ LOG.warn("Rejected request due to low free memory after
GC, " +
+ "method={}, path={}, presumableFreeMemMB={}, " +
+ "recheckedFreeMemMB={}, minFreeMemoryMB={}",
+ context.getMethod(),
context.getUriInfo().getPath(),
+ presumableFreeMem, recheckedFreeMem,
minFreeMemory);
+ }
+ presumableFreeMem = recheckedFreeMem;
+ } else if (this.memoryRejectLogPermit.getAsBoolean()) {
+ LOG.warn("Rejected request due to low free memory, method={},
path={}, " +
+ "presumableFreeMemMB={}, minFreeMemoryMB={}",
+ context.getMethod(), context.getUriInfo().getPath(),
+ presumableFreeMem, minFreeMemory);
+ }
throw new ServiceUnavailableException(String.format(
"The server available memory %s(MB) is below than " +
"threshold %s(MB) and can't process the request, " +
@@ -92,18 +162,4 @@ public class LoadDetectFilter implements
ContainerRequestFilter {
ServerOptions.MIN_FREE_MEMORY.name()));
}
}
-
- public static boolean isWhiteAPI(ContainerRequestContext context) {
- List<PathSegment> segments = context.getUriInfo().getPathSegments();
- E.checkArgument(!segments.isEmpty(), "Invalid request uri '%s'",
- context.getUriInfo().getPath());
- String rootPath = segments.get(0).getPath();
- return WHITE_API_LIST.contains(rootPath);
- }
-
- private static void gcIfNeeded() {
- if (GC_RATE_LIMITER.tryAcquire(1)) {
- System.gc();
- }
- }
}
diff --git
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
index 52bf104b0..4a62e48bb 100644
---
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
+++
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
@@ -18,6 +18,7 @@
package org.apache.hugegraph.unit;
import org.apache.hugegraph.core.RoleElectionStateMachineTest;
+import org.apache.hugegraph.unit.api.filter.LoadDetectFilterTest;
import org.apache.hugegraph.unit.api.filter.PathFilterTest;
import org.apache.hugegraph.unit.api.gremlin.GremlinQueryAPITest;
import org.apache.hugegraph.unit.auth.HugeGraphAuthProxyTest;
@@ -80,6 +81,7 @@ import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
/* api filter */
+ LoadDetectFilterTest.class,
PathFilterTest.class,
/* api gremlin */
diff --git
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/api/filter/LoadDetectFilterTest.java
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/api/filter/LoadDetectFilterTest.java
new file mode 100644
index 000000000..5be5b64a9
--- /dev/null
+++
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/api/filter/LoadDetectFilterTest.java
@@ -0,0 +1,399 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.unit.api.filter;
+
+import java.io.Serializable;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.function.BooleanSupplier;
+import java.util.function.LongSupplier;
+import java.util.stream.Collectors;
+
+import org.apache.commons.configuration2.Configuration;
+import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.hugegraph.api.filter.LoadDetectFilter;
+import org.apache.hugegraph.config.HugeConfig;
+import org.apache.hugegraph.config.ServerOptions;
+import org.apache.hugegraph.define.WorkLoad;
+import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.testutil.Whitebox;
+import org.apache.hugegraph.unit.BaseUnitTest;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.config.Property;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import jakarta.inject.Provider;
+import jakarta.ws.rs.ServiceUnavailableException;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.core.PathSegment;
+import jakarta.ws.rs.core.UriInfo;
+
+public class LoadDetectFilterTest extends BaseUnitTest {
+
+ private static final String TEST_LOGGER_NAME =
LoadDetectFilter.class.getName();
+
+ private LoadDetectFilter loadDetectFilter;
+ private ContainerRequestContext requestContext;
+ private UriInfo uriInfo;
+ private WorkLoad workLoad;
+ private TestAppender testAppender;
+ private LoggerContext loggerContext;
+ private org.apache.logging.log4j.core.config.Configuration
loggerConfiguration;
+ private LoggerConfig loggerConfig;
+
+ @Before
+ public void setup() {
+ this.requestContext = Mockito.mock(ContainerRequestContext.class);
+ this.uriInfo = Mockito.mock(UriInfo.class);
+ this.workLoad = new WorkLoad();
+ this.testAppender = new TestAppender();
+ this.testAppender.start();
+ this.loggerContext = (LoggerContext) LogManager.getContext(false);
+ this.loggerConfiguration = this.loggerContext.getConfiguration();
+ this.loggerConfig = new LoggerConfig(TEST_LOGGER_NAME, Level.WARN,
false);
+ this.loggerConfig.addAppender(this.testAppender, Level.WARN, null);
+ this.loggerConfiguration.addLogger(TEST_LOGGER_NAME,
this.loggerConfig);
+ this.loggerContext.updateLoggers();
+
+
Mockito.when(this.requestContext.getUriInfo()).thenReturn(this.uriInfo);
+ Mockito.when(this.requestContext.getMethod()).thenReturn("GET");
+
+ this.loadDetectFilter = new LoadDetectFilter();
+ this.setLoadProvider(this.workLoad);
+ this.setConfigProvider(createConfig(8, 0));
+ this.setGcResults(false);
+ this.setBusyLogPermits(true);
+ this.setMemoryLogPermits(true);
+ this.setFreeMemorySamples(1024L, 1024L);
+ }
+
+ @After
+ public void teardown() {
+ this.loggerConfiguration.removeLogger(TEST_LOGGER_NAME);
+ this.loggerContext.updateLoggers();
+ this.testAppender.stop();
+ }
+
+ @Test
+ public void testFilter_WhiteListPathIgnored() {
+ setupPath("", List.of(""));
+ this.workLoad.incrementAndGet();
+
+ this.loadDetectFilter.filter(this.requestContext);
+
+ Assert.assertEquals(1, this.workLoad.get().get());
+ Assert.assertTrue(this.testAppender.events().isEmpty());
+ }
+
+ @Test
+ public void testFilter_RejectsWhenWorkerLoadIsTooHigh() {
+ setupPath("graphs/hugegraph/vertices",
+ List.of("graphs", "hugegraph", "vertices"));
+ this.setConfigProvider(createConfig(2, 0));
+ this.workLoad.incrementAndGet();
+
+ ServiceUnavailableException exception = (ServiceUnavailableException)
Assert.assertThrows(
+ ServiceUnavailableException.class,
+ () -> this.loadDetectFilter.filter(this.requestContext));
+
+ Assert.assertContains("The server is too busy to process the request",
+ exception.getMessage());
+ Assert.assertContains(ServerOptions.MAX_WORKER_THREADS.name(),
+ exception.getMessage());
+ Assert.assertEquals(1, this.testAppender.events().size());
+ this.assertWarnLogContains("Rejected request due to high worker load");
+ this.assertWarnLogContains("method=GET");
+ this.assertWarnLogContains("path=graphs/hugegraph/vertices");
+ this.assertWarnLogContains("currentLoad=2");
+ }
+
+ @Test
+ public void testFilter_RejectsWhenFreeMemoryIsTooLow() {
+ setupPath("graphs/hugegraph/vertices",
+ List.of("graphs", "hugegraph", "vertices"));
+ this.setConfigProvider(createConfig(8, 512));
+ this.setFreeMemorySamples(256L);
+ this.setGcResults(false);
+
+ ServiceUnavailableException exception = (ServiceUnavailableException)
Assert.assertThrows(
+ ServiceUnavailableException.class,
+ () -> this.loadDetectFilter.filter(this.requestContext));
+
+ Assert.assertContains("The server available memory 256(MB) is below
than threshold 512(MB)",
+ exception.getMessage());
+ Assert.assertEquals(1, this.testAppender.events().size());
+ this.assertWarnLogContains("Rejected request due to low free memory");
+ this.assertWarnLogContains("method=GET");
+ this.assertWarnLogContains("path=graphs/hugegraph/vertices");
+ this.assertWarnLogContains("presumableFreeMemMB=256");
+ this.assertWarnLogContains("minFreeMemoryMB=512");
+ this.assertWarnLogNotContains("recheckedFreeMemMB");
+ }
+
+ @Test
+ public void testFilter_RejectsWhenFreeMemoryIsStillLowAfterGc() {
+ setupPath("graphs/hugegraph/vertices",
+ List.of("graphs", "hugegraph", "vertices"));
+ this.setConfigProvider(createConfig(8, 512));
+ this.setFreeMemorySamples(256L, 128L);
+ this.setGcResults(true);
+
+ ServiceUnavailableException exception = (ServiceUnavailableException)
Assert.assertThrows(
+ ServiceUnavailableException.class,
+ () -> this.loadDetectFilter.filter(this.requestContext));
+
+ Assert.assertContains("The server available memory 128(MB) is below
than threshold 512(MB)",
+ exception.getMessage());
+ Assert.assertEquals(1, this.testAppender.events().size());
+ this.assertWarnLogContains("Rejected request due to low free memory
after GC");
+ this.assertWarnLogContains("presumableFreeMemMB=256");
+ this.assertWarnLogContains("recheckedFreeMemMB=128");
+ this.assertWarnLogContains("minFreeMemoryMB=512");
+ }
+
+ @Test
+ public void testFilter_AllowsRequestWhenFreeMemoryRecoversAfterGc() {
+ setupPath("graphs/hugegraph/vertices",
+ List.of("graphs", "hugegraph", "vertices"));
+ this.setConfigProvider(createConfig(8, 512));
+ this.setFreeMemorySamples(256L, 1024L);
+ this.setGcResults(true);
+
+ this.loadDetectFilter.filter(this.requestContext);
+
+ Assert.assertEquals(1, this.workLoad.get().get());
+ Assert.assertEquals(1, this.testAppender.events().size());
+ this.assertWarnLogContains("Low free memory recovered after GC");
+ this.assertWarnLogContains("presumableFreeMemMB=256");
+ this.assertWarnLogContains("recheckedFreeMemMB=1024");
+ this.assertWarnLogContains("minFreeMemoryMB=512");
+ }
+
+ @Test
+ public void testFilter_RejectsWhenFreeMemoryIsTooLowWithoutLogging() {
+ setupPath("graphs/hugegraph/vertices",
+ List.of("graphs", "hugegraph", "vertices"));
+ this.setConfigProvider(createConfig(8, 512));
+ this.setFreeMemorySamples(256L);
+ this.setGcResults(false);
+ this.setMemoryLogPermits(false);
+
+ ServiceUnavailableException exception = (ServiceUnavailableException)
Assert.assertThrows(
+ ServiceUnavailableException.class,
+ () -> this.loadDetectFilter.filter(this.requestContext));
+
+ Assert.assertContains("The server available memory 256(MB) is below
than threshold 512(MB)",
+ exception.getMessage());
+ Assert.assertTrue(this.testAppender.events().isEmpty());
+ }
+
+ @Test
+ public void testFilter_AllowsRequestWhenLoadAndMemoryAreHealthy() {
+ setupPath("graphs/hugegraph/vertices",
+ List.of("graphs", "hugegraph", "vertices"));
+ this.setConfigProvider(createConfig(8, 0));
+
+ this.loadDetectFilter.filter(this.requestContext);
+
+ Assert.assertEquals(1, this.workLoad.get().get());
+ Assert.assertTrue(this.testAppender.events().isEmpty());
+ }
+
+ @Test
+ public void testFilter_RejectLogIsRateLimited() {
+ setupPath("graphs/hugegraph/vertices",
+ List.of("graphs", "hugegraph", "vertices"));
+ this.setConfigProvider(createConfig(2, 0));
+ this.setBusyLogPermits(true, false);
+
+ this.workLoad.incrementAndGet();
+ Assert.assertThrows(ServiceUnavailableException.class,
+ () ->
this.loadDetectFilter.filter(this.requestContext));
+
+ this.workLoad.get().set(1);
+ Assert.assertThrows(ServiceUnavailableException.class,
+ () ->
this.loadDetectFilter.filter(this.requestContext));
+
+ Assert.assertEquals(1, this.testAppender.events().size());
+ this.assertWarnLogContains("Rejected request due to high worker load");
+ }
+
+ @Test
+ public void testFilter_BusyRejectLogPermitDoesNotAffectMemoryRejectLog() {
+ setupPath("graphs/hugegraph/vertices",
+ List.of("graphs", "hugegraph", "vertices"));
+ this.setConfigProvider(createConfig(2, 0));
+ this.setBusyLogPermits(false);
+ this.workLoad.incrementAndGet();
+
+ Assert.assertThrows(ServiceUnavailableException.class,
+ () ->
this.loadDetectFilter.filter(this.requestContext));
+ Assert.assertTrue(this.testAppender.events().isEmpty());
+
+ this.workLoad.get().set(0);
+ this.setConfigProvider(createConfig(8, 512));
+ this.setFreeMemorySamples(256L);
+ this.setGcResults(false);
+ this.setMemoryLogPermits(true);
+
+ Assert.assertThrows(ServiceUnavailableException.class,
+ () ->
this.loadDetectFilter.filter(this.requestContext));
+
+ Assert.assertEquals(1, this.testAppender.events().size());
+ this.assertWarnLogContains("Rejected request due to low free memory");
+ }
+
+ @Test
+ public void testFilter_MemoryRejectLogPermitDoesNotAffectBusyRejectLog() {
+ setupPath("graphs/hugegraph/vertices",
+ List.of("graphs", "hugegraph", "vertices"));
+ this.setConfigProvider(createConfig(8, 512));
+ this.setFreeMemorySamples(256L);
+ this.setGcResults(false);
+ this.setMemoryLogPermits(false);
+
+ Assert.assertThrows(ServiceUnavailableException.class,
+ () ->
this.loadDetectFilter.filter(this.requestContext));
+ Assert.assertTrue(this.testAppender.events().isEmpty());
+
+ this.workLoad.get().set(1);
+ this.setConfigProvider(createConfig(2, 0));
+ this.setBusyLogPermits(true);
+
+ Assert.assertThrows(ServiceUnavailableException.class,
+ () ->
this.loadDetectFilter.filter(this.requestContext));
+
+ Assert.assertEquals(1, this.testAppender.events().size());
+ this.assertWarnLogContains("Rejected request due to high worker load");
+ }
+
+ private HugeConfig createConfig(int maxWorkerThreads, int minFreeMemory) {
+ Configuration conf = new PropertiesConfiguration();
+ conf.setProperty(ServerOptions.MAX_WORKER_THREADS.name(),
maxWorkerThreads);
+ conf.setProperty(ServerOptions.MIN_FREE_MEMORY.name(), minFreeMemory);
+ return new HugeConfig(conf);
+ }
+
+ private void setupPath(String path, List<String> segments) {
+ List<PathSegment> pathSegments = segments.stream()
+ .map(this::createPathSegment)
+ .collect(Collectors.toList());
+ Mockito.when(this.uriInfo.getPath()).thenReturn(path);
+ Mockito.when(this.uriInfo.getPathSegments()).thenReturn(pathSegments);
+ }
+
+ private PathSegment createPathSegment(String path) {
+ PathSegment segment = Mockito.mock(PathSegment.class);
+ Mockito.when(segment.getPath()).thenReturn(path);
+ return segment;
+ }
+
+ private void setLoadProvider(WorkLoad workLoad) {
+ Whitebox.setInternalState(this.loadDetectFilter, "loadProvider",
+ (Provider<WorkLoad>) () -> workLoad);
+ }
+
+ private void setConfigProvider(HugeConfig config) {
+ Whitebox.setInternalState(this.loadDetectFilter, "configProvider",
+ (Provider<HugeConfig>) () -> config);
+ }
+
+ private void setGcResults(boolean... gcResults) {
+ this.setBooleanSupplier("gcTrigger", false, gcResults);
+ }
+
+ private void setBusyLogPermits(boolean... permits) {
+ this.setBooleanSupplier("busyRejectLogPermit", true, permits);
+ }
+
+ private void setMemoryLogPermits(boolean... permits) {
+ this.setBooleanSupplier("memoryRejectLogPermit", true, permits);
+ }
+
+ private void setFreeMemorySamples(long... freeMemorySamples) {
+ Assert.assertTrue(freeMemorySamples.length > 0);
+ Deque<Long> samples = new ArrayDeque<>();
+ for (long freeMemorySample : freeMemorySamples) {
+ samples.addLast(freeMemorySample);
+ }
+ long fallback = freeMemorySamples[freeMemorySamples.length - 1];
+ Whitebox.setInternalState(this.loadDetectFilter, "freeMemorySupplier",
+ (LongSupplier) () -> samples.isEmpty() ?
fallback :
+ samples.removeFirst());
+ }
+
+ private void setBooleanSupplier(String fieldName, boolean fallback,
+ boolean... values) {
+ Deque<Boolean> results = new ArrayDeque<>();
+ for (boolean value : values) {
+ results.addLast(value);
+ }
+ Whitebox.setInternalState(this.loadDetectFilter, fieldName,
+ (BooleanSupplier) () -> results.isEmpty() ?
+ fallback :
+
results.removeFirst());
+ }
+
+ private void assertWarnLogContains(String expectedContent) {
+ Assert.assertFalse(this.testAppender.events().isEmpty());
+ LogEvent event = this.testAppender.events().get(0);
+ Assert.assertEquals(Level.WARN, event.getLevel());
+ Assert.assertContains(expectedContent,
+ event.getMessage().getFormattedMessage());
+ }
+
+ private void assertWarnLogNotContains(String unexpectedContent) {
+ Assert.assertFalse(this.testAppender.events().isEmpty());
+ LogEvent event = this.testAppender.events().get(0);
+ Assert.assertFalse(event.getMessage().getFormattedMessage()
+ .contains(unexpectedContent));
+ }
+
+ private static class TestAppender extends AbstractAppender {
+
+ private final List<LogEvent> events;
+
+ protected TestAppender() {
+ super("LoadDetectFilterTestAppender", (Filter) null,
+ (Layout<? extends Serializable>) null, false,
+ Property.EMPTY_ARRAY);
+ this.events = new ArrayList<>();
+ }
+
+ @Override
+ public void append(LogEvent event) {
+ this.events.add(event.toImmutable());
+ }
+
+ public List<LogEvent> events() {
+ return this.events;
+ }
+ }
+}