Недавно увидела проблему, когда все 200 потоков веб-контейнера зависли, то есть ни один из них не был доступен для обслуживания входящих запросов, и поэтому приложение зависло.
Вот простое веб-приложение и тест JMeter, который, я думаю, демонстрируетпричина этой проблемы.Веб-приложение состоит из двух классов: следующий сервлет:
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();
}
}
и следующая реализация HttpSessionListener и 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());
}
}
В тест JMeter поступает 100 запросов каждую секунду.:
<?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>
Когда этот тест запускается для тестового веб-приложения, развернутого в WebSphere 7, приложение быстро перестает отвечать, и дамп ядра показывает это:
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/MemorySession@00000000A38EA0C8/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/MemorySession@00000000A14E22C0/00000000A14E22CC:
3LKDEADLOCKOWN which is owned by:
2LKDEADLOCKTHR Thread "WebContainer : 2" (0x000000000225C600)
NULL
Похоже, что когдапоток (T1), выполняющий метод doGet () сервлета, вызывает setAttribute () в экземпляре реализации HttpSession (S1), он блокируется на мониторе S1.Удерживая эту блокировку, она входит в итерацию allSessions внутри метода attributeAdded () слушателя и вызывает getAttribute ().Похоже, что внутри getAttribute () WebSphere блокирует монитор этого экземпляра (возможно, потому что он устанавливает поле lastUpdateTime?).Таким образом, T1, в свою очередь, блокирует мониторы S1, S2, S3, S4, S5 ... все время удерживая блокировку на S1 из вызова setAttribute () в сервлете.
Так что если нав то же время другой поток (T2) блокирует монитор другого сеанса (S2) в сервлете, а затем входит в цикл в addAttribute (), блокировка потоков на мониторах S1 и S2.
У меня естьне удалось найти ничего явного в спецификациях J2EE по этому поводу, но эта часть спецификации Servlet 2.4 подразумевает, что контейнер не должен синхронизироваться в экземплярах реализаций HttpSession:
SRV.7.7.1 Проблемы с потоками
Несколько сервлетов, выполняющих потоки запросов, могут иметь активный доступ к одному объекту сеанса одновременно.Разработчик несет ответственность за синхронизацию доступа к ресурсам сеанса в зависимости от ситуации.
JBoss не показывает никаких тупиков, когда мы запускаем тест для него.Итак, мои вопросы:
- Правильно ли мое понимание?
- Если да, то это ошибка или нарушение спецификации J2EE в WebSphere?
- Если нет,и это правильное поведение, о котором должен знать разработчик и кодировать его, документировано ли это поведение где-нибудь?
Спасибо