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 1rQTG0-001Uht-Q3 for pgsql-hackers@arkaria.postgresql.org; Thu, 18 Jan 2024 14:20:37 +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 1rQTFz-002xXo-Mx for pgsql-hackers@arkaria.postgresql.org; Thu, 18 Jan 2024 14:20:35 +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 1rQTFz-002xXe-89 for pgsql-hackers@lists.postgresql.org; Thu, 18 Jan 2024 14:20:35 +0000 Received: from mail-wm1-x32a.google.com ([2a00:1450:4864:20::32a]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1rQTFv-002A9g-TB for pgsql-hackers@lists.postgresql.org; Thu, 18 Jan 2024 14:20:33 +0000 Received: by mail-wm1-x32a.google.com with SMTP id 5b1f17b1804b1-40e60e135a7so62927845e9.0 for ; Thu, 18 Jan 2024 06:20:31 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1705587630; x=1706192430; darn=lists.postgresql.org; h=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=/IsXy5QhRejTrSSLoMw+DsZr9+1/9+sUcLWc7kuD9/Q=; b=iHhWo4/4fZWvcvPLDPZTc6XjQvqyCRb3MZU17pVCuvpWt9Fz2LO5Sfbzi4EUpV2LSZ hlbCuMUApcVKu9O83bKNuZBtY5HN65/5OClU/Qg/6OvJiGIlrNRffBhrHKb2RXcmnUoO 3d/7PbxrtNyj41w8UfQx17YCBpSTc1hyMK6NgulCfmeNj3W6xa7so6r3pzARRc5Oi1O1 2yTZFt/iMO2jkn2PFwXqH66h/VuOnMfcVM95Ul+nyfwPVN+TPuUlfnSMuG18+i1Sp71A NiKCXDWQ8QFiKGrZQBf0xpMR9ASE4oYPxNcD71t+abSDOHZiqCXFcqks4YaSF9kUZ5pA 0oSg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1705587630; x=1706192430; h=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=/IsXy5QhRejTrSSLoMw+DsZr9+1/9+sUcLWc7kuD9/Q=; b=mgxZEQLaSRMMzjpUUQJZ7+OUUWaWgL5D0Q6mwmT5VWZqUcbaDPmz1MErcyfiJ1RtR2 +gVD09klY3eBMkMbmlsnVihBPgoRN0cr7GaLm5SrMRuukEPYJaXZpv1hqqeudavm7T08 NMZHAxG/9aPF06XP/BYzQJeZa4cnFej97z6xdncX7/ldhASNYl1kLwZMbmEIuNOSdC14 w9AG45CrUBZp3oqcb2nyqyspKhpcSemr6A1wMo7eqQsqtZLV2NgRv6QEfqu3+rjgG5lm NfBdKKT7S17PGxX4uucj4iJqvKIpj4OpRQRVCueyeB4vTDUKbSrXAWDHa34/ldqumcBy v+fg== X-Gm-Message-State: AOJu0Yy8iHLgksiieb3X2J66Zdkk4R1vSr5CnIE2vcIVzAnLwjvegQ8K GDX5LukwnlHoZhctAKfLZ4n79JWeVXEJYpeKYYe4pnUbkYSHlsyI X-Google-Smtp-Source: AGHT+IF6I/wB6OdB9ychkD7PYZMhZ1EZEDl63Hw4Kk4qbMxlzMqEX7E/uCM8sbbZ9sDWknsq3HCFaw== X-Received: by 2002:a7b:c8d1:0:b0:40e:761d:a553 with SMTP id f17-20020a7bc8d1000000b0040e761da553mr582729wml.37.1705587629977; Thu, 18 Jan 2024 06:20:29 -0800 (PST) Received: from ip-10-97-1-34.eu-west-3.compute.internal (ec2-15-236-134-5.eu-west-3.compute.amazonaws.com. [15.236.134.5]) by smtp.gmail.com with ESMTPSA id m21-20020a05600c4f5500b0040e4733aecbsm25854921wmq.15.2024.01.18.06.20.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 18 Jan 2024 06:20:29 -0800 (PST) Date: Thu, 18 Jan 2024 14:20:28 +0000 From: Bertrand Drouvot To: Bharath Rupireddy Cc: pgsql-hackers@lists.postgresql.org, exclusion@gmail.com, michael@paquier.xyz Subject: Re: Fix race condition in InvalidatePossiblyObsoleteSlot() Message-ID: References: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="sNJPyp3cC3c4Kpsd" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --sNJPyp3cC3c4Kpsd Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi, On Thu, Jan 18, 2024 at 04:59:39PM +0530, Bharath Rupireddy wrote: > IIUC, the issue is that we terminate the process holding the > replication slot, and the conflict cause that we recorded before > terminating the process changes in the next iteration due to the > advancement in effective_xmin and/or effective_catalog_xmin. Thanks for looking at it! Yeah, and that could lead to no conflict detected anymore (like in the case [2] reported up-thread). > FWIW, a test code something like [1], can help detect above race issues, right? I think so and I've added it in v2 attached (except that it uses the new "terminated" variable, see below), thanks! > Some comments on the patch: > > 1. > last_signaled_pid = active_pid; > + terminated = true; > } > > Why is a separate variable needed? Can't last_signaled_pid != 0 enough > to determine that a process was terminated earlier? Yeah probably, I thought about it but preferred to add a new variable for this purpose for clarity and avoid race conditions (in case futur patches "touch" the last_signaled_pid anyhow). I was thinking that this part of the code is already not that easy. > 2. If my understanding of the racy behavior is right, can the same > issue happen due to the advancement in restart_lsn? I'm not sure as I never saw it but it should not hurt to also consider this "potential" case so it's done in v2 attached. > I'm not sure if it > can happen at all, but I think we can rely on previous conflict reason > instead of previous effective_xmin/effective_catalog_xmin/restart_lsn. I'm not sure what you mean here. I think we should still keep the "initial" LSN so that the next check done with it still makes sense. The previous conflict reason as you're proposing also makes sense to me but for another reason: PANIC in case the issue still happen (for cases we did not think about, means not covered by what the added previous LSNs are covering). > 3. Is there a way to reproduce this racy issue, may be by adding some > sleeps or something? If yes, can you share it here, just for the > records and verify the whatever fix provided actually works? Alexander was able to reproduce it on a slow machine and the issue was not there anymore with v1 in place. I think it's tricky to reproduce as it would need the slot to advance between the 2 checks. Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com --sNJPyp3cC3c4Kpsd Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0001-Fix-race-condition-in-InvalidatePossiblyObsoleteS.patch" From 4665945cba163bcb4beadc6391ee65c755e566d8 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot Date: Thu, 11 Jan 2024 13:57:53 +0000 Subject: [PATCH v2] Fix race condition in InvalidatePossiblyObsoleteSlot() In case of an active slot we proceed in 2 steps: - terminate the backend holding the slot - report the slot as obsolete This is racy because between the two we release the mutex on the slot, which means the effective_xmin and effective_catalog_xmin could advance during that time. Holding the mutex longer is not an option (given what we'd to do while holding it) so record the previous LSNs instead that were used during the backend termination. --- src/backend/replication/slot.c | 37 ++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) 100.0% src/backend/replication/ diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 52da694c79..f136916285 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -1352,6 +1352,11 @@ InvalidatePossiblyObsoleteSlot(ReplicationSlotInvalidationCause cause, { int last_signaled_pid = 0; bool released_lock = false; + bool terminated = false; + XLogRecPtr initial_effective_xmin = InvalidXLogRecPtr; + XLogRecPtr initial_catalog_effective_xmin = InvalidXLogRecPtr; + XLogRecPtr initial_restart_lsn = InvalidXLogRecPtr; + ReplicationSlotInvalidationCause conflict_prev = RS_INVAL_NONE; for (;;) { @@ -1386,11 +1391,18 @@ InvalidatePossiblyObsoleteSlot(ReplicationSlotInvalidationCause cause, */ if (s->data.invalidated == RS_INVAL_NONE) { + if (!terminated) + { + initial_restart_lsn = s->data.restart_lsn; + initial_effective_xmin = s->effective_xmin; + initial_catalog_effective_xmin = s->effective_catalog_xmin; + } + switch (cause) { case RS_INVAL_WAL_REMOVED: - if (s->data.restart_lsn != InvalidXLogRecPtr && - s->data.restart_lsn < oldestLSN) + if (initial_restart_lsn != InvalidXLogRecPtr && + initial_restart_lsn < oldestLSN) conflict = cause; break; case RS_INVAL_HORIZON: @@ -1399,12 +1411,12 @@ InvalidatePossiblyObsoleteSlot(ReplicationSlotInvalidationCause cause, /* invalid DB oid signals a shared relation */ if (dboid != InvalidOid && dboid != s->data.database) break; - if (TransactionIdIsValid(s->effective_xmin) && - TransactionIdPrecedesOrEquals(s->effective_xmin, + if (TransactionIdIsValid(initial_effective_xmin) && + TransactionIdPrecedesOrEquals(initial_effective_xmin, snapshotConflictHorizon)) conflict = cause; - else if (TransactionIdIsValid(s->effective_catalog_xmin) && - TransactionIdPrecedesOrEquals(s->effective_catalog_xmin, + else if (TransactionIdIsValid(initial_catalog_effective_xmin) && + TransactionIdPrecedesOrEquals(initial_catalog_effective_xmin, snapshotConflictHorizon)) conflict = cause; break; @@ -1417,6 +1429,17 @@ InvalidatePossiblyObsoleteSlot(ReplicationSlotInvalidationCause cause, } } + /* + * Check if the conflict cause recorded previously before we terminate + * the process changed now for any reason. + */ + if (conflict_prev != RS_INVAL_NONE && terminated && + conflict_prev != conflict) + elog(PANIC, "conflict cause recorded before terminating process %d has been changed; previous cause: %d, current cause: %d", + last_signaled_pid, + conflict_prev, + conflict); + /* if there's no conflict, we're done */ if (conflict == RS_INVAL_NONE) { @@ -1499,6 +1522,8 @@ InvalidatePossiblyObsoleteSlot(ReplicationSlotInvalidationCause cause, (void) kill(active_pid, SIGTERM); last_signaled_pid = active_pid; + terminated = true; + conflict_prev = conflict; } /* Wait until the slot is released. */ -- 2.34.1 --sNJPyp3cC3c4Kpsd--