/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.fabric.netty.client; import com.liferay.portal.fabric.client.FabricClient; import com.liferay.portal.fabric.local.agent.LocalFabricAgent; import com.liferay.portal.fabric.netty.agent.NettyFabricAgentConfig; import com.liferay.portal.fabric.netty.codec.serialization.AnnotatedObjectDecoder; import com.liferay.portal.fabric.netty.codec.serialization.AnnotatedObjectEncoder; import com.liferay.portal.fabric.netty.fileserver.handlers.FileRequestChannelHandler; import com.liferay.portal.fabric.netty.fileserver.handlers.FileResponseChannelHandler; import com.liferay.portal.fabric.netty.handlers.NettyChannelAttributes; import com.liferay.portal.fabric.netty.handlers.NettyFabricWorkerExecutionChannelHandler; import com.liferay.portal.fabric.netty.repository.NettyRepository; import com.liferay.portal.fabric.netty.rpc.handlers.NettyRPCChannelHandler; import com.liferay.portal.fabric.netty.util.NettyUtil; import com.liferay.portal.fabric.repository.Repository; import com.liferay.portal.fabric.worker.FabricWorker; import com.liferay.portal.kernel.concurrent.DefaultNoticeableFuture; import com.liferay.portal.kernel.concurrent.NoticeableFuture; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.process.ProcessCallable; import com.liferay.portal.kernel.process.ProcessExecutor; import com.liferay.portal.kernel.process.TerminationProcessException; import com.liferay.portal.kernel.util.NamedThreadFactory; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.concurrent.DefaultEventExecutorGroup; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import java.io.IOException; import java.io.Serializable; import java.lang.Thread.State; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; /** * @author Shuyang Zhou */ public class NettyFabricClient implements FabricClient { public NettyFabricClient( ProcessExecutor processExecutor, NettyFabricClientConfig nettyFabricClientConfig, NettyFabricClientShutdownCallback nettyFabricClientShutdownCallback) { _processExecutor = processExecutor; _nettyFabricClientConfig = nettyFabricClientConfig; _nettyFabricClientShutdownCallback = nettyFabricClientShutdownCallback; } @Override public synchronized void connect() { if (_channel != null) { throw new IllegalStateException( "Netty fabric client was already started"); } if (_log.isInfoEnabled()) { _log.info( "Starting Netty fabric client using " + _nettyFabricClientConfig); } Runtime runtime = Runtime.getRuntime(); runtime.addShutdownHook(_shutdownThread); _bootstrap = new Bootstrap(); _bootstrap.channel(NioSocketChannel.class); _bootstrap.group( new NioEventLoopGroup( _nettyFabricClientConfig.getEventLoopGroupThreadCount(), new NamedThreadFactory( "Netty Fabric Client/NIO Event Loop Group", Thread.NORM_PRIORITY, null))); _bootstrap.handler(new NettyFabricClientChannelInitializer()); int reconnectCount = _nettyFabricClientConfig.getReconnectCount(); if (reconnectCount < 0) { reconnectCount = Integer.MAX_VALUE; } _reconnectCounter.set(reconnectCount); doConnect(); } @Override public synchronized java.util.concurrent.Future<?> disconnect() { if (_channel == null) { throw new IllegalStateException( "Netty fabric client is not started"); } _reconnectCounter.set(0); _channel.close(); EventExecutorGroup eventExecutorGroup = _bootstrap.group(); Future<?> future = eventExecutorGroup.terminationFuture(); final DefaultNoticeableFuture<?> defaultNoticeableFuture = new DefaultNoticeableFuture<>(); future.addListener( new FutureListener<Object>() { @Override public void operationComplete(Future<Object> future) { defaultNoticeableFuture.run(); } }); return defaultNoticeableFuture; } protected EventExecutorGroup createEventExecutorGroup( int threadCount, String threadPoolName) { EventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup( threadCount, new NamedThreadFactory(threadPoolName, Thread.NORM_PRIORITY, null)); NettyUtil.bindShutdown( _bootstrap.group(), eventExecutorGroup, _nettyFabricClientConfig.getShutdownQuietPeriod(), _nettyFabricClientConfig.getShutdownTimeout()); return eventExecutorGroup; } protected void doConnect() { ChannelFuture channelFuture = _bootstrap.connect( _nettyFabricClientConfig.getNettyFabricServerHost(), _nettyFabricClientConfig.getNettyFabricServerPort()); _channel = channelFuture.channel(); channelFuture.addListener(new PostConnectChannelFutureListener()); } protected void terminateFabricWorkers(Channel channel) { Map<Long, FabricWorker<?>> fabricWorkers = NettyChannelAttributes.getFabricWorkers(channel); if (fabricWorkers == null) { return; } for (Map.Entry<Long, FabricWorker<?>> entry : fabricWorkers.entrySet()) { FabricWorker<?> fabricWorker = entry.getValue(); fabricWorker.write(_runtimeExitProcessCallable); NoticeableFuture<?> noticeableFuture = fabricWorker.getProcessNoticeableFuture(); try { try { noticeableFuture.get( _nettyFabricClientConfig.getExecutionTimeout(), TimeUnit.MILLISECONDS); } catch (TimeoutException te) { fabricWorker.write(_runtimeHaltProcessCallable); noticeableFuture.get( _nettyFabricClientConfig.getExecutionTimeout(), TimeUnit.MILLISECONDS); } } catch (Throwable t) { if (t instanceof ExecutionException) { Throwable cause = t.getCause(); if (cause instanceof TerminationProcessException) { TerminationProcessException tpe = (TerminationProcessException)cause; if (_log.isWarnEnabled()) { _log.warn( "Forcibly terminate fabric worker " + entry.getKey() + " with exit code " + tpe.getExitCode()); } continue; } } _log.error( "Unable to terminate fabric worker " + entry.getKey(), t); } } } protected class NettyFabricClientChannelInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel socketChannel) throws IOException { Path repositoryPath = _nettyFabricClientConfig.getRepositoryPath(); Files.createDirectories(repositoryPath); Repository<Channel> repository = new NettyRepository( repositoryPath, _nettyFabricClientConfig.getRepositoryGetFileTimeout()); ChannelFuture channelFuture = socketChannel.closeFuture(); channelFuture.addListener( new PostDisconnectChannelFutureListener(repository)); ChannelPipeline channelPipeline = socketChannel.pipeline(); channelPipeline.addLast( AnnotatedObjectEncoder.NAME, AnnotatedObjectEncoder.INSTANCE); channelPipeline.addLast( AnnotatedObjectDecoder.NAME, new AnnotatedObjectDecoder()); EventExecutorGroup fileServerEventExecutorGroup = createEventExecutorGroup( _nettyFabricClientConfig.getFileServerGroupThreadCount(), "Netty Fabric Client/File Server Event Executor Group"); channelPipeline.addLast( fileServerEventExecutorGroup, FileRequestChannelHandler.NAME, new FileRequestChannelHandler( _nettyFabricClientConfig. getFileServerFolderCompressionLevel())); channelPipeline.addLast( new FileResponseChannelHandler( repository.getAsyncBroker(), fileServerEventExecutorGroup)); channelPipeline.addLast( createEventExecutorGroup( _nettyFabricClientConfig.getRPCGroupThreadCount(), "Netty Fabric Client/RPC Event Executor Group"), NettyRPCChannelHandler.NAME, NettyRPCChannelHandler.INSTANCE); channelPipeline.addLast( createEventExecutorGroup( _nettyFabricClientConfig.getExecutionGroupThreadCount(), "Netty Fabric Client/Execution Event Executor Group"), new NettyFabricWorkerExecutionChannelHandler( repository, new LocalFabricAgent(_processExecutor), _nettyFabricClientConfig.getExecutionTimeout())); } } protected class PostConnectChannelFutureListener implements ChannelFutureListener { @Override public void operationComplete(ChannelFuture channelFuture) { Channel channel = channelFuture.channel(); if (channelFuture.isSuccess()) { if (_log.isInfoEnabled()) { _log.info("Connected to " + channel.remoteAddress()); } Path repositoryPath = _nettyFabricClientConfig.getRepositoryPath(); ChannelFuture registerChannelFuture = _channel.writeAndFlush( new NettyFabricAgentConfig(repositoryPath.toFile())); registerChannelFuture.addListener( new PostRegisterChannelFutureListener()); return; } String serverAddress = _nettyFabricClientConfig.getNettyFabricServerHost() + ":" + _nettyFabricClientConfig.getNettyFabricServerPort(); if (channelFuture.isCancelled()) { _log.error("Cancelled connecting to " + serverAddress); } else { _log.error( "Unable to connect to " + serverAddress, channelFuture.cause()); } } } protected class PostDisconnectChannelFutureListener implements ChannelFutureListener { @Override public void operationComplete(ChannelFuture channelFuture) { terminateFabricWorkers(_channel); repository.dispose(true); EventLoopGroup eventLoopGroup = _bootstrap.group(); if (_reconnectCounter.getAndDecrement() > 0) { eventLoopGroup.schedule( new Runnable() { @Override public void run() { doConnect(); } }, _nettyFabricClientConfig.getReconnectInterval(), TimeUnit.MILLISECONDS); if (_log.isInfoEnabled()) { _log.info( "Try to reconnect " + _nettyFabricClientConfig.getReconnectInterval() + " ms later"); } } else { if (_log.isInfoEnabled()) { _log.info( "Shutting down Netty fabric client on " + _channel); } Future<?> future = eventLoopGroup.shutdownGracefully( _nettyFabricClientConfig.getShutdownQuietPeriod(), _nettyFabricClientConfig.getShutdownTimeout(), TimeUnit.MILLISECONDS); future.addListener(new PostShutdownChannelFutureListener()); } } protected PostDisconnectChannelFutureListener( Repository<Channel> repository) { this.repository = repository; } protected final Repository<Channel> repository; } protected class PostRegisterChannelFutureListener implements ChannelFutureListener { @Override public void operationComplete(ChannelFuture channelFuture) { if (channelFuture.isSuccess()) { int reconnectCount = _nettyFabricClientConfig.getReconnectCount(); if (reconnectCount < 0) { reconnectCount = Integer.MAX_VALUE; } _reconnectCounter.set(reconnectCount); if (_log.isInfoEnabled()) { _log.info("Registered Netty fabric agent on " + _channel); } return; } _log.error("Unable to register Netty fabric agent on " + _channel); _channel.close(); } } protected class PostShutdownChannelFutureListener implements FutureListener<Object> { @Override public void operationComplete(Future<Object> future) { _channel = null; _bootstrap = null; _nettyFabricClientShutdownCallback.shutdown(); if (_shutdownThread.getState() == State.NEW) { Runtime runtime = Runtime.getRuntime(); runtime.removeShutdownHook(_shutdownThread); } } } private static final int _FABRIC_AGENT_SHUTDOWN_CODE = 211; private static final Log _log = LogFactoryUtil.getLog( NettyFabricClient.class); private static final ProcessCallable<Serializable> _runtimeExitProcessCallable = new ProcessCallable<Serializable>() { @Override public Serializable call() { Runtime runtime = Runtime.getRuntime(); runtime.exit(_FABRIC_AGENT_SHUTDOWN_CODE); return null; } private static final long serialVersionUID = 1L; }; private static final ProcessCallable<Serializable> _runtimeHaltProcessCallable = new ProcessCallable<Serializable>() { @Override public Serializable call() { Runtime runtime = Runtime.getRuntime(); runtime.halt(_FABRIC_AGENT_SHUTDOWN_CODE); return null; } private static final long serialVersionUID = 1L; }; private volatile Bootstrap _bootstrap; private volatile Channel _channel; private final NettyFabricClientConfig _nettyFabricClientConfig; private final NettyFabricClientShutdownCallback _nettyFabricClientShutdownCallback; private final ProcessExecutor _processExecutor; private final AtomicInteger _reconnectCounter = new AtomicInteger(); private final Thread _shutdownThread = new Thread() { @Override public void run() { Channel channel = _channel; if (channel != null) { _reconnectCounter.set(0); ChannelFuture channelFuture = channel.close(); channelFuture.syncUninterruptibly(); } } }; }