Hi,

In spring boot I create two MongoDB reactive clients:

List<MongoClientSettingsBuilderCustomizer> customizerList;   // autowired
MongoClientSettings.Builder builder = MongoClientSettings.builder().applyConnectionString(connectionString);
customizerList.forEach(customizer -> customizer.customize(builder));
MongoClient mongoClient = MongoClients.create(builder.build());

I see problem with MongoReactiveAutoConfiguration, rather NettyDriverMongoClientSettingsBuilderCustomizer. In my case the method NettyDriverMongoClientSettingsBuilderCustomizer#customize is called twice

                        private volatile EventLoopGroup eventLoopGroup;

            @Override
            public void customize(Builder builder) {
                if (!isStreamFactoryFactoryDefined(this.settings.getIfAvailable())) {
                    NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
                    this.eventLoopGroup = eventLoopGroup;
                    builder.streamFactoryFactory(NettyStreamFactoryFactory.builder()
                            .eventLoopGroup(eventLoopGroup).build());
                }
            }

eventually new NioEventLoopGroup() is created twice, but reference only last instance. destroy method clear only last NioEventLoopGroup

            @Override
            public void destroy() {
                EventLoopGroup eventLoopGroup = this.eventLoopGroup;
                if (eventLoopGroup != null) {
                    eventLoopGroup.shutdownGracefully().awaitUninterruptibly();
                    this.eventLoopGroup = null;
                }
            }

And my application not shutdown correctly.

Why we must create new NioEventLoopGroup() on each call customize? Why not:

                        private EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

            @Override
            public void customize(Builder builder) {
                if (!isStreamFactoryFactoryDefined(this.settings.getIfAvailable())) {
                    builder.streamFactoryFactory(NettyStreamFactoryFactory.builder()
                            .eventLoopGroup(eventLoopGroup).build());
                }
            }

Comment From: mp911de

I think we need to ask a slightly different question: How are Netty EventLoopGroup resources managed? Should each component bring its own or should we rather have a single place that manages lifecycle for MongoDB, Lettuce, Reactor, Couchbase, and RSocket?

Paging @bclozel @simonbasle @smaldini @rstoyanchev

Comment From: deripas

Good question. I believe that reuse EventLoopGroup working idea (it's work fine for my case). I think that you can set the default behavior to use a single EventLoopGroup.

BUT: Please add the ability to override or disable current NettyDriverMongoClientSettingsBuildercustomizer - this will be very helpful.

I can create custom MongoClientSettingsBuilderCustomizer and independently set streamFactoryFactory, but NettyDriverMongoClientSettingsBuildercustomizer will still work. To disable original NettyDriverMongoClientSettingsBuildercustomizer I have to create artificial MongoClientSettings- I'm not sure this is the best idea. My current workaround:

@Deprecated
@Configuration
@AutoConfigureBefore(MongoReactiveAutoConfiguration.class)
@ConditionalOnClass({SocketChannel.class, NioEventLoopGroup.class})
public class NettyDriverConfiguration {

    @Bean(destroyMethod = "shutdownGracefully")
    public NioEventLoopGroup eventLoopGroup() {
        return new NioEventLoopGroup();
    }

    @Bean
    @ConditionalOnMissingBean
    public MongoClientSettings workaroundMongoClientSettings(NioEventLoopGroup eventLoopGroup) {
        MongoClientSettings.Builder builder = MongoClientSettings.builder();
        builder.streamFactoryFactory(NettyStreamFactoryFactory.builder().eventLoopGroup(eventLoopGroup).build());
        return builder.build();
    }
}

This allows me to use my own NettyDriverMongoClientSettingsBuildercustomizer:

    @RequiredArgsConstructor
    public class NettyDriverMongoClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer {

        private final EventLoopGroup eventLoopGroup;

        @Override
        public void customize(MongoClientSettings.Builder builder) {
            builder.streamFactoryFactory(NettyStreamFactoryFactory.builder().eventLoopGroup(eventLoopGroup).build());
        }
    }

Comment From: wilkinsona

I wonder if we should only define the nettyDriverCustomizer in the absence of a com.mongodb.reactivestreams.client.MongoClient bean. If there's already a MongoClient bean defined, the customizer won't be used so it would be a little bit more efficient not to define it. It would also give users more control and allow them to define their own MongoClient bean using MongoClientSettingsBuilderCustomizer beans without worrying about what NettyDriverMongoClientSettingsBuilderCustomizer may do.

Comment From: deripas

Also true. This option is also acceptable.

Comment From: wilkinsona

I've opened https://github.com/spring-projects/spring-boot/issues/17559 to fine-tune the auto-configuration of the settings builder customizer. We will use this issue to figure out what we want to do with the event loop groups.

Comment From: wilkinsona

We've decided against changing the auto-configuration of the settings build customizer for the reasons outlined here. We'll keep this issue open to tackle the problem from the event loop group side of things.

Comment From: bclozel

We've discussed this issue and we think that this approach is flawed.

Spring Boot *Customizer classes are meant to be applied on each instance of the thing meant to be customized. So in this case, creating a new EventLoop for each call in the customizer itself, or changing the client itself to only accept a single instance are not valid approaches for this issue.

For a similar issue, Spring Framework introduced a ReactorResourceFactory for sharing resources between the server and clients. An instance of that class is auto-configured and managed as a bean by Spring Boot. This component is also bound to the context lifecycle, so that the resources are properly cleaned when the application context shuts down.

A better approach would be to create a similar component (implementing InitializingBean, DisposableBean) and inject it as a dependency in the customizer. Applying that customizer multiple times would configure the same resources on all clients.

Now the ReactorResourceFactory is only meant for Reactor Netty, and the Spring Boot team doesn't think that it would be wise at that point to create such a component for all libraries using Netty. We could reconsider a better arrangement in the future, but for now the wide variety of libraries, repackaged Netty versions and use of those resources will not make that a trivial change.