All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.microprofile.fault.tolerance.tck.TimeoutUninterruptableTest Maven / Gradle / Ivy

The newest version!
/*
 *******************************************************************************
 * Copyright (c) 2018-2020 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * Licensed 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.eclipse.microprofile.fault.tolerance.tck;

import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expect;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expectTimeout;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThan;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.fail;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.eclipse.microprofile.fault.tolerance.tck.config.ConfigAnnotationAsset;
import org.eclipse.microprofile.fault.tolerance.tck.timeout.clientserver.UninterruptableTimeoutClient;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.eclipse.microprofile.fault.tolerance.tck.util.TCKConfig;
import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException;
import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;

import jakarta.inject.Inject;

/**
 * Test behaviour when a {@code @Timeout} is used but the method does not respond to interrupts.
 * 

* This provokes a lot of edge case interactions between Timeout and other annotations. *

* Includes test for Timeout, Timeout + Async, Timeout + Async + Bulkhead, Timeout + Async + Retry. */ public class TimeoutUninterruptableTest extends Arquillian { @Deployment public static WebArchive deployment() { ConfigAnnotationAsset timeoutConfig = new ConfigAnnotationAsset() .autoscaleMethod(UninterruptableTimeoutClient.class, "serviceTimeout") .autoscaleMethod(UninterruptableTimeoutClient.class, "serviceTimeoutAsync") .autoscaleMethod(UninterruptableTimeoutClient.class, "serviceTimeoutAsyncCS") .autoscaleMethod(UninterruptableTimeoutClient.class, "serviceTimeoutAsyncBulkhead") .autoscaleMethod(UninterruptableTimeoutClient.class, "serviceTimeoutAsyncBulkheadQueueTimed") .autoscaleMethod(UninterruptableTimeoutClient.class, "serviceTimeoutAsyncRetry") .autoscaleMethod(UninterruptableTimeoutClient.class, "serviceTimeoutAsyncFallback"); JavaArchive testJar = ShrinkWrap.create(JavaArchive.class, "ftTimeoutUninterruptable.jar") .addClass(UninterruptableTimeoutClient.class) .addPackage(Packages.UTILS) .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml") .addAsManifestResource(timeoutConfig, "microprofile-config.properties"); WebArchive testWar = ShrinkWrap.create(WebArchive.class, "ftTimeoutUninterruptable.war") .addAsLibrary(testJar); return testWar; } private List> waitingFutures = new ArrayList<>(); @Inject private UninterruptableTimeoutClient client; private final TCKConfig config = TCKConfig.getConfig(); @Test public void testTimeout() { long startTime = System.nanoTime(); expectTimeout(() -> client.serviceTimeout(config.getTimeoutInMillis(1000))); long endTime = System.nanoTime(); // Interrupt flag should not be set assertFalse(Thread.interrupted(), "Thread was still interrupted when method returned"); // Expect that execution will take the full 1000ms because the method does not respond to being interrupted assertThat("Execution time", Duration.ofNanos(endTime - startTime), greaterThanOrEqualTo(config.getTimeoutInDuration(800))); } @Test public void testTimeoutAsync() throws Exception { CompletableFuture waitingFuture = newWaitingFuture(); CompletableFuture completionFuture = new CompletableFuture(); long startTime = System.nanoTime(); Future result = client.serviceTimeoutAsync(waitingFuture, completionFuture); expect(TimeoutException.class, result); long resultTime = System.nanoTime(); // We should get the TimeoutException after 500ms, allow up to 1500ms assertThat("Time for result to be complete", Duration.ofNanos(resultTime - startTime), lessThan(config.getTimeoutInDuration(1500))); assertFalse(completionFuture.isDone(), "Method should still be running"); // If we release the waitingFuture, the method should quickly complete waitingFuture.complete(null); completionFuture.get(config.getTimeoutInMillis(5000), TimeUnit.MILLISECONDS); } @Test public void testTimeoutAsyncCS() throws InterruptedException { AtomicBoolean wasInterrupted = new AtomicBoolean(false); CompletableFuture completionFuture = new CompletableFuture<>(); AtomicLong endTime = new AtomicLong(); long startTime = System.nanoTime(); client.serviceTimeoutAsyncCS(config.getTimeoutInMillis(4000)) .thenRun(() -> completionFuture.complete(null)) .exceptionally((e) -> { completionFuture.completeExceptionally(e); return null; }) .thenRun(() -> endTime.set(System.nanoTime())) .thenRun(() -> wasInterrupted.set(Thread.interrupted())); expect(TimeoutException.class, completionFuture); // Interrupt flag should not be set assertFalse(wasInterrupted.get(), "Thread was still interrupted when thenRun steps were run"); // Expect that the method will timeout after 500ms, allow up to 1500ms assertThat("Execution time", Duration.ofNanos(endTime.get() - startTime), lessThan(config.getTimeoutInDuration(1500))); } @Test public void testTimeoutAsyncBulkhead() throws InterruptedException { CompletableFuture waitingFuture = newWaitingFuture(); long startTime = System.nanoTime(); Future resultA = client.serviceTimeoutAsyncBulkhead(waitingFuture); expect(TimeoutException.class, resultA); long resultTime = System.nanoTime(); // Should get the TimeoutException after 500ms, allow up to 1500ms assertThat("Time for result to be complete", Duration.ofNanos(resultTime - startTime), lessThan(config.getTimeoutInDuration(1500))); // Should record one execution assertEquals(client.getTimeoutAsyncBulkheadCounter(), 1, "Execution count after first call"); // At this point, the first execution should still be running, so the next one should get queued startTime = System.nanoTime(); Future resultB = client.serviceTimeoutAsyncBulkhead(waitingFuture); expect(TimeoutException.class, resultB); resultTime = System.nanoTime(); // Should get the TimeoutException after 500ms, allow up to 1500ms assertThat("Time for result to be complete", Duration.ofNanos(resultTime - startTime), lessThan(config.getTimeoutInDuration(1500))); // This time though, we shouldn't record a second execution since the request timed out before it got to start // running assertEquals(client.getTimeoutAsyncBulkheadCounter(), 1, "Execution count after second call"); // Make two more calls with a short gap Future resultC = client.serviceTimeoutAsyncBulkhead(waitingFuture); Thread.sleep(config.getTimeoutInMillis(100)); Future resultD = client.serviceTimeoutAsyncBulkhead(waitingFuture); // The first call should be queued and eventually time out // The second call should get a BulkheadException because the queue is full expect(TimeoutException.class, resultC); expect(BulkheadException.class, resultD); // Lastly, neither of these calls actually got to run, so there should be no more executions assertEquals(client.getTimeoutAsyncBulkheadCounter(), 1, "Execution count after fourth call"); // Complete the waiting future and check that, after a short wait, we have no additional executions waitingFuture.complete(null); Thread.sleep(config.getTimeoutInMillis(300)); assertEquals(client.getTimeoutAsyncBulkheadCounter(), 1, "Execution count after completing all tasks"); } /** * Test that the timeout timer is started when the execution is added to the queue * * @throws InterruptedException * if the test is interrupted * @throws ExecutionException */ @Test public void testTimeoutAsyncBulkheadQueueTimed() throws InterruptedException, ExecutionException { CompletableFuture waitingFutureA = newWaitingFuture(); CompletableFuture waitingFutureB = newWaitingFuture(); Future resultA = client.serviceTimeoutAsyncBulkheadQueueTimed(waitingFutureA); Thread.sleep(config.getTimeoutInMillis(100)); long startTime = System.nanoTime(); Future resultB = client.serviceTimeoutAsyncBulkheadQueueTimed(waitingFutureB); Thread.sleep(config.getTimeoutInMillis(300)); // Allow call A to finish, this should allow call B to start waitingFutureA.complete(null); // Assert there were no exceptions for call A resultA.get(); // Wait for call B to time out expect(TimeoutException.class, resultB); long endTime = System.nanoTime(); // B should time out 500ms after it was submitted, even though it spent 300ms queued // Allow up to 750ms. Bound is tighter here as more than 800ms would represent incorrect behavior assertThat("Time taken for call B to timeout", Duration.ofNanos(endTime - startTime), lessThan(config.getTimeoutInDuration(750))); } @Test public void testTimeoutAsyncRetry() { CompletableFuture waitingFuture = newWaitingFuture(); // Expect to run method three times, each one timing out after 500ms long startTime = System.nanoTime(); Future result = client.serviceTimeoutAsyncRetry(waitingFuture); expect(TimeoutException.class, result); long resultTime = System.nanoTime(); // Should get TimeoutException after 1500ms (3 attempts * 500ms), allow up to 3000ms assertThat("Time for result to complete", Duration.ofNanos(resultTime - startTime), lessThan(config.getTimeoutInDuration(3000))); // Expect all executions to be run assertEquals(client.getTimeoutAsyncRetryCounter(), 3, "Execution count after one call"); } /** * Test that the fallback is run as soon as the timeout occurs * * @throws InterruptedException * if the test is interrupted */ @Test public void testTimeoutAsyncFallback() throws InterruptedException { CompletableFuture waitingFuture = newWaitingFuture(); long startTime = System.nanoTime(); Future resultFuture = client.serviceTimeoutAsyncFallback(waitingFuture); try { // Expect TimeoutException causing fallback to be invoked and the result returned assertEquals(resultFuture.get(config.getTimeoutInMillis(10000), TimeUnit.MILLISECONDS), "FALLBACK"); } catch (java.util.concurrent.TimeoutException e) { fail("Method did not complete", e); } catch (ExecutionException e) { fail("Unexpected exception thrown", e); } long resultTime = System.nanoTime(); // Should get the TimeoutException + Fallback after 500ms, allow up to 1500ms assertThat("Time for result to be complete", Duration.ofNanos(resultTime - startTime), lessThan(config.getTimeoutInDuration(1500))); } /** * Creates a waiting future and adds it to a list to be cleaned up at the end of the test * * @return the waiting future */ private CompletableFuture newWaitingFuture() { CompletableFuture waitingFuture = new CompletableFuture(); waitingFutures.add(waitingFuture); return waitingFuture; } /** * Cleans up any waiting futures that have been created in the test */ @AfterMethod public void cleanup() { for (CompletableFuture future : waitingFutures) { future.complete(null); } waitingFutures.clear(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy