userspace-rcu (URCU)

repository·master·Indexed 20 days ago

https://github.com/urcu/userspace-rcu

A library providing userspace implementations of Read-Copy-Update (RCU) mechanisms for high-performance, lock-free read access to shared data structures. It offers multiple implementation flavors (memb, qsbr, mb, bp) to balance performance and intrusiveness, along with a suite of concurrent data structures including RCU-based lists, lock-free stacks, queues, and a resizable RCU hash table.

Tokens
5.3K
Snippets
11
Records
26
Agent score
73%

What's inside userspace-rcu

  1. Use Lock-Free and Wait-Free Data Structures

    master

    The library includes several high-performance structures that do not strictly rely on RCU for their core mechanics, but offer different synchronization guarantees:

    Stacks

    • urcu/wfstack.h: Provides wait-free push and wait-free pop_all. Supports both blocking and non-blocking pop and traversal. Note: Requires external synchronization techniques to handle the ABA problem during pop.
    • urcu/lfstack.h: Provides lock-free push and lock-free pop, along with wait-free pop_all and wait-free traversal. (Note: This deprecates urcu/rculfstack.h). Requires external synchronization to handle ABA.

    Queues

    • urcu/wfcqueue.h: Concurrent queue with wait-free enqueue. Supports blocking/non-blocking dequeue, splice (moving all elements from one queue to another), and traversal. Uses mutual exclusion for dequeue, splice (from source), and traversal. (Note: This deprecates urcu/wfqueue.h).
    • urcu/rculfqueue.h: RCU-based queue with lock-free enqueue and lock-free dequeue. Uses RCU to provide existence guarantees.
  2. Choose a Concurrent Data Structure (CDS) API

    master

    The urcu library provides several concurrent data structures categorized by their synchronization requirements and performance characteristics. When choosing a structure, consider whether you need RCU-based read traversals, lock-free/wait-free properties, or specific structures like hash tables.

    RCU-based Lists

    • urcu/list.h: Standard doubly-linked list. Requires mutual exclusion for both updates and reads.
    • urcu/rculist.h: Doubly-linked list that allows RCU read traversals. Requires mutual exclusion for updates, but reads can be performed without locks.
    • urcu/hlist.h: Doubly-linked list with a single-pointer head (useful for hash tables). Requires mutual exclusion for updates and reads. Tail lookup is $O(n)$.
    • urcu/rcuhlist.h: Doubly-linked list with a single-pointer head. Allows RCU read traversals. Requires mutual exclusion for updates. Tail lookup is $O(n)$.
  3. Wait for RCU grace periods

    master

    RCU provides two main ways to wait for grace periods (the time during which all pre-existing read-side critical sections have completed):

    Blocking Wait

    Use synchronize_rcu() to block the current thread until every pre-existing RCU read-side critical section has completed. Note that this is not a reader-writer lock; it does not necessarily wait for critical sections that have not yet started.

    Polling Wait

    If you need to check for completion without blocking, use the polling API. This requires the caller to be a registered RCU read-side thread (and for the QSBR flavor, the caller must be online).

    1. Call start_poll_synchronize_rcu() to obtain a struct urcu_gp_poll_state handle.
    2. Periodically call poll_state_synchronize_rcu(state) with that handle. It returns true if the grace period has completed, and false otherwise.
    // Polling example
    struct urcu_gp_poll_state state = start_poll_synchronize_rcu();
    while (!poll_state_synchronize_rcu(state)) {
        // Do other work or sleep
    }
  4. Choose the right liburcu flavor

    master

    Userspace RCU provides several flavors of the RCU implementation. The API members for a specific flavor are prefixed with urcu_<flavor>_. Choose the one that best fits your performance and intrusiveness requirements:

    FlavorHeaderLinkingCharacteristics
    memb<urcu/urcu-memb.h>-lurcu-membPreferred. Fast grace-period detection and read-side speed. Uses sys_membarrier() if available.
    qsbr<urcu/urcu-qsbr.h>-lurcu-qsbrFastest read-side. Requires reader threads to call urcu_qsbr_quiescent_state() periodically. Highly intrusive.
    mb<urcu/urcu-mb.h>-lurcu-mbUses memory barriers on both writer and reader sides. Faster grace-period detection but slower reads.
    bp<urcu/urcu-bp.h>-lurcu-bpBulletproof. Designed for tracing libraries to hook applications without modification. High overhead.
  5. Memory barrier behavior in the uatomic API

    master

    When using the <urcu/uatomic.h> API, it is critical to understand which operations provide memory barriers.

    Full Memory Barriers: The following operations imply a full memory barrier both before and after the atomic operation:

    • uatomic_xchg()
    • uatomic_cmpxchg()
    • uatomic_add_return()
    • uatomic_sub_return()

    No Guaranteed Barriers: Other primitives (like uatomic_set, uatomic_read, uatomic_and, uatomic_or, uatomic_add, uatomic_sub, uatomic_inc, and uatomic_dec) do not guarantee any memory barrier. If your logic requires memory ordering, you must use the explicit barrier functions provided by the library.

  6. Avoid deadlocks with Mutexes and RCU

    master

    Interactions between RCU synchronization and mutexes can cause deadlocks. Follow these rules:

    1. The Dependency Rule: If you call urcu_<flavor>_synchronize_rcu() while holding a mutex, that mutex (and any mutex in its dependency chain) must not be acquired from within an RCU read-side critical section.
    2. QSBR Specifics: In the QSBR flavor, a registered reader thread is considered to be in a read-side critical section by default unless explicitly put 'offline'. Therefore, if urcu_qsbr_synchronize_rcu() is called with a mutex held, that mutex should only be taken when the RCU reader thread is 'offline' (using urcu_qsbr_thread_offline()).
  7. Supported atomic types and architecture constraints

    master

    The uatomic API supports atomic operations on integers (int and long, both signed and unsigned) on all architectures.

    Architecture-specific support: Some architectures support additional sizes. You can check for support using these macros defined when uatomic.h is included:

    • UATOMIC_HAS_ATOMIC_BYTE: 1-byte support
    • UATOMIC_HAS_ATOMIC_SHORT: 2-byte support
    • UATOMIC_HAS_ATOMIC_LLONG: 8-byte support

    Requirements for type:

    • The type must be at most word-sized.
    • The alignment of addr must be greater than or equal to its size.
    • Attempting an atomic operation on an unsupported type size will result in a compile-time static assert.
  8. Implement RCU reader and writer patterns

    master

    Regardless of the flavor chosen, the core RCU lifecycle follows these patterns:

    1. Thread Registration

    Every thread that performs reader critical sections must register with the library:

    • Call urcu_<flavor>_register_thread() before entering critical sections.
    • Call urcu_<flavor>_unregister_thread() before the thread exits.

    2. Reading (Reader Critical Section)

    Protect reads using lock/unlock pairs. Inside the lock, use rcu_dereference() to safely access protected pointers:

    // Example pattern
    urcu_<flavor>_read_lock();
    // ... perform reads using rcu_dereference(ptr) ...
    urcu_<flavor>_read_unlock();

    3. Writing (Updating Data)

    Updates are performed by assigning new pointers and then waiting for the grace period to end:

    • Use rcu_assign_pointer() or rcu_xchg_pointer() to update pointers.
    • Call urcu_<flavor>_synchronize_rcu() to wait for the grace period. Once this returns, the old values are safe to reclaim.

    Polling Alternative: Instead of blocking in synchronize_rcu(), you can poll for completion:

    1. Call urcu_<flavor>_start_poll_synchronize_rcu() to start polling.
    2. Periodically call urcu_<flavor>_poll_state_synchronize_rcu(). It returns true when the grace period is complete, false otherwise.
  9. Compare liburcu ABI versions using abidiff

    master

    You can use the serialized ABI definitions provided in the extras/abi/ directory to check for breaking changes in the liburcu libraries. By comparing a serialized ABI file (generated via libabigail) against a specific shared object (.so), you can identify changes in the Application Binary Interface (ABI).

    To compare an in-tree built version of liburcu-memb.so with the serialized ABI of version 0.13, use the abidiff command.

    abidiff \
      extras/abi/0.13/x86_64-pc-linux-gnu/liburcu-memb.so.8.xml \
      src/.libs/liburcu-memb.so
  10. Build and install Userspace RCU

    master

    To build and install the library from a Git tree, follow these steps:

    1. Prepare the tree: ./bootstrap (skip if using a tarball).
    2. Configure: ./configure.
    3. Compile: make.
    4. Install: make install.
    5. Update linker cache: ldconfig.

    Customizing the build via CFLAGS:

    • 32-bit build: CFLAGS="-m32 -g -O2" ./configure
    • 64-bit build: CFLAGS="-m64 -g -O2" ./configure
    • Sparcv9 32-bit: CFLAGS="-m32 -Wa,-Av9a -g -O2" ./configure
    ./bootstrap
    ./configure
    make
    make install
    ldconfig
  11. Initialize and manage RCU read-side critical sections

    master

    To use the Userspace RCU API, you must first initialize the library. Read-side critical sections allow multiple threads to access data concurrently without locking. These sections can be nested.

    1. Call rcu_init() once before any other RCU functions.
    2. Use rcu_read_lock() to enter a critical section.
    3. Use rcu_read_unlock() to exit the critical section.
    4. If your thread calls rcu_read_lock(), it must be registered using rcu_register_thread() before the first call.
    5. Registered threads must call rcu_unregister_thread() before exiting (e.g., before pthread_exit() or returning from the top-level function).
    rcu_init();
    
    // In a thread:
    rcu_register_thread();
    
    rcu_read_lock();
    // ... perform RCU-protected reads ...
    rcu_read_unlock();
    
    rcu_unregister_thread();
  12. Use liburcu-memb (Preferred flavor)

    master

    liburcu-memb is the recommended version due to its balance of speed and flexibility. It dynamically detects kernel support for sys_membarrier(). If unsupported, it falls back to the urcu-mb scheme.

    Setup:

    1. Include #include <urcu/urcu-memb.h>
    2. Link with -lurcu-memb

    Note: To prevent falling back to the slower urcu-mb scheme, use the --disable-sys-membarrier-fallback configure option. This will cause the library to abort in the constructor if sys_membarrier() is unavailable.

    # Example linking command
    gcc my_app.c -lurcu-memb