02/10/2025 09:37, Morten Brørup:
> > +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_mbuf_history_dump_mempool, 25.11)
> > +void rte_mbuf_history_dump_mempool(FILE *f, struct rte_mempool *mp)
> > +{
> > +#if !RTE_MBUF_HISTORY_DEBUG
> > + RTE_SET_USED(f);
> > + RTE_SET_USED(mp);
> > + MBUF_LOG(INFO, "mbuf history recorder is not enabled");
> > +#else
> > + if (f == NULL) {
> > + MBUF_LOG(ERR, "Invalid mbuf dump file.");
> > + return;
> > + }
> > + if (mp == NULL) {
> > + fprintf(f, "ERROR: Invalid mempool pointer\n");
>
> Should be MBUF_LOG(ERR, ...), not fprintf().
>
> > + return;
> > + }
> > + if (rte_mbuf_history_field_offset < 0) {
> > + fprintf(f, "WARNING: mbuf history not initialized. Call
> > rte_mbuf_history_init() first.\n");
>
> Should be MBUF_LOG(ERR, ...), not fprintf().
>
> > + return;
> > + }
>
> Since the type of objects held in a mempool is not identifiable as mbufs, you
> should check that (mp->elt_size >= sizeof(struct rte_mbuf)). Imagine some
> non-mbuf mempool holding 64 byte sized objects, and
> rte_mbuf_history_field_offset being in the second cache line.
>
> You might want to log an error if called directly, and silently skip of
> called from rte_mbuf_history_dump_all(), so suggest adding a wrapper when
> calling this function through rte_mempool_walk().
Yes good idea.
> > + mbuf_history_get_stats(mp, f);
> > +#endif
> > +}
> [...]
>
> > +/**
> > + * Mark an mbuf with a history event.
> > + *
> > + * @param m
> > + * Pointer to the mbuf.
> > + * @param op
> > + * The operation to record.
> > + */
> > +static inline void rte_mbuf_history_mark(struct rte_mbuf *m, uint32_t
> > op)
> > +{
> > +#if !RTE_MBUF_HISTORY_DEBUG
> > + RTE_SET_USED(m);
> > + RTE_SET_USED(op);
> > +#else
> > + RTE_ASSERT(rte_mbuf_history_field_offset >= 0);
> > + RTE_ASSERT(op < RTE_MBUF_HISTORY_OP_MAX);
> > + if (unlikely (m == NULL))
> > + return;
> > +
> > + rte_mbuf_history_t *history_field = RTE_MBUF_DYNFIELD(m,
> > + rte_mbuf_history_field_offset, rte_mbuf_history_t *);
> > + uint64_t history = rte_atomic_load_explicit(history_field,
> > rte_memory_order_acquire);
> > + history = (history << RTE_MBUF_HISTORY_BITS) | op;
> > + rte_atomic_store_explicit(history_field, history,
> > rte_memory_order_release);
>
> This is not thread safe.
> Some other thread can race to update history_field after this thread loads
> it, so when this tread stores the updated history, the other thread's history
> update is overwritten and lost.
You're right.
I suppose this change was to align with the atomic read operation
done in the "get" function.
> To make it thread safe, you must use a CAS operation and start over if it
> failed.
By "failed", you mean if the previous value returned by the CAS operation
is different of what we used earlier to build our new value?
I suggest this:
rte_mbuf_history_t *history_field = RTE_MBUF_DYNFIELD(m,
rte_mbuf_history_field_offset, rte_mbuf_history_t *);
uint64_t old_history = rte_atomic_load_explicit(history_field,
rte_memory_order_acquire);
uint64_t new_history;
do {
new_history = (old_history << RTE_MBUF_HISTORY_BITS) | op;
} while (!rte_atomic_compare_exchange_weak_explicit(history_field,
&old_history, new_history,
rte_memory_order_release, rte_memory_order_relaxed));