Customize ShutdownHooks

Updated at:

Graceful shutdown involves two scenarios. In one scenario, SOFARPC serves as a client. In the other scenario, SOFARPC serves as a server.

Server SOFARPC

When SOFARPC serves as a server, it cannot be forcibly shut down. You can perform the following operations to shut it down gracefully.

  1. Query the SOFARPC status:

    com.alipay.sofa.rpc.context.RpcRuntimeContext
  2. Add a ShutdownHook to the static initialization block.

        // Add a JVM shutdown event. 
        if(RpcConfigs.getOrDefaultValue(RpcOptions.JVM_SHUTDOWN_HOOK,true)){
            Runtime.getRuntime().addShutdownHook(newThread(newRunnable(){
                @Override
                public void run(){
                    if(LOGGER.isWarnEnabled()){
                        LOGGER.warn("SOFA RPC Framework catch JVM shutdown event, Run shutdown hook now.");
                    }
                    destroy(false);
                }
            },"SOFA-RPC-ShutdownHook"));
        }

    When the publishing platform or a user runs the kill pid command, the logic in the ShutdownHook will be preferentially executed. When you perform a destroy operation, SOFARPC deregisters the service from the registry and disables the service port first. Sample code is shown as below:

    private static void destroy(boolean active){
            RpcRunningState.setShuttingDown(true);
            for(Destroyable.DestroyHook destroyHook : DESTROY_HOOKS){
                destroyHook.preDestroy();
            }
            List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
            for(ProviderBootstrap bootstrap : EXPORTED_PROVIDER_CONFIGS){
                providerConfigs.add(bootstrap.getProviderConfig());
            }
            // Deregister the service. 
            List<Registry> registries =RegistryFactory.getRegistries();
            if(CommonUtils.isNotEmpty(registries) && CommonUtils.isNotEmpty(providerConfigs)) {
                for(Registry registry : registries){
                    registry.batchUnRegister(providerConfigs);
                }
            }
            // Disable the port in use. 
            ServerFactory.destroyAll();
            // Disable the published service. 
            for(ProviderBootstrap bootstrap : EXPORTED_PROVIDER_CONFIGS){
                bootstrap.unExport();
            }
            // Disable the called service. 
            for(ConsumerBootstrap bootstrap : REFERRED_CONSUMER_CONFIGS){
                ConsumerConfig config = bootstrap.getConsumerConfig();
                    if(!CommonUtils.isFalse(config.getParameter(RpcConstants.HIDDEN_KEY_DESTROY))){
                    // Unless unsubscription is not allowed. 
                    bootstrap.unRefer();
                }
            }
            // Disable the registry. 
            RegistryFactory.destroyAll();
            // Disable common resources of the client. 
            ClientTransportFactory.closeAll();
            // Uninstall the module. 
            if(!RpcRunningState.isUnitTestMode()){
                ModuleFactory.uninstallModules();
            }
            // Uninstall the hook. 
            for(Destroyable.DestroyHook destroyHook : DESTROY_HOOKS){
                destroyHook.postDestroy();
            }
            // Clear the cache. 
            RpcCacheManager.clearAll();
            RpcRunningState.setShuttingDown(false);
            if(LOGGER.isWarnEnabled()){
                LOGGER.warn("SOFA RPC Framework has been release all resources {}...",
                        active ?"actively ":"");
            }
     }

    In this example, a Bolt service is used. SOFARPC does not immediately disable the port, but identifies connections on the server and tasks in the queue, completes the tasks in the queue, and then disables the port.

        @Override
        public void destroy(){
            if(!started){
                return;
            }
            int stopTimeout = serverConfig.getStopTimeout();
            if(stopTimeout >0){// The waiting time before shutdown.
                AtomicInteger count = boltServerProcessor.processingCount;
                // A request is ongoing or waiting in the queue.
                if(count.get()>0|| bizThreadPool.getQueue().size()>0){
                    long start =RpcRuntimeContext.now();
                    if(LOGGER.isInfoEnabled()){
                        LOGGER.info("There are {} call in processing and {} call in queue, wait {} ms to end",
                                count, bizThreadPool.getQueue().size(), stopTimeout);
                    }
                    while((count.get()>0|| bizThreadPool.getQueue().size()>0)
                            && RpcRuntimeContext.now()- start < stopTimeout){
                        // Wait for a response.
                        try{
                            Thread.sleep(10);
                        }catch(InterruptedException ignore){
                        }
                    }
                }
                // Check existing requests before shutdown. 
            }
            // Disable the thread pool.
            bizThreadPool.shutdown();
            stop();
        }

Client SOFARPC

When SOFARPC serves as a client, closing the client connection is equivalent to shutting down the cluster. For more information about how to disable the called service, see com.alipay.sofa.rpc.client.AbstractCluster.

   /**
     * Graceful shutdown hook 
     */
    protected class GracefulDestroyHook implements DestroyHook{
        @Override
        public void preDestroy(){
            // Prepare to close the connection. 
            int count = countOfInvoke.get();
            final int timeout = consumerConfig.getDisconnectTimeout();// The response timeout period. 
            if(count >0){// A call request is ongoing. 
                long start =RpcRuntimeContext.now();
                if(LOGGER.isWarnEnabled()){
                    LOGGER.warn("There are {} outstanding call in client, will close transports util return",
                            count);
                }
                while(countOfInvoke.get()>0 && RpcRuntimeContext.now()- start < timeout){// Wait for a response. 
                    try{
                        Thread.sleep(10);
                    }catch(InterruptedException ignore){
                    }
                }
            }
        }

        @Override
        public void postDestroy(){
        }
    }

The client successively completes ongoing call requests and then shuts down.

Note

Graceful shutdown requires interaction with the publishing platform. If you run a forcible kill command, none of the graceful shutdown plans takes effect. In the future, SOFABoot will provide a unified API for the publishing platform instead of relying on hook logic.