Skip to content

Tomcat 不能 graceful shutdown #535

Description

@gtyang

我的配置是 tomcat + spring boot mvc,从spring boot 的例子工程中修改出来的,sdk 的版本 2.9.6。部署到tomcat下运行,不能正常的shutdown,有很多线程不能shutdown,例如:

12-Apr-2018 20:24:51.951 WARNING [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [ROOT] appears to have started a thread named [pool-2-thread-9] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 sun.misc.Unsafe.park(Native Method)
 java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
 java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
 java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
 java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
 java.lang.Thread.run(Thread.java:748)

还有这类的:

12-Apr-2018 20:24:52.004 SEVERE [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [ROOT] created a ThreadLocal with key of type [java.lang.ThreadLocal] (value [java.lang.ThreadLocal@2926f399]) and a value of type [com.github.binarywang.wxpay.bean.WxPayApiData] (value [
【请求地址】:https://api.mch.weixin.qq.com/pay/unifiedorder
【请求数据】:<xml>
  <appid>wxxxxxxx</appid>
  <mch_id>1xxx</mch_id>
  <nonce_str>1523530799119</nonce_str>
  <sign>3D1?????????????</sign>
  <body>abcddd</body>
  <out_trade_no>tx-341</out_trade_no>
  <total_fee>1</total_fee>
  <spbill_create_ip>172.31.233.29</spbill_create_ip>
  <time_start>20180412185959</time_start>
  <time_expire>20180412205959</time_expire>
  <notify_url>http://mp.x.moxxbi.com/pay/notify</notify_url>
  <trade_type>JSAPI</trade_type>
  <openid>oEKyxCRT93OmoI</openid>
</xml>
【响应数据】:<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wx????da0???]]></appid>
<mch_id><![CDATA[14966786487324-002]]></mch_id>
<nonce_str><![CDATA[EyuXgXeSJyDAeM4W]]></nonce_str>
<sign><![CDATA[F1C2CF6??????618B4D56EE74495A4686]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx12x883721bd45d2797498456]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

我没看到在哪里设置graceful shutdown相关的配置,

请求帮助

Activity

  1. binarywang commented on Apr 12, 2018

    @binarywang
    Owner

    你这两种错误都属于网上常见警告或错误,并不影响使用,不过既然你觉得不爽,就可以在网上搜索对应相关错误信息,会找到很多种不同的分析和解决办法

  2. gtyang commented on Apr 13, 2018

    @gtyang
    Author

    这个问题的影响是不能正常的 shutdown tomcat,还要手工的 kill 进程,这个比较影响运维。

    通常来讲这个问题是线程池没有shutdown,但是我不知道是那里的线程池,能找到这个线程池,我说可以在shutdown的钩子里去关闭它。

  3. binarywang commented on Apr 13, 2018

    @binarywang
    Owner

    那我明白你的意思了,第一个我估计应该是那个后台定期关闭空闲http请求的线程,第二个的话是使用的ThreadLocal的那个类,类名上面也列出来了,你可以先研究下怎么处理,如有可行办法,可以提供下,供其他人参考,或者如果是sdk需要配合修改的,麻烦提供一个PR,谢谢了

  4. gtyang commented on Apr 13, 2018

    @gtyang
    Author

    初步判断,是 WxMpMessageRouter 启动了一个线程池,但是没有shutdown。这个类没有暴露出这个线程池,所以没有办法处理这个池。

    
    因为这个类只有一个构造方法 WxMpMessageRouter(WxMpService wxMpService),在这个构造方法中创建了线程池,所以后面即使在注入了外部的线程池,那么还是会存在一个没有shutdown的线程池的。
    
    应该要有方法来关闭这个线程池才行。
    
    ```
     * @author Daniel Qian
     */
    public class WxMpMessageRouter {
    
      private static final int DEFAULT_THREAD_POOL_SIZE = 100;
      protected final Logger log = LoggerFactory.getLogger(WxMpMessageRouter.class);
      private final List<WxMpMessageRouterRule> rules = new ArrayList<>();
    
      private final WxMpService wxMpService;
    
      private ExecutorService executorService;
    
      private WxMessageDuplicateChecker messageDuplicateChecker;
    
      private WxSessionManager sessionManager;
    
      private WxErrorExceptionHandler exceptionHandler;
    
      public WxMpMessageRouter(WxMpService wxMpService) {
        this.wxMpService = wxMpService;
        this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE);
        this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker();
        this.sessionManager = new StandardSessionManager();
        this.exceptionHandler = new LogExceptionHandler();
      }
    
      /**
       * <pre>
       * 设置自定义的 {@link ExecutorService}
       * 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100)
       * </pre>
       */
      public void setExecutorService(ExecutorService executorService) {
        this.executorService = executorService;
      }
    
    ```
    
  5. added a commit that references this issue on Apr 14, 2018
  6. added a commit that references this issue on Apr 16, 2018
  7. binarywang commented on Apr 16, 2018

    @binarywang
    Owner

    3.0.2.BETA版本已加入相关支持

  8. gtyang commented on Apr 17, 2018

    @gtyang
    Author

    实证问题解决了 :)

  9. nadirvishun commented on Apr 10, 2022

    @nadirvishun

    同样遇到此问题,但是目前此pr只是能解决“微信公众号”的问题,企业微信的还是没有shutdown方法关闭,而且企业微信与公众号不同的是多个应用会存在多个WxCpService和多个WxCpMessageRouter,因为他不是和企业微信一样是用単例实现用switchover方式切换的,所以仅仅是增加shutdown方法也不好调用。而且shutdown方法最好增加awaitTermination()方法来保证超时关闭。
    @binarywang ,麻烦看下能否修改企业微信sdk的调用方式和微信公众号一致(或者能实现关闭线程池也行)?并增加shutdown方法?

  10. binarywang commented on Apr 10, 2022

    @binarywang
    Owner

    同样遇到此问题,但是目前此pr只是能解决“微信公众号”的问题,企业微信的还是没有shutdown方法关闭,而且企业微信与公众号不同的是多个应用会存在多个WxCpService和多个WxCpMessageRouter,因为他不是和企业微信一样是用単例实现用switchover方式切换的,所以仅仅是增加shutdown方法也不好调用。而且shutdown方法最好增加awaitTermination()方法来保证超时关闭。 @binarywang ,麻烦看下能否修改企业微信sdk的调用方式和微信公众号一致(或者能实现关闭线程池也行)?并增加shutdown方法?

    欢迎直接提供PR

  11. nadirvishun commented on Apr 11, 2022

    @nadirvishun

    企业微信如果要改成和公众号类似的单例的形式我这边要提交pr的话有几个问题:

    • agentId并不一定是唯一的,如果恰好要集成多个企业的微信的话,可能会有冲突,所以是不是在WxCpDefaultConfigImpl中再增加一个成员变量cpId比较好,如果是单个企业,则可以用agentId赋值给cpId,多个企业可以用corpId + agentId赋值给cpId,或者是自定义能保证唯一就行。
    • 对于企业微信第三方的同样是有这个单例及线程池问题,但是我这边从来没作过第三方的集成,所以不是很清楚相关的逻辑。
    • 如果改动的话貌似是不好兼容以前的版本,这样没问题吗?

    上方的pr仅仅是提交了增加线程池关闭的方法,如果有多个实例,那就遍历多个来结束线程池,暂时是能满足要求的,但是其它的都是单例的形式运行,就企业微信要实例化好几个,从行为上来说不是很统一。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions