简体   繁体   中英

Unexpected state inside Java synchronized block

I've got the following situation in some open source code I'm investigating. It is using a synchronised block which does the following:

  1. sets a private boolean instance variable hasListener to true
  2. calls a method which does some wait()ing with a big try block around it which stores any exceptions inside an instance variable
  3. sets hasListener to false
  4. throws any exceptions previously stored

There is another similar method in the same class which does exactly the same thing.

In theory this should ensure that hasListener is only ever false when you enter. Yet somehow , an exception is being thrown at the bottom (see comment marked //?) because it enters the method and hasListener is true. I've checked that there are no other places where hasListener is set, and the default value is false. Are there any conceivable situations where throwing an exception at the bottom of waitFirstMessage() would prevent the variable being set back to false? Any other possible situations?

From the logs, it looks like a legitimate exception occurs ("Time to complete operation exceeded") and from that point on the exception (//?) is thrown fairly often.

protected void waitFirstMessage (int msgId) throws LDAPException {
    synchronized (this) {
        if (!hasListener) {
            hasListener = true;
            while ((request != null) && (request.id == msgId) &&
                (m_exception == null) && (response == null)) {
                waitForMessage();
            }        
            hasListener = false;
            // Network exception occurred ?
            if (m_exception != null) {
                LDAPException ex = m_exception;
                m_exception = null;
                throw ex;
            }
        } else {
            //?
            throw new LDAPException();
        }
    }
}

 private void waitForMessage () throws LDAPException {
    try {
        if (request.timeToComplete > 0) {
            long timeToWait = request.timeToComplete -
                System.currentTimeMillis();
            if (timeToWait > 0) {
                wait(timeToWait);
                if (notified) {
                    notified = false;
                } else if (request.timeToComplete < System.currentTimeMillis()) {
                    // Spurious wakeup before timeout.
                    return;
                } else {
                    request = null;
                    m_exception = new LDAPException(
                        "Time to complete operation exceeded",
                        LDAPException.LDAP_TIMEOUT);
                }
            } else {
                request = null;
                m_exception = new LDAPException(
                    "Time to complete operation exceeded",
                    LDAPException.LDAP_TIMEOUT);
            }
        } else {
            wait();
            notified = false;
        }
    } catch (InterruptedException e) {
        m_exception = new LDAPInterruptedException("Interrupted LDAP operation");
    } catch (Exception e) {
      m_exception = new LDAPException("Unexpected exception while waiting for response",
          LDAPException.OTHER, e.getMessage());
    }
}

EDIT

Ok, it turns out that my question was incorrect. The version currently running in production where the logs were coming from is slightly earlier than the code I was looking at and somebody has clearly addressed this very problem. In the previous version the waitForMessage() method was throwing exceptions. These were interrupting waitFirstMessage(int msgId) and then hasListener was never being set to false. The universe makes sense again.

Thanks a lot for the replies. Now I need to get this fix into production!

If waitForMessage completes abruptly by throwing an exception, hasListener = false will not be reached. If this code was meant to ensure that hasListener = false is executed, it should have placed that into a finally block.

hasListener would be left set to true in the case where waitForMessage throws. Since waitForMessage catches all Exceptions, this could IMHO just be the case when something gets thrown that is not an exception (some other Throwable ), or if an exception is thrown during instatiation of m_exception .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM