/** * diqube: Distributed Query Base. * * Copyright (C) 2015 Bastian Gloeckle * * This file is part of diqube. * * diqube is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.diqube.server; import java.net.InetSocketAddress; import java.util.List; import javax.inject.Inject; import org.apache.thrift.TMultiplexedProcessor; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.server.TThreadedSelectorServer; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TNonblockingServerSocket; import org.apache.thrift.transport.TNonblockingServerTransport; import org.apache.thrift.transport.TTransportException; import org.diqube.config.Config; import org.diqube.config.ConfigKey; import org.diqube.connection.integrity.IntegrityCheckingProtocol; import org.diqube.connection.integrity.IntegritySecretHelper; import org.diqube.context.AutoInstatiate; import org.diqube.context.InjectOptional; import org.diqube.context.shutdown.ContextShutdownListener; import org.diqube.context.shutdown.ShutdownUtil; import org.diqube.listeners.ServingListener; import org.diqube.remote.cluster.ClusterConsensusServiceConstants; import org.diqube.remote.cluster.ClusterFlattenServiceConstants; import org.diqube.remote.cluster.ClusterManagementServiceConstants; import org.diqube.remote.cluster.ClusterQueryServiceConstants; import org.diqube.remote.cluster.thrift.ClusterConsensusService; import org.diqube.remote.cluster.thrift.ClusterFlattenService; import org.diqube.remote.cluster.thrift.ClusterManagementService; import org.diqube.remote.cluster.thrift.ClusterQueryService; import org.diqube.remote.query.ClusterInformationServiceConstants; import org.diqube.remote.query.FlattenPreparationServiceConstants; import org.diqube.remote.query.IdentityCallbackServiceConstants; import org.diqube.remote.query.IdentityServiceConstants; import org.diqube.remote.query.KeepAliveServiceConstants; import org.diqube.remote.query.QueryServiceConstants; import org.diqube.remote.query.TableMetadataServiceConstants; import org.diqube.remote.query.thrift.ClusterInformationService; import org.diqube.remote.query.thrift.FlattenPreparationService; import org.diqube.remote.query.thrift.IdentityCallbackService; import org.diqube.remote.query.thrift.IdentityService; import org.diqube.remote.query.thrift.KeepAliveService; import org.diqube.remote.query.thrift.QueryService; import org.diqube.remote.query.thrift.TableMetadataService; import org.diqube.server.thrift.ThriftServer; import org.diqube.threads.ExecutorManager; import org.diqube.thrift.util.RememberingTransport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ConfigurableApplicationContext; /** * Starts a thrift server and serves any incoming connections. * * @author Bastian Gloeckle */ @AutoInstatiate public class ServerImplementation { private static final Logger logger = LoggerFactory.getLogger(ServerImplementation.class); @Config(ConfigKey.PORT) private int port; @Config(ConfigKey.SELECTOR_THREADS) private int selectorThreads; @Config(ConfigKey.BIND) private String bind; @Inject private IntegritySecretHelper integritySecretHelper; @Inject private ClusterQueryService.Iface clusterQueryHandler; @Inject private ClusterManagementService.Iface clusterManagementHandler; @Inject private ClusterInformationService.Iface clusterInformationHandler; @Inject private ClusterFlattenService.Iface clusterFlattenHandler; @Inject private QueryService.Iface queryHandler; @Inject private KeepAliveService.Iface keepAliveHandler; @Inject private FlattenPreparationService.Iface flattenPreparationHandler; @Inject private ClusterConsensusService.Iface clusterConsensusHandler; @Inject private IdentityService.Iface identityHandler; @Inject private IdentityCallbackService.Iface identityCallbackHandler; @Inject private TableMetadataService.Iface tableMetadataHandler; @Inject private ExecutorManager executorManager; @InjectOptional private List<ServingListener> servingListeners; @InjectOptional private List<ContextShutdownListener> gracefulShutdownListeners; @Inject private ConfigurableApplicationContext applicationContext; private TThreadedSelectorServer server; public void serve() { TThreadedSelectorServer.Args serverArgs = createServerArgs(); if (serverArgs == null) return; server = new ThriftServer(serverArgs, "server-selector-%d", servingListeners); // Make sure we at least try to clean up a little bit when the VM is shut down. Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { logger.info("Shutting down server..."); if (gracefulShutdownListeners != null) { logger.debug("Executing graceful shutdown listeners..."); new ShutdownUtil(applicationContext).callShutdownListeners(); } logger.debug("Stopping all local beans..."); applicationContext.close(); logger.debug("Stopping thrift server..."); server.stop(); logger.debug("Shutting down everything of remaining queries..."); executorManager.shutdownEverythingOfAllQueries(); logger.info("Shutdown complete."); } }, "shutdown-thread")); if ("".equals(bind)) logger.info("Listening for incoming requests on port {}...", port); else logger.info("Listening for incoming requests on port {} (bound to {})...", port, bind); server.serve(); } private TThreadedSelectorServer.Args createServerArgs() { TMultiplexedProcessor multiProcessor = new TMultiplexedProcessor(); // not-integrity-checked services: communication from "outside" of diqube-servers multiProcessor.registerProcessor(QueryServiceConstants.SERVICE_NAME, new IntegrityCheckingProtocol.IntegrityCheckDisablingProcessor( new QueryService.Processor<QueryService.Iface>(queryHandler))); multiProcessor.registerProcessor(KeepAliveServiceConstants.SERVICE_NAME, new IntegrityCheckingProtocol.IntegrityCheckDisablingProcessor( new KeepAliveService.Processor<KeepAliveService.Iface>(keepAliveHandler))); multiProcessor.registerProcessor(FlattenPreparationServiceConstants.SERVICE_NAME, new IntegrityCheckingProtocol.IntegrityCheckDisablingProcessor( new FlattenPreparationService.Processor<FlattenPreparationService.Iface>(flattenPreparationHandler))); multiProcessor.registerProcessor(ClusterInformationServiceConstants.SERVICE_NAME, new IntegrityCheckingProtocol.IntegrityCheckDisablingProcessor( new ClusterInformationService.Processor<ClusterInformationService.Iface>(clusterInformationHandler))); multiProcessor.registerProcessor(IdentityServiceConstants.SERVICE_NAME, new IntegrityCheckingProtocol.IntegrityCheckDisablingProcessor( new IdentityService.Processor<IdentityService.Iface>(identityHandler))); multiProcessor.registerProcessor(IdentityCallbackServiceConstants.SERVICE_NAME, new IntegrityCheckingProtocol.IntegrityCheckDisablingProcessor( new IdentityCallbackService.Processor<IdentityCallbackService.Iface>(identityCallbackHandler))); multiProcessor.registerProcessor(TableMetadataServiceConstants.SERVICE_NAME, new IntegrityCheckingProtocol.IntegrityCheckDisablingProcessor( new TableMetadataService.Processor<TableMetadataService.Iface>(tableMetadataHandler))); // integrity-checked services: Communication between diqube-servers multiProcessor.registerProcessor(ClusterQueryServiceConstants.SERVICE_NAME, new ClusterQueryService.Processor<ClusterQueryService.Iface>(clusterQueryHandler)); multiProcessor.registerProcessor(ClusterManagementServiceConstants.SERVICE_NAME, new ClusterManagementService.Processor<ClusterManagementService.Iface>(clusterManagementHandler)); multiProcessor.registerProcessor(ClusterFlattenServiceConstants.SERVICE_NAME, new ClusterFlattenService.Processor<ClusterFlattenService.Iface>(clusterFlattenHandler)); multiProcessor.registerProcessor(ClusterConsensusServiceConstants.SERVICE_NAME, new ClusterConsensusService.Processor<ClusterConsensusService.Iface>(clusterConsensusHandler)); TNonblockingServerTransport transport; try { if ("".equals(bind)) transport = new TNonblockingServerSocket(port); else transport = new TNonblockingServerSocket(new InetSocketAddress(bind, port)); } catch (TTransportException e) { logger.error("Could not bind to port {}", port, e); return null; } // TThreadedSelectorServer: // 1 Accept Thread // selectorThreads number of selector threads: Read and write for accepted connections // uses ExecutorService to actually invoke any methods. TThreadedSelectorServer.Args serverArgs = new TThreadedSelectorServer.Args(transport); serverArgs.processor(multiProcessor); serverArgs.transportFactory(new RememberingTransport.Factory(new TFramedTransport.Factory())); serverArgs.protocolFactory(new IntegrityCheckingProtocol.Factory(new TCompactProtocol.Factory(), integritySecretHelper.provideMessageIntegritySecrets())); logger.info("Thrift server will use {} selector threads.", selectorThreads); serverArgs.selectorThreads(selectorThreads); serverArgs .executorService(executorManager.newCachedThreadPool("server-worker-%d", new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { logger.error("Uncaught exception in one of the server workers", e); server.stop(); // stop everything and shut down. } })); return serverArgs; } }