Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sfAkR-002ZlI-9k for pgsql-hackers@arkaria.postgresql.org; Sat, 17 Aug 2024 04:09:03 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1sfAjP-002Aee-R1 for pgsql-hackers@arkaria.postgresql.org; Sat, 17 Aug 2024 04:08:00 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sfAjP-002AeV-4r for pgsql-hackers@lists.postgresql.org; Sat, 17 Aug 2024 04:08:00 +0000 Received: from mail-ot1-x32e.google.com ([2607:f8b0:4864:20::32e]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1sfAjJ-00037o-0o for pgsql-hackers@postgresql.org; Sat, 17 Aug 2024 04:07:58 +0000 Received: by mail-ot1-x32e.google.com with SMTP id 46e09a7af769-7094641d4e6so1128237a34.3 for ; Fri, 16 Aug 2024 21:07:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=leadboat.com; s=google; t=1723867671; x=1724472471; darn=postgresql.org; h=user-agent:in-reply-to:content-disposition:mime-version:references :message-id:subject:cc:to:from:date:from:to:cc:subject:date :message-id:reply-to; bh=DXAt0LYAgmtlSkJcUacdaV3Q9EyPh4tBXNw9qVNz9Yc=; b=KW2KKX0AgEFGGzNd8wAC/rpQ+o3hOfWgIRMKWtDQ4b3mmg5AzygQantmvF3VeYUt75 QGnCivdB6G36gc3cHzGksaQ9RF4TReapZ9IJr/1AR94gjqjNLk0t4XaWNlNmECW/ckxD 5IacQwo4rvUWcofMXZlKZDGVhhG+jBdhvdqYE= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1723867672; x=1724472472; h=user-agent:in-reply-to:content-disposition:mime-version:references :message-id:subject:cc:to:from:date:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=DXAt0LYAgmtlSkJcUacdaV3Q9EyPh4tBXNw9qVNz9Yc=; b=uc49YS8VkuH4ZsxvZ9FoS/XIYPT7dPiWgED1l109cpjC0ykQv4Q6YuUI5YhIzt+7n2 RqcS9wT+FzQDa7KGGUNdAYkWfgEx2FAos9e9eQLTvFBfY6KUtmeAgiuXYnO6uvKgTi0u Bnxwry6RlqG+eZzPMmUj/6x3jucLsWOPDvOTiCpTwDemmtWVT3Yrlo/qLH6ErBG86tLW jUwzFhFS+iBjYwe7R3nkdUICpAtnkHsZmgX2j2dEarET0RlEyftCWe/SiDn+wPNRP1mk bG5loYVNTXnmHuonu6FXNazduipvpra3kN6wkSH7NRpKZhFbyTB5iP8c0B+TvwYrkLVi iiKA== X-Forwarded-Encrypted: i=1; AJvYcCUpU8F5M7taGlUY4lajJo38g7CD+pSUoX43KKPyEgrKmB1F72ntgGDx5mGkypovP/j4T8We/e0aAtfY2M0j@postgresql.org X-Gm-Message-State: AOJu0Yw5xCYlNNHDGNbHbu6G3VdrX7Z84RAjXekttt5T9NYV0tEWYsvs 1XJlmvopdX07OpNIMnZYdqf6h1hKONW1IwZZizcDe4AydXpRqF3n8s6rWGKnpg== X-Google-Smtp-Source: AGHT+IEyEkzeIY5nutsr3PEIJWdOUxrpXxdI3lRj15M31D3ggfQDJJuhPOeM2DX0RB7N0GiT+Dde2A== X-Received: by 2002:a05:6870:b28b:b0:270:172c:32ae with SMTP id 586e51a60fabf-2701c52ee79mr5515093fac.32.1723867671657; Fri, 16 Aug 2024 21:07:51 -0700 (PDT) Received: from google.com ([2600:1702:a20:5750::48]) by smtp.gmail.com with ESMTPSA id 586e51a60fabf-27029dffdfasm555303fac.40.2024.08.16.21.07.50 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 16 Aug 2024 21:07:50 -0700 (PDT) Date: Fri, 16 Aug 2024 21:07:48 -0700 From: Noah Misch To: Heikki Linnakangas Cc: Robert Haas , pgsql-hackers@postgresql.org, Tom Lane , Smolkin Grigory , Michael Paquier , Alexander Lakhin Subject: Re: race condition in pg_class Message-ID: <20240817040748.f8.nmisch@google.com> References: <20240605181706.fe.nmisch@google.com> <20240611024525.9f.nmisch@google.com> <20240614003549.c2.nmisch@google.com> <20240621212842.98.nmisch@google.com> <20240628051353.a0.nmisch@google.com> <20240704220816.51.nmisch@google.com> <20240714174800.de.nmisch@google.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: User-Agent: Mutt/2.2.12 (2023-09-09) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk 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?