Zephyr OS Thread-Local Storage: Verify State Isolation Without Guessing from errno
Two threads calling the same library function do not necessarily share every internal variable, but enabling thread-local storage does not make an arbitrary library thread-safe. Verify which state is actually per-thread and which remains shared. In Zephyr, even errno can be per-thread through more than one storage mechanism, so its behavior alone does not prove compiler TLS is enabled.
This article uses Zephyr 4.4.0 and focuses on statically integrated firmware libraries. Runtime loading of shared objects and their TLS models is a separate problem. No target test or library certification is claimed here.

Read the effective support chain
The tagged kernel configuration makes THREAD_LOCAL_STORAGE depend on both architecture and toolchain support. A source declaration is therefore only one part of the contract. Inspect the generated configuration, selected compiler, architecture, and library build options used for the actual executable.
Do not assume that a prebuilt library was compiled with the same assumptions as the application. If its state model is unclear, consult its build configuration or source. A successful link can establish symbol compatibility without demonstrating that two concurrent callers get independent state.
Keep the review specific: identify the variable or library operation whose isolation matters. “TLS enabled” is not a useful answer to a question about a separate static scratch buffer that never uses TLS.
Do not use errno as a shortcut
Zephyr's configuration distinguishes external-libc errno from the kernel's errno support. ERRNO_IN_TLS selects TLS storage only when its dependencies permit it; the documented alternative is storage in the kernel thread structure. Consequently, observing independent errno values is compatible with more than one implementation.
Test the actual state of interest. If a library documents a per-thread error slot, verify that slot through its public interface. If the problem is a shared conversion buffer or parser context, an errno test says little about it.
Also separate the function's return value from errno semantics. Do not assume every successful call clears errno or every API uses it. Use the selected library's documented contract when designing an error-state test.
Interleave two callers deliberately
A useful proposed isolation test creates two threads that write distinct values into the intended thread-local state. Synchronize them so both assignments occur before either verifies its result, then alternate further updates. This avoids a test that passes simply because one thread completes before the other begins.
For a library API, choose operations with distinguishable state and inspect only behavior the API promises. A hypothetical formatter returning a pointer to a shared static buffer should not be called “broken TLS” merely because the caller assumed the buffer was private.
Keep the test's synchronization separate from the state being tested. A global lock around every library call may prevent interference while hiding the absence of per-thread storage. That can be a valid production policy, but it answers a different question.
Include thread creation and lifetime
The tagged thread setup implementation invokes architecture TLS setup when configured and accounts for its storage within the thread stack-buffer setup. TLS is therefore part of thread construction and memory accounting, not an unlimited pool detached from the thread's resources.
Inspect memory requirements when adding a large thread-local object or increasing the number of threads. Do not derive usable call-stack capacity solely from the source-level stack declaration without understanding the target's layout and accounting.
If the product creates and destroys workers, include that lifecycle in testing. A pointer retained after the owning thread ends does not remain a safe access path merely because its original variable was thread-local. Prefer transferring an owned value rather than exporting a lifetime-dependent address.
Keep thread safety broader than TLS
A library may use private error state while still sharing a hardware device, heap, cache, or global configuration. Those resources need their own concurrency contract. Conversely, a library can be thread-safe through explicit caller-provided contexts without relying on compiler TLS at all.
The useful conclusion names the mechanism and evidence: this state is isolated per thread under this build, while these resources remain shared. That supports an integration decision without confusing one successful errno experiment with proof that the entire library is reentrant.