Search in sources :

Example 1 with LazyValue

use of io.helidon.common.LazyValue in project helidon by oracle.

the class BackpressureTest method overloadEventLoop.

/**
 * Attempts to overload webserver subscriber with higher data flow than Netty's NioEventLoop can
 * send at first iteration. By causing incomplete write leaves the rest of the bytebuffer to be written by the next
 * event loop iteration.
 * <p>
 * This can overflow Netty buffer or, in case of single threaded unbounded request, prevent event loop from ever reaching next
 * iteration.
 * <p>
 * Incomplete write is not flushed and its ChannelFuture's listener isn't executed, leaving DataChunk NOT released.
 * That should lead to OutOfMemory error or assertion error in sample DataChunk batch,
 * depends on the JVM memory settings.
 *
 * @param multi publisher providing endless stream of high volume(preferably more than 2 MB but not less than 1264 kB) data chunks
 */
void overloadEventLoop(Multi<DataChunk> multi) {
    AtomicBoolean firstChunk = new AtomicBoolean(true);
    AtomicBoolean shuttingDown = new AtomicBoolean(false);
    AtomicReference<Optional<Throwable>> serverUpstreamError = new AtomicReference<>(Optional.empty());
    List<DataChunk> firstBatch = new ArrayList<>(5);
    Multi<DataChunk> dataChunkMulti = // Kill server publisher when client is done
    multi.takeWhile(ch -> !shuttingDown.get()).peek(chunk -> {
        if (firstChunk.getAndSet(false)) {
            // skip first chunk, it gets released on complete
            return;
        }
        // Keep 2 - 6 chunk references
        if (firstBatch.size() < 5) {
            firstBatch.add(chunk);
        }
    }).onError(Throwable::printStackTrace).onError(t -> serverUpstreamError.set(Optional.of(t)));
    AtomicLong byteCnt = new AtomicLong();
    LazyValue<Boolean> validateOnce = LazyValue.create(() -> {
        Collection<DataChunk> snapshot = Collections.unmodifiableCollection(firstBatch);
        LOGGER.info("======== DataChunk sample batch ========");
        IntStream.range(0, snapshot.size()).forEach(i -> LOGGER.info("Chunk #" + (i + 2) + " released: " + firstBatch.get(i).isReleased()));
        boolean result = firstBatch.stream().allMatch(DataChunk::isReleased);
        // clean up
        firstBatch.forEach(DataChunk::release);
        return result;
    });
    WebServer webServer = null;
    try {
        webServer = WebServer.builder().host("localhost").routing(Routing.builder().get("/", (req, res) -> res.send(dataChunkMulti)).build()).build().start().await(TIMEOUT_SEC, TimeUnit.SECONDS);
        WebClient.builder().baseUri("http://localhost:" + webServer.port()).build().get().path("/").request().peek(res -> assertThat(res.status().reasonPhrase(), res.status().code(), is(200))).flatMap(WebClientResponse::content).takeWhile(ws -> byteCnt.get() < (300 * 1024 * 1024)).forEach(chunk -> {
            long actCnt = byteCnt.addAndGet(chunk.bytes().length);
            if (actCnt % (100 * 1024 * 1024) == 0) {
                LOGGER.info("Client received " + (actCnt / (1024 * 1024)) + "MB");
            }
            if (actCnt > (200 * 1024 * 1024)) {
                // After 200 MB check fist 5 chunks if those are released
                // but keep the pressure and don't kill the stream
                assertThat("Not all chunks from the first batch are released!", validateOnce.get());
            }
            chunk.release();
        }).onTerminate(() -> shuttingDown.set(true)).await(TIMEOUT_SEC, TimeUnit.SECONDS);
    } finally {
        if (webServer != null) {
            webServer.shutdown().await(TIMEOUT_SEC, TimeUnit.SECONDS);
        }
    }
    serverUpstreamError.get().ifPresent(Assertions::fail);
}
Also used : IntStream(java.util.stream.IntStream) WebClient(io.helidon.webclient.WebClient) WebClientResponse(io.helidon.webclient.WebClientResponse) DataChunk(io.helidon.common.http.DataChunk) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Random(java.util.Random) AtomicReference(java.util.concurrent.atomic.AtomicReference) ByteBuffer(java.nio.ByteBuffer) ArrayList(java.util.ArrayList) LazyValue(io.helidon.common.LazyValue) MatcherAssert.assertThat(org.hamcrest.MatcherAssert.assertThat) Multi(io.helidon.common.reactive.Multi) Iterator(java.util.Iterator) IoMulti(io.helidon.common.reactive.IoMulti) Collection(java.util.Collection) IOException(java.io.IOException) Logger(java.util.logging.Logger) TimeUnit(java.util.concurrent.TimeUnit) Test(org.junit.jupiter.api.Test) AtomicLong(java.util.concurrent.atomic.AtomicLong) List(java.util.List) Assertions(org.junit.jupiter.api.Assertions) Optional(java.util.Optional) Matchers.is(org.hamcrest.Matchers.is) Collections(java.util.Collections) InputStream(java.io.InputStream) Optional(java.util.Optional) ArrayList(java.util.ArrayList) AtomicReference(java.util.concurrent.atomic.AtomicReference) Assertions(org.junit.jupiter.api.Assertions) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) WebClientResponse(io.helidon.webclient.WebClientResponse) AtomicLong(java.util.concurrent.atomic.AtomicLong) DataChunk(io.helidon.common.http.DataChunk) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean)

Aggregations

LazyValue (io.helidon.common.LazyValue)1 DataChunk (io.helidon.common.http.DataChunk)1 IoMulti (io.helidon.common.reactive.IoMulti)1 Multi (io.helidon.common.reactive.Multi)1 WebClient (io.helidon.webclient.WebClient)1 WebClientResponse (io.helidon.webclient.WebClientResponse)1 IOException (java.io.IOException)1 InputStream (java.io.InputStream)1 ByteBuffer (java.nio.ByteBuffer)1 ArrayList (java.util.ArrayList)1 Collection (java.util.Collection)1 Collections (java.util.Collections)1 Iterator (java.util.Iterator)1 List (java.util.List)1 Optional (java.util.Optional)1 Random (java.util.Random)1 TimeUnit (java.util.concurrent.TimeUnit)1 AtomicBoolean (java.util.concurrent.atomic.AtomicBoolean)1 AtomicLong (java.util.concurrent.atomic.AtomicLong)1 AtomicReference (java.util.concurrent.atomic.AtomicReference)1