2012-01-24 6 views
12

Recentemente verificato un problema in cui sono stati bloccati tutti i 200 thread del contenitore Web, ovvero nessuno era disponibile per soddisfare le richieste in arrivo e quindi l'applicazione si è bloccata.L'implementazione di WebSphere 7 HTTPSession è in conflitto con le specifiche J2EE?

Ecco una semplice applicazione Web e un test JMeter che ritengo dimostrino la causa di questo problema. L'applicazione web è costituito da due classi, il seguente servlet:

public class SessionTestServlet extends HttpServlet { 

    protected static final String SESSION_KEY = "session_key"; 

    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
      throws ServletException, IOException { 
     // set data on session so the listener is invoked 
     String sessionData = new String("Session data"); 
     request.getSession().setAttribute(SESSION_KEY, sessionData); 
     PrintWriter writer = response.getWriter();       
     writer.println("<html><body>OK</body></html>");      
     writer.flush(); 
     writer.close(); 
    } 
} 

e la successiva implementazione di HttpSessionListener e HTTPSessionAttributeListener:

public class SessionTestListener implements 
     HttpSessionListener, HttpSessionAttributeListener { 

    private static final ConcurrentMap<String, HttpSession> allSessions 
     = new ConcurrentHashMap<String, HttpSession>(); 

    public void attributeRemoved(HttpSessionBindingEvent hsbe) {} 

    public void attributeAdded(HttpSessionBindingEvent hsbe) { 
     System.out.println("Attribute added, " + hsbe.getName() 
      + "=" + hsbe.getValue()); 

     int count = 0; 
     for (HttpSession session : allSessions.values()) { 
      if (session.getAttribute(SessionTestServlet.SESSION_KEY) != null) { 
       count++; 
      } 
     } 
     System.out.println(count + " of " + allSessions.size() 
      + " sessions have attribute set."); 
    } 

    public void attributeReplaced(HttpSessionBindingEvent hsbe) {} 

    public void sessionCreated(HttpSessionEvent hse) { 
     allSessions.put(hse.getSession().getId(), session);        
    } 

    public void sessionDestroyed(HttpSessionEvent hse) { 
     allSessions.remove(hse.getSession().getId()); 
    }     
} 

Il test JMeter ha 100 richieste colpito il servlet ogni secondo:

<?xml version="1.0" encoding="UTF-8"?> 
<jmeterTestPlan version="1.2" properties="2.1"> 
    <hashTree> 
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> 
     <stringProp name="TestPlan.comments"></stringProp> 
     <boolProp name="TestPlan.functional_mode">false</boolProp> 
     <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> 
     <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> 
     <collectionProp name="Arguments.arguments"/> 
     </elementProp> 
     <stringProp name="TestPlan.user_define_classpath"></stringProp> 
    </TestPlan> 
    <hashTree> 
     <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> 
     <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> 
     <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> 
      <boolProp name="LoopController.continue_forever">false</boolProp> 
      <intProp name="LoopController.loops">-1</intProp> 
     </elementProp> 
     <stringProp name="ThreadGroup.num_threads">100</stringProp> 
     <stringProp name="ThreadGroup.ramp_time">1</stringProp> 
     <longProp name="ThreadGroup.start_time">1327193422000</longProp> 
     <longProp name="ThreadGroup.end_time">1327193422000</longProp> 
     <boolProp name="ThreadGroup.scheduler">false</boolProp> 
     <stringProp name="ThreadGroup.duration"></stringProp> 
     <stringProp name="ThreadGroup.delay"></stringProp> 
     </ThreadGroup> 
     <hashTree> 
     <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> 
      <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> 
      <collectionProp name="Arguments.arguments"/> 
      </elementProp> 
      <stringProp name="HTTPSampler.domain">localhost</stringProp> 
      <stringProp name="HTTPSampler.port">9080</stringProp> 
      <stringProp name="HTTPSampler.connect_timeout"></stringProp> 
      <stringProp name="HTTPSampler.response_timeout"></stringProp> 
      <stringProp name="HTTPSampler.protocol">http</stringProp> 
      <stringProp name="HTTPSampler.contentEncoding"></stringProp> 
      <stringProp name="HTTPSampler.path">/SESSION_TESTWeb/SessionTestServlet</stringProp> 
      <stringProp name="HTTPSampler.method">GET</stringProp> 
      <boolProp name="HTTPSampler.follow_redirects">true</boolProp> 
      <boolProp name="HTTPSampler.auto_redirects">false</boolProp> 
      <boolProp name="HTTPSampler.use_keepalive">true</boolProp> 
      <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> 
      <boolProp name="HTTPSampler.monitor">false</boolProp> 
      <stringProp name="HTTPSampler.embedded_url_re"></stringProp> 
     </HTTPSamplerProxy> 
     <hashTree> 
      <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true"> 
      <stringProp name="ConstantTimer.delay">1000</stringProp> 
      </ConstantTimer> 
      <hashTree/> 
     </hashTree> 
     </hashTree> 
     <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> 
     <boolProp name="ResultCollector.error_logging">false</boolProp> 
     <objProp> 
      <name>saveConfig</name> 
      <value class="SampleSaveConfiguration"> 
      <time>true</time> 
      <latency>true</latency> 
      <timestamp>true</timestamp> 
      <success>true</success> 
      <label>true</label> 
      <code>true</code> 
      <message>true</message> 
      <threadName>true</threadName> 
      <dataType>true</dataType> 
      <encoding>false</encoding> 
      <assertions>true</assertions> 
      <subresults>true</subresults> 
      <responseData>false</responseData> 
      <samplerData>false</samplerData> 
      <xml>true</xml> 
      <fieldNames>false</fieldNames> 
      <responseHeaders>false</responseHeaders> 
      <requestHeaders>false</requestHeaders> 
      <responseDataOnError>false</responseDataOnError> 
      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage> 
      <assertionsResultsToSave>0</assertionsResultsToSave> 
      <bytes>true</bytes> 
      </value> 
     </objProp> 
     <stringProp name="filename"></stringProp> 
     </ResultCollector> 
     <hashTree/> 
     <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> 
     <boolProp name="ResultCollector.error_logging">false</boolProp> 
     <objProp> 
      <name>saveConfig</name> 
      <value class="SampleSaveConfiguration"> 
      <time>true</time> 
      <latency>true</latency> 
      <timestamp>true</timestamp> 
      <success>true</success> 
      <label>true</label> 
      <code>true</code> 
      <message>true</message> 
      <threadName>true</threadName> 
      <dataType>true</dataType> 
      <encoding>false</encoding> 
      <assertions>true</assertions> 
      <subresults>true</subresults> 
      <responseData>false</responseData> 
      <samplerData>false</samplerData> 
      <xml>true</xml> 
      <fieldNames>false</fieldNames> 
      <responseHeaders>false</responseHeaders> 
      <requestHeaders>false</requestHeaders> 
      <responseDataOnError>false</responseDataOnError> 
      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage> 
      <assertionsResultsToSave>0</assertionsResultsToSave> 
      <bytes>true</bytes> 
      </value> 
     </objProp> 
     <stringProp name="filename"></stringProp> 
     </ResultCollector> 
     <hashTree/> 
    </hashTree> 
    </hashTree> 
</jmeterTestPlan> 

Quando questo test viene eseguito sull'app Web di prova distribuita su WebSphere 7, l'applicazione smette rapidamente di rispondere e un core dump mostra questo:

1LKDEADLOCK Deadlock detected !!! 
NULL   --------------------- 
NULL   
2LKDEADLOCKTHR Thread "WebContainer : 2" (0x000000000225C600) 
3LKDEADLOCKWTR is waiting for: 
4LKDEADLOCKMON  sys_mon_t:0x00000000151938C0 infl_mon_t: 0x0000000015193930: 
4LKDEADLOCKOBJ  com/ibm/ws/session/store/memory/[email protected]/00000000A38EA0D4: 
3LKDEADLOCKOWN which is owned by: 
2LKDEADLOCKTHR Thread "WebContainer : 1" (0x00000000021FB500) 
3LKDEADLOCKWTR which is waiting for: 
4LKDEADLOCKMON  sys_mon_t:0x0000000015193820 infl_mon_t: 0x0000000015193890: 
4LKDEADLOCKOBJ  com/ibm/ws/session/store/memory/[email protected]/00000000A14E22CC: 
3LKDEADLOCKOWN which is owned by: 
2LKDEADLOCKTHR Thread "WebContainer : 2" (0x000000000225C600) 
NULL 

Risulta che quando un thread (T1) eseguire metodo doGet() del servlet chiama setAttribute() nell'istanza di attuazione HttpSession (S1), si blocca sul monitor di S1. Mentre tiene premuto quel blocco, va nell'iterazione di allSession all'interno del metodo attributeAdded() del listener e chiama getAttribute(). Assomiglia a getAttribute(), WebSphere si blocca sul monitor di quell'istanza (probabilmente perché sta impostando un campo lastUpdateTime?). Quindi, T1 a sua volta bloccherà i monitor di S1, S2, S3, S4, S5 ... per tutto il tempo tenendo il blocco su S1 dalla chiamata setAttribute() nel servlet.

Quindi se allo stesso tempo un altro thread (T2) si blocca su un altro monitor di sessione (S2) nel servlet e quindi entra nel ciclo in addAttribute(), il deadlock dei thread sui monitor S1 e S2.

sono stato in grado di trovare qualcosa di esplicito nelle specifiche J2EE su questo, ma questa parte del Servlet 2.4 spec implica che il contenitore non deve essere sincronizzato su istanze di implementazioni HttpSession:

SRV.7.7 .1 Threading Issues

Più thread in esecuzione di thread di richieste possono avere accesso attivo a un oggetto sessione singolo allo stesso tempo. Lo sviluppatore ha la responsabilità dello per la sincronizzazione dell'accesso alle risorse di sessione come appropriato.

JBoss non mostra alcun deadlock quando eseguiamo il test contro di esso. Quindi le mie domande sono:

  • La mia comprensione è corretta?
  • In tal caso, si tratta di un bug o di una violazione della specifica J2EE in WebSphere?
  • In caso contrario, ed è un comportamento valido che lo sviluppatore dovrebbe conoscere e codificare, questo comportamento è documentato ovunque?

Grazie

+3

WAS continua a stupirmi. Bel banco di prova Non posso rispondere per esperienza o risorse autorevoli, ma non sarò sorpreso quando questo è davvero un altro capriccio. – BalusC

risposta

3

Il Servlet 2.5 MR6 contiene un clarification alla parte della specifica Servlet citato nella domanda:

Chiarire SRV 7.7.1 "problemi di threading" (Numero 33)

Cambiare il paragrafo che è attualmente

"Più thread di esecuzione esecuzione servlet possono avere accesso attivo a un oggetto singola sessione allo stesso tempo. Il sviluppatore ha la responsabilità per la sincronizzazione l'accesso alla sessione risorse a seconda dei casi. "

per leggere

" Più servlet esecuzione discussioni richiesta possono avere accesso attivo allo stesso oggetto sessione a stesso tempo. Il contenitore deve garantire che la manipolazione delle strutture di dati interne che rappresentano gli attributi di sessione venga eseguita in un modo thread-safe . Lo sviluppatore ha la responsabilità di threadsafe l'accesso a se stessi gli oggetti attributo. Questo proteggerà la collezione attributi all'interno dell'oggetto HttpSession dalla concomitante accesso, eliminando la possibilità di un'applicazione per causare che raccolta venga danneggiato."

Questo è ancora attuale nel Servlet 3.0 MR1 e rende WAS di comportamento sembra più ragionevole, tuttavia, vorrei prendere che * set * L'attributo potrebbe essere sincronizzato ma non quello * get * L'attributo sarebbe.

Quindi penso che la risposta è:

  • WAS è conformi alle specifiche Servlet secondo il chiarimento in 2,5 MR6
  • Le specifiche lascia spazio a malintesi
  • WAS è più zelanti con la sua sincronizzazione di quanto sarebbe ragionevolmente aspettare da spec e per quanto ne so questo comportamento non è chiaramente documentato da nessuna parte

(Come nota a margine, cambiando il caso di test in modo che listener.attributeAdded() chiama setAttribute invece di getAttribute non causa deadlock su JBoss 4 o 5.)

1

Si hanno probabilmente trovato un caso d'uso non supportata di HttpSession in IBM WebSphere specifica implementazione. Perché non segnalarlo ad IBM?

Un punto che si è perso per l'implementazione: il contenitore JavaEE può passivare gli oggetti HttpSession (serializzandolo su disco o database) per liberare memoria se il server deve gestire troppe sessioni sotto carico. Il listener impedisce al garbage collector di liberare le sessioni.

A proposito, l'oggetto HttpSession deve essere utilizzato solo dal thread che corrisponde alla propria sessione. Cui è stato trovato nella specifica, in caso di più thread simultanei dalla stessa sessione, il codice deve utilizzare meccanismo di sincronizzazione sul HttpSession objet.

Gli ascoltatori di sessione sono basati su eventi con tutte le informazioni necessarie in evento, tale design è sufficiente per evitare che l'ascoltatore tenga tutti i riferimenti agli oggetti viventi HttpSession come si fa.

quering da un thread tutte le sessioni che vivono nel contenitore è strano e inaspettato. Non è il lavoro di un'applicazione web ma di uno strumento di monitoraggio o auditing. In tal caso, devono essere impiegati altri mezzi come interrogazione JMX o interfaccia PMI nel contesto WebSphere specifico.

Per aiutarti, ecco un'implementazione alternativa del listener per ottenere lo stesso conteggio degli attributi di sessione ma senza mantenere alcun riferimento su HttpSession. Attenzione: non è stato né compilato né testato.

public class SessionTestListener implements 
     HttpSessionListener, HttpSessionAttributeListener { 

    private static final Set<String> sessionsIds 
     = new ConcurrentSkipListSet<String>(); 

    private static final ConcurrentMap<String, Object> sessionsKeys 
     = new ConcurrentHashMap<String, Object>(); 

    public void attributeRemoved(HttpSessionBindingEvent hsbe) { 
     System.out.println("Attribute removed, " + hsbe.getName() 
      + "=" + hsbe.getValue()); 
     if (SessionTestServlet.SESSION_KEY.equals(hsbe.getName())) { 
      sessionsKeys.remove(hsbe.getSession().getId()); 
     } 
    } 

    public void attributeAdded(HttpSessionBindingEvent hsbe) { 
     System.out.println("Attribute added, " + hsbe.getName() 
      + "=" + hsbe.getValue()); 

     if (SessionTestServlet.SESSION_KEY.equals(hsbe.getName())) { 
      if (hsbe.getValue() == null) { 
       sessionsKeys.remove(hsbe.getSession().getId()); 
      } else { 
       sessionsKeys.put(hsbe.getSession().getId(), hsbe.getValue()); 
      } 
     } 
     System.out.println(sessionsKeys.size() + " of " + sessionsIds.size() 
      + " sessions have attribute set."); 
    } 

    public void attributeReplaced(HttpSessionBindingEvent hsbe) {} 

    public void sessionCreated(HttpSessionEvent hse) { 
     sessionsIds.add(hse.getSession().getId()); 
    } 

    public void sessionDestroyed(HttpSessionEvent hse) { 
     sessionsIds.remove(hse.getSession().getId()); 
     sessionsKeys.remove(hse.getSession().getId()); 
    }     
} 
+0

Grazie Yves. È "l'oggetto HttpSession dovrebbe essere utilizzato solo dal thread che corrisponde alla propria sessione" una linea guida o una regola o best practice? –

+0

Direi che è il caso d'uso standard che regola l'implementazione di HttpSession dal punto di vista dei fornitori di contenitori JavaEE. Qualsiasi altro caso d'uso è rilevante solo per gli strumenti interni del contenitore o strumenti di monitoraggio/controllo. A proposito, hai ancora la limitazione per accedere alle sessioni passive senza dettagli specifici dell'implementazione del contenitore. –

Problemi correlati