Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does subscribe work and block doesn't in Spring reactive Mongo?

I created a project fresh out of the Spring Initializr by choosing Kotlin, Gradle, M7 and Web-reactive.

I made a small project:

data class Person (val id: String)

@Component class PersonHandler(val template: ReactiveMongoTemplate) 
{
    init
    {
        println("Initializing")

        val jim: Mono<Person> =  template.save(Person("Jim"))
        val john: Mono<Person> = template.save(Person("John"))
        val jack: Mono<Person> = template.save(Person("Jack"))

        launch(jim)
        launch(john)
        launch(jack)

        println("Finished Initializing")
    }

    fun launch(mono: Mono<Person>)
    {
        mono.subscribe({println(it.id)}, {println("Error")}) // This works
        // mono.block()  This just hangs
    } 
}

I try to save three persons into the database. The save method returns just a Mono which needs to be executed. If I try to execute it by simply subscribing, everything works nice:

Initializing
Finished Initializing
2017-12-21 13:14:39.513  INFO 17278 --- [      Thread-13] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:3, serverValue:158}] to localhost:27017
2017-12-21 13:14:39.515  INFO 17278 --- [      Thread-12] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:4, serverValue:159}] to localhost:27017
2017-12-21 13:14:39.520  INFO 17278 --- [      Thread-14] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:5, serverValue:160}] to localhost:27017
Jim
Jack
John

However, when I use block instead of subscribe the application hangs:

Initializing
2017-12-21 13:16:47.200  INFO 17463 --- [      Thread-14] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:3, serverValue:163}] to localhost:27017

If I query the database manually, I see that Jim has been saved, but not Jack and John.

Is this a bug, or am I doing something wrong? I would like to have the guarantee that the users are in the database before the code goes any further so I would really like to use block.

I am not sure if it is relevant, but I get a compiler warning

Accessing nonfinal property template in constructor

There is a minimal working example. It contains two branches. One is a workaround for the issue.

https://github.com/martin-drozdik/spring-mongo-bug-example

like image 926
Martin Drozdik Avatar asked Dec 21 '17 12:12

Martin Drozdik


People also ask

What is subscribe in reactive spring?

In the Reactive Streams API there are four main interfaces: Publisher — Emits events to subscribers based on the demands received from its subscribers. A publisher can serve multiple subscribers and it has only one method: subscribe. Subscriber — Receives events emitted by the Publisher.

Is Mono subscribe blocking?

As you know, Mono is an asynchronous call that executes in a non-blocking way.

What is Spring data reactive MongoDB?

The spring-boot-starter-data-mongodb-reactive is a Spring Boot starter for using MongoDB document-oriented database and Spring Data MongoDB Reactive. resources/application.properties. spring.main.banner-mode=off. In the application.

Why is spring program reactive?

Reactive code does more work with fewer resources. Project Reactor and Spring WebFlux let developers take advantage of multi-core, next-generation processors—handling potentially massive numbers of concurrent connections. With reactive processing, you can satisfy more concurrent users with fewer microservice instances.


2 Answers

I think this might be a Spring Framework bug / usability issue.

First, let me underline the difference between subscribe and block:

  • the subscribe method kicks off the work and returns immediately. So you get no guarantee that the operation is done when other parts of your application run.
  • block is a blocking operation: it triggers the operation and waits for its completion.

For initialisation work, composing operations and calling block once is probably the best choice:

val jim: Mono<Person> =  template.save(Person("Jim"))
val john: Mono<Person> = template.save(Person("John"))
val jack: Mono<Person> = template.save(Person("Jack"))
jim.then(john).then(jack).block();

As you've stated, using block hangs the application. I suspect this might be an Spring context initialisation issue - If I remember correctly, this process might assume a single thread in some parts and using a reactive pipeline there schedules work on many threads.

Could you create a minimal sample application (using just Java/Spring Boot/Spring Data Reactive Mongo) and report that on https://jira.spring.io?

like image 79
Brian Clozel Avatar answered Sep 28 '22 07:09

Brian Clozel


I had a similar situation where by calling "reactiveMongoTemplate.save(model).block()" the application was hanging.

The issue was caused by the @PostConstruct in one of my classes designed to create my system users after the application initialization. I think somehow It was invoked before full Spring context initialization.

@Configuration
public class InitialDataPostLoader  {
    private Logger logger = LogManager.getLogger(this.getClass());


    @PostConstruct
    public void init() {
        logger.info(String.format(MSG_SERVICE_JOB, "System Metadata initialization"));
        createDefaultUsers();
    }

By replacing @PostConstruct with ContextRefreshEvent listener the issues got resolved.

@Configuration 
public class InitialDataPostLoader implements
 ApplicationListener<ContextRefreshedEvent> {
     private Logger logger = LogManager.getLogger(this.getClass());

     @Override
     public void onApplicationEvent(ContextRefreshedEvent arg0) {

         logger.info(String.format(MSG_SERVICE_JOB, "System Metadata initialization"));
         createDefaultUsers();

     }
like image 24
Andrei Maimas Avatar answered Sep 28 '22 05:09

Andrei Maimas