Thread: ReplicationSlotsComputeRequiredXmin seems pretty questionable

ReplicationSlotsComputeRequiredXmin seems pretty questionable

From
Tom Lane
Date:
While trying to make sense of Adam Sjøgren's problem [1], I found
myself staring at ReplicationSlotsComputeRequiredXmin() in slot.c.
It seems to me that that is very shaky code, on two different
grounds:

1. Sometimes it's called with ProcArrayLock already held exclusively.
This means that any delay in acquiring the ReplicationSlotControlLock
translates directly into a hold on ProcArrayLock; in other words,
every acquisition of the ReplicationSlotControlLock is just as bad
for concurrency as an acquisition of ProcArrayLock.  While I didn't
see any places that were doing really obviously slow things while
holding ReplicationSlotControlLock, this is disturbing.  Do we really
need it to be like that?

2. On the other hand, the code is *releasing* the
ReplicationSlotControlLock before it calls
ProcArraySetReplicationSlotXmin, and that seems like a flat out
concurrency bug.  How can we be sure that the values we're storing
into the shared xmin fields aren't stale by the time we acquire
the ProcArrayLock (in the case where we didn't hold it already)?
I'm concerned that in the worst case this code could make the
shared xmin fields go backwards.

Both of these issues could be solved, I think, if we got rid of
the provision for calling with ProcArrayLock already held and
moved the ProcArraySetReplicationSlotXmin call inside the hold
of ReplicationSlotControlLock.  But maybe I'm missing something
about why that would be worse.

            regards, tom lane

[1] https://www.postgresql.org/message-id/flat/87364kdsim.fsf%40tullinup.koldfront.dk



Re: ReplicationSlotsComputeRequiredXmin seems pretty questionable

From
Amit Kapila
Date:
On Thu, Aug 20, 2020 at 10:28 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> While trying to make sense of Adam Sjøgren's problem [1], I found
> myself staring at ReplicationSlotsComputeRequiredXmin() in slot.c.
> It seems to me that that is very shaky code, on two different
> grounds:
>
> 1. Sometimes it's called with ProcArrayLock already held exclusively.
> This means that any delay in acquiring the ReplicationSlotControlLock
> translates directly into a hold on ProcArrayLock; in other words,
> every acquisition of the ReplicationSlotControlLock is just as bad
> for concurrency as an acquisition of ProcArrayLock.  While I didn't
> see any places that were doing really obviously slow things while
> holding ReplicationSlotControlLock, this is disturbing.  Do we really
> need it to be like that?
>
> 2. On the other hand, the code is *releasing* the
> ReplicationSlotControlLock before it calls
> ProcArraySetReplicationSlotXmin, and that seems like a flat out
> concurrency bug.  How can we be sure that the values we're storing
> into the shared xmin fields aren't stale by the time we acquire
> the ProcArrayLock (in the case where we didn't hold it already)?
> I'm concerned that in the worst case this code could make the
> shared xmin fields go backwards.
>

It is not clear to me how those values can go backward. Basically, we
install those values in slots after getting it from
GetOldestSafeDecodingTransactionId() and then those always seem to get
advanced. And GetOldestSafeDecodingTransactionId() takes into account
the already stored shared values of replication_slot_xmin and
replication_slot_catalog_xmin for computing the xmin_horizon for slot.

--
With Regards,
Amit Kapila.



Re: ReplicationSlotsComputeRequiredXmin seems pretty questionable

From
Tom Lane
Date:
Amit Kapila <amit.kapila16@gmail.com> writes:
> On Thu, Aug 20, 2020 at 10:28 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> 2. On the other hand, the code is *releasing* the
>> ReplicationSlotControlLock before it calls
>> ProcArraySetReplicationSlotXmin, and that seems like a flat out
>> concurrency bug.

> It is not clear to me how those values can go backward.

After releasing ReplicationSlotControlLock, that code is holding no
lock at all (in the already_locked=false case I'm concerned about).
Thus the scenario to consider is:

1. Walsender A runs ReplicationSlotsComputeRequiredXmin, computes
some perfectly-valid xmins, releases ReplicationSlotControlLock,
amd then gets swapped out to Timbuktu.

2. Time passes and the "true values" of those xmins advance thanks
to other walsender activity.

3. Walsender B runs ReplicationSlotsComputeRequiredXmin, computes
some perfectly-valid xmins, and successfully stores them in the
procarray.

4. Walsender A returns from never-never land, and stores its now
quite stale results in the procarray, causing the globally visible
xmins to go backwards from where they were after step 3.

I see no mechanism in the code that prevents this scenario.
On reflection I'm not even very sure that the code change
I'm suggesting would prevent it.  It'd prevent walsenders
from entering or exiting until we've updated the procarray,
but there's nothing to stop the furthest-back walsender from
advancing its values.

There may be some argument why this can't lead to a problem,
but I don't see any comments making such an argument, either.

            regards, tom lane



Re: ReplicationSlotsComputeRequiredXmin seems pretty questionable

From
Amit Kapila
Date:
On Sat, Aug 22, 2020 at 8:33 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Amit Kapila <amit.kapila16@gmail.com> writes:
> > On Thu, Aug 20, 2020 at 10:28 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >> 2. On the other hand, the code is *releasing* the
> >> ReplicationSlotControlLock before it calls
> >> ProcArraySetReplicationSlotXmin, and that seems like a flat out
> >> concurrency bug.
>
> > It is not clear to me how those values can go backward.
>
> After releasing ReplicationSlotControlLock, that code is holding no
> lock at all (in the already_locked=false case I'm concerned about).
> Thus the scenario to consider is:
>
> 1. Walsender A runs ReplicationSlotsComputeRequiredXmin, computes
> some perfectly-valid xmins, releases ReplicationSlotControlLock,
> amd then gets swapped out to Timbuktu.
>
> 2. Time passes and the "true values" of those xmins advance thanks
> to other walsender activity.
>
> 3. Walsender B runs ReplicationSlotsComputeRequiredXmin, computes
> some perfectly-valid xmins, and successfully stores them in the
> procarray.
>
> 4. Walsender A returns from never-never land, and stores its now
> quite stale results in the procarray, causing the globally visible
> xmins to go backwards from where they were after step 3.
>
> I see no mechanism in the code that prevents this scenario.
> On reflection I'm not even very sure that the code change
> I'm suggesting would prevent it.  It'd prevent walsenders
> from entering or exiting until we've updated the procarray,
> but there's nothing to stop the furthest-back walsender from
> advancing its values.
>

I think we can prevent that if we allow
ProcArraySetReplicationSlotXmin to update the shared values only when
new xmin values follows the shared values. I am not very sure if it is
safe but I am not able to think of a problem with it. The other way
could be to always acquire ProcArrayLock before calling
ReplicationSlotsComputeRequiredXmin or before acquiring
ReplicationSlotControlLock but that seems too restrictive.

-- 
With Regards,
Amit Kapila.