How Do I get total number of records in Flux of server side events - mono

I'm using reactive programming where the client receives a flux of event streams of Server side events then those events are consumed. Functionality wise it works. I've a problem when I try to log the count of total records in the the flux stream. Below are the code snippets.
Let's create an instance connected to the server
final WebClient client = WebClient
.builder()
.baseUrl(url)
.build();
And then we start the connection, subscribing to its topic
final Flux<ServerSentEvent<SomeEvent>> eventStream = client.get()
.uri("/bus/sse?id=" + subscription)
.accept(MediaType.TEXT_EVENT_STREAM)
.exchange()
.flatMapMany(response -> response.bodyToFlux(type))
.repeat();
log(eventStream, "connectSSE");
eventStream
.doOnError(throwable -> this.onError(throwable, eventStream))
.doOnComplete(() -> this.onComplete(eventStream));
subscribe = eventStream.subscribe(someServerSideEvent -> this.onEvent(someServerSideEvent , eventStream));
The below method handles the event
private void onEvent(final ServerSentEvent<SomeEvent> content, Flux<ServerSentEvent<SomeEvent>> eventStream) {
log(eventStream, "onEvent");
//Code for handling event
}
I've a issue with the below piece of code. Actually I want to log the count of records in the stream and I was expecting it will print some numbers but it prints something like below. Need some solution without using .block(). Any help is welcome.
"Counted values in this Flux: MonoMapFuseable, caller onEvent"
private void log1(Flux<ServerSentEvent<SomeEvent>> eventStream, final String caller) {
try {
eventStream.count().map(count -> {
LOGGER.info("Counted values in this Flux: {}, caller {}", count.longValue(), caller);
return count;
});
} catch (final Exception e) {
LOGGER.info("Counted values in this Flux failed", e);
}
}

Related

Correct way of using spring webclient in spring amqp

I have below tech stack for a spring amqp application consuming messages from rabbitmq -
Spring boot 2.2.6.RELEASE
Reactor Netty 0.9.12.RELEASE
Reactor Core 3.3.10.RELEASE
Application is deployed on 4 core RHEL.
Below are some of the configurations being used for rabbitmq
#Bean
public CachingConnectionFactory connectionFactory() {
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
cachingConnectionFactory.setHost(<<HOST NAME>>);
cachingConnectionFactory.setUsername(<<USERNAME>>);
cachingConnectionFactory.setPassword(<<PASSWORD>>);
cachingConnectionFactory.setChannelCacheSize(50);
return cachingConnectionFactory;
}
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setMaxConcurrentConsumers(50);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
factory.setDefaultRequeueRejected(false); /** DLQ is in place **/
return factory;
}
The consumers make downstream API calls using spring webclient in synchronous mode. Below is configuration for Webclient
#Bean
public WebClient webClient() {
ConnectionProvider connectionProvider = ConnectionProvider
.builder("fixed")
.lifo()
.pendingAcquireTimeout(Duration.ofMillis(200000))
.maxConnections(16)
.pendingAcquireMaxCount(3000)
.maxIdleTime(Duration.ofMillis(290000))
.build();
HttpClient client = HttpClient.create(connectionProvider);
client.tcpConfiguration(<<connection timeout, read timeout, write timeout is set here....>>);
Webclient.Builder builder =
Webclient.builder().baseUrl(<<base URL>>).clientConnector(new ReactorClientHttpConnector(client));
return builder.build();
}
This webclient is autowired into a #Service class as
#Autowired
private Webclient webClient;
and used as below in two places. First place is one call -
public DownstreamStatusEnum downstream(String messageid, String payload, String contentType) {
return call(messageid,payload,contentType);
}
private DownstreamStatusEnum call(String messageid, String payload, String contentType) {
DownstreamResponse response = sendRequest(messageid,payload,contentType).**block()**;
return response;
}
private Mono<DownstreamResponse> sendRequest(String messageid, String payload, String contentType) {
return webClient
.method(POST)
.uri(<<URI>>)
.contentType(MediaType.valueOf(contentType))
.body(BodyInserters.fromValue(payload))
.exchange()
.flatMap(response -> response.bodyToMono(DownstreamResponse.class));
}
Other place requires parallel downstream calls and has been implemented as below
private Flux<DownstreamResponse> getValues (List<DownstreamRequest> reqList, String messageid) {
return Flux
.fromIterable(reqList)
.parallel()
.runOn(Schedulers.elastic())
.flatMap(s -> {
return webClient
.method(POST)
.uri(<<downstream url>>)
.body(BodyInserters.fromValue(s))
.exchange()
.flatMap(response -> {
if(response.statusCode().isError()) {
return Mono.just(new DownstreamResponse());
}
return response.bodyToMono(DownstreamResponse.class);
});
}).sequential();
}
public List<DownstreamResponse> updateValue (List<DownstreamRequest> reqList,String messageid) {
return getValues(reqList,messageid).collectList().**block()**;
}
The application has been working fine for past one year or so. Of late, we are seeing an issue whereby one or more consumers seem to just get stuck with the default prefetch (250) number of messages in unack status. The only way to fix the issue is to restart app.
We have not done any code changes recently. Also there have been no infra changes recently either.
When this happens, we took thread dumps. The pattern observed is similar. Most of the consumer threads are in TIMED_WAITING status while one or two consumers show in WAITING state with below stacks -
"org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-13" waiting for condition ...
java.lang.Thread.State: WAITING (parking)
- parking to wait for ......
at .......
at .......
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(......
at reactor.core.publisher.Mono.block(....
at .........WebClientServiceImpl.call(...
Also see below -
"org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-13" waiting for condition ...
java.lang.Thread.State: WAITING (parking)
- parking to wait for ......
at .......
at .......
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(......
at reactor.core.publisher.Mono.block(....
at .........WebClientServiceImpl.updateValue(...
Not exactly sure if this thread dump is showing that consumer threads are actually stuck at this
"block" call.
Please help advise what could be the issue here and what steps need to be taken to fix this. Earlier we thought it may be some issue with rabbitmq/spring aqmp but based on thread dump, looks like issue with webclient "block" call.
On adding Blockhound, it is printing below stacktrace in log file -
Error has been observed at following site(s)
Checkpoint Request to POST https://....... [DefaultWebClient]
Stack Trace:
at java.lang.Object.wait
......
at java.net.InetAddress.checkLookupTable
at java.net.InetAddress.getAddressFromNameService
......
at io.netty.util.internal.SocketUtils$8.run
......
at io.netty.resolver.DefaultNameResolver.doResolve
Sorry, just realized that the flatMap in parallel flux call was actually like below
.flatMap(response -> {
if(response.statusCode().isError()) {
return Mono.just(new DownstreamResponse());
}
return response.bodyToMono(DownstreamResponse.class);
});
So in error scenarios, I think the underlying connection was not being properly released. When I updated it like below, it seemed to have fixed the issue -
.flatMap(response -> {
if(response.statusCode().isError()) {
response.releaseBody().thenReturn(Mono.just(new DownstreamResponse()));
}
return response.bodyToMono(DownstreamResponse.class);
});

ServerResponse returns before execution of Mono

I'm trying to validate the request body then based on it either return a bad request or proceed further. The issue is that the proceed flow is not executed. I'm attaching two solutions that I have tried:
Solution 1
public Mono<ServerResponse> startOrder(ServerRequest request) {
return request.bodyToMono(OrderDto.class)
.map(order -> Utils.validate(order))
.flatMap(listErrors -> {
if(listErrors.isEmpty()){
Mono<OrderResponse> orderResponseMono = this.service.startOrder(request.bodyToMono(OrderDto.class));
return ServerResponse.ok().body(orderResponseMono, OrderResponse.class);
}
Mono<OrderResponse> res = Mono.just(new OrderResponse(HttpStatus.BAD_REQUEST.value(), new ApiError(list.toString())));
return ServerResponse.badRequest().body(res, OrderResponse.class);
});
}
Solution 2
return request.bodyToMono(OrderDto.class)
.map(tt -> Utils.validate(tt))
.filter(res -> !res.isEmpty())
.map(r -> new OrderResponse(HttpStatus.BAD_REQUEST.value(), new ApiError("validation errors")))
.switchIfEmpty(this.service.initOrder(request.bodyToMono(OrderDto.class), HeaderValues.create(request.headers())))
.flatMap(res -> ServerResponse.badRequest().body(Mono.just(res), OrderResponse.class));
Validation method
public static List<String> validate(OrderDto request) {
List<String> result = new ArrayList<>();
if(request.getId == null){
result.add("Id should not be null");
}
if(request.getTitle() == null){
result.add("Title should not be null");
}
if(request.getDescription() == null){
result.add("Description should not be null");
}
return result;
}
When validation succeeds, the body with the result is returned but not when it fails.
What can cause the Mono to not be executed?
The issue you have is that you are trying to consume the response twice by calling response.bodyToMono(OrderDTO.class) two times in your code.
Once you have consumed the body from a response, the server will be able to close the connection to the called system.
If you call it multiple times it will (probably, not checked or verified) return a Mono.empty() which means it will not continue the flow as expected.
You should make it a habit of consuming the response body as quick as possible so that the server can close the connection to free up resources instead of passing around the response object.
Since we are working with streams, the connection will not be freed until the response is consumed.

Why Flux.flatMap() doesn't wait for completion of inner publisher?

Could you please explain what exactly happens in Flux/Mono returned by HttpClient.response() ? I thought value generated by http client will NOT be passed downstream until Mono completes but I see that tons of requests are generated which ends up with reactor.netty.internal.shaded.reactor.pool.PoolAcquirePendingLimitException: Pending acquire queue has reached its maximum size of 8 exception. It works as expected (items being processed one by one) if I replace call to testRequest() with Mono.fromCallable { }.
What am I missing ?
Test code:
import org.asynchttpclient.netty.util.ByteBufUtils
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.netty.http.client.HttpClient
import reactor.netty.resources.ConnectionProvider
class Test {
private val client = HttpClient.create(ConnectionProvider.create("meh", 4))
fun main() {
Flux.fromIterable(0..99)
.flatMap { obj ->
println("Creating request for: $obj")
testRequest()
.doOnError { ex ->
println("Failed request for: $obj")
ex.printStackTrace()
}
.map { res ->
obj to res
}
}
.doOnNext { (obj, res) ->
println("Created request for: $obj ${res.length} characters")
}
.collectList().block()!!
}
fun testRequest(): Mono<String> {
return client.get()
.uri("https://projectreactor.io/docs/netty/release/reference/index.html#_connection_pool")
.responseContent()
.reduce(StringBuilder(), { sb, buf ->
val str= ByteBufUtils.byteBuf2String(Charsets.UTF_8, buf)
sb.append(str)
})
.map { it.toString() }
}
}
When you create the ConnectionProvider like this ConnectionProvider.create("meh", 4), this means connection pool with max connections 4 and max pending requests 8. See here more about this.
When you use flatMap this means Transform the elements emitted by this Flux asynchronously into Publishers, then flatten these inner publishers into a single Flux through merging, which allow them to interleave See here more about this.
So what happens is that you are trying to run all requests simultaneously.
So you have two options:
If you want to use flatMap then increase the number of the pending requests.
If you want to keep the number of the pending requests you may consider for example using concatMap instead of flatMap, which means Transform the elements emitted by this Flux asynchronously into Publishers, then flatten these inner publishers into a single Flux, sequentially and preserving order using concatenation. See more here about this.

Spring Web Flux web client doesn't receive value one by one

#GetMapping("/test")
fun fluxTest(): Flux<Int> {
return Flux.create {em ->
Thread{
(0..10).forEach{
em.next(it)
Thread.sleep(1000)
}
em.complete()
}.run()
}
}
So the code above is a Spring MVC controller method to emit 0 ~ 10 numbers at interval of 1 second.
This is my client code.
val client = WebClient.builder().baseUrl("http://localhost:8083/api/v1")
.build()
val disposable = client.get()
.uri("/test")
.retrieve()
.bodyToFlux(Int::class.java)
.subscribe ({
System.out.println("Value arrived : $it")
}, {err ->
err.printStackTrace()
})
The issue is that client program prints out 0~10 at once, rather than one by one at interval of 1 second.
So it doesn't print values from server one by one but print whole received values when stream is completed.
Can anyone help me with this issue?
Thanks
Looks like you should enable Server-Sent Events, easy way just add producer to the enpoint like this:
#GetMapping(path = "/test", produces=MediaType.TEXT_EVENT_STREAM_VALUE)

How to call a private method from reactive subscribe of Mono and return a specific type in Spring 5?

I have a main method whose return type WebClient. In this method I get a Mono object and using subscribe I'm trying to call another method which returns webclient object. Now within subscribe, I have webclient object which I want to return. I'm blocked here as I'm not sure how to return the object and where to put the return keyword.
Main method:-
public WebClient getWebClientWithAuthorization(String t) {
-----
----
Mono<AccessToken> accessToken = authenticationProvider.getUserAccessToken(serviceConnectionDetails, queryParams);
Disposable disposable = accessToken.subscribe(
value -> getWebClientBuilder(t, value.getAccessToken()),
error -> error.printStackTrace(),
() -> System.out.println("completed without a value")
);
}
Below getWebClientBuilder method returns webclient object:-
private WebClient getWebClientBuilder(String tenantDomain, String accessToken) {
//TODO Logic for Saving the Token using Caching/Redis mechanism will be taken up from here and implemented in future Sprints
logger.info("Bearer token received: "+ CommerceConnectorConstants.REQUEST_HEADER_AUTHORIZATION_BEARER +" "+ accessToken);
if (null != proxyHost) {
return utilsbuilder.baseUrl(tenantDomain).filter(oauth2Credentials(accessToken)).clientConnector(getHttpConnector()).build();
} else {
return utilsbuilder
.baseUrl(tenantDomain)
.filter(oauth2Credentials(accessToken))
.build();
}
}
Now in getWebClientWithAuthorization method, where to put the return keyword inside subscribe or outside subscribe.
Think "Reactive" end to end
In my opinion, what is the most important when star building application using Reactive Programming is treating any call as asynchronous hence providing end to end asynchronous and non-blocking communication.
Thus, what I suggest you is providing instead of synchronous type a Mono<WebClient> in the following way:
public Mono<WebClient> getWebClientWithAuthorization(String t) {
-----
----
Mono<AccessToken> accessToken = authenticationProvider.getUserAccessToken(serviceConnectionDetails, queryParams);
return accessToken.map(value -> getWebClientBuilder(t, value.getAccessToken()))
.doOnError(error -> error.printStackTrace())
.doOnComplete(() -> System.out.println("completed without a value"))
);
}
So, now you may easily map value to the WebClient's instance and send it to the downstream. In turn, your downstream may react to that value and transform WebClient to the execution of HTTP call as it is shown in the following example:
getWebClientWithAuthorization("some magic string here")
.flatMapMany(webClient -> webClient.get()
.uri("some uri here")
.retrieve()
.bodyToFlux(MessageResponse.class))
// operate with downstream items somehow
// .map
// .filter
// .etc
// and return Mono or Flux again to just continue the flow
And remember, just continue the flow and everywhere specify reactive types if async communication is supposed. There is no sense to subscribe to the source until you met some network boundary or some logical end of the stream where you do not have to return something back.