Re: shared-memory based stats collector

From: Andres Freund <andres(at)anarazel(dot)de>
To: Kyotaro Horiguchi <horikyota(dot)ntt(at)gmail(dot)com>
Cc: masao(dot)fujii(at)oss(dot)nttdata(dot)com, gkokolatos(at)protonmail(dot)com, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: shared-memory based stats collector
Date: 2021-03-19 21:27:38
Message-ID: 20210319212738.bxabibx6sybzadrj@alap3.anarazel.de
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

On 2021-03-10 20:26:56 -0800, Andres Freund wrote:
> > + shhashent = dshash_find_extended(pgStatSharedHash, &key,
> > + create, nowait, create, &shfound);
> > + if (shhashent)
> > + {
> > + if (create && !shfound)
> > + {
> > + /* Create new stats entry. */
> > + dsa_pointer chunk = dsa_allocate0(area,
> > + pgstat_sharedentsize[type]);
> > +
> > + shheader = dsa_get_address(area, chunk);
> > + LWLockInitialize(&shheader->lock, LWTRANCHE_STATS);
> > + pg_atomic_init_u32(&shheader->refcount, 0);
> > +
> > + /* Link the new entry from the hash entry. */
> > + shhashent->body = chunk;
> > + }
> > + else
> > + shheader = dsa_get_address(area, shhashent->body);
> > +
> > + /*
> > + * We expose this shared entry now. You might think that the entry
> > + * can be removed by a concurrent backend, but since we are creating
> > + * an stats entry, the object actually exists and used in the upper
> > + * layer. Such an object cannot be dropped until the first vacuum
> > + * after the current transaction ends.
> > + */
> > + dshash_release_lock(pgStatSharedHash, shhashent);
>
> I don't think you can safely release the lock before you incremented the
> refcount? What if, once the lock is released, somebody looks up that
> entry, increments the refcount, and decrements it again? It'll see a
> refcount of 0 at the end and decide to free the memory. Then the code
> below will access already freed / reused memory, no?

Yep, it's not even particularly hard to hit:

S0: CREATE TABLE a_table();
S0: INSERT INTO a_table();
S0: disconnect
S1: set a breakpoint to just after the dshash_release_lock(), with an
if objid == a_table_oid
S1: SELECT pg_stat_get_live_tuples('a_table'::regclass);
(will break at above breakpoint, without having incremented the
refcount yet)
S2: DROP TABLE a_table;
S2: VACUUM pg_class;

At that point S2's call to pgstat_vacuum_stat() will find the shared
stats entry for a_table, delete the entry from the shared hash table,
see that the stats data has a zero refcount, and free it. Once S1 wakes
up it'll use already freed (and potentially since reused) memory.

Greetings,

Andres Freund

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Andres Freund 2021-03-19 21:29:46 Re: [HACKERS] Custom compression methods
Previous Message Tomas Vondra 2021-03-19 21:19:49 Re: [HACKERS] Custom compression methods