Skip to content

Commit f24f3fa

Browse files
committed
fix(api): add load rejection warning logs
1 parent 41d0dbc commit f24f3fa

3 files changed

Lines changed: 167 additions & 1 deletion

File tree

hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/LoadDetectFilter.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import org.apache.hugegraph.define.WorkLoad;
2626
import org.apache.hugegraph.util.Bytes;
2727
import org.apache.hugegraph.util.E;
28+
import org.apache.hugegraph.util.Log;
29+
import org.slf4j.Logger;
2830

2931
import com.google.common.collect.ImmutableSet;
3032
import com.google.common.util.concurrent.RateLimiter;
@@ -43,6 +45,8 @@
4345
@PreMatching
4446
public class LoadDetectFilter implements ContainerRequestFilter {
4547

48+
private static final Logger LOG = Log.logger(LoadDetectFilter.class);
49+
4650
private static final Set<String> WHITE_API_LIST = ImmutableSet.of(
4751
"",
4852
"apis",
@@ -70,7 +74,12 @@ public void filter(ContainerRequestContext context) {
7074
int maxWorkerThreads = config.get(ServerOptions.MAX_WORKER_THREADS);
7175
WorkLoad load = this.loadProvider.get();
7276
// There will be a thread doesn't work, dedicated to statistics
73-
if (load.incrementAndGet() >= maxWorkerThreads) {
77+
int currentLoad = load.incrementAndGet();
78+
if (currentLoad >= maxWorkerThreads) {
79+
LOG.warn("Rejected request due to high worker load, method={}, path={}, " +
80+
"currentLoad={}, maxWorkerThreads={}",
81+
context.getMethod(), context.getUriInfo().getPath(),
82+
currentLoad, maxWorkerThreads);
7483
throw new ServiceUnavailableException(String.format(
7584
"The server is too busy to process the request, " +
7685
"you can config %s to adjust it or try again later",
@@ -84,6 +93,10 @@ public void filter(ContainerRequestContext context) {
8493
allocatedMem) / Bytes.MB;
8594
if (presumableFreeMem < minFreeMemory) {
8695
gcIfNeeded();
96+
LOG.warn("Rejected request due to low free memory, method={}, path={}, " +
97+
"presumableFreeMemMB={}, minFreeMemoryMB={}",
98+
context.getMethod(), context.getUriInfo().getPath(),
99+
presumableFreeMem, minFreeMemory);
87100
throw new ServiceUnavailableException(String.format(
88101
"The server available memory %s(MB) is below than " +
89102
"threshold %s(MB) and can't process the request, " +

hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hugegraph.unit;
1919

2020
import org.apache.hugegraph.core.RoleElectionStateMachineTest;
21+
import org.apache.hugegraph.unit.api.filter.LoadDetectFilterTest;
2122
import org.apache.hugegraph.unit.api.filter.PathFilterTest;
2223
import org.apache.hugegraph.unit.cache.CacheManagerTest;
2324
import org.apache.hugegraph.unit.cache.CacheTest;
@@ -78,6 +79,7 @@
7879
@RunWith(Suite.class)
7980
@Suite.SuiteClasses({
8081
/* api filter */
82+
LoadDetectFilterTest.class,
8183
PathFilterTest.class,
8284

8385
/* cache */
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.hugegraph.unit.api.filter;
19+
20+
import java.util.List;
21+
import java.util.stream.Collectors;
22+
23+
import org.apache.commons.configuration2.Configuration;
24+
import org.apache.commons.configuration2.PropertiesConfiguration;
25+
import org.apache.hugegraph.api.filter.LoadDetectFilter;
26+
import org.apache.hugegraph.config.HugeConfig;
27+
import org.apache.hugegraph.config.ServerOptions;
28+
import org.apache.hugegraph.define.WorkLoad;
29+
import org.apache.hugegraph.testutil.Assert;
30+
import org.apache.hugegraph.unit.BaseUnitTest;
31+
import org.junit.Before;
32+
import org.junit.Test;
33+
import org.mockito.Mockito;
34+
35+
import jakarta.inject.Provider;
36+
import jakarta.ws.rs.ServiceUnavailableException;
37+
import jakarta.ws.rs.container.ContainerRequestContext;
38+
import jakarta.ws.rs.core.PathSegment;
39+
import jakarta.ws.rs.core.UriInfo;
40+
41+
public class LoadDetectFilterTest extends BaseUnitTest {
42+
43+
private LoadDetectFilter loadDetectFilter;
44+
private ContainerRequestContext requestContext;
45+
private UriInfo uriInfo;
46+
private WorkLoad workLoad;
47+
48+
@Before
49+
public void setup() {
50+
this.requestContext = Mockito.mock(ContainerRequestContext.class);
51+
this.uriInfo = Mockito.mock(UriInfo.class);
52+
this.workLoad = new WorkLoad();
53+
54+
Mockito.when(this.requestContext.getUriInfo()).thenReturn(this.uriInfo);
55+
Mockito.when(this.requestContext.getMethod()).thenReturn("GET");
56+
57+
this.loadDetectFilter = new LoadDetectFilter();
58+
injectProvider(this.loadDetectFilter, "loadProvider", () -> this.workLoad);
59+
injectProvider(this.loadDetectFilter, "configProvider",
60+
() -> createConfig(8, 0));
61+
}
62+
63+
@Test
64+
public void testFilter_WhiteListPathIgnored() {
65+
setupPath("", List.of(""));
66+
this.workLoad.incrementAndGet();
67+
68+
this.loadDetectFilter.filter(this.requestContext);
69+
70+
Assert.assertEquals(1, this.workLoad.get().get());
71+
}
72+
73+
@Test
74+
public void testFilter_RejectsWhenWorkerLoadIsTooHigh() {
75+
setupPath("graphs/hugegraph/vertices",
76+
List.of("graphs", "hugegraph", "vertices"));
77+
injectProvider(this.loadDetectFilter, "configProvider",
78+
() -> createConfig(2, 0));
79+
this.workLoad.incrementAndGet();
80+
81+
ServiceUnavailableException exception = (ServiceUnavailableException) Assert.assertThrows(
82+
ServiceUnavailableException.class,
83+
() -> this.loadDetectFilter.filter(this.requestContext));
84+
85+
Assert.assertContains("The server is too busy to process the request",
86+
exception.getMessage());
87+
Assert.assertContains(ServerOptions.MAX_WORKER_THREADS.name(),
88+
exception.getMessage());
89+
}
90+
91+
@Test
92+
public void testFilter_RejectsWhenFreeMemoryIsTooLow() {
93+
setupPath("graphs/hugegraph/vertices",
94+
List.of("graphs", "hugegraph", "vertices"));
95+
injectProvider(this.loadDetectFilter, "configProvider",
96+
() -> createConfig(8, Integer.MAX_VALUE));
97+
98+
ServiceUnavailableException exception = (ServiceUnavailableException) Assert.assertThrows(
99+
ServiceUnavailableException.class,
100+
() -> this.loadDetectFilter.filter(this.requestContext));
101+
102+
Assert.assertContains("The server available memory",
103+
exception.getMessage());
104+
Assert.assertContains(ServerOptions.MIN_FREE_MEMORY.name(),
105+
exception.getMessage());
106+
}
107+
108+
@Test
109+
public void testFilter_AllowsRequestWhenLoadAndMemoryAreHealthy() {
110+
setupPath("graphs/hugegraph/vertices",
111+
List.of("graphs", "hugegraph", "vertices"));
112+
injectProvider(this.loadDetectFilter, "configProvider",
113+
() -> createConfig(8, 0));
114+
115+
this.loadDetectFilter.filter(this.requestContext);
116+
117+
Assert.assertEquals(1, this.workLoad.get().get());
118+
}
119+
120+
private HugeConfig createConfig(int maxWorkerThreads, int minFreeMemory) {
121+
Configuration conf = new PropertiesConfiguration();
122+
conf.setProperty(ServerOptions.MAX_WORKER_THREADS.name(), maxWorkerThreads);
123+
conf.setProperty(ServerOptions.MIN_FREE_MEMORY.name(), minFreeMemory);
124+
return new HugeConfig(conf);
125+
}
126+
127+
private void setupPath(String path, List<String> segments) {
128+
List<PathSegment> pathSegments = segments.stream()
129+
.map(this::createPathSegment)
130+
.collect(Collectors.toList());
131+
Mockito.when(this.uriInfo.getPath()).thenReturn(path);
132+
Mockito.when(this.uriInfo.getPathSegments()).thenReturn(pathSegments);
133+
}
134+
135+
private PathSegment createPathSegment(String path) {
136+
PathSegment segment = Mockito.mock(PathSegment.class);
137+
Mockito.when(segment.getPath()).thenReturn(path);
138+
return segment;
139+
}
140+
141+
private <T> void injectProvider(LoadDetectFilter filter, String fieldName,
142+
Provider<T> provider) {
143+
try {
144+
java.lang.reflect.Field field = LoadDetectFilter.class.getDeclaredField(fieldName);
145+
field.setAccessible(true);
146+
field.set(filter, provider);
147+
} catch (Exception e) {
148+
throw new RuntimeException("Failed to inject provider: " + fieldName, e);
149+
}
150+
}
151+
}

0 commit comments

Comments
 (0)