From ee7bed1849bdd74b71e5fe4ba34dd89cf75d313a Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Mon, 4 Mar 2019 14:34:48 +0200 Subject: [PATCH] Ensure that MongoClient's EventLoopGroup is shut down during context close See gh-16087 --- .../mongo/MongoReactiveAutoConfiguration.java | 50 +++++++++++++++---- .../MongoReactiveAutoConfigurationTests.java | 14 ++++-- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java index a73356ee682a..569f816b78c3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfiguration.java @@ -21,11 +21,15 @@ import javax.annotation.PreDestroy; import com.mongodb.MongoClientSettings; +import com.mongodb.MongoClientSettings.Builder; import com.mongodb.connection.netty.NettyStreamFactoryFactory; import com.mongodb.reactivestreams.client.MongoClient; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import reactor.core.publisher.Flux; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -77,23 +81,51 @@ public MongoClient reactiveStreamsMongoClient(MongoProperties properties, } @Configuration - @ConditionalOnClass(SocketChannel.class) + @ConditionalOnClass({ SocketChannel.class, NioEventLoopGroup.class }) static class NettyDriverConfiguration { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public MongoClientSettingsBuilderCustomizer nettyDriverCustomizer( ObjectProvider settings) { - return (builder) -> { - if (!isStreamFactoryFactoryDefined(settings.getIfAvailable())) { - builder.streamFactoryFactory( - NettyStreamFactoryFactory.builder().build()); - } - }; + return new EventLoopGroupMongoClientSettingsBuilderCustomizer(settings); } - private boolean isStreamFactoryFactoryDefined(MongoClientSettings settings) { - return settings != null && settings.getStreamFactoryFactory() != null; + private static final class EventLoopGroupMongoClientSettingsBuilderCustomizer + implements MongoClientSettingsBuilderCustomizer, DisposableBean { + + private final ObjectProvider settings; + + private EventLoopGroup eventLoopGroup; + + private EventLoopGroupMongoClientSettingsBuilderCustomizer( + ObjectProvider settings) { + this.settings = settings; + } + + @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()); + } + } + + @Override + public void destroy() { + EventLoopGroup eventLoopGroup = this.eventLoopGroup; + if (eventLoopGroup != null) { + eventLoopGroup.shutdownGracefully().awaitUninterruptibly(); + this.eventLoopGroup = null; + } + } + + private boolean isStreamFactoryFactoryDefined(MongoClientSettings settings) { + return settings != null && settings.getStreamFactoryFactory() != null; + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java index eb5bc9f0f16b..382838552335 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mongo/MongoReactiveAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.mongo; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import com.mongodb.MongoClientSettings; import com.mongodb.ReadPreference; @@ -25,6 +26,7 @@ import com.mongodb.connection.StreamFactoryFactory; import com.mongodb.connection.netty.NettyStreamFactoryFactory; import com.mongodb.reactivestreams.client.MongoClient; +import io.netty.channel.EventLoopGroup; import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -89,11 +91,17 @@ public void optionsSslConfig() { @Test public void nettyStreamFactoryFactoryIsConfiguredAutomatically() { + AtomicReference capture = new AtomicReference<>(); this.contextRunner.run((context) -> { assertThat(context).hasSingleBean(MongoClient.class); - assertThat(getSettings(context).getStreamFactoryFactory()) - .isInstanceOf(NettyStreamFactoryFactory.class); + StreamFactoryFactory factory = getSettings(context).getStreamFactoryFactory(); + assertThat(factory).isInstanceOf(NettyStreamFactoryFactory.class); + capture.set((EventLoopGroup) ReflectionTestUtils.getField(factory, + "eventLoopGroup")); + assertThat(capture.get()).isNotNull(); + assertThat(capture.get().isShutdown()).isFalse(); }); + assertThat(capture.get().isShutdown()).isTrue(); } @Test