/** * A variant of {@link #ReactorNettyTcpClient(String, int, ReactorNettyCodec)} * that still manages the lifecycle of the {@link TcpClient} and underlying * resources, but allows for direct configuration of other properties of the * client through a {@code Function<TcpClient, TcpClient>}. * @param clientConfigurer the configurer function * @param codec for encoding and decoding the input/output byte streams * @since 5.1.3 * @see org.springframework.messaging.simp.stomp.StompReactorNettyCodec */ public ReactorNettyTcpClient(Function<TcpClient, TcpClient> clientConfigurer, ReactorNettyCodec<P> codec) { Assert.notNull(codec, "ReactorNettyCodec is required"); this.channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE); this.loopResources = LoopResources.create("tcp-client-loop"); this.poolResources = ConnectionProvider.elastic("tcp-client-pool"); this.codec = codec; this.tcpClient = clientConfigurer.apply(TcpClient .create(this.poolResources) .runOn(this.loopResources, false) .doOnConnected(conn -> this.channelGroup.add(conn.channel()))); }
@Override public void destroy() { if (this.useGlobalResources) { HttpResources.disposeLoopsAndConnections(); } else { try { ConnectionProvider provider = this.connectionProvider; if (provider != null && this.manageConnectionProvider) { provider.dispose(); } } catch (Throwable ex) { // ignore } try { LoopResources resources = this.loopResources; if (resources != null && this.manageLoopResources) { resources.dispose(); } } catch (Throwable ex) { // ignore } } }
@Override public ListenableFuture<Void> shutdown() { if (this.stopping) { SettableListenableFuture<Void> future = new SettableListenableFuture<>(); future.set(null); return future; } this.stopping = true; Mono<Void> result; if (this.channelGroup != null) { result = FutureMono.from(this.channelGroup.close()); if (this.loopResources != null) { result = result.onErrorResume(ex -> Mono.empty()).then(this.loopResources.disposeLater()); } if (this.poolResources != null) { result = result.onErrorResume(ex -> Mono.empty()).then(this.poolResources.disposeLater()); } result = result.onErrorResume(ex -> Mono.empty()).then(stopScheduler()); } else { result = stopScheduler(); } return new MonoToListenableFutureAdapter<>(result); }
@Test public void testIssue192() { LoopResources resources = LoopResources.create("testIssue192"); UdpServer server = UdpServer.create() .runOn(resources); UdpClient client = UdpClient.create() .runOn(resources); assertThat(Thread.getAllStackTraces().keySet().stream().noneMatch(t -> t.getName().startsWith("testIssue192"))).isTrue(); server.bind(); client.connect(); assertThat(Thread.getAllStackTraces().keySet().stream().anyMatch(t -> t.getName().startsWith("testIssue192"))).isTrue(); resources.dispose(); } }
@Test public void testIssue416() { TestResources resources = TestResources.get(); TestResources.set(ConnectionProvider.fixed("test")); assertThat(resources.provider.isDisposed()).isTrue(); assertThat(resources.loops.isDisposed()).isFalse(); TestResources.set(LoopResources.create("test")); assertThat(resources.loops.isDisposed()).isTrue(); assertThat(resources.isDisposed()).isTrue(); }
@Test public void localResources() throws Exception { this.resourceFactory.setUseGlobalResources(false); this.resourceFactory.afterPropertiesSet(); ConnectionProvider connectionProvider = this.resourceFactory.getConnectionProvider(); LoopResources loopResources = this.resourceFactory.getLoopResources(); assertNotSame(HttpResources.get(), connectionProvider); assertNotSame(HttpResources.get(), loopResources); // The below does not work since ConnectionPoolProvider simply checks if pool is empty. // assertFalse(connectionProvider.isDisposed()); assertFalse(loopResources.isDisposed()); this.resourceFactory.destroy(); assertTrue(connectionProvider.isDisposed()); assertTrue(loopResources.isDisposed()); }
static void configure(Bootstrap b, boolean preferNative, LoopResources resources) { SslProvider sslProvider = SslProvider.findSslSupport(b); boolean useNative = preferNative && (sslProvider == null || !(sslProvider.sslContext instanceof JdkSslContext)); EventLoopGroup elg = resources.onClient(useNative); b.group(elg).channel(resources.onChannel(elg)); } }
EventLoopGroup cacheNativeClientLoops() { EventLoopGroup eventLoopGroup = cacheNativeClientLoops.get(); if (null == eventLoopGroup) { EventLoopGroup newEventLoopGroup = LoopResources.colocate(cacheNativeServerLoops()); if (!cacheNativeClientLoops.compareAndSet(null, newEventLoopGroup)) { // Do not shutdown newEventLoopGroup as this will shutdown the server loops } eventLoopGroup = cacheNativeClientLoops(); } return eventLoopGroup; }
@Override public boolean daemon() { return defaultLoops.daemon(); }
@Override public Class<? extends Channel> onChannel(EventLoopGroup group) { return defaultLoops.onChannel(group); }
@Test public void testTcpClient1ThreadAcquire() { LoopResources resources = LoopResources.create("test", 1, true); Connection client = TcpClient.create() .host("localhost") .port(echoServerPort) .runOn(resources) .wiretap(true) .connectNow(); client.disposeNow(); resources.dispose(); assertThat("client was configured", client instanceof ChannelOperations); }
@Override public boolean isDisposed() { return defaultLoops.isDisposed(); }
static void configure(Bootstrap b, boolean preferNative, LoopResources resources) { SslProvider sslProvider = SslProvider.findSslSupport(b); boolean useNative = preferNative && (sslProvider == null || !(sslProvider.sslContext instanceof JdkSslContext)); EventLoopGroup elg = resources.onClient(useNative); b.group(elg).channel(resources.onChannel(elg)); } }
EventLoopGroup cacheNioClientLoops() { EventLoopGroup eventLoopGroup = clientLoops.get(); if (null == eventLoopGroup) { EventLoopGroup newEventLoopGroup = LoopResources.colocate(cacheNioServerLoops()); if (!clientLoops.compareAndSet(null, newEventLoopGroup)) { // Do not shutdown newEventLoopGroup as this will shutdown the server loops } eventLoopGroup = cacheNioClientLoops(); } return eventLoopGroup; }
@Override public boolean daemon() { return defaultLoops.daemon(); }
@Override public Class<? extends Channel> onChannel(EventLoopGroup group) { return defaultLoops.onChannel(group); }
/** * Simple constructor with the host and port to use to connect to. * <p>This constructor manages the lifecycle of the {@link TcpClient} and * underlying resources such as {@link ConnectionProvider}, * {@link LoopResources}, and {@link ChannelGroup}. * <p>For full control over the initialization and lifecycle of the * TcpClient, use {@link #ReactorNettyTcpClient(TcpClient, ReactorNettyCodec)}. * @param host the host to connect to * @param port the port to connect to * @param codec for encoding and decoding the input/output byte streams * @see org.springframework.messaging.simp.stomp.StompReactorNettyCodec */ public ReactorNettyTcpClient(String host, int port, ReactorNettyCodec<P> codec) { Assert.notNull(host, "host is required"); Assert.notNull(codec, "ReactorNettyCodec is required"); this.channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE); this.loopResources = LoopResources.create("tcp-client-loop"); this.poolResources = ConnectionProvider.elastic("tcp-client-pool"); this.codec = codec; this.tcpClient = TcpClient.create(this.poolResources) .host(host).port(port) .runOn(this.loopResources, false) .doOnConnected(conn -> this.channelGroup.add(conn.channel())); }
@Override public void destroy() { if (this.useGlobalResources) { HttpResources.disposeLoopsAndConnections(); } else { try { ConnectionProvider provider = this.connectionProvider; if (provider != null && this.manageConnectionProvider) { provider.dispose(); } } catch (Throwable ex) { // ignore } try { LoopResources resources = this.loopResources; if (resources != null && this.manageLoopResources) { resources.dispose(); } } catch (Throwable ex) { // ignore } } }
@Override public ListenableFuture<Void> shutdown() { if (this.stopping) { SettableListenableFuture<Void> future = new SettableListenableFuture<>(); future.set(null); return future; } this.stopping = true; Mono<Void> result; if (this.channelGroup != null) { result = FutureMono.from(this.channelGroup.close()); if (this.loopResources != null) { result = result.onErrorResume(ex -> Mono.empty()).then(this.loopResources.disposeLater()); } if (this.poolResources != null) { result = result.onErrorResume(ex -> Mono.empty()).then(this.poolResources.disposeLater()); } result = result.onErrorResume(ex -> Mono.empty()).then(stopScheduler()); } else { result = stopScheduler(); } return new MonoToListenableFutureAdapter<>(result); }
@Override public boolean isDisposed() { return defaultLoops.isDisposed(); }