{"record":{"id":"4ff23ed8b1f0ea02","repo":"java-native-access/jna","slug":"jna-could-not-detach-thread-after-callback-handling","errorCode":null,"errorMessage":"JNA: could not detach thread after callback handling\n","messagePattern":"JNA: could not detach thread after callback handling\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"native/dispatch.c","lineNumber":2092,"sourceCode":"  }\n  else {\n    obj = (*env)->NewLocalRef(env, cb->object);\n    if ((*env)->IsSameObject(env, obj, NULL)) {\n      fprintf(stderr, \"JNA: callback object has been garbage collected\\n\");\n      if (cif->rtype->type != FFI_TYPE_VOID)\n        memset(resp, 0, cif->rtype->size);\n    }\n    else {\n      (*env)->CallVoidMethod(env, obj, MID_ffi_callback_invoke,\n                             A2L(cif), A2L(resp), A2L(argp));\n    }\n\n    (*env)->PopLocalFrame(env, NULL);\n  }\n\n  if (!attached) {\n    if ((*jvm)->DetachCurrentThread(jvm) != 0) {\n      fprintf(stderr, \"JNA: could not detach thread after callback handling\\n\");\n    }\n  }\n}\n\n////////////////////\n// API Methods\n////////////////////\n\n/*\n * Class:     com_sun_jna_Native\n * Method:    invokePointer\n * Signature: (Lcom/sun/jna/Function;JI[Ljava/lang/Object;)J\n */\nJNIEXPORT jlong JNICALL \nJava_com_sun_jna_Native_invokePointer (JNIEnv *env, jclass UNUSED(cls),\n                                       jobject UNUSED(function), jlong fp,\n                                       jint callconv, jobjectArray arr)\n{","sourceCodeStart":2074,"sourceCodeEnd":2110,"githubUrl":"https://github.com/java-native-access/jna/blob/d036ad9781adad4b66693e8fa7098e4ac665e0a3/native/dispatch.c#L2074-L2110","documentation":"After a native thread finished invoking a Java callback, JNA detaches that thread from the JVM if it attached it for the duration of the call. This message is printed to stderr when DetachCurrentThread returns non-zero, meaning the thread could not be detached and stays registered with the VM — a potential thread/JNI resource leak, though the callback itself already ran.","triggerScenarios":"A callback fired on a foreign native thread; JNA attached it, invoked the Java method, popped the local frame, and then DetachCurrentThread failed (other JNI references/monitors still held on the thread, or JVM is shutting down).","commonSituations":"Native libraries (media codecs, device SDKs, event loops) that invoke callbacks from their own pooled threads; JVM teardown racing with in-flight callbacks; reentrant callbacks that leave state on the thread.","solutions":["Avoid foreign-thread callbacks: have native code post events to a queue drained by a Java thread, so no attach/detach happens per callback.","Audit the callback for anything that leaves JNI references or monitors held on the thread (reentrancy, native mutexes acquired around the Java call).","Ensure native threads are quiesced before JVM shutdown so detach is attempted on a live VM.","Monitor thread counts to verify whether failed detaches are leaking, and restart or restructure if accumulation is observed."],"exampleFix":"// before: native lib calls Java callback directly on C thread\nnative_set_event_handler(cb); // each event: attach + detach\n// after: native fills a queue; Java thread drains it\nnative_start_queue_drain();\nnew Thread(() -> { while (running) handleEvent(native_pop_event()); }).start();","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Design native integrations so callbacks execute on Java-owned threads.","Avoid reentrant native mutexes held across Java callback invocations.","Track thread counts to detect leaks from repeated failed detaches."],"tags":["jni","jna","native","threads","callbacks","detach"],"backgroundTag":"jni-thread-detach-failed","analyzedSha":"d036ad9781adad4b66693e8fa7098e4ac665e0a3","analyzedAt":"2026-09-12T06:50:59.239Z","contentChangedAt":"2026-09-12T06:50:59.239Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}