JDK-6603573 : Runtime abruptly terminates after passing api/java_awt/Toolkit/index.html#Cursor
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 6
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2007-09-12
  • Updated: 2012-03-22
  • Resolved: 2007-09-19
Related Reports
Duplicate :  
Description
Test Challenger Name and Company:

   Vitaly Mikheev, Excelsior LLC

Specification Name and Version:

   The Java Language Specification, 3rd Edition; Java Platform Standard Ed 6.0

Test Suite Name and Version:

   JCK 6a (29-May-2007, build b14)

TestName:
   api/java_awt/Toolkit/index.html#Cursor

COMPLAINT:

From time to time, the Runtime abruptly terminates after passing this test, that is, after it reported that all the test cases were successfully passed.

HOW TO REPRODUCE ON THE RI

It's hard to reproduce the problem on the Sun RI because its appearance strongly depends on timing effects and thread scheduling pictute.

ROOT CAUSE ANALYSIS:

Due to lack of proper synchronization during AWT resource cleanup upon application exit,
the destructor ~AwtCursor() may be invoked twice on the same C++ object.

It provokes access violation and Runtime crash.

DETAILS:

Upon test exit, a Java Runtime cleans up the native resources (OS hadles, C++ objects, C memory and the like) associated with AWT Java objects.

Event loop thread
-------------------------

After exiting the event loop in the method

    Java_sun_awt_windows_WToolkit_eventLoop

the cleanup is done by the Dispose() method

    Java_sun_awt_windows_WToolkit_eventLoop(JNIEnv *env, jobject self) {
       ....
       AwtToolkit::GetInstance().Dispose();
       ....
    }

which eventually invokes

void AwtObjectList::Cleanup()
{
    CriticalSection::Lock l(theAwtObjectList.m_lock);

    CriticalSection &syncCS = AwtToolkit::GetInstance().GetSyncCS();
    BOOL entered = syncCS.TryEnter();
    if (entered) {
        AwtObjectListItem* item = theAwtObjectList.m_head;
        while (item != NULL) {
            AwtObjectListItem* next = item->next;
            delete item->obj;   //delete #1
            item = next;
        }
        theAwtObjectList.m_head = NULL;
        syncCS.Leave();
    } else {
        ....
    }
}

Disposer thread (sun.java2d.Disposer.run)
-------------------------------------------------------------

At the same time, clean up is done in the disposer thread.

The class java.awt.Cursor has a disposer record associated with the class instances.

public class Cursor .... {
    static class CursorDisposer implements sun.java2d.DisposerRecord {
        long pData;
        public void dispose() {
            finalizeImpl(pData);
        }
    }

The dispose() method invokes finalizeImpl:

Java_java_awt_Cursor_finalizeImpl(JNIEnv *env, jclass clazz, jlong pData)
{
    ....
    AwtToolkit::GetInstance().SyncCall(AwtCursor::_Dispose, jlong_to_ptr(pData));
    ....
}

that invokes this callback method

void AwtCursor::_Dispose(void *param) {
    AwtCursor *c = (AwtCursor *)param;
    if (c != NULL) {
        JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

        jobject localObj = env->NewLocalRef(c->jCursor);
        if (localObj != NULL) {
            setPData(localObj, ptr_to_jlong(NULL));
            env->DeleteLocalRef(localObj);
        }

        delete c;  //delete #2
    }
}

wrapped into a synchronization function.

void *AwtToolkit::SyncCall(void *(*ftn)(void *), void *param) {
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    if (!IsMainThread()) {
        CriticalSection::Lock l(GetSyncCS());
        return (*ftn)(param);
    } else {
        return (*ftn)(param);
    }
}

-------------------

The same C++ object (instance of AwtCursor) is placed in both:

   - AwtObjectListItem processed in the event loop
   - the disposer record associated with a java.awt.Cursor instance

-------------------

The thread scheduling picture that causes the double delete of the same C++ object is as follows.

1) AwtObjectList::Cleanup() invoked in the event loop thread enters in the critical section

   CriticalSection &syncCS = AwtToolkit::GetInstance().GetSyncCS();
    BOOL entered = syncCS.TryEnter();

   and starts the cleanup of the AwtObjectListItem

2) At the same time, the disposer of a java.awt.Cursor instance is invoked in the disposer thread

   The disposer is blocked on the critical section owned by AwtObjectList::Cleanup()

3) AwtObjectList::Cleanup() finishes the cleanup and releases the critical section

4) AwtCursor::_Dispose continues execution and tries to delete the object passed to it as "param" despite the object has been already deleted in AwtObjectList::Cleanup()

Comments
EVALUATION Special thanks to the submitter for such a deep analysis. This problem is fixed with 6507549 in 7.0-b09 and backported to 6u4.
19-09-2007