Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't execute JavaVM->DetachCurrentThread(): "attempting to detach while still running code"

I have an Android app that uses NDK - a regular Android Java app with regular UI and C++ core. There are places in the core where I need to call Java methods, which means I need a JNIEnv* for that thread, which in turn means that I need to call JavaVM->AttachCurrentThread() to get the valid env.

Previously, was just doing AttachCurrentThread and didn't bother to detach at all. It worked fine in Dalvik, but ART aborts the application as soon as a thread that has called AttachCurrentThread exits without calling DetachCurrentThread. So I've read the JNI reference, and indeed it says that I must call DetachCurrentThread. But when I do that, ART aborts the app with the following message:

attempting to detach while still running code

What's the problem here, and how to call DetachCurrentThread properly?

like image 279
Violet Giraffe Avatar asked Jan 13 '15 14:01

Violet Giraffe


2 Answers

Dalvik will also abort if the thread exits without detaching. This is implemented through a pthread key -- see threadExitCheck() in Thread.cpp.

A thread may not detach unless its call stack is empty. The reasoning behind this is to ensure that any resources like monitor locks (i.e. synchronized statements) are properly released as the stack unwinds.

The second and subsequent attach calls are, as defined by the spec, low-cost no-ops. There's no reference counting, so detach always detaches, no matter how many attaches have happened. One solution is to add your own reference-counted wrapper.

Another approach is to attach and detach every time. This is used by the app framework on certain callbacks. This wasn't so much a deliberate choice as a side-effect of wrapping Java sources around code developed primarily in C++, and trying to shoe-horn the functionality in. If you look at SurfaceTexture.cpp, particularly JNISurfaceTextureContext::onFrameAvailable(), you can see that when SurfaceTexture needs to invoke a Java-language callback function, it will attach the thread, invoke the callback, and then if the thread was just attached it will immediately detach it. The "needsDetach" flag is set by calling GetEnv to see if the thread was previously attached.

This isn't a great thing performance-wise, as each attach needs to allocate a Thread object and do some internal VM housekeeping, but it does yield the correct behavior.

like image 151
fadden Avatar answered Nov 19 '22 00:11

fadden


I'll try a direct and practical approach (with sample code, without use of classes) answering this question for the occasional developer that came up with this error in android, in cases where they had it working and after a OS or framework update (Qt?) it started to give problems with that error and message.

    JNIEXPORT void Java_com_package_class_function(JNIEnv* env.... {

        JavaVM* jvm;
        env->GetJavaVM(&jvm);

        JNIEnv* myNewEnv; // as the code to run might be in a different thread (connections to signals for example) we will have a 'new one'
        JavaVMAttachArgs jvmArgs;
        jvmArgs.version = JNI_VERSION_1_6;

        int attachedHere = 0; // know if detaching at the end is necessary
        jint res = jvm->GetEnv((void**)&myNewEnv, JNI_VERSION_1_6); // checks if current env needs attaching or it is already attached
        if (JNI_EDETACHED == res) {
            // Supported but not attached yet, needs to call AttachCurrentThread
            res = jvm->AttachCurrentThread(reinterpret_cast<JNIEnv **>(&myNewEnv), &jvmArgs);
            if (JNI_OK == res) {
                attachedHere = 1;
            } else {
                // Failed to attach, cancel
                return;
            }
        } else if (JNI_OK == res) {
            // Current thread already attached, do not attach 'again' (just to save the attachedHere flag)
            // We make sure to keep attachedHere = 0
        } else {
            // JNI_EVERSION, specified version is not supported cancel this..
            return;
        }

        // Execute code using myNewEnv
        // ...

        if (attachedHere) { // Key check
            jvm->DetachCurrentThread(); // Done only when attachment was done here
        }
    }

Everything made sense after seeing the The Invocation API docs for GetEnv:

RETURNS: If the current thread is not attached to the VM, sets *env to NULL, and returns JNI_EDETACHED. If the specified version is not supported, sets *env to NULL, and returns JNI_EVERSION. Otherwise, sets *env to the appropriate interface, and returns JNI_OK.

Credits to: - This question Getting error "attempting to detach while still running code" when calling JavaVm->DetachCurrentThread that in its example made it clear that it was necessary to double check every time (even though before calling detach it doesn't do it). - @Michael that in this question comments he notes it clearly about not calling detach. - What @fadden said: "There's no reference counting, so detach always detaches, no matter how many attaches have happened."

like image 28
DNax Avatar answered Nov 19 '22 00:11

DNax