Re: race condition in pg_class
Noah Misch <noah@leadboat.com>
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Replace tests of ALTER DATABASE RESET TABLESPACE.
- 4adb0380b9bf 19 (unreleased) landed
-
meson: Flush stdout in testwrap
- d9bc60705274 16.9 landed
- 9de86e367a67 17.5 landed
- 5941946d0934 18.0 landed
-
Fix catcache invalidation of a list entry that's being built
- af8cd1639ab2 18.0 cited
-
Stop reading uninitialized memory in heap_inplace_lock().
- a0c0078b1355 12.21 landed
- ad24b7565910 14.14 landed
- 3e5ea478d875 13.17 landed
- d34ffbaa1029 15.9 landed
- 2d63c964f010 16.5 landed
- e11907682846 17.1 landed
- e947224cbbce 18.0 landed
-
Fix use of uninitialized value in previous commit.
- 0cf3d41cbb42 15.9 landed
- 90f5412a9a51 16.5 landed
-
Back-patch "Refactor code in tablecmds.c to check and process tablespace moves"
- dc845383cd12 12.21 landed
- 1299564cb9c7 13.17 landed
-
Fix data loss at inplace update after heap_update().
- 7354b680ab64 12.21 landed
- a8ad1929d2ec 13.17 landed
- 8590c942c1a6 15.9 landed
- 82c2d9e02206 14.14 landed
- 63f01980560a 16.5 landed
- fd27b878c2ea 17.1 landed
- a07e03fd8fa7 18.0 landed
-
For inplace update durability, make heap_update() callers wait.
- cafcc3ad0ed3 12.21 landed
- 14c57cb63907 13.17 landed
- f51b34b3eddb 14.14 landed
- 5c837f8fa022 15.9 landed
- 51ff46de29f6 16.5 landed
- 3b7a689e1a80 17.1 landed
- aac2c9b4fde8 18.0 landed
-
Warn if LOCKTAG_TUPLE is held at commit, under debug_assertions.
- b779d37a3db2 12.21 landed
- 5ef9b4a2f796 13.17 landed
- c9de1b8ac8df 14.14 landed
- 41e0ba33d5ab 15.9 landed
- acc63ef4fbe6 16.5 landed
- f33bf1c7d501 17.1 landed
- dbf3f974ee04 18.0 landed
-
Don't lose partitioned table reltuples=0 after relhassubclass=f.
- e81deeefcac2 16.4 landed
- 2b4a2a79edd2 15.8 landed
- 2b415e95a8a7 14.13 landed
- f5bb46fb2e65 17.0 landed
- 7102070329d8 18.0 landed
-
Fix new assertion for MERGE view_name ... DO NOTHING.
- 0d3b35c367c2 17.0 landed
- d5e6891502ca 18.0 landed
-
Remove configuration-dependent output from new inplace-inval test.
- 79567599cd21 12.20 landed
- f31c1e34bc80 14.13 landed
- ca30e8ba958a 13.16 landed
- 7857c8dc4481 16.4 landed
- 458fada72b32 15.8 landed
- b99414de9052 17.0 landed
-
AccessExclusiveLock new relations just after assigning the OID.
- 7af775bbae57 12.20 landed
- e038b7756df8 14.13 landed
- b899cde0b86a 13.16 landed
- 0f2835ea6fc1 15.8 landed
- fc8c25806e84 16.4 landed
- 5b823b179e5e 17.0 landed
-
Cope with inplace update making catcache stale during TOAST fetch.
- 7a21306aee0a 13.16 landed
- 11f3815d6af8 12.20 landed
- af73e37fa181 14.13 landed
- b08a4b6163eb 15.8 landed
- e4afd7153bd8 16.4 landed
- f9f47f0d93d1 17.0 landed
-
Expand comments and add an assertion in nodeModifyTable.c.
- fea47de3016a 12.20 landed
- c3437fb7255b 13.16 landed
- ad4653268c12 14.13 landed
- f0544432c3cc 15.8 landed
- f8135d8b54ff 16.4 landed
- d5f788b41dc2 17.0 landed
-
Improve test coverage for changes to inplace-updated catalogs.
- 796d191028bb 12.20 landed
- 65aae93c456a 13.16 landed
- 2d06bdbc0cc3 14.13 landed
- 3c6becd3d180 15.8 landed
- dd8008e8ec95 16.4 landed
- 0844b3968985 17.0 landed
-
Lock before setting relhassubclass on RELKIND_PARTITIONED_INDEX.
- e2eefb97e31a 12.20 landed
- 1a6d65b64e84 13.16 landed
- 578db9c9257d 14.13 landed
- a338e41374e4 15.8 landed
- 480b58fabd35 16.4 landed
- 0cecc908e974 17.0 landed
-
Lock owned sequences during ALTER TABLE SET { LOGGED | UNLOGGED }.
- 24561b498f11 15.8 landed
- 112d055706bf 16.4 landed
- f88cdb36c457 17.0 landed
-
Make TAP todo_start effects the same under Meson and prove_check.
- 288426902ee5 16.4 landed
- 22a4b104ba70 17.0 landed
-
Add an injection_points isolation test suite.
- c35f419d6efb 17.0 landed
-
Add wait event type "InjectionPoint", a custom type like "Extension".
- bb93640a681c 17.0 landed
-
Create waitfuncs.c for pg_isolation_test_session_is_blocked().
- abfbd13af0e9 17.0 landed
-
Rework planning and execution of UPDATE and DELETE.
- 86dc90056dfd 14.0 cited
Thanks for reviewing.
On Fri, Aug 16, 2024 at 12:26:28PM +0300, Heikki Linnakangas wrote:
> On 14/07/2024 20:48, Noah Misch wrote:
> > I've pushed the two patches for your reports. To placate cfbot, I'm attaching
> > the remaining patches.
>
> inplace090-LOCKTAG_TUPLE-eoxact-v8.patch: Makes sense. A comment would be in
> order, it looks pretty random as it is. Something like:
>
> /*
> * Tuple locks are currently only held for short durations within a
> * transaction. Check that we didn't forget to release one.
> */
Will add.
> inplace110-successors-v8.patch: Makes sense.
>
> The README changes would be better as part of the third patch, as this patch
> doesn't actually do any of the new locking described in the README, and it
> fixes the "inplace update updates wrong tuple" bug even without those tuple
> locks.
That should work. Will confirm.
> > + * ... [any slow preparation not requiring oldtup] ...
> > + * heap_inplace_update_scan([...], &tup, &inplace_state);
> > + * if (!HeapTupleIsValid(tup))
> > + * elog(ERROR, [...]);
> > + * ... [buffer is exclusive-locked; mutate "tup"] ...
> > + * if (dirty)
> > + * heap_inplace_update_finish(inplace_state, tup);
> > + * else
> > + * heap_inplace_update_cancel(inplace_state);
>
> I wonder if the functions should be called "systable_*" and placed in
> genam.c rather than in heapam.c. The interface looks more like the existing
> systable functions. It feels like a modularity violation for a function in
> heapam.c to take an argument like "indexId", and call back into systable_*
> functions.
Yes, _scan() and _cancel() especially are wrappers around systable. Some API
options follow. Any preference or other ideas?
==== direct s/heap_/systable_/ rename
systable_inplace_update_scan([...], &tup, &inplace_state);
if (!HeapTupleIsValid(tup))
elog(ERROR, [...]);
... [buffer is exclusive-locked; mutate "tup"] ...
if (dirty)
systable_inplace_update_finish(inplace_state, tup);
else
systable_inplace_update_cancel(inplace_state);
==== make the first and last steps more systable-like
systable_inplace_update_begin([...], &tup, &inplace_state);
if (!HeapTupleIsValid(tup))
elog(ERROR, [...]);
... [buffer is exclusive-locked; mutate "tup"] ...
if (dirty)
systable_inplace_update(inplace_state, tup);
systable_inplace_update_end(inplace_state);
==== no systable_ wrapper for middle step, more like CatalogTupleUpdate
systable_inplace_update_begin([...], &tup, &inplace_state);
if (!HeapTupleIsValid(tup))
elog(ERROR, [...]);
... [buffer is exclusive-locked; mutate "tup"] ...
if (dirty)
heap_inplace_update(relation,
systable_inplace_old_tuple(inplace_state),
tup,
systable_inplace_buffer(inplace_state));
systable_inplace_update_end(inplace_state);
> > /*----------
> > * XXX A crash here can allow datfrozenxid() to get ahead of relfrozenxid:
> > *
> > * ["D" is a VACUUM (ONLY_DATABASE_STATS)]
> > * ["R" is a VACUUM tbl]
> > * D: vac_update_datfrozenid() -> systable_beginscan(pg_class)
> > * c * D: systable_getnext() returns pg_class tuple of tbl
> > * R: memcpy() into pg_class tuple of tbl
> > * D: raise pg_database.datfrozenxid, XLogInsert(), finish
> > * [crash]
> > * [recovery restores datfrozenxid w/o relfrozenxid]
> > */
>
> Hmm, that's a tight race, but feels bad to leave it unfixed. One approach
> would be to modify the tuple on the buffer only after WAL-logging it. That
> way, D cannot read the updated value before it has been WAL logged. Just
> need to make sure that the change still gets included in the WAL record.
> Maybe something like:
>
> if (RelationNeedsWAL(relation))
> {
> /*
> * Make a temporary copy of the page that includes the change, in
> * case the a full-page image is logged
> */
> PGAlignedBlock tmppage;
>
> memcpy(tmppage.data, page, BLCKSZ);
>
> /* copy the tuple to the temporary copy */
> memcpy(...);
>
> XLogRegisterBlock(0, ..., tmppage, REGBUF_STANDARD);
> XLogInsert();
> }
>
> /* copy the tuple to the buffer */
> memcpy(...);
Yes, that's the essence of
inplace180-datfrozenxid-overtakes-relfrozenxid-v1.patch from
https://postgr.es/m/flat/20240620012908.92.nmisch@google.com.
> > pg_class heap_inplace_update_scan() callers: before the call, acquire
> > LOCKTAG_RELATION in mode ShareLock (CREATE INDEX), ShareUpdateExclusiveLock
> > (VACUUM), or a mode with strictly more conflicts. If the update targets a
> > row of RELKIND_INDEX (but not RELKIND_PARTITIONED_INDEX), that lock must be
> > on the table. Locking the index rel is optional. (This allows VACUUM to
> > overwrite per-index pg_class while holding a lock on the table alone.) We
> > could allow weaker locks, in which case the next paragraph would simply call
> > for stronger locks for its class of commands. heap_inplace_update_scan()
> > acquires and releases LOCKTAG_TUPLE in InplaceUpdateTupleLock, an alias for
> > ExclusiveLock, on each tuple it overwrites.
> >
> > pg_class heap_update() callers: before copying the tuple to modify, take a
> > lock that conflicts with at least one of those from the preceding paragraph.
> > SearchSysCacheLocked1() is one convenient way to acquire LOCKTAG_TUPLE.
> > After heap_update(), release any LOCKTAG_TUPLE. Most of these callers opt
> > to acquire just the LOCKTAG_RELATION.
>
> These rules seem complicated. Phrasing this slightly differently, if I
> understand correctly: for a heap_update() caller, it's always sufficient to
> hold LOCKTAG_TUPLE, but if you happen to hold some other lock on the
> relation that conflicts with those mentioned in the first paragraph, then
> you can skip the LOCKTAG_TUPLE lock.
Yes.
> Could we just stipulate that you must always hold LOCKTAG_TUPLE when you
> call heap_update() on pg_class or pg_database? That'd make the rule simple.
We could. That would change more code sites. Rough estimate:
$ git grep -E CatalogTupleUpd'.*(class|relrelation|relationRelation)' | wc -l
23
If the count were 2, I'd say let's simplify the rule like you're exploring.
(I originally had a complicated rule for pg_database, but I abandoned that
when it helped few code sites.) If it were 100, I'd say the complicated rule
is worth it. A count of 23 makes both choices fair.
Long-term, I hope relfrozenxid gets reimplemented with storage outside
pg_class, removing the need for inplace updates. So the additional 23 code
sites might change back at a future date. That shouldn't be a big
consideration, though.
Another option here would be to preface that README section with a simplified
view, something like, "If a warning brought you here, take a tuple lock. The
rest of this section is just for people needing to understand the conditions
for --enable-casserts emitting that warning." How about that instead of
simplifying the rules?