2014-01-09 65 views
1

我正面臨着一個奇怪的問題與氣氛,並不能如何解決它。 我試圖實現服務器推送通知。 Notofications應廣播給所有連接的客戶端(最多10個,它是Intranet Web應用程序),屬於ANDROID 4.2瀏覽器。Spring + Atmosphere + Tomcat線程泄漏

通知工作正常,所有的東西都被推送,但氣氛創建了大量的tomcat線程,最終在約3-4k頁面請求後發生線程泄漏。 Tomcat的7.0.40如果構造成與連接器NIO,用150個最大線程數和超時的60000

web.xml中:

<?xml version="1.0" encoding="UTF-8"?> 
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
    id="WebApp_ID" version="3.0"> 

    <context-param> 
     <param-name>contextClass</param-name> 
     <param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value> 
    </context-param> 
    <listener> 
     <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> 
    </listener> 
    <listener> 
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
    </listener> 
    <listener> 
     <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> 
    </listener> 

    <!-- Hibernate --> 
    <filter> 
     <filter-name>hibernateFilter</filter-name> 
     <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class> 
     <async-supported>true</async-supported> 
     <init-param> 
      <param-name>singleSession</param-name> 
      <param-value>true</param-value> 
     </init-param> 
    </filter> 
    <filter-mapping> 
     <filter-name>hibernateFilter</filter-name> 
     <url-pattern>/*</url-pattern> 
    </filter-mapping> 

    <!-- SECURITY --> 
    <filter> 
     <filter-name>springSecurityFilterChain</filter-name> 
     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
     <async-supported>true</async-supported> 
    </filter> 
    <filter-mapping> 
     <filter-name>springSecurityFilterChain</filter-name> 
     <url-pattern>/*</url-pattern> 
    </filter-mapping> 

    <!-- char encoding --> 
    <filter> 
     <filter-name>encodingFilter</filter-name> 
     <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 
     <async-supported>true</async-supported> 
     <init-param> 
      <param-name>encoding</param-name> 
      <param-value>UTF-8</param-value> 
     </init-param> 
     <init-param> 
      <param-name>forceEncoding</param-name> 
      <param-value>true</param-value> 
     </init-param> 
    </filter> 
    <filter-mapping> 
     <filter-name>encodingFilter</filter-name> 
     <url-pattern>/*</url-pattern> 
    </filter-mapping> 

    <!-- Declare a DispatcherServlet as usual --> 

    <servlet> 
     <servlet-name>dispatcher</servlet-name> 
     <servlet-class>org.atmosphere.cpr.MeteorServlet</servlet-class> 
     <init-param> 
      <param-name>org.atmosphere.servlet</param-name> 
      <param-value>org.springframework.web.servlet.DispatcherServlet</param-value> 
     </init-param> 
     <init-param> 
      <param-name>org.atmosphere.cpr.asyncSupport</param-name> 
      <param-value>org.atmosphere.container.Tomcat7BIOSupportWithWebSocket</param-value> 
     </init-param> 
     <init-param> 
      <param-name>org.atmosphere.cpr.broadcasterClass</param-name> 
      <param-value>org.atmosphere.cpr.DefaultBroadcaster</param-value> 
     </init-param> 
     <init-param> 
      <param-name>org.atmosphere.cpr.broadcasterCacheClass</param-name> 
      <param-value>org.atmosphere.cache.UUIDBroadcasterCache</param-value> 
     </init-param> 
<!--  <init-param> --> 
<!--   <param-name>org.atmosphere.cpr.sessionSupport</param-name> --> 
<!--   <param-value>true</param-value> --> 
<!--  </init-param> --> 
     <init-param> 
      <param-name>org.atmosphere.resumeOnBroadcast</param-name> 
      <param-value>true</param-value> 
     </init-param> 
     <init-param> 
      <param-name>org.atmosphere.cpr.broadcaster.shareableThreadPool</param-name> 
      <param-value>true</param-value> 
     </init-param> 
     <init-param> 
      <param-name>org.atmosphere.cpr.broadcaster.maxProcessingThreads</param-name> 
      <param-value>20</param-value> 
     </init-param> 
     <init-param> 
      <param-name>org.atmosphere.cpr.broadcaster.maxAsyncWriteThreads</param-name> 
      <param-value>20</param-value> 
     </init-param> 
     <init-param> 
      <param-name>org.atmosphere.useNative</param-name> 
      <param-value>true</param-value> 
     </init-param> 
<!--   <init-param> --> 
<!--   <param-name>org.atmosphere.useBlocking</param-name> --> 
<!--   <param-value>false</param-value> --> 
<!--  </init-param> --> 
     <init-param> 
      <param-name>org.atmosphere.useStream</param-name> 
      <param-value>true</param-value> 
     </init-param> 
     <init-param> 
      <param-name>org.atmosphere.cpr.AtmosphereInterceptor</param-name> 
      <param-value>org.atmosphere.interceptor.HeartbeatInterceptor</param-value> 
     </init-param> 
     <init-param> 
      <param-name>org.atmosphere.interceptor.HeartbeatInterceptor.heartbeatFrequencyInSeconds</param-name> 
      <param-value>60</param-value> 
     </init-param> 
     <init-param> 
      <param-name>org.atmosphere.cpr.broadcasterLifeCyclePolicy</param-name> 
      <param-value>EMPTY_DESTROY</param-value> 
     </init-param> 
     <init-param> 
      <param-name>contextConfigLocation</param-name> 
      <param-value>/WEB-INF/dispatcher-servlet.xml</param-value> 
     </init-param> 
     <load-on-startup>1</load-on-startup> 
     <async-supported>true</async-supported> 
    </servlet> 

    <servlet-mapping> 
     <servlet-name>dispatcher</servlet-name> 
     <url-pattern>/</url-pattern> 
    </servlet-mapping> 

    <!-- Default page to serve --> 
    <display-name>cielo-cp</display-name> 
    <welcome-file-list> 
     <welcome-file>/</welcome-file> 
    </welcome-file-list> 

    <session-config> 
     <session-timeout>1440</session-timeout> 
    </session-config> 

    <error-page> 
     <error-code>404</error-code> 
     <location>/404</location> 
    </error-page> 
    <error-page> 
     <error-code>403</error-code> 
     <location>/accessDenied</location> 
    </error-page> 
</web-app> 

Spring MVC的控制器:

@Controller 
public class PushController extends AbstractController{ 

    @ResponseBody 
    @RequestMapping(value="/push") 
    public void pushAsync(AtmosphereResource atmosphereResource){ 
     AtmosphereUtils.suspend(atmosphereResource); 
    } 

} 

BroadcasterService:

@Service 
public class BroadcasterService { 

    @Autowired 
    private PushNotificationService service; 

    private ObjectMapper mapper = new ObjectMapper(); 


    public void receiveMessage(@Observes PushEvent e) { 
     List<PushableMessage> messages = service.pollMessages(e.getType()); 
     try { 
      Broadcaster b = AtmosphereUtils.lookupBroadcaster(false);  
      try { 
       b.broadcast(mapper.writeValueAsString(messages)); 

      } catch(Exception ex){ 
       logger.error(ex.getMessage(), ex); 
      } 
     } catch (Throwable t){ 
      logger.debug(t.getMessage(), t); 
     } 
    } 

} 

and

@Service 
public class PushNotificationService { 

    private static Logger logger = Logger.getLogger(PushNotificationService.class); 

    @Autowired 
    private Event<PushEvent> event; 


    public List<PushableMessage> pollMessages(TipologiaPush key) { 
      .... 
     return result; 
    } 


    public void pushMessage(PushableMessage message){ 
     ... 
     queue.put(message); 
     event.fire(message.getEvent()); 
    } 

}

和utils的:

public final class AtmosphereUtils { 

    public static AtmosphereResource getAtmosphereResource(HttpServletRequest request) { 
     return getMeteor(request).getAtmosphereResource(); 
    } 

    public static Meteor getMeteor(HttpServletRequest request) { 
     return Meteor.build(request); 
    } 

    public static void suspend(final AtmosphereResource resource) { 

     final CountDownLatch countDownLatch = new CountDownLatch(1); 
     resource.addEventListener(new AtmosphereResourceEventListenerAdapter() { 
      @Override 
      public void onSuspend(AtmosphereResourceEvent event) { 
       countDownLatch.countDown(); 
       logger.debug("Suspending Client..." + resource.uuid() + " with transport " + resource.transport()); 
       resource.removeEventListener(this); 
      } 

      @Override 
      public void onDisconnect(AtmosphereResourceEvent event) { 
       logger.debug("Disconnecting Client..." + resource.uuid()); 
       super.onDisconnect(event); 
      }  
     }); 

     if (AtmosphereResource.TRANSPORT.LONG_POLLING.equals(resource.transport())) { 
      resource.resumeOnBroadcast(true).suspend(); 
     } else { 
      resource.suspend(); 
     } 

     try { 
      countDownLatch.await(); 
     } catch (InterruptedException e) { 
      logger.error("Interrupted while trying to suspend resource {}", resource); 
     } 

     AtmosphereUtils.lookupBroadcaster(true).addAtmosphereResource(resource); 
    } 

    public static Broadcaster lookupBroadcaster(boolean create) { 
     Broadcaster b = BroadcasterFactory.getDefault().lookup("/*", create); 
     return b; 
    } 

} 

其他服務呼叫:notificationService.pushMessage(....); 的JavaScript部分是:

var socket = $.atmosphere; 

function handleAtmosphere(url, handleResult) { 

    var request = new $.atmosphere.AtmosphereRequest(); 
    request.transport = "websocket"; // "streaming is even worse"; 
    request.url = url; 
    request.contentType = "application/json"; 
    request.fallbackTransport = "long-polling"; //for android 4.2, default browser don't support websocket 

    request.onMessage = function(response){ 
     .... 
    }; 


    var subSocket = socket.subscribe(request); 

} 

他這樣說,我已經嘗試了很多配置,但還是有些線程保持活躍。 我使用Spring 3.2.5,SpringSec 3.2 RC2和大氣2.0.4

+0

請張貼片段,而不是所有的腳本,如果你這樣做,你的問題得到解決的機會將增加。 –

+1

我沒有經驗的氣氛。也就是說,大量線程的原因之一可能是線程沒有被釋放回來。如果有人在提供請求後仍然拒絕,那麼就是我要檢查的代碼/配置。我無法檢查你提供的所有代碼,但是我會檢查你是否處理/管理任何線程或任何氣氛線程,請先檢查它們。 – prabugp

+0

好的,我刪除了不相關的代碼。 –

回答

0

檢查了這一點:https://github.com/Atmosphere/atmosphere/issues/717

夫婦的事情,從該線程注意: 隨着共享線程池,你必須關閉相關的執行者自己。這是一個框架限制。但是,爲什麼BroadcastConfig越來越停止,未AsyncWrite需要調查

而且

線程運行與ExecutorServices有關。這是ExecutorService來決定何時殺死這些線程,而不是Atmosphere。由於您使用的是CachedThreadPool,因此這是預期的。我已經測試了Tomcat 7.0.27,共享線程池和NIO連接器。如果您使用BIO連接器EMPTY_DESTROY將無法工作,因爲不會檢測到斷開連接。

所以我建議你使用一個有界的線程池,這樣你就不會有太多的線程運行。

看看這是你所面臨的。

+0

另外,請分享線程轉儲以查看線程被阻止的位置。 – jfarcand

+0

是不是與1.x版本相關的線程?我使用2.0.4,我認爲它是固定的。我會嘗試獲得線程轉儲(從未完成) –

+0

似乎Atmosphere-Shared-DispatchOp和Atmosphere-Shared-AsyncOp正確綁定,線程泄露全部爲** http-nio-8080-exec **類型 –