java - WebSphere 7 HTTPSession 实现是否违反了 J2EE 规范?

标签 java jakarta-ee websphere deadlock

最近看到一个问题,所有 200 个 Web 容器线程都挂起,这意味着没有一个可用于为传入请求提供服务,因此应用程序卡住。

这是一个简单的 Web 应用程序和 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/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 

看起来,当执行 servlet 的 doGet() 方法的线程 (T1) 在 HttpSession 实现 (S1) 的实例上调用 setAttribute() 时,它锁定了 S1 的监视器。在持有该锁的同时,它进入监听器的 attributeAdded() 方法内的 allSessions 迭代并调用 getAttribute()。看起来在 getAttribute() 内部,WebSphere 锁定了该实例的监视器(可能是因为它正在设置 lastUpdateTime 字段?)。因此,T1 将依次锁定 S1、S2、S3、S4、S5... 的监视器,同时通过 servlet 中的 setAttribute() 调用一直保持对 S1 的锁定。

因此,如果同时另一个线程 (T2) 在 servlet 中锁定另一个 session (S2) 的监视器,然后进入 addAttribute() 中的循环,则线程会在 S1 和 S2 监视器上死锁。

我无法在 J2EE 规范中找到关于此的任何明确内容,但 Servlet 2.4 规范的这一部分暗示容器不应在 HttpSession 实现的实例上同步:

SRV.7.7.1 Threading Issues

Multiple servlets executing request threads may have active access to a single session object at the same time. The Developer has the responsibility for synchronizing access to session resources as appropriate.

当我们对 JBoss 运行测试时,它没有显示任何死锁。所以我的问题是:

  • 我的理解正确吗?
  • 如果是这样,这是否是 WebSphere 中的错误或违反 J2EE 规范的行为?
  • 如果不是,并且它是开发人员应该了解并编码的有效行为,是否在任何地方记录了这种行为?

谢谢

最佳答案

Servlet 2.5 MR6 包含一个 clarification问题中引用的 Servlet 规范部分:

Clarify SRV 7.7.1 "Threading Issues" (Issue 33)

Change the paragraph which currently is

"Multiple servlets executing request threads may have active access to a single session object at the same time. The Developer has the responsibility for synchronizing access to session resources as appropriate."

to read

"Multiple servlets executing request threads may have active access to the same session object at the same time. The container must ensure that manipulation of internal data structures representing the session attributes is performed in a threadsafe manner. The Developer has the responsibility for threadsafe access to the attribute objects themselves. This will protect the attribute collection inside the HttpSession object from concurrent access, eliminating the opportunity for an application to cause that collection to become corrupted."

这在 Servlet 3.0 MR1 中仍然是最新的,并且使 WAS 的行为看起来更合理。但是,我认为 *set*Attribute 可能是同步的,但 *get*Attribute 不是。

所以我认为答案是:

  • 根据 2.5 MR6 中的说明,WAS 符合 Servlet 规范
  • 规范为误解留下了空间
  • WAS 对其同步的热衷程度超出了规范和 AFAIK 的合理预期,这种行为在任何地方都没有明确记录

(附带说明,更改测试用例,以便 listener.attributeAdded() 调用 setAttribute 而不是 getAttribute 不会导致 JBoss 4 或 5 上的死锁。)

关于java - WebSphere 7 HTTPSession 实现是否违反了 J2EE 规范?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8989304/

相关文章:

java - 如何防止在数据库中快速连续创建重复条目? (日本公共(public)事务局)

java - 将任意文件安装到 Websphere 中所有节点的脚本接口(interface)?

java - 如何让我的800*600游戏适合所有屏幕分辨率?

java - 我在 Openshift 中的 DIY 应用程序的端口转发失败

java - 如何使用 Java Stream 在 Java 中查找包含某个单词的行数?

jakarta-ee - 比较OpenEjb和Glassfish

java - 支持 weblogic 中的多个 cookie 域

java - Websphere 8.5.0.2 抛出不兼容的 java 版本

java.lang.NoSuchMethodError : org/jboss/logging/Logger. 调试

java - Mybatis:如何将<include refid/>与mapper.xml中的String连接?