最近發生了一個問題,即所有200個Web容器線程都掛起,這意味着沒有可用的服務來傳入請求,因此應用程序凍結。WebSphere 7 HTTPSession的實現是否違反J2EE規範?
這裏是一個簡單的網絡應用程序和JMeter測試,我認爲證明了這個問題的原因。 Web應用程序包括兩個類,下面的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();
}
}
及以下實施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個請求命中servlet的每秒:
<?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上部署的測試Web應用運行此測試時,應用程序會迅速停止響應,並且核心轉儲會顯示此消息:
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
看來,當執行servlet的doGet()方法的線程(T1)調用的setAttribute()對HttpSession的執行情況(S1)的情況下,它鎖定S1的監視器上。在持有該鎖的同時,它進入listener的attributeAdded()方法內的allSessions迭代並調用getAttribute()。它看起來像在getAttribute()裏面,WebSphere在該實例的監視器上鎖定(可能是因爲它設置了lastUpdateTime字段?)。因此,T1將依次鎖定S1,S2,S3,S4,S5 ...的監視器,同時在servlet中從setAttribute()調用中鎖定S1。
因此,如果同時另一個線程(T2)在servlet中的另一個會話(S2)的監視器上進行鎖定,然後進入addAttribute()中的循環,則S1和S2上的線程死鎖將監視。
我一直無法找到任何東西在這個J2EE的規範明確,但在Servlet 2.4規範,這部分意味着容器不應該在HttpSession中實現的實例來進行同步:
SRV.7.7 .1線程問題
執行請求線程的多個servlet可能同時具有對單個會話對象的主動訪問權限 。開發人員具有 職責,用於同步訪問會話資源,如 合適。
當我們對它運行測試時,JBoss不會顯示任何死鎖。所以我的問題是:
- 我的理解是否正確?
- 如果是這樣,這是WebSphere中的J2EE規範的缺陷還是違反了?
- 如果沒有,並且開發人員應該瞭解並編碼的有效行爲,這種行爲在任何地方都有記錄嗎?
感謝
WAS讓我驚歎不已。好的測試用例。我不能從經驗或權威資源回答,但當這確實是另一個WAS怪癖時,我不會感到驚訝。 – BalusC