Skip to content

Latest commit

 

History

History
60 lines (52 loc) · 3.41 KB

thread_safety.md

File metadata and controls

60 lines (52 loc) · 3.41 KB

Thread Safety in Stumpless

As of version 2.0, stumpless is completely thread safe. All functions provided by the library are documented with their safety level using safety attributes described in the POSIX standard and conditional safety features described by some GNU libc manual (find the relevant section here ).

In addition to thread safety, the async safety and async cancellation safety is also documented. However, most functions are not safe in either of these situations. You can look at any function documentation in the public header files for examples of how this is formatted.

Implementation Approach

There are a number of ways to achieve thread safety, and stumpless uses a combination of them to balance performance and simplicity. You can find the symbols for working with each of these types in the private/config/wrapper/thread_safety.h include file.

Mutexes

Most structs are protected with a mutex, which is used to coordinate reads and writes to all members of the struct. Some targets also use a (separate) mutex to coordinate writes, such as the file and stream targets. These locks are held for as short of a time as possible. Especially try to avoid holding a mutex during expensive operations like memory allocations.

Mutexes are defined by the typdef config_mutex_t, and there are four functions for working with them. config_lock_mutex and config_unlock_mutex acquire and release the mutex respectively. config_init_mutex sets up the mutex for first use, and config_destroy_mutex cleans it up when it is no longer needed. The init and destroy functions must be called at the beginning and end of the mutex's life. For a good example of how mutexes can be used, check out the slab cache implementation in src/cache.c and include/private/cache.h, which uses a mutex to make each cache thread safe.

Thread Local Storage

Some structures do not necessarily need to be available to all threads all the time, and can use more efficient safety mechanisms instead. Good examples of this are the per-thread error structures and the cached entry struct used by string-based logging functions like stump. These are place in thread-local storage, and therefore no locking is needed on them. If you can use this strategy, it is less susceptible to contention and will be faster in most cases.

You can declare any variable as thread-local using the CONFIG_THREAD_LOCAL_STORAGE modifier in its definition. After doing this, the variable will work just as normal and no other special work is needed.

Atomic Types

Atomic values are the final strategy used in implementations. These can be used to have a single field that can be read and written without a lock to synchronize access. This synchronization method can be the most performant of the options, but are also tricky to use correctly. The implementatin of stumpless_get_default_target is a good example of this, requiring more logic than a simple read and update in order to avoid a potential memory leak.

Atomic types are defined for each type needed, and come with functions for reading, writing, and doing a compare and exchange operation. You will also need to intialize the variable when it is defined, using the appropriate initializer. The current and default target variables in src/target.c are good examples of how to create and work with an atomic type.