/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.camel.component.reactive.streams;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import org.apache.camel.Exchange;
import org.apache.camel.LoggingLevel;
import org.apache.camel.StatefulService;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.reactive.streams.api.CamelReactiveStreams;
import org.apache.camel.component.reactive.streams.support.TestSubscriber;
import org.apache.camel.impl.ThrottlingInflightRoutePolicy;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;
import org.reactivestreams.Publisher;
public class BackpressurePublisherRoutePolicyTest extends CamelTestSupport {
@Test
public void testThatBackpressureCausesTemporaryRouteStop() throws Exception {
CountDownLatch generationLatch = new CountDownLatch(25);
new RouteBuilder() {
@Override
public void configure() throws Exception {
ThrottlingInflightRoutePolicy policy = new ThrottlingInflightRoutePolicy();
policy.setMaxInflightExchanges(10);
policy.setScope(ThrottlingInflightRoutePolicy.ThrottlingScope.Route);
policy.setResumePercentOfMax(70);
policy.setLoggingLevel(LoggingLevel.INFO);
from("timer:tick?period=50&repeatCount=35")
.id("policy-route")
.routePolicy(policy)
.process(x -> generationLatch.countDown())
.to("reactive-streams:pub");
}
}.addRoutesToCamelContext(context);
CountDownLatch receptionLatch = new CountDownLatch(35);
Publisher<Exchange> pub = CamelReactiveStreams.get(context()).fromStream("pub", Exchange.class);
TestSubscriber<Exchange> subscriber = new TestSubscriber<Exchange>() {
@Override
public void onNext(Exchange o) {
super.onNext(o);
receptionLatch.countDown();
}
};
subscriber.setInitiallyRequested(10);
pub.subscribe(subscriber);
// Add another (fast) subscription that should not affect the backpressure on the route
Observable.fromPublisher(pub)
.subscribe();
context.start();
generationLatch.await(5, TimeUnit.SECONDS); // after 25 messages are generated
// The number of exchanges should be 10 (requested by the subscriber), so 35-10=25
assertEquals(25, receptionLatch.getCount());
// fire a delayed request from the subscriber (required by camel core)
subscriber.request(1);
Thread.sleep(250);
StatefulService service = (StatefulService) context().getRoute("policy-route").getConsumer();
// ensure the route is stopped or suspended
assertTrue(service.isStopped() || service.isSuspended());
// request all the remaining exchanges
subscriber.request(24);
assertTrue(receptionLatch.await(5, TimeUnit.SECONDS));
// The reception latch has gone to 0
}
@Test
public void testThatRouteRestartsOnUnsubscription() throws Exception {
CountDownLatch generationLatch = new CountDownLatch(25);
new RouteBuilder() {
@Override
public void configure() throws Exception {
ThrottlingInflightRoutePolicy policy = new ThrottlingInflightRoutePolicy();
policy.setMaxInflightExchanges(10);
policy.setScope(ThrottlingInflightRoutePolicy.ThrottlingScope.Route);
policy.setResumePercentOfMax(70);
policy.setLoggingLevel(LoggingLevel.INFO);
from("timer:tick?period=50") // unbounded
.id("policy-route")
.routePolicy(policy)
.process(x -> generationLatch.countDown())
.to("reactive-streams:pub");
}
}.addRoutesToCamelContext(context);
CountDownLatch receptionLatch = new CountDownLatch(35);
Publisher<Exchange> pub = CamelReactiveStreams.get(context()).fromStream("pub", Exchange.class);
TestSubscriber<Exchange> subscriber = new TestSubscriber<Exchange>() {
@Override
public void onNext(Exchange o) {
super.onNext(o);
receptionLatch.countDown();
}
};
subscriber.setInitiallyRequested(10);
pub.subscribe(subscriber);
// Add another (fast) subscription that should not affect the backpressure on the route
Observable.fromPublisher(pub)
.subscribe();
context.start();
generationLatch.await(5, TimeUnit.SECONDS); // after 25 messages are generated
// The number of exchanges should be 10 (requested by the subscriber), so 35-10=25
assertEquals(25, receptionLatch.getCount());
// fire a delayed request from the subscriber (required by camel core)
subscriber.request(1);
Thread.sleep(250);
StatefulService service = (StatefulService) context().getRoute("policy-route").getConsumer();
// ensure the route is stopped or suspended
assertTrue(service.isStopped() || service.isSuspended());
subscriber.cancel();
// request other exchanges to ensure that the route works
CountDownLatch latch = new CountDownLatch(20);
Observable.fromPublisher(pub)
.subscribe(n -> {
latch.countDown();
});
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Override
public boolean isUseRouteBuilder() {
return false;
}
}