Bug Report

Versions

  • Driver: 1.0.0.RELEASE
  • Database: postgres:13
  • Java: liberica-17.0.6
  • OS: Windows 10 Pro 22H2

Current Behavior

After the reactive repository (R2dbcRepository), operators are executed in one thread (reactor-tcp-nio-1) out of 10 available

Table schema

CREATE TABLE value (
  id bigint NOT NULL GENERATED ALWAYS AS IDENTITY,
  value varchar null
)
Input Code
package com.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class R2DBCApplication {
    private static final Logger log = LoggerFactory.getLogger(R2DBCApplication.class);

    public static void main(final String[] args) {
        SpringApplication.run(R2DBCApplication.class, args);
    }

}

package com.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

import java.util.UUID;

@RestController
public class ValueController {
    private static final Logger log = LoggerFactory.getLogger(ValueController.class);

    @Autowired
    private ValueRepository repository;

    @GetMapping("/value")
    public Mono<String> get() {
        final var uuid = UUID.randomUUID().toString();
        log.info("start for uuid = {}", uuid);
        return repository
                .findById(1L)
                .map(ValueEntity::getId)
                .map(String::valueOf)
                .switchIfEmpty(Mono.fromSupplier(() -> uuid))
                .doOnNext(v -> log.info("after switchIfEmpty uuid = {}", v))
                .doOnNext(v -> log.info("breakpoint uuid = {}", v));
    }
}

package com.example;

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

@Table("value")
public class ValueEntity {

    @Id
    @Column("id")
    private Long id;

    @Column("value")
    private String value;

    public Long getId() {
        return id;
    }

    public void setId(final Long id) {
        this.id = id;
    }

    public String getValue() {
        return value;
    }

    public void setValue(final String value) {
        this.value = value;
    }
}

package com.example;

import org.springframework.data.r2dbc.repository.R2dbcRepository;

public interface ValueRepository extends R2dbcRepository<ValueEntity, Long> {

}
server.port: 9080
spring:
  application.name: r2dbc-service
  r2dbc:
    url: r2dbc:postgresql://localhost:5433/postgres
    username: postgres
    password: 12345
    properties:
      schema: public

logging.level.root: DEBUG
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-r2dbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>r2dbc-postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Steps to reproduce

  1. set breakpoint in ValueController on lambda (not line) in .doOnNext(v -> log.info("breakpoint uuid = {}", v))
  2. open url http://localhost:9080/value in browser
  3. open url http://localhost:9080/value in browser

result: breakpoint won't hit a second time (reactor-tcp-nio-1 stopped, second connection wait while reactor-tcp-nio-1 release)

Expected behavior

After the reactive repository (R2dbcRepository), operators are executed in separated threads (thread per connection)

Comment From: philwebb

I suspect that your breakpoint is stopping on the ValueController.get() method rather than the doOnNext callback in which case I'd expect the behavior you're describing.

Regardless, Spring Boot does little more than configure the stack so if you do think this is a bug you'll either have to raise it at https://github.com/spring-projects/spring-framework or https://github.com/spring-projects/spring-data-r2dbc.

If you do open another issue, please attach your sample application as a zip file. This is easier for us to run than copying snippets from the issue.