use of io.servicetalk.concurrent.api.TestExecutor in project servicetalk by apple.
the class TimeoutPublisherTest method concurrentTimeoutInvocation.
@Test
void concurrentTimeoutInvocation() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
AtomicReference<Throwable> causeRef = new AtomicReference<>();
// In order to simulate concurrent execution, we introduce an Executor that does not respect the delay for the
// first timer schedule. Internally, we expect the TimeoutPublisher to reschedule the timer. For that we use
// TestExecutor, which will allow us to advance the time and trigger the actual timeout, which will simulate
// concurrent execution.
toSource(publisher.timeout(10, MILLISECONDS, new Executor() {
private final AtomicInteger timerCount = new AtomicInteger();
@Override
public Cancellable schedule(final Runnable task, final long delay, final TimeUnit unit) {
int count = timerCount.incrementAndGet();
if (count <= 2) {
if (count == 1) {
try {
task.run();
} catch (Throwable cause) {
causeRef.compareAndSet(null, cause);
countDownToZero(latch);
}
latch.countDown();
} else {
try {
try {
testExecutor.schedule(task, delay, unit);
testExecutor.advanceTimeBy(delay, unit);
} catch (Throwable cause) {
causeRef.compareAndSet(null, cause);
countDownToZero(latch);
}
latch.countDown();
} catch (Throwable cause) {
causeRef.compareAndSet(null, cause);
countDownToZero(latch);
}
}
}
return IGNORE_CANCEL;
}
@Override
public long currentTime(final TimeUnit unit) {
return testExecutor.currentTime(unit);
}
@Override
public Completable closeAsync() {
throw new UnsupportedOperationException();
}
@Override
public Completable onClose() {
throw new UnsupportedOperationException();
}
@Override
public Cancellable execute(final Runnable task) throws RejectedExecutionException {
throw new UnsupportedOperationException();
}
})).subscribe(subscriber);
latch.await();
assertNull(causeRef.get());
assertThat(subscriber.awaitOnError(), instanceOf(TimeoutException.class));
}
use of io.servicetalk.concurrent.api.TestExecutor in project servicetalk by apple.
the class TimeoutCompletableTest method defaultExecutorSubscribeTimeout.
@Test
@Disabled("Requires the notion of globally mocked time source using a TestExecutor")
void defaultExecutorSubscribeTimeout() {
DelayedOnSubscribeCompletable delayedCompletable = new DelayedOnSubscribeCompletable();
// Assume the timeout call is not under the user's control, e.g. comes from a library of Completable providers
final Completable operationThatInternallyTimesOut = delayedCompletable.timeout(Duration.ofDays(1));
TestExecutor testExecutor = new TestExecutor();
// TODO(dariusz): Replace all executors created with the test instance
// Executors.setFactory(AllExecutorFactory.create(() -> testExecutor));
toSource(operationThatInternallyTimesOut).subscribe(listener);
testExecutor.advanceTimeBy(1, DAYS);
CompletableSource.Subscriber subscriber = delayedCompletable.subscriber;
assertNotNull(subscriber);
assertThat(listener.awaitOnError(), instanceOf(TimeoutException.class));
}
use of io.servicetalk.concurrent.api.TestExecutor in project servicetalk by apple.
the class RoundRobinLoadBalancerTest method hostUnhealthyDoesntRaceToRunHealthCheck.
// Concurrency test, run multiple times (at least 1000).
@Test
void hostUnhealthyDoesntRaceToRunHealthCheck() throws Exception {
serviceDiscoveryPublisher.onComplete();
final Single<TestLoadBalancedConnection> properConnection = newRealizedConnectionSingle("address-1");
final int timeAdvancementsTillHealthy = 3;
final UnhealthyHostConnectionFactory unhealthyHostConnectionFactory = new UnhealthyHostConnectionFactory("address-1", timeAdvancementsTillHealthy, properConnection);
final DelegatingConnectionFactory connectionFactory = unhealthyHostConnectionFactory.createFactory();
lb = defaultLb(connectionFactory);
sendServiceDiscoveryEvents(upEvent("address-1"));
// Imitate concurrency by running multiple threads attempting to establish connections.
ExecutorService executor = Executors.newFixedThreadPool(3);
try {
final Runnable runnable = () -> assertThrows(ExecutionException.class, () -> lb.selectConnection(any()).toFuture().get());
for (int i = 0; i < 1000; i++) {
executor.submit(runnable);
}
// From test main thread, wait until the host becomes UNHEALTHY, which is apparent from
// NoHostAvailableException being thrown from selection AFTER a health check was scheduled by any thread.
final Executor executorForRetries = io.servicetalk.concurrent.api.Executors.newFixedSizeExecutor(1);
try {
awaitIndefinitely(lb.selectConnection(any()).retryWhen(retryWithConstantBackoffFullJitter((t) -> t instanceof DeliberateException || testExecutor.scheduledTasksPending() == 0, // try to prevent stack overflow
Duration.ofMillis(30), executorForRetries)));
} catch (Exception e) {
assertThat(e.getCause(), instanceOf(NoAvailableHostException.class));
} finally {
executorForRetries.closeAsync().toFuture().get();
}
// is not selected. If our assumption doesn't hold, it means more than one health check was scheduled.
for (int i = 0; i < timeAdvancementsTillHealthy - 1; ++i) {
unhealthyHostConnectionFactory.advanceTime(testExecutor);
// Assert still unhealthy
Exception e = assertThrows(ExecutionException.class, () -> lb.selectConnection(any()).toFuture().get());
assertThat(e.getCause(), instanceOf(NoAvailableHostException.class));
}
} finally {
// Shutdown the concurrent validation of unhealthiness.
executor.shutdownNow();
executor.awaitTermination(10, SECONDS);
}
unhealthyHostConnectionFactory.advanceTime(testExecutor);
final TestLoadBalancedConnection selectedConnection = lb.selectConnection(any()).toFuture().get();
assertThat(selectedConnection, equalTo(properConnection.toFuture().get()));
}
Aggregations