From ee7bed1849bdd74b71e5fe4ba34dd89cf75d313a Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Mon, 4 Mar 2019 14:34:48 +0200 Subject: [PATCH 1/2] 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 a73356ee68..569f816b78 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 java.util.stream.Collectors; 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 class MongoReactiveAutoConfiguration { } @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 eb5bc9f0f1..3828385523 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.StreamFactory; 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 class MongoReactiveAutoConfigurationTests { @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 From f20d9a62aeee672c399fd9c2e6fae29783f184db Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 2 Apr 2019 11:04:00 +0100 Subject: [PATCH 2/2] Polish "Ensure that MongoClient's EventLoopGroup is shut down during context close" See gh-16087 --- .../mongo/MongoReactiveAutoConfiguration.java | 12 ++++++------ .../mongo/MongoReactiveAutoConfigurationTests.java | 12 ++++++------ 2 files changed, 12 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 569f816b78..62ce95e47c 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 @@ -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. @@ -86,19 +86,19 @@ public class MongoReactiveAutoConfiguration { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) - public MongoClientSettingsBuilderCustomizer nettyDriverCustomizer( + public NettyDriverMongoClientSettingsBuilderCustomizer nettyDriverCustomizer( ObjectProvider settings) { - return new EventLoopGroupMongoClientSettingsBuilderCustomizer(settings); + return new NettyDriverMongoClientSettingsBuilderCustomizer(settings); } - private static final class EventLoopGroupMongoClientSettingsBuilderCustomizer + private static final class NettyDriverMongoClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer, DisposableBean { private final ObjectProvider settings; - private EventLoopGroup eventLoopGroup; + private volatile EventLoopGroup eventLoopGroup; - private EventLoopGroupMongoClientSettingsBuilderCustomizer( + private NettyDriverMongoClientSettingsBuilderCustomizer( ObjectProvider settings) { this.settings = settings; } 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 3828385523..a0cedbb334 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 @@ -91,17 +91,17 @@ public class MongoReactiveAutoConfigurationTests { @Test public void nettyStreamFactoryFactoryIsConfiguredAutomatically() { - AtomicReference capture = new AtomicReference<>(); + AtomicReference eventLoopGroupReference = new AtomicReference<>(); this.contextRunner.run((context) -> { assertThat(context).hasSingleBean(MongoClient.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(); + EventLoopGroup eventLoopGroup = (EventLoopGroup) ReflectionTestUtils + .getField(factory, "eventLoopGroup"); + assertThat(eventLoopGroup.isShutdown()).isFalse(); + eventLoopGroupReference.set(eventLoopGroup); }); - assertThat(capture.get().isShutdown()).isTrue(); + assertThat(eventLoopGroupReference.get().isShutdown()).isTrue(); } @Test