DPDK implementation of MPSC ring buffer

While going through the implementation of the DPDK MPSC (multi-produce & single-consumer) Ring Buffer API, i found the code to move the head of the producer for inserting new elements in the Ring buffer. The function is as follows :

    static __rte_always_inline unsigned int
    __rte_ring_move_prod_head(struct rte_ring *r, unsigned int is_sp,
            unsigned int n, enum rte_ring_queue_behavior behavior,
            uint32_t *old_head, uint32_t *new_head,
            uint32_t *free_entries)
    {
        const uint32_t capacity = r->capacity;
        uint32_t cons_tail;
        unsigned int max = n;
        int success;
    
        *old_head = __atomic_load_n(&r->prod.head, __ATOMIC_RELAXED);
        do {
            /* Reset n to the initial burst count */
            n = max;
    
            /* Ensure the head is read before tail */
            __atomic_thread_fence(__ATOMIC_ACQUIRE);
    
            /* load-acquire synchronize with store-release of ht->tail
             * in update_tail.
             */
            cons_tail = __atomic_load_n(&r->cons.tail,
                        __ATOMIC_ACQUIRE);
    
            /* The subtraction is done between two unsigned 32bits value
             * (the result is always modulo 32 bits even if we have
             * *old_head > cons_tail). So 'free_entries' is always between 0
             * and capacity (which is < size).
             */
            *free_entries = (capacity + cons_tail - *old_head);
    
            /* check that we have enough room in ring */
            if (unlikely(n > *free_entries))
                n = (behavior == RTE_RING_QUEUE_FIXED) ?
                        0 : *free_entries;
    
            if (n == 0)
                return 0;
    
            *new_head = *old_head + n;
            if (is_sp)
                r->prod.head = *new_head, success = 1;
            else
                /* on failure, *old_head is updated */
                success = __atomic_compare_exchange_n(&r->prod.head,
                        old_head, *new_head,
                        0, __ATOMIC_RELAXED,
                        __ATOMIC_RELAXED);
        } while (unlikely(success == 0));
        return n;
    }

The load and compare exchange of the producer's head is done using __ATOMIC_RELAXED memory ordering. Isn't this a problem when multiple producers from different threads produce to the queue. Or am I missing something?

https://doc.dpdk.org/guides/prog_guide/ring_lib.html describes the basic mechanism that DPDK uses for implementing the Ring buffer.