{"record":{"id":"4c618666ae5165e0","repo":"java-native-access/jna","slug":"jna-couldn-t-obtain-thread-local-storage","errorCode":null,"errorMessage":"JNA: couldn't obtain thread-local storage\n","messagePattern":"JNA: couldn't obtain thread-local storage\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"native/callback.c","lineNumber":738,"sourceCode":"      }\n      fprintf(stderr, \"JNA: Can't attach native thread to VM for callback: %d (check stacksize for callbacks)\\n\", attach_status);\n      return;\n    }\n    tls = get_thread_storage(env);\n    if (tls) {\n      snprintf(tls->name, sizeof(tls->name), \"%s\", args.name ? args.name : \"<unconfigured native thread>\");\n      tls->needs_detach = needs_detach;\n      tls->jvm_thread = JNI_FALSE;\n    }\n    // Dispose of allocated memory\n    free((void *)args.name);\n    if (args.group) {\n      (*env)->DeleteWeakGlobalRef(env, args.group);\n    }\n  }\n\n  if (!tls) {\n    fprintf(stderr, \"JNA: couldn't obtain thread-local storage\\n\");\n    return;\n  }\n\n  // Give the callback glue 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  }\n  else {\n    invoke_callback(env, cb, cif, resp, cbargs);\n    // Make note of whether the callback wants to avoid detach\n    needs_detach = tls->needs_detach && !tls->jvm_thread;\n    (*env)->PopLocalFrame(env, NULL);\n  }\n  \n  if (needs_detach) {\n    if ((*jvm)->DetachCurrentThread(jvm) != 0) {\n      fprintf(stderr, \"JNA: could not detach thread\\n\");","sourceCodeStart":720,"sourceCodeEnd":756,"githubUrl":"https://github.com/java-native-access/jna/blob/d036ad9781adad4b66693e8fa7098e4ac665e0a3/native/callback.c#L720-L756","documentation":"After attaching, dispatch_callback calls get_thread_storage to fetch the thread's TLS block (jvm pointer, name, flags). If it returns NULL, JNA prints this message and returns without invoking the callback. Without TLS, JNA cannot track whether the thread needs detaching or set per-thread state for the callback glue.","triggerScenarios":"TlsAlloc/tls allocation failed or the key lookup returned NULL on the just-attached native thread — e.g. TLS slots exhausted, TlsGetValue returning NULL with no stored value, or JNA native state not initialized in this thread/DLL context.","commonSituations":"Windows TLS slot exhaustion from many DLLs using TlsAlloc, callbacks fired on threads in a different DLL context where JNA's per-thread init never ran, native thread teardown racing with callback dispatch.","solutions":["Reduce the number of DLLs/threads consuming TLS slots on Windows; check for TLS leaks in native code.","Ensure JNA's native library is fully initialized (JNA_callback_init ran) before callbacks fire on that thread.","Retry callback dispatch on a Java-managed thread instead of a raw native thread.","Update JNA — get_thread_storage handles allocation/retry differently across versions."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ensure JNA's native library initializes once and only once per process","On Windows, audit DLLs for TLS slot leaks (TlsAlloc without TlsFree)","Dispatch callbacks on long-lived, well-initialized threads rather than ad-hoc threads","Update JNA to get TLS robustness fixes"],"tags":["jni","threading","tls","callback"],"backgroundTag":"thread-local-storage-unavailable","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"}