{"record":{"id":"badf6f6197877346","repo":"java-native-access/jna","slug":"jna-can-t-attach-native-thread-to-vm-for-closure-handler","errorCode":null,"errorMessage":"JNA: Can't attach native thread to VM for closure handler\n","messagePattern":"JNA: Can't attach native thread to VM for closure handler\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"native/dispatch.c","lineNumber":2065,"sourceCode":"  }\n\n  if (throw_type) {\n    throwByName(env, throw_type, throw_msg);\n  }\n}\n\nstatic void\nclosure_handler(ffi_cif* cif, void* resp, void** argp, void *cdata)\n{\n  callback* cb = (callback *)cdata;\n  JavaVM* jvm = cb->vm;\n  JNIEnv* env;\n  jobject obj;\n  int attached = (*jvm)->GetEnv(jvm, (void *)&env, JNI_VERSION_1_4) == JNI_OK;\n\n  if (!attached) {\n    if ((*jvm)->AttachCurrentThread(jvm, (void *)&env, NULL) != JNI_OK) {\n      fprintf(stderr, \"JNA: Can't attach native thread to VM for closure handler\\n\");\n      return;\n    }\n  }\n\n  // Give the callback its own local frame to ensure all local references\n  // are properly disposed\n  if ((*env)->PushLocalFrame(env, 16) < 0) {\n    fprintf(stderr, \"JNA: Out of memory: Can't allocate local frame\");\n  }\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,","sourceCodeStart":2047,"sourceCodeEnd":2083,"githubUrl":"https://github.com/java-native-access/jna/blob/d036ad9781adad4b66693e8fa7098e4ac665e0a3/native/dispatch.c#L2047-L2083","documentation":"When a native callback (closure) fires on a thread the JVM has never seen, JNA tries AttachCurrentThread to obtain a JNIEnv. If attachment fails, it prints this message to stderr and aborts the callback invocation entirely — the Java callback method is never called and the native call site sees no invocation.","triggerScenarios":"A native library invokes a JNA Callback from a native thread (e.g. C thread pool, signal handler, OS timer) while AttachCurrentThread returns != JNI_OK, typically because the JVM is being destroyed or the thread cannot be registered.","commonSituations":"Callbacks fired during JVM shutdown; native audio/network/device driver threads calling back into Java; daemon native threads outliving the JVM; embedding JNA in a host app where the VM was destroyed before the native library finished.","solutions":["Ensure the JVM stays alive for the entire lifetime of the native code that can invoke callbacks; join/stop native threads before System.exit or library unload.","Route the callback through a Java-created thread (have Java own the event loop and poll/queue events) so AttachCurrentThread is never required on foreign threads.","Check that jvm pointer is valid and the library is loaded inside a running JVM, not after JNI_OnUnload.","Capture stderr around reproduction to confirm the failure happens only at shutdown, and defer callback unregistration until after native threads stop."],"exampleFix":"// before: unregister callback while native worker still runs\nruntime.exit(0); // JVM dies, in-flight callback attach fails\n// after: stop native work first\nnativeLib.stopWorker();\nworkerThread.join(5000);\nruntime.exit(0);","handlingStrategy":"validation","validationCode":"// Before enabling native-thread callbacks, verify the JVM outlives them:\nif (!jvmHealthy || shutdownInProgress) {\n  throw new IllegalStateException(\"Refusing to register native callback: JVM not guaranteed alive\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never let native threads outlive the JVM: stop and join them before shutdown hooks run.","Prefer queue-based event delivery over direct native-to-Java callbacks on foreign threads.","Test callback paths under rapid start/stop cycles to catch attach failures early."],"tags":["jni","jna","native","threads","callbacks","attach"],"backgroundTag":"jni-thread-attach-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"}