Add the sse-websocket module
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package com.ankurm.ssews;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.boot.tomcat.TomcatWebServer;
|
||||
import org.springframework.boot.web.server.context.WebServerApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Forty open SSE streams on a ten-thread Tomcat.
|
||||
*
|
||||
* <p>This is the test that answers the objection people raise first — "doesn't SSE pin a
|
||||
* thread per client?" It does not. {@code SseEmitter} puts the request into asynchronous mode,
|
||||
* the request thread returns to the pool, and the response stays open with no thread attached to
|
||||
* it. Forty concurrent streams on a pool of ten is the cheapest way to show that; the same test
|
||||
* with a blocking handler deadlocks at the eleventh client.
|
||||
*
|
||||
* <p>What SSE does cost is a <em>socket</em> per stream, plus one serialise-and-write per stream
|
||||
* per broadcast, both of which the transcript makes visible.
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
properties = {"dashboard.broadcast.enabled=false", "server.tomcat.threads.max=10"})
|
||||
class SseConcurrencyTest {
|
||||
|
||||
private static final int CLIENTS = 40;
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@Autowired
|
||||
EmitterRegistry registry;
|
||||
|
||||
@Autowired
|
||||
WebServerApplicationContext context;
|
||||
|
||||
@Test
|
||||
void fortyStreamsOnTenThreads() throws Exception {
|
||||
List<SseClient> clients = new ArrayList<>();
|
||||
try {
|
||||
for (int i = 0; i < CLIENTS; i++) {
|
||||
clients.add(new SseClient(port, "/sse/metrics"));
|
||||
}
|
||||
for (int i = 0; i < 200 && registry.open() < CLIENTS; i++) {
|
||||
Thread.sleep(25);
|
||||
}
|
||||
|
||||
// Read the pool size off THIS context's own connector. Counting threads by name
|
||||
// does not work here: other @SpringBootTest contexts in the same JVM have their own
|
||||
// http-nio pools, and with a random port Tomcat names its threads http-nio-auto-N,
|
||||
// not http-nio-<actual port>. That mismatch made this assertion pass alone and fail
|
||||
// in the full suite.
|
||||
TomcatWebServer server = (TomcatWebServer) context.getWebServer();
|
||||
ThreadPoolExecutor pool = (ThreadPoolExecutor) server.getTomcat().getConnector()
|
||||
.getProtocolHandler().getExecutor();
|
||||
|
||||
System.out.println("=== server.tomcat.threads.max=10, clients=" + CLIENTS + " ===");
|
||||
System.out.println("emitters open : " + registry.open());
|
||||
System.out.println("connector pool size : " + pool.getPoolSize()
|
||||
+ " (max " + pool.getMaximumPoolSize() + ")");
|
||||
System.out.println("connector active : " + pool.getActiveCount());
|
||||
|
||||
assertThat(registry.open()).isEqualTo(CLIENTS);
|
||||
assertThat(pool.getMaximumPoolSize()).isEqualTo(10);
|
||||
assertThat(pool.getPoolSize()).isLessThanOrEqualTo(10);
|
||||
|
||||
int delivered = registry.broadcast("metric",
|
||||
new MetricSnapshot(1, "node-a", 0.5, 300, Instant.now()));
|
||||
System.out.println("one broadcast reached: " + delivered + " streams");
|
||||
assertThat(delivered).isEqualTo(CLIENTS);
|
||||
|
||||
int received = 0;
|
||||
for (SseClient c : clients) {
|
||||
if (String.join("\n", c.read(12)).contains("event:metric")) {
|
||||
received++;
|
||||
}
|
||||
}
|
||||
System.out.println("clients that read it : " + received);
|
||||
assertThat(received).isEqualTo(CLIENTS);
|
||||
}
|
||||
finally {
|
||||
for (SseClient c : clients) {
|
||||
try { c.close(); } catch (Exception ignored) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user