복붙노트

[SPRING] 웹 애플 리케이션의 다른 모든 bean이 파괴되기 전에 Spring task executor / scheduler 풀을 어떻게 종료 할 수 있습니까?

SPRING

웹 애플 리케이션의 다른 모든 bean이 파괴되기 전에 Spring task executor / scheduler 풀을 어떻게 종료 할 수 있습니까?

Spring 웹 애플리케이션에서 나는 여러 DAO와 서비스 레이어 빈을 가지고있다. 하나의 서비스 레이어 빈에는 @Async / @Scheduled 메소드가 주석 처리되어있다. 이 메소드는 다른 (autowired) bean에 의존합니다. XML에서 두 개의 스레드 풀을 구성했습니다.

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
     <property name="corePoolSize" value="2" />
     <property name="maxPoolSize" value="5" />
     <property name="queueCapacity" value="5" />
     <property name="waitForTasksToCompleteOnShutdown" value="true" />
     <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
        </property>
    </bean>

<bean id="taskScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
     <property name="poolSize" value="10" />
     <property name="waitForTasksToCompleteOnShutdown" value="true" />
     <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
        </property>
    </bean>

    <task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>

모든 것이 예상대로 작동합니다. 내 문제는 내가 작업 풀의 깨끗한 종료를 얻을 수 없다는 것입니다. 작업은 데이터베이스와 파일 시스템에서 작동합니다. 웹 응용 프로그램을 중지하면 중지 될 때까지 약간의 시간이 걸립니다. waitForTasksToCompleteOnShutdown 속성이 작동 함을 나타냅니다. 그러나 로그에서 IllegalStateExceptions가 발생하여 일부 Bean은 이미 파괴되었지만 일부 작업자 스레드는 여전히 실행 중이며 종속성이 없어지기 때문에 실패합니다.

관련성이있는 JIRA 문제가 있습니다 : SPR-5387

내 질문은 : Spring에게 task executor / scheduler beans를 마지막으로 초기화하도록 지시하는 방법이 있는가? 아니면 Spring에 먼저 destroy하도록 지시하는 방법이 있는가?

내 이해는 파괴가 초기화 된 순서와 반대로 일어난다는 것이다. 따라서 init'ed 마지막 bean은 먼저 파괴 될 것입니다. 스레드 풀 빈이 먼저 소멸되면 현재 실행중인 모든 작업이 완료되어 종속 Bean에 계속 액세스 할 수 있습니다.

또한 @Async 및 @Scheduled 주석이있는 서비스 bean을 참조하는 스레드 풀의 depends-on 속성을 사용해 보았습니다. 그들은 결코 실행되지 않는 것 같아 문맥 초기화 오류가 발생하지 않습니다. 주석이 달린 서비스 빈은 어떻게 든이 스레드 풀을 먼저 초기화해야한다고 가정하고, 의존성을 사용하면 순서를 뒤집어 비 기능적으로 만듭니다.

해결법

  1. ==============================

    1.두 가지 방법:

    두 가지 방법:

    어쨌든 당신은 빈 파괴 메커니즘이 일어나기 전에 작업 내용을 종료 할 수 있습니다.

    예 :

    @Component
    public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
        @Autowired ThreadPoolTaskExecutor executor;
        @Autowired ThreadPoolTaskScheduler scheduler;
    
        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            scheduler.shutdown();
            executor.shutdown();
        }       
    }
    

    (편집 : 고정 방법 서명)

  2. ==============================

    2.사용할 수있는 작업을 종료하려면 아래 코드를 추가했습니다. 재시도 횟수를 변경할 수 있습니다.

    사용할 수있는 작업을 종료하려면 아래 코드를 추가했습니다. 재시도 횟수를 변경할 수 있습니다.

    package com.xxx.test.schedulers;
    
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    import org.apache.log4j.Logger;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.ApplicationListener;
    import org.springframework.context.event.ContextClosedEvent;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
    import org.springframework.stereotype.Component;
    
    import com.xxx.core.XProvLogger;
    
    @Component
    class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> , ApplicationContextAware,BeanPostProcessor{
    
    
    private ApplicationContext context;
    
    public Logger logger = XProvLogger.getInstance().x;
    
    public void onApplicationEvent(ContextClosedEvent event) {
    
    
        Map<String, ThreadPoolTaskScheduler> schedulers = context.getBeansOfType(ThreadPoolTaskScheduler.class);
    
        for (ThreadPoolTaskScheduler scheduler : schedulers.values()) {         
            scheduler.getScheduledExecutor().shutdown();
            try {
                scheduler.getScheduledExecutor().awaitTermination(20000, TimeUnit.MILLISECONDS);
                if(scheduler.getScheduledExecutor().isTerminated() || scheduler.getScheduledExecutor().isShutdown())
                    logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has stoped");
                else{
                    logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has not stoped normally and will be shut down immediately");
                    scheduler.getScheduledExecutor().shutdownNow();
                    logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has shut down immediately");
                }
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        Map<String, ThreadPoolTaskExecutor> executers = context.getBeansOfType(ThreadPoolTaskExecutor.class);
    
        for (ThreadPoolTaskExecutor executor: executers.values()) {
            int retryCount = 0;
            while(executor.getActiveCount()>0 && ++retryCount<51){
                try {
                    logger.info("Executer "+executor.getThreadNamePrefix()+" is still working with active " + executor.getActiveCount()+" work. Retry count is "+retryCount);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if(!(retryCount<51))
                logger.info("Executer "+executor.getThreadNamePrefix()+" is still working.Since Retry count exceeded max value "+retryCount+", will be killed immediately");
            executor.shutdown();
            logger.info("Executer "+executor.getThreadNamePrefix()+" with active " + executor.getActiveCount()+" work has killed");
        }
    }
    
    
    @Override
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        this.context = context;
    
    }
    
    
    @Override
    public Object postProcessAfterInitialization(Object object, String arg1)
            throws BeansException {
        return object;
    }
    
    
    @Override
    public Object postProcessBeforeInitialization(Object object, String arg1)
            throws BeansException {
        if(object instanceof ThreadPoolTaskScheduler)
            ((ThreadPoolTaskScheduler)object).setWaitForTasksToCompleteOnShutdown(true);
        if(object instanceof ThreadPoolTaskExecutor)
            ((ThreadPoolTaskExecutor)object).setWaitForTasksToCompleteOnShutdown(true);
        return object;
    }
    

    }

  3. ==============================

    3.나는 스프링 빈에서 시작되는 쓰레드와 비슷한 이슈를 가지고있다. @PreDestroy 메서드에서 executor.shutdownNow ()를 호출 한 후 이러한 스레드가 제대로 닫히지 않았습니다. 그래서 나를위한 해결책은 IO가있는 스레드 finsih가 이미 시작되었고 IO가 더 이상 시작되지 않도록 한 다음 @PreDestroy가 호출되면서였습니다. 그리고 여기 @PreDestroy 메서드가 있습니다. 나의 신청을 위해 1 초를 기다리는 것이 받아 들여질 수 있었다.

    나는 스프링 빈에서 시작되는 쓰레드와 비슷한 이슈를 가지고있다. @PreDestroy 메서드에서 executor.shutdownNow ()를 호출 한 후 이러한 스레드가 제대로 닫히지 않았습니다. 그래서 나를위한 해결책은 IO가있는 스레드 finsih가 이미 시작되었고 IO가 더 이상 시작되지 않도록 한 다음 @PreDestroy가 호출되면서였습니다. 그리고 여기 @PreDestroy 메서드가 있습니다. 나의 신청을 위해 1 초를 기다리는 것이 받아 들여질 수 있었다.

    @PreDestroy
        public void beandestroy() {
            this.stopThread = true;
            if(executorService != null){
                try {
                    // wait 1 second for closing all threads
                    executorService.awaitTermination(1, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    

    여기 스레드를 닫으려고 할 때 직면하는 모든 문제를 설명했습니다 .http : //programtalk.com/java/executorservice-not-shutting-down/

  4. ==============================

    4.웹 기반 응용 프로그램이 될 경우 ServletContextListener 인터페이스를 사용할 수도 있습니다.

    웹 기반 응용 프로그램이 될 경우 ServletContextListener 인터페이스를 사용할 수도 있습니다.

    public class SLF4JBridgeListener implements ServletContextListener {
    
       @Autowired 
       ThreadPoolTaskExecutor executor;
    
       @Autowired 
       ThreadPoolTaskScheduler scheduler;
    
        @Override
        public void contextInitialized(ServletContextEvent sce) {
    
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
             scheduler.shutdown();
             executor.shutdown();     
    
        }
    

    }

  5. ==============================

    5.아래와 같이 taskExecutor와 taskScheduler에 대해 "AwaitTerminationSeconds"속성을 추가 할 수 있습니다.

    아래와 같이 taskExecutor와 taskScheduler에 대해 "AwaitTerminationSeconds"속성을 추가 할 수 있습니다.

    <property name="awaitTerminationSeconds" value="${taskExecutor .awaitTerminationSeconds}" />
    
    <property name="awaitTerminationSeconds" value="${taskScheduler .awaitTerminationSeconds}" />
    

    "waitForTasksToCompleteOnShutdown"속성에 대한 문서에서 종료가 호출 될 때 말합니다.

    "진행중인 작업이 완료되면 Spring의 컨테이너 시스템 종료가 계속됩니다.이 실행 프로그램을 차단하고 나머지 컨테이너가 계속 종료되기 전에 작업 종료를 기다리도록하려면 - 예를 들어 작업에 필요할 수있는 다른 리소스를 유지해야합니다 -,이 속성 대신 또는 추가로 "awaitTerminationSeconds"속성을 설정하십시오. "

    https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.html#setWaitForTasksToCompleteOnShutdown-boolean-

    따라서 항상 waitForTasksToCompleteOnShutdown 및 awaitTerminationSeconds 속성을 함께 사용하는 것이 좋습니다. awaitTerminationSeconds 값은 응용 프로그램에 따라 다릅니다.

  6. from https://stackoverflow.com/questions/6603051/how-can-i-shutdown-spring-task-executor-scheduler-pools-before-all-other-beans-i by cc-by-sa and MIT license