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.96) (envelope-from ) id 1x6Rpv-000F1J-2N for pgsql-bugs@arkaria.postgresql.org; Tue, 15 Sep 2026 12:00:32 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1x6Rpv-001hHJ-0V for pgsql-bugs@arkaria.postgresql.org; Tue, 15 Sep 2026 12:00:31 +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.96) (envelope-from ) id 1x6PtX-0018wj-1i for pgsql-bugs@lists.postgresql.org; Tue, 15 Sep 2026 09:56:07 +0000 Received: from mahout.postgresql.org ([2001:4800:3e1:1::227]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x6PtV-00000000AUn-0cwC for pgsql-bugs@lists.postgresql.org; Tue, 15 Sep 2026 09:56:06 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=postgresql.org; s=20171124; h=Message-ID:Date:Reply-To:Cc:From:To:Subject: Content-Transfer-Encoding:MIME-Version:Content-Type:Sender:Content-ID: Content-Description:In-Reply-To:References; bh=xyoilA9hkCZsvoERPfsSQ/pSERsJfNVgu+XhPBcC2Fc=; b=PzSFuocwMutL/LEuShx5uFgr4e sO7FfmRalwibVfVepngHb7GkhGVBPVXqcKpPX2zrPvkaWSgFPHFl+jpl/dakyTf/twFvuMww6HKsm IwtCmgPkQfoYzyD1/w8fy8Vdj55BZw5ID2dbrMbz8xQzyEw+euKASToLkaYTfLenEcWQ8NidmS6c8 PZXwxBH3owTnBtfcjm6IvQrw2t38K5ZP7W+4tQE/RQ6nWuf9MEMhGHuar2V80hp6/sL0hmfHqcH7v WF+Eauq/adB25+2MLjKumgbmB8v5gOkDQnZ5U2aP8lJnDubE7HywL5K3mOGBDx07sVzYicdmP6WjQ RGK8aAhA==; Received: from wrigleys.postgresql.org ([2a02:16a8:dc51::60]) by mahout.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1x6PtU-000Tpf-1m for pgsql-bugs@lists.postgresql.org; Tue, 15 Sep 2026 09:56:04 +0000 Received: from localhost ([127.0.0.1] helo=wrigleys.postgresql.org) by wrigleys.postgresql.org with esmtp (Exim 4.98.2) (envelope-from ) id 1x6PtT-00000003mf2-0jEa for pgsql-bugs@lists.postgresql.org; Tue, 15 Sep 2026 09:56:03 +0000 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Subject: BUG #19690: Possible stale partition descriptor after concurrent ATTACH PARTITION To: pgsql-bugs@lists.postgresql.org From: PG Bug reporting form Cc: happydogly@gmail.com Reply-To: happydogly@gmail.com, pgsql-bugs@lists.postgresql.org Date: Tue, 15 Sep 2026 09:55:16 +0000 Message-ID: <19690-5619e7d182d18d5d@postgresql.org> X-Auto-Response-Suppress: All Auto-Submitted: auto-generated List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk The following bug has been logged on the website: Bug reference: 19690 Logged by: liuyu Email address: happydogly@gmail.com PostgreSQL version: 17.9 Operating system: macOS 26.6.2, arm64 Description: =20 I would like to ask about a possible build-time invalidation race in RelationBuildPartitionDesc(). This issue was reproduced in a clean PostgreSQL upstream checkout . The checkout was current master at commit ff39a858b984d8088caa2a9777948d211f549b65. SELECT version(); returned: PostgreSQL 20devel on aarch64-apple-darwin25.6.0, compiled by Apple clang version 21.0.0 (clang-2100.1.1.101), 64-bit The host runs macOS 26.6.2 on arm64. Observed behavior ----------------- Backend A starts a read that builds the partition descriptor when the parent has one partition. During that build, backend B commits ATTACH PARTITION and inserts a row into the newly attached partition. A subsequent statement in the same reader connection still reports only the original row and partition: first_count ------------- 1 second_count | partitions_seen --------------+----------------- 1 | 1 An independent query after the writer commits reports 2 rows across 2 partitions. Both SELECT statements run in the same psql connection with normal autocommit. The second SELECT follows SELECT pg_sleep(1). The writer command returned ALTER TABLE and INSERT 0 1 before the reader finished. Test-only instrumentation ------------------------- Immediately after find_inheritance_children_extended() in RelationBuildPartitionDesc(), and before processing the child list, this temporary code was added: #ifdef RELCACHE_PARTDESC_TEST_DELAY if (debug_query_string !=3D NULL && strstr(debug_query_string, "partdesc-race-reader") !=3D NULL) pg_usleep(8000000L); #endif partdesc.o was compiled with -DRELCACHE_PARTDESC_TEST_DELAY and the backend was relinked. The hook was removed after the test. The sleep widens the timing window; it is not a synchronization barrier, so this is an instrumented timing reproduction. Setup ----- CREATE TABLE race_parent_clean(k int) PARTITION BY RANGE(k); CREATE TABLE race_p1_clean PARTITION OF race_parent_clean FOR VALUES FROM (0) TO (10); CREATE TABLE race_p2_clean(k int); INSERT INTO race_p1_clean VALUES (1); Reader A, in one psql connection, sends these as separate statements: /* partdesc-race-reader */ SELECT count(*) AS first_count FROM race_parent_clean; SELECT pg_sleep(1); SELECT count(*) AS second_count, count(DISTINCT tableoid) AS partitions_seen FROM race_parent_clean; About one second after starting the marked reader query, writer B runs: BEGIN; ALTER TABLE race_parent_clean ATTACH PARTITION race_p2_clean FOR VALUES FROM (10) TO (20); INSERT INTO race_p2_clean VALUES (11); COMMIT; Suspected mechanism ------------------- 1. RelationBuildPartitionDesc() obtains a child OID list using inheritance snapshot S1. 2. B commits ATTACH after S1. ATTACH takes ShareUpdateExclusiveLock on the parent, compatible with the reader's AccessShareLock. 3. A later child syscache miss can open pg_class. LockRelationOid() calls AcceptInvalidationMessages() when the lock acquisition is not LOCKACQUIRE_ALREADY_CLEAR, potentially consuming B's parent relcache invalidation during the build. 4. The builder finishes from the S1 list and publishes rd_partdesc without checking whether it was invalidated during construction. 5. Later statements may reuse that descriptor because the invalidation was already consumed. The existing boundspec retry appears to handle a child already in the list whose bound is unavailable, but not an additional child committed after the list was collected. Is there an upstream mechanism that prevents this ordering, or should descriptor construction track invalidations during the build before publishing its cached result? Please confirm whether this is known or fixed. The clean-upstream reproduction used the temporary delay hook described above.