Re: [PATCH 5/5] perf dso: Defer dropping the open list reference until after the lock

From: Ian Rogers

Date: Thu Sep 03 2026 - 13:23:46 EST


On Thu, Sep 3, 2026 at 6:23 AM Arnaldo Carvalho de Melo <acme@xxxxxxxxxx> wrote:
>
> From: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
>
> There was a problem in the code with some resources potentially being
> left unbalanced, and the logic on dso__data_close() becoming confused
> if the fd had been closed already.
>
> The reference taken by dso__list_add() on the open list cannot be
> dropped while holding the open lock: dso__put() may call
> dso__data_close(), which takes dso__data_open_lock() itself,
> deadlocking and leaving the list and its counter inconsistent for
> concurrent threads.
>
> Fix it by changing dso__list_del() to transfer the reference to a
> deferred node, drained by dso__put_deferred() right after every
> unlock of dso__data_open_lock(). Since the counter is now decremented
> under the open lock, do_open()'s close_first_dso() no longer races
> with a stale count.

So I'm not a fan of this change due to its complexity. There reference
counting with dso_data is funny, see:
https://lore.kernel.org/r/20240506180104.485674-5-irogers@xxxxxxxxxx
Basically a dso has a dso_data embedded within it. Perhaps the cleaner
fix is to allocate the dso_data, separate from the dso, and have a
reference to the dso from the dso_data. We should be able to scope our
lock usage and avoid deadlock without resorting to a deferral
mechanism.

Thanks,
Ian

> Reported-by: sashiko-bot <sashiko-bot@xxxxxxxxxx>
> Cc: Stephane Eranian <eranian@xxxxxxxxxx>
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
> ---
> tools/perf/util/dso.c | 75 ++++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 71 insertions(+), 4 deletions(-)
>
> diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
> index 42bfe30a3b518e80..a4b2361bc7420084 100644
> --- a/tools/perf/util/dso.c
> +++ b/tools/perf/util/dso.c
> @@ -531,18 +531,79 @@ static void dso__list_add(struct dso *dso) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_o
> dso__data_open_cnt++;
> }
>
> +#ifdef REFCNT_CHECKING
> +/*
> + * A deferred put: carries the reference taken by dso__list_add() for an
> + * entry removed from dso__data_open. Dedicated nodes are used so that
> + * the dso_data's own open_entry node can be relinked by a concurrent
> + * dso__list_add() without corrupting this list or its reference.
> + */
> +struct dso_data_put {
> + struct list_head entry;
> + struct dso *dso;
> +};
> +static LIST_HEAD(dso__data_open_put);
> +#endif
> +
> static void dso__list_del(struct dso *dso) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
> {
> - list_del_init(&dso__data(dso)->open_entry);
> #ifdef REFCNT_CHECKING
> - mutex_unlock(dso__data_open_lock());
> - dso__put(dso__data(dso)->dso);
> - mutex_lock(dso__data_open_lock());
> + struct dso_data_put *put;
> #endif
> +
> + list_del_init(&dso__data(dso)->open_entry);
> WARN_ONCE(dso__data_open_cnt <= 0,
> "DSO data fd counter out of bounds.");
> dso__data_open_cnt--;
> +#ifdef REFCNT_CHECKING
> + /*
> + * The reference taken in dso__list_add() cannot be dropped while
> + * holding the open lock: dso__put() may call dso__data_close(),
> + * which takes dso__data_open_lock itself, deadlocking and leaving
> + * the list/counter state inconsistent for concurrent threads.
> + * Transfer the reference to a deferred node drained by
> + * dso__put_deferred() once the lock is released.
> + */
> + put = zalloc(sizeof(*put));
> +
> + if (put == NULL)
> + return;
> +
> + put->dso = dso__data(dso)->dso;
> + dso__data(dso)->dso = NULL;
> + list_add_tail(&put->entry, &dso__data_open_put);
> +#endif
> +}
> +
> +#ifdef REFCNT_CHECKING
> +/*
> + * Drop the references deferred by dso__list_del(). Must be called
> + * without holding dso__data_open_lock: dso__put() may re-enter it via
> + * dso__data_close().
> + */
> +static void dso__put_deferred(void) LOCKS_EXCLUDED(_dso__data_open_lock)
> +{
> + for (;;) {
> + struct dso_data_put *put;
> + struct dso *dso;
> +
> + mutex_lock(dso__data_open_lock());
> + put = list_first_entry_or_null(&dso__data_open_put, struct dso_data_put, entry);
> + if (put == NULL) {
> + mutex_unlock(dso__data_open_lock());
> + return;
> + }
> + list_del_init(&put->entry);
> + dso = put->dso;
> + mutex_unlock(dso__data_open_lock());
> +
> + free(put);
> + dso__put(dso);
> + }
> }
> +#else
> +static void dso__put_deferred(void) {}
> +#endif
>
> static void close_first_dso(void);
>
> @@ -805,6 +866,7 @@ void dso__data_close(struct dso *dso)
> mutex_lock(dso__data_open_lock());
> close_dso(dso);
> mutex_unlock(dso__data_open_lock());
> + dso__put_deferred();
> }
>
> static void try_to_open_dso(struct dso *dso, struct machine *machine)
> @@ -865,12 +927,14 @@ bool dso__data_get_fd(struct dso *dso, struct machine *machine, int *fd)
> return true;
>
> mutex_unlock(dso__data_open_lock());
> + dso__put_deferred();
> return false;
> }
>
> void dso__data_put_fd(struct dso *dso __maybe_unused)
> {
> mutex_unlock(dso__data_open_lock());
> + dso__put_deferred();
> }
>
> bool dso__data_status_seen(struct dso *dso, enum dso_data_status_seen by)
> @@ -1058,6 +1122,7 @@ static ssize_t file_read(struct dso *dso, struct machine *machine,
> ret = pread(dso__data(dso)->fd, data, DSO__DATA_CACHE_SIZE, offset);
> out:
> mutex_unlock(dso__data_open_lock());
> + dso__put_deferred();
> return ret;
> }
>
> @@ -1188,6 +1253,7 @@ static int file_size(struct dso *dso, struct machine *machine)
>
> out:
> mutex_unlock(dso__data_open_lock());
> + dso__put_deferred();
> return ret;
> }
>
> @@ -1405,6 +1471,7 @@ uint16_t dso__e_machine_endian(struct dso *dso, struct machine *machine, uint32_
> *e_flags = 0;
>
> mutex_unlock(dso__data_open_lock());
> + dso__put_deferred();
> return e_machine;
> }
>
> --
> 2.55.0
>