Skip to content

Commit 0b9b7e2

Browse files
committed
feat: support long link test
1 parent eb8d4ae commit 0b9b7e2

2 files changed

Lines changed: 484 additions & 0 deletions

File tree

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
/*
2+
* Tencent is pleased to support the open source community by making tRPC available.
3+
*
4+
* Copyright (C) 2023 Tencent.
5+
* All rights reserved.
6+
*
7+
* If you have downloaded a copy of the tRPC source code from Tencent,
8+
* please note that tRPC source code is licensed under the Apache 2.0 License,
9+
* A copy of the Apache 2.0 License can be found in the LICENSE file.
10+
*/
11+
12+
package com.tencent.trpc.proto.http;
13+
14+
import static com.tencent.trpc.transport.http.common.Constants.HTTP_SCHEME;
15+
import static org.junit.Assert.assertEquals;
16+
import static org.junit.Assert.assertNotNull;
17+
import static org.junit.Assert.assertTrue;
18+
19+
import com.tencent.trpc.core.common.ConfigManager;
20+
import com.tencent.trpc.core.common.config.BackendConfig;
21+
import com.tencent.trpc.core.common.config.ConsumerConfig;
22+
import com.tencent.trpc.core.common.config.ProviderConfig;
23+
import com.tencent.trpc.core.common.config.ServerConfig;
24+
import com.tencent.trpc.core.common.config.ServiceConfig;
25+
import com.tencent.trpc.core.rpc.RpcClientContext;
26+
import com.tencent.trpc.core.rpc.RpcContext;
27+
import com.tencent.trpc.core.utils.NetUtils;
28+
import java.util.HashMap;
29+
import java.util.HashSet;
30+
import java.util.Set;
31+
import java.util.concurrent.ConcurrentHashMap;
32+
import java.util.concurrent.CountDownLatch;
33+
import java.util.concurrent.TimeUnit;
34+
import java.util.concurrent.atomic.AtomicInteger;
35+
import org.junit.AfterClass;
36+
import org.junit.BeforeClass;
37+
import org.junit.Test;
38+
import tests.service.GreeterService;
39+
import tests.service.HelloRequestProtocol.HelloRequest;
40+
import tests.service.HelloRequestProtocol.HelloResponse;
41+
import tests.service.TestBeanConvertWithGetMethodReq;
42+
import tests.service.TestBeanConvertWithGetMethodRsp;
43+
44+
/**
45+
* HTTP-protocol counterpart of the tRPC concurrent multi-port test in {@code trpc-proto-standard}.
46+
*
47+
* <p>Setup: 10 standalone HTTP servers (jetty) on consecutive ports backed by
48+
* {@link PortAwareGreeterServiceImpl} that tags each response with its own listening port.
49+
* One shared {@link BackendConfig} lists all 10 endpoints in the comma-separated namingUrl;
50+
* 100 concurrent threads × 1000 requests each fan-out via {@code ip://} random load balance.</p>
51+
*
52+
* <p>Final assertions:
53+
* <ul>
54+
* <li>every request succeeds and the echoed message matches the request payload,</li>
55+
* <li>every backend port is hit at least once,</li>
56+
* <li>distribution roughly balanced — random over N=10 with R=100000 trials,
57+
* expected 10000 / port; tolerated range {@code [2000, 20000]}, far exceeding any
58+
* realistic random outlier.</li>
59+
* </ul>
60+
* </p>
61+
*/
62+
public class HttpMultiPortNamingUrlConcurrentTest {
63+
64+
private static final int BASE_PORT = 18500;
65+
private static final int SERVER_COUNT = 10;
66+
private static final int THREAD_COUNT = 100;
67+
private static final int CYCLE_PER_THREAD = 1000;
68+
69+
private static final int REQUEST_TIMEOUT_MS = 60_000;
70+
private static final int MAX_CONNECTIONS = 20480;
71+
72+
private static ServerConfig serverConfig;
73+
74+
@BeforeClass
75+
public static void startHttpServers() {
76+
ConfigManager.stopTest();
77+
ConfigManager.startTest();
78+
79+
HashMap<String, ServiceConfig> providers = new HashMap<>();
80+
for (int i = 0; i < SERVER_COUNT; i++) {
81+
int port = BASE_PORT + i;
82+
ProviderConfig<GreeterService> pc = new ProviderConfig<>();
83+
pc.setServiceInterface(GreeterService.class);
84+
pc.setRef(new PortAwareGreeterServiceImpl(port));
85+
86+
ServiceConfig serviceConfig = new ServiceConfig();
87+
serviceConfig.setName("multi-port-server-" + port);
88+
serviceConfig.getProviderConfigs().add(pc);
89+
serviceConfig.setIp(NetUtils.LOCAL_HOST);
90+
serviceConfig.setPort(port);
91+
serviceConfig.setProtocol(HTTP_SCHEME);
92+
serviceConfig.setTransporter("jetty");
93+
providers.put(serviceConfig.getName(), serviceConfig);
94+
}
95+
96+
ServerConfig sc = new ServerConfig();
97+
sc.setServiceMap(providers);
98+
sc.setApp("http-multi-port-test");
99+
sc.setLocalIp(NetUtils.LOCAL_HOST);
100+
sc.init();
101+
serverConfig = sc;
102+
}
103+
104+
@AfterClass
105+
public static void stopHttpServers() {
106+
if (serverConfig != null) {
107+
serverConfig.stop();
108+
serverConfig = null;
109+
}
110+
ConfigManager.stopTest();
111+
}
112+
113+
@Test
114+
public void testHttpMultiPortNamingUrlConcurrent() throws InterruptedException {
115+
// Build "ip://127.0.0.1:p1,127.0.0.1:p2,...,127.0.0.1:p10".
116+
StringBuilder urlBuilder = new StringBuilder("ip://");
117+
for (int i = 0; i < SERVER_COUNT; i++) {
118+
if (i > 0) {
119+
urlBuilder.append(',');
120+
}
121+
urlBuilder.append(NetUtils.LOCAL_HOST).append(':').append(BASE_PORT + i);
122+
}
123+
String namingUrl = urlBuilder.toString();
124+
125+
BackendConfig backendConfig = new BackendConfig();
126+
backendConfig.setName("http-multi-port-client");
127+
backendConfig.setNamingUrl(namingUrl);
128+
backendConfig.setProtocol("http");
129+
backendConfig.setRequestTimeout(REQUEST_TIMEOUT_MS);
130+
backendConfig.setMaxConns(MAX_CONNECTIONS);
131+
backendConfig.setConnsPerAddr(2);
132+
backendConfig.setKeepAlive(true);
133+
134+
ConsumerConfig<GreeterService> consumerConfig = new ConsumerConfig<>();
135+
consumerConfig.setServiceInterface(GreeterService.class);
136+
consumerConfig.setBackendConfig(backendConfig);
137+
138+
try {
139+
final GreeterService proxy = consumerConfig.getProxy();
140+
141+
// Per-port hit counter aggregated across all worker threads.
142+
ConcurrentHashMap<Integer, AtomicInteger> portHits = new ConcurrentHashMap<>();
143+
for (int i = 0; i < SERVER_COUNT; i++) {
144+
portHits.put(BASE_PORT + i, new AtomicInteger(0));
145+
}
146+
147+
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
148+
TestResult[] results = new TestResult[THREAD_COUNT];
149+
for (int t = 0; t < THREAD_COUNT; t++) {
150+
final TestResult r = new TestResult();
151+
results[t] = r;
152+
final int threadIndex = t;
153+
new Thread(() -> {
154+
try {
155+
for (int i = 0; i < CYCLE_PER_THREAD; i++) {
156+
String reqPayload = "req-" + threadIndex + "-" + i;
157+
RpcClientContext ctx = new RpcClientContext();
158+
HelloResponse rsp = proxy.sayHello(ctx, HelloRequest.newBuilder()
159+
.setMessage(reqPayload)
160+
.build());
161+
assertNotNull("response must not be null", rsp);
162+
String message = rsp.getMessage();
163+
// Server returns "<echoedPayload>|port=<actualPort>".
164+
int sep = message.lastIndexOf("|port=");
165+
assertTrue("response missing port marker: " + message, sep > 0);
166+
String echoed = message.substring(0, sep);
167+
int port = Integer.parseInt(message.substring(sep + "|port=".length()));
168+
assertEquals("echoed payload must match request", reqPayload, echoed);
169+
AtomicInteger counter = portHits.get(port);
170+
assertTrue("response from unexpected port: " + port, counter != null);
171+
counter.incrementAndGet();
172+
}
173+
r.succ = true;
174+
} catch (Throwable ex) {
175+
r.succ = false;
176+
r.ex = ex;
177+
ex.printStackTrace();
178+
} finally {
179+
latch.countDown();
180+
}
181+
}, "http-concurrent-caller-" + t).start();
182+
}
183+
184+
// 300s upper bound for HTTP — slower per-request than tRPC due to HTTP framing.
185+
boolean done = latch.await(300, TimeUnit.SECONDS);
186+
assertTrue("concurrent calls timed out before completion", done);
187+
188+
for (int i = 0; i < results.length; i++) {
189+
TestResult r = results[i];
190+
assertTrue("worker thread " + i + " failed: "
191+
+ (r.ex == null ? "<no exception>" : r.ex.toString()), r.succ);
192+
}
193+
194+
// ---- aggregate assertions ----
195+
int totalRequests = THREAD_COUNT * CYCLE_PER_THREAD;
196+
int sum = 0;
197+
Set<Integer> hitPorts = new HashSet<>();
198+
for (int i = 0; i < SERVER_COUNT; i++) {
199+
int port = BASE_PORT + i;
200+
int hits = portHits.get(port).get();
201+
sum += hits;
202+
if (hits > 0) {
203+
hitPorts.add(port);
204+
}
205+
// Random over 10 with 100000 trials → expected 10000/server.
206+
// [2000, 20000] leaves >>3-sigma headroom; CI-safe.
207+
assertTrue("port " + port + " never received a request", hits > 0);
208+
assertTrue("port " + port + " too few hits: " + hits, hits >= 2000);
209+
assertTrue("port " + port + " too many hits: " + hits, hits <= 20000);
210+
}
211+
assertEquals("total responses should equal total requests", totalRequests, sum);
212+
assertEquals("all 10 backend ports must be hit", SERVER_COUNT, hitPorts.size());
213+
} finally {
214+
backendConfig.stop();
215+
}
216+
}
217+
218+
/**
219+
* Service impl that tags every response with its own listening port so the test can verify
220+
* the actual server that handled each request.
221+
*/
222+
private static class PortAwareGreeterServiceImpl implements GreeterService {
223+
224+
private final int port;
225+
226+
PortAwareGreeterServiceImpl(int port) {
227+
this.port = port;
228+
}
229+
230+
@Override
231+
public HelloResponse sayHello(RpcContext context, HelloRequest request) {
232+
String message = request.getMessage();
233+
return HelloResponse.newBuilder()
234+
.setMessage(message + "|port=" + port)
235+
.build();
236+
}
237+
238+
@Override
239+
public String sayBlankHello(RpcContext context, HelloRequest request) {
240+
return "";
241+
}
242+
243+
@Override
244+
public TestBeanConvertWithGetMethodRsp sayHelloNonPbType(RpcContext context,
245+
TestBeanConvertWithGetMethodReq request) {
246+
return new TestBeanConvertWithGetMethodRsp(request.getMessage(),
247+
request.getStatus(), request.getComments());
248+
}
249+
}
250+
251+
private static class TestResult {
252+
253+
boolean succ;
254+
Throwable ex;
255+
}
256+
}

0 commit comments

Comments
 (0)