Are calls to subscribe's consumer argument sequential in Project Reactor? - project-reactor

With the following code:
flux.subscribe(consumer)
the calls to consumer may take place on different threads, depending on how flux was constructed (e.g., with the use of subscribeOn or publishOn). Is there a guarantee that, even though calls to consumer may take place on different threads, the calls are sequential, i.e., each call completes before the next one starts?
A more specific example below (using Reactor-Kafka):
val resultFlux: Flux<Pair<TopicPartition, Long>> = KafkaReceiver
.create<K, V>(receiverOptions)
.receive()
.groupBy { m -> m.receiverOffset().topicPartition() }
.flatMap { partitionFlux ->
val parallelRoFlux = partitionFlux
.publishOn(scheduler)
.flatMapSequential(::processRecord, parallelism)
parallelRoFlux.map { ro ->
acknowledge(ro)
Pair(ro.topicPartition(), ro.offset())
}
}
resultFlux.doOnNext { Thread.sleep(2000); log.info("doOnNext: $it") }
.subscribe { Thread.sleep(1000); log.info("subscribe: $it") }
produces the following output snippet:
13:44:26.401 [elastic-6] INFO consumerSvcFlow - Message_5>>>processed
13:44:28.402 [elastic-6] INFO consumerExecutable - doOnNext: (demo-topic-0, 15)
13:44:29.402 [elastic-6] INFO consumerExecutable - subscribe: (demo-topic-0, 15)
13:44:29.435 [elastic-8] INFO consumerSvcFlow - Message_8>>>processed
13:44:31.435 [elastic-8] INFO consumerExecutable - doOnNext: (demo-topic-0, 16)
13:44:32.436 [elastic-8] INFO consumerExecutable - subscribe: (demo-topic-0, 16)
13:44:32.461 [elastic-6] INFO consumerSvcFlow - Message_9>>>processed
13:44:34.462 [elastic-6] INFO consumerExecutable - doOnNext: (demo-topic-0, 17)
13:44:35.462 [elastic-6] INFO consumerExecutable - subscribe: (demo-topic-0, 17)
13:44:35.494 [elastic-8] INFO consumerSvcFlow - Message_15>>>processed
13:44:37.494 [elastic-8] INFO consumerExecutable - doOnNext: (demo-topic-0, 18)
13:44:38.495 [elastic-8] INFO consumerExecutable - subscribe: (demo-topic-0, 18)
13:44:38.497 [elastic-6] INFO consumerSvcFlow - Message_18>>>processed
13:44:40.498 [elastic-6] INFO consumerExecutable - doOnNext: (demo-topic-0, 19)
13:44:41.499 [elastic-6] INFO consumerExecutable - subscribe: (demo-topic-0, 19)
13:44:41.539 [elastic-8] INFO consumerSvcFlow - Message_19>>>processed
13:44:43.540 [elastic-8] INFO consumerExecutable - doOnNext: (demo-topic-0, 20)
13:44:44.540 [elastic-8] INFO consumerExecutable - subscribe: (demo-topic-0, 20)
The calls to the subscribe consumer argument are sequential but some calls are on thread [elastic-6] and some are on thread [elastic-8].

Yes there is such a guarantee, per the Reactive Streams specification.
First, the calls might happen on a different thread than the one from which you called subscribe(). But all consumer calls happen on the same thread.
Second, value consumer in the subscribe(Consumer<T>) method is actually considered an onNext signal in a Subscriber, so the spec enforces that such calls are serialized with respect to one another and to onComplete and onError signals.
Edit: now that you've added some snippet, the fact that you have 2 threads in there comes from the publishOn done inside flatMap. Each group of the groupBy could therefore pick a different Worker of the Scheduler (if it has many). The processing done in these inner sequences can therefore be executed in parallel. The result however, when merged by flatMap, are serialized => the subscribe(Consumer) are sequential.

Related

How to get nicely formatted file logs with Serilog outputTemplate (fixed width and truncation of SourceContext)

I'm switching from log4net to Serilog but miss some of the formatting possibilities I had in log4net. I don't find any documentation for what formatters I can use in the outputTemplate. Is there a way to accomplish what I describe below?
Using the outputTemplate
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}"
It looks like this
[2020-03-30 11:31:06.464 DBG] (DomainLogic.TCMessageHandler) >>>Poll
[2020-03-30 11:31:06.481 DBG] (AmqpReader.Reader) >>>Read
[2020-03-30 11:31:06.485 INF] (AmqpReader.Reader) Fetched a message from the queue.
[2020-03-30 11:31:06.487 DBG] (AmqpReader.Reader) <<<Read - 00:00:00.0066941
[2020-03-30 11:31:06.504 DBG] (DomainLogic.TCMessageHandler) <<<Poll - 00:00:00.0399191
This is what I want
[2020-03-30 11:31:06.464 DBG] (DomainLogic.TCMessageHandler) >>>Poll
[2020-03-30 11:31:06.481 DBG] (AmqpReader.Reader ) >>>Read
[2020-03-30 11:31:06.485 INF] (AmqpReader.Reader ) Fetched a message from the queue.
[2020-03-30 11:31:06.487 DBG] (AmqpReader.Reader ) <<<Read - 00:00:00.0066941
[2020-03-30 11:31:06.504 DBG] (DomainLogic.TCMessageHandler) <<<Poll - 00:00:00.0399191
And if I set a fixed width and the SourceContext is longer than that, I'd like it to truncate from the left. Like this
[2020-03-30 11:31:06.464 DBG] (ogic.TCMessageHandler) >>>Poll
[2020-03-30 11:31:06.481 DBG] (AmqpReader.Reader ) >>>Read
[2020-03-30 11:31:06.485 INF] (AmqpReader.Reader ) Fetched a message from the queue.
[2020-03-30 11:31:06.487 DBG] (AmqpReader.Reader ) <<<Read - 00:00:00.0066941
[2020-03-30 11:31:06.504 DBG] (ogic.TCMessageHandler) <<<Poll - 00:00:00.0399191
Serilog output templates (and message templates) are based on .NET format strings, and these don't support truncation of substituted values. There's no way to do this directly in the output template, though you could write an ILogEventEnricher that substitutes the property for a truncated version of it, depending on how else you plan to consume the events.
Here's an enricher that does just what I asked for.
public class StaticWidthSourceContextEnricher : ILogEventEnricher
{
private readonly int _width;
public StaticWidthSourceContextEnricher(int width)
{
_width = width;
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var typeName = logEvent.Properties.GetValueOrDefault("SourceContext").ToString();
if (typeName.Length > _width)
{
typeName = typeName.Substring(typeName.Length - _width);
}
else if (typeName.Length < _width)
{
typeName = typeName + new string(' ', _width - typeName.Length);
}
logEvent.AddOrUpdateProperty(propertyFactory.CreateProperty("SourceContext", typeName));
}
}

Why is the coder after using parallelStream not executed?

I intend to execute some time consuming code using using parallelStream. This seems to work well but I have the problem that the subsequent code is not executed:
#PreDestroy
public void tearDown() {
final int mapSize = eventStreamProcessorMap.size();
LOG.info("There are {} subscriptions to be stopped!", mapSize);
final long start = System.currentTimeMillis();
LocalTime time = LocalTime.now();
final AtomicInteger count = new AtomicInteger();
eventStreamProcessorMap.entrySet().parallelStream().forEach(entry -> {
final Subscription sub = entry.getKey();
final StreamProcessor processor = entry.getValue();
LOG.info("Attempting to stop subscription {} of {} with id {} at {}", count.incrementAndGet(), mapSize, sub.id(), LocalTime.now().format(formatter));
LOG.info("Stopping processor...");
processor.stop();
LOG.info("Processor stopped.");
LOG.info("Removing subscription...");
eventStreamProcessorMap.remove(sub);
LOG.info("Subscription {} removed.", sub.id());
LOG.info("Finished stopping processor {} with subscription {} in ParallelStream at {}: ", processor, sub, LocalTime.now().format(formatter));
LOG.info(String.format("Duration: %02d:%02d:%02d:%03d (hh:mm:ss:SSS)",
TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis() - start),
TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - start)%60,
TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - star0)%60,
TimeUnit.MILLISECONDS.toMillis(System.currentTimeMillis() - start)%1000));
LOG.info("--------------------------------------------------------------------------");
});
LOG.info("Helloooooooooooooo?????");
LOG.info(String.format("Overall shutdown duration: %02d:%02d:%02d:%03d (hh:mm:ss:SSS)",
TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis() - start),
TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - start)%60,
TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - start)%60,
TimeUnit.MILLISECONDS.toMillis(System.currentTimeMillis() - start)%1000));
LOG.info("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
}
The code after the parallelStream processing is not executed:
LOG.info("Helloooooooooooooo?????");
does never appear in the log. Why not?
This is caused by eventStreamProcessorMap.remove(sub); (which you have removed from the code now with the edit that you made). You are streaming over a Map entrySet (eventStreamProcessorMap) and removing elements from it - this is not allowed, that is why you get that ConcurrentModificationException.
If you really want to remove while iterating, use an Iterator or map.entrySet().removeIf(x -> {...})

Reactor Flux and asynchronous processing

I am trying to learn Reactor but I am having a lot of trouble with it. I wanted to do a very simple proof of concept where I simulate calling a slow down stream service 1 or more times. If you use reactor and stream the response the caller doesn't have to wait for all the results.
So I created a very simple controller but it is not behaving like I expect. When the delay is "inside" my flatMap (inside the method I call) the response is not returned until everything is complete. But when I add a delay after the flatMap the data is streamed.
Why does this code result in a stream of JSON
#GetMapping(value = "/test", produces = { MediaType.APPLICATION_STREAM_JSON_VALUE })
Flux<HashMap<String, Object>> customerCards(#PathVariable String customerId) {
Integer count = service.getCount(customerId);
return Flux.range(1, count).
flatMap(k -> service.doRestCall(k)).delayElements(Duration.ofMillis(5000));
}
But this does not
#GetMapping(value = "/test2", produces = { MediaType.APPLICATION_STREAM_JSON_VALUE })
Flux<HashMap<String, Object>> customerCards(#PathVariable String customerId) {
Integer count = service.getCount(customerId);
return Flux.range(1, count).
flatMap(k -> service.doRestCallWithDelay(k));
}
It think I am missing something very basic of the reactor API. On that note. can anyone point to a good book or tutorial on reactor? I can't seem to find anything good to learn this.
Thanks
The code inside the flatMap runs on the main thread (that is the thread the controller runs). As a result the whole process is blocked and the method doesnt return immediately. Have in mind that Reactor doesnt impose a particular threading model.
On the contrary, according to the documentation, in the delayElements method signals are delayed and continue on the parallel default Scheduler. That means that the main thread is not blocked and returns immediately.
Here are two corresponding examples:
Blokcing code:
Flux.range(1, 500)
.map(i -> {
//blocking code
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - Item : " + i);
return i;
})
.subscribe();
System.out.println("main completed");
Result:
main - Item : 1
main - Item : 2
main - Item : 3
...
main - Item : 500
main completed
Non-blocking code:
Flux.range(1, 500)
.delayElements(Duration.ofSeconds(1))
.subscribe(i -> {
System.out.println(Thread.currentThread().getName() + " - Item : " + i);
});
System.out.println("main Completed");
//sleep main thread in order to be able to print the println of the flux
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Result:
main Completed
parallel-1 - Item : 1
parallel-2 - Item : 2
parallel-3 - Item : 3
parallel-4 - Item : 4
...
Here is the project reactor reference guide
"delayElements" method only delay flux element by a given duration, see javadoc for more details
I think you should post details about methods "service.doRestCallWithDelay(k)" and "service.doRestCall(k)" if you need more help.

Apache Camel sending multiple REST requests without timeout

i'm trying to send many REST requests via camel. I expected a sequential
processing but after 11 requests the test stopped and it started producing timeouts.
Is it really necessary to configure the restlet thread pool settings described here? Why is the processing stopping after some requests? I don't want parallelism...
Thank you in advance
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {MultipleRestRequestsTest.ContextConfig.class}, loader = CamelSpringDelegatingTestContextLoader.class)
public class MultipleRestRequestsTest extends AbstractJUnit4SpringContextTests {
#EndpointInject(uri = "mock:result")
protected MockEndpoint resultEndpoint;
#Produce(uri = "direct:start")
protected ProducerTemplate template;
#Test
public void testMultipleRequests() throws Exception {
for (int i = 1; i <= 1000; i++) {
template.sendBody(i);
}
resultEndpoint.setExpectedCount(1000);
resultEndpoint.assertIsNotSatisfied(50000);
}
#Configuration
public static class ContextConfig extends SingleRouteCamelConfiguration {
#Bean(name={"restlet"})
public RestletComponent restlet() {
RestletComponent restlet = new RestletComponent();
restlet.setMaxThreads(100);
restlet.setThreadMaxIdleTimeMs(10000);
restlet.setMaxQueued(20);
return restlet;
}
#Bean
public RouteBuilder route() {
return new RouteBuilder() {
public void configure() {
from("direct:start")
.log("Request ${body}")
.to("restlet://http://localhost:8080/mock-service")
.to("mock:result");
}
};
}
}
}
INFORMATION: Starting the Apache HTTP client 2017-06-07 08:06:27,761
INFO SpringCamelContext:2835 - Apache Camel 2.17.0 (CamelContext:
camel-1) started in 0.440 seconds 2017-06-07 08:06:27,784
INFO route1:159 - Request 1 2017-06-07 08:06:27,926 INFO
route1:159 - Request 2 2017-06-07 08:06:27,931 INFO
route1:159 - Request 3 2017-06-07 08:06:27,935 INFO
route1:159 - Request 4 2017-06-07 08:06:27,940 INFO
route1:159 - Request 5 2017-06-07 08:06:27,944 INFO
route1:159 - Request 6 2017-06-07 08:06:27,949 INFO
route1:159 - Request 7 2017-06-07 08:06:27,953 INFO
route1:159 - Request 8 2017-06-07 08:06:27,958 INFO
route1:159 - Request 9 2017-06-07 08:06:27,962 INFO
route1:159 - Request 10 2017-06-07 08:06:27,967 INFO
route1:159 - Request 11 Jun 07, 2017 8:06:57 AM
org.restlet.ext.httpclient.internal.HttpMethodCall sendRequest
WARNUNG: An error occurred during the communication with the remote
HTTP server. org.apache.http.conn.ConnectionPoolTimeoutException:
Timeout waiting for connection from pool at
org.apache.http.impl.conn.tsccm.ConnPoolByRoute.getEntryBlocking(ConnPoolByRoute.java:412)
at
org.apache.http.impl.conn.tsccm.ConnPoolByRoute$1.getPoolEntry(ConnPoolByRoute.java:298)
at
org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager$1.getConnection(ThreadSafeClientConnManager.java:238)
at
org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:422)
at
org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:863)
at
org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at
org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:106)
at
org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:57)
at
org.restlet.ext.httpclient.internal.HttpMethodCall.sendRequest(HttpMethodCall.java:339)
at
org.restlet.ext.httpclient.internal.HttpMethodCall.sendRequest(HttpMethodCall.java:363)
at
org.restlet.engine.adapter.ClientAdapter.commit(ClientAdapter.java:81)
at
org.restlet.engine.adapter.HttpClientHelper.handle(HttpClientHelper.java:119)
at org.restlet.Client.handle(Client.java:153) at
org.restlet.Restlet.handle(Restlet.java:342) at
org.restlet.Restlet.handle(Restlet.java:355)

ESP8266 with Nodemcu and the http-modul: sequencing a POST operation after a GET failed. No callback-method

i want to make a button with my ESP8266. It needs to communicate with my local Synapse (Matrix.org) Server.
I need to get my access_key, and a room_id and need to login my ESP8266.
So i have three http-methods. Each of it works fine, but when they are all three on my ESP8266 then it doesnt work. It seems that the first one dosent get a response but the second one. And then the skript stops because of some variable are nil. Thats why i didnt post the third-method.
My NodeMcu-Firmeware:
NodeMCU custom build by frightanic.com
branch: master
commit: 22e1adc4b06c931797539b986c85e229e5942a5f
SSL: true
modules: bit,cjson,crypto,encoder,file,gpio,http,net,node,sntp,tmr,uart,wifi,tls
build built on: 2017-05-01 14:03
powered by Lua 5.1.4 on SDK 2.0.0(656edbf)
My Code:
`ok, jsonheader = pcall(cjson.encode, {Accept="application/json"})
if ok then
print(jsonheader)
else
print("failed to encode!")
end
ok, jsonbody = pcall(cjson.encode, {type="m.login.password",user="admin", password="admin"})
if ok then
print(jsonbody)
else
print("failed to encode!")
end
http.post("http://localhost:8008/_matrix/client/r0/login", jsonheader, jsonbody , function(code, data)
if (code< 0) then
print("HTTP_GET request failed")
else
print(code, data)
accessToken = cjson.decode(data).access_token
print(accessToken)
end
end)
http.get("http://localhost:8008/_matrix/client/r0/directory/room/%23ButtonPrinter%3Ahomeserver", jsonheader, function(code1, data1)
if (code1< 0) then
print("HTTP_GET request failed")
else
print("___________________________________")
print(code1, data1)
roomId = cjson.decode(data1).room_id
print(roomId)
end
end)
The print out shows me, that it is not going in the first callback-method from http.post.
pin:6, level:1
Calling: 00000001
LED On
Push Counter is: 3
___________________________________
200 {"room_id":"!TpYsyBpFLoxXrbVBZv:homeserver","servers"["homeserver"]}
!TpYsyBpFLoxXrbVBZv:homeserver
And the log-file from the server shows that:
2017-05-06 10:09:16,233 - synapse.storage.TIME - 215 - INFO - - Total database time: 0.000% {update_cached_last_access_time(0): 0.000%, store_device(0): 0.000%, get_users_in_room(0): 0.000%} {}
2017-05-06 10:09:18,246 - synapse.access.http.8008 - 59 - INFO - GET-6- 192.168.178.XX - 8008 - Received request: GET /_matrix/client/r0/directory/room/%23ButtonPrinter%3Ahomeserver
2017-05-06 10:09:18,249 - synapse.util.async - 201 - INFO - GET-6- Acquired linearizer lock 'state_resolve_lock' for key frozenset([17, 18])
2017-05-06 10:09:18,250 - synapse.util.async - 208 - INFO - GET-6- Releasing linearizer lock 'state_resolve_lock' for key frozenset([17, 18])
2017-05-06 10:09:18,251 - synapse.access.http.8008 - 91 - INFO - GET-6- 192.168.178.XX - 8008 - {None} Processed request: 4ms (0ms, 0ms) (0ms/2) 69B 200 "GET /_matrix/client/r0/directory/room/%23ButtonPrinter%3Ahomeserver HTTP/1.1" "None"
2017-05-06 10:09:18,253 - synapse.access.http.8008 - 59 - INFO - POST-7- 192.168.178.XX - 8008 - Received request: POST /_matrix/client/r0/login
2017-05-06 10:09:18,545 - synapse.handlers.auth - 433 - INFO - POST-7- Logging in user #admin:homeserver on device DWOVBGCIOD
2017-05-06 10:09:18,548 - synapse.access.http.8008 - 91 - INFO - POST-7- 192.168.178.XX - 8008 - {None} Processed request: 295ms (0ms, 0ms) (3ms/5) 364B 200 "POST /_matrix/client/r0/login HTTP/1.1" "None"
2017-05-06 10:09:21,198 - synapse.handlers.typing - 79 - INFO - - Checking for typing timeouts
2017-05-06 10:09:21,199 - synapse.handlers.presence - 329 - INFO - - Handling presence timeouts
2017-05-06 10:09:26,197 - synapse.handlers.typing - 79 - INFO - - Checking for typing timeouts
2017-05-06 10:09:26,198 - synapse.handlers.presence - 329 - INFO - - Handling presence timeouts
2017-05-06 10:09:26,232 - synapse.storage.TIME - 215 - INFO - - Total database time: 0.042% {store_device(2): 0.017%, add_device_change_to_streams(1): 0.010%, add_access_token_to_user(1): 0.009%} {}
I tried so much, i wrapped all in tail-method, a tried it with an alarm.
But nothing works. I need all response in the right sequence.
What i am doing wrong?
I'm afraid that's sort of a classic.
It is not possible to execute concurrent HTTP requests using this module.
Source: https://nodemcu.readthedocs.io/en/latest/en/modules/http/
Since all http.xxx operations are asynchronous (all NodeMCU functions with callbacks are...) you're effectively trying to run http.post and http.get in parallel.
Solution 1: request chaining
http.post(url, jsonheader, jsonbody, function(code, data)
if (code < 0) then
print("HTTP request failed")
else
print(code, data)
-- http.get()
end
end)
Solution 2: task dispatching
http.post(url, jsonheader, jsonbody, function(code, data)
if (code < 0) then
print("HTTP request failed")
else
print(code, data)
node.task.post(function()
-- http.get()
end)
end
end)
See https://nodemcu.readthedocs.io/en/latest/en/modules/node/#nodetaskpost for details.

Resources