Thread
Commits
-
Add WL_EXIT_ON_PM_DEATH pseudo-event.
- cfdf4dc4fc96 12.0 landed
-
While waiting for a condition variable, detect postmaster death.
- 80259d4dbf47 11.0 landed
- d56a5f994c21 10.2 landed
-
Fix race condition during replication origin drop.
- 8a906204aec4 11.0 landed
- 1f5adbd799cf 10.2 landed
-
Allow ConditionVariable[PrepareTo]Sleep to auto-switch between CVs.
- 4af2190eb04b 10.2 landed
- 13db3b936359 11.0 landed
-
Cosmetic improvements in condition_variable.[hc].
- e35dba475a44 11.0 landed
-
Improve error detection capability in proclists.
- ea8e1bbc5384 11.0 landed
-
Remove return values of ConditionVariableSignal/Broadcast.
- ccf312a4488a 11.0 landed
-
Reorder steps in ConditionVariablePrepareToSleep for more safety.
- 83fe2708d668 10.2 landed
- 3cac0ec85992 11.0 landed
-
Rewrite ConditionVariableBroadcast() to avoid live-lock.
- aced5a92bf46 11.0 landed
- 1c77e990833a 10.2 landed
-
Add parallel-aware hash joins.
- 1804284042e6 11.0 cited
-
Condition variable live lock
Thomas Munro <thomas.munro@enterprisedb.com> — 2017-12-22T03:46:21Z
Hi hackers, While debugging a build farm assertion failure after commit 18042840, and with the assumption that the problem is timing/scheduling sensitive, I tried hammering the problem workload on a few different machines and noticed that my slow 2-core test machine fairly regularly got into a live lock state for tens to millions of milliseconds at a time when there were 3+ active processes, in here: int ConditionVariableBroadcast(ConditionVariable *cv) { int nwoken = 0; /* * Let's just do this the dumbest way possible. We could try to dequeue * all the sleepers at once to save spinlock cycles, but it's a bit hard * to get that right in the face of possible sleep cancelations, and we * don't want to loop holding the mutex. */ while (ConditionVariableSignal(cv)) ++nwoken; return nwoken; } The problem is that another backend can be woken up, determine that it would like to wait for the condition variable again, and then get itself added to the back of the wait queue *before the above loop has finished*, so this interprocess ping-pong isn't guaranteed to terminate. It seems that we'll need something slightly smarter than the above to avoid that. I don't currently suspect this phenomenon of being responsible for the problem I'm hunting, even though it occurs on the only machine I've been able to reproduce my real problem on. AFAICT the problem described in this email should deliver arbitrary numbers of spurious wake-ups wasting arbitrary CPU time but cause no harm that would affect program correctness. So I didn't try to write a patch to fix that just yet. I think we should probably back patch a fix when we have one though, because it could bite Parallel Index Scan in REL_10_STABLE. -- Thomas Munro http://www.enterprisedb.com -
Re: Condition variable live lock
Thomas Munro <thomas.munro@enterprisedb.com> — 2017-12-28T23:16:20Z
On Fri, Dec 22, 2017 at 4:46 PM, Thomas Munro <thomas.munro@enterprisedb.com> wrote: > while (ConditionVariableSignal(cv)) > ++nwoken; > > The problem is that another backend can be woken up, determine that it > would like to wait for the condition variable again, and then get > itself added to the back of the wait queue *before the above loop has > finished*, so this interprocess ping-pong isn't guaranteed to > terminate. It seems that we'll need something slightly smarter than > the above to avoid that. Here is one way to fix it: track the wait queue size and use that number to limit the wakeup loop. See attached. That's unbackpatchable though, because it changes the size of struct ConditionVariable, potentially breaking extensions compiled against an earlier point release. Maybe this problem won't really cause problems in v10 anyway? It requires a particular interaction pattern that barrier.c produces but more typical client code might not: the awoken backends keep re-adding themselves because they're waiting for everyone (including the waker) to do something, but the waker is stuck in that broadcast loop. Thoughts? -- Thomas Munro http://www.enterprisedb.com
-
Re: Condition variable live lock
Andres Freund <andres@anarazel.de> — 2017-12-29T19:38:43Z
On 2017-12-29 12:16:20 +1300, Thomas Munro wrote: > Here is one way to fix it: track the wait queue size and use that > number to limit the wakeup loop. See attached. > > That's unbackpatchable though, because it changes the size of struct > ConditionVariable, potentially breaking extensions compiled against an > earlier point release. Maybe this problem won't really cause problems > in v10 anyway? It requires a particular interaction pattern that > barrier.c produces but more typical client code might not: the awoken > backends keep re-adding themselves because they're waiting for > everyone (including the waker) to do something, but the waker is stuck > in that broadcast loop. Hm, I'm not quite convinced by this approach. Partially because of the backpatch issue you mention, partially because using the list length as a limit doesn't seem quite nice. Given that the proclist_contains() checks in condition_variable.c are already racy, I think it might be feasible to collect all procnos to signal while holding the spinlock, and then signal all of them in one go. Obviously it'd be nicer to not hold a spinlock while looping, but that seems like something we can't fix in the back branches. [insert rant about never using spinlocks unless there's very very clear convicing reasons]. - Andres
-
Re: Condition variable live lock
Robert Haas <robertmhaas@gmail.com> — 2018-01-04T17:39:47Z
On Fri, Dec 29, 2017 at 2:38 PM, Andres Freund <andres@anarazel.de> wrote: > Hm, I'm not quite convinced by this approach. Partially because of the > backpatch issue you mention, partially because using the list length as > a limit doesn't seem quite nice. Seems OK to me. Certainly better than your competing proposal. > Given that the proclist_contains() checks in condition_variable.c are > already racy, I think it might be feasible to collect all procnos to > signal while holding the spinlock, and then signal all of them in one > go. That doesn't seem very nice at all. Not only does it violate the coding rule against looping while holding a spinlock, but it seems that it would require allocating memory while holding one, which is a non-starter. > Obviously it'd be nicer to not hold a spinlock while looping, but that > seems like something we can't fix in the back branches. [insert rant > about never using spinlocks unless there's very very clear convicing > reasons]. I don't think that's a coding rule that I'd be prepared to endorse. We've routinely used spinlocks for years in cases where the critical section was very short, just to keep the overhead down. I think it works fine in that case, although I admit that I failed to appreciate how unpleasant the livelock possibilities were in this case. It's not clear to me that we entirely need a back-patchable fix for this. It could be that parallel index scan can have the same issue, but I'm not aware of any user complaints. Parallel bitmap heap only ever waits once so it's probably fine. If we do need a back-patchable fix, I suppose slock_t mutex could be replaced by pg_atomic_uint32 state. I think that would avoid changing the size of the structure on common platforms, though obscure systems with spinlocks > 4 bytes might be affected. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: Condition variable live lock
Andres Freund <andres@anarazel.de> — 2018-01-04T20:54:47Z
On 2018-01-04 12:39:47 -0500, Robert Haas wrote: > > Given that the proclist_contains() checks in condition_variable.c are > > already racy, I think it might be feasible to collect all procnos to > > signal while holding the spinlock, and then signal all of them in one > > go. > > That doesn't seem very nice at all. Not only does it violate the > coding rule against looping while holding a spinlock, but it seems > that it would require allocating memory while holding one, which is a > non-starter. We could just use a sufficiently sized buffer beforehand. There's an obvious upper boundary, so that shouldn't be a big issue. > > Obviously it'd be nicer to not hold a spinlock while looping, but that > > seems like something we can't fix in the back branches. [insert rant > > about never using spinlocks unless there's very very clear convicing > > reasons]. > > I don't think that's a coding rule that I'd be prepared to endorse. > We've routinely used spinlocks for years in cases where the critical > section was very short, just to keep the overhead down. The problem is that due to the contention handling they really don't keep the overhead that low unless you're absolutely absolutely maximizing for low number of cycles and have very little contention. Which isn't actually common. I think part of the conventional wisdom when to use spinlock vs lwlocks went out of the window once we got better scaling lwlocks. > It's not clear to me that we entirely need a back-patchable fix for > this. It could be that parallel index scan can have the same issue, > but I'm not aware of any user complaints. I don't think many users are going to be able to diagnose this one, and it's probably not easily diagnosable even if they complain about performance. Greetings, Andres Freund
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-05T04:27:09Z
Andres Freund <andres@anarazel.de> writes: > On 2018-01-04 12:39:47 -0500, Robert Haas wrote: >>> Given that the proclist_contains() checks in condition_variable.c are >>> already racy, I think it might be feasible to collect all procnos to >>> signal while holding the spinlock, and then signal all of them in one >>> go. >> That doesn't seem very nice at all. Not only does it violate the >> coding rule against looping while holding a spinlock, but it seems >> that it would require allocating memory while holding one, which is a >> non-starter. > We could just use a sufficiently sized buffer beforehand. There's an > obvious upper boundary, so that shouldn't be a big issue. I share Robert's discomfort with that solution, but it seems to me there might be a better way. The attached patch uses our own cvWaitLink as a sentinel to detect when we've woken everybody who was on the wait list before we arrived. That gives exactly the desired semantics, not just an approximation to them. Now, the limitation with this is that we can't be waiting for any *other* condition variable, because then we'd be trashing our state about that variable. As coded, we can't be waiting for the target CV either, but that case could actually be handled if we needed to, as per the comment. I do not know if this is likely to be a problematic limitation ... discuss. (The patch does survive check-world, FWIW.) regards, tom lane
-
Re: Condition variable live lock
Thomas Munro <thomas.munro@enterprisedb.com> — 2018-01-05T05:47:54Z
On Fri, Jan 5, 2018 at 5:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > I share Robert's discomfort with that solution, but it seems to me there > might be a better way. The attached patch uses our own cvWaitLink as a > sentinel to detect when we've woken everybody who was on the wait list > before we arrived. That gives exactly the desired semantics, not just an > approximation to them. Very clever. It works correctly for my test case. > Now, the limitation with this is that we can't be waiting for any *other* > condition variable, because then we'd be trashing our state about that > variable. As coded, we can't be waiting for the target CV either, but > that case could actually be handled if we needed to, as per the comment. > I do not know if this is likely to be a problematic limitation > ... discuss. (The patch does survive check-world, FWIW.) I think that restriction is probably OK. Even if you have some kind of chain of CVs where you get woken up, check your interesting condition and discover that it's now true so you exit you loop and immediately want to broadcast a signal to some other CV, you'd simply have to make sure that you put ConditionVariableCancelSleep() before ConditionVariableBroadcast(): ConditionVariablePrepareToSleep(cv1); while (condition for which we are waiting is not true) ConditionVariableSleep(cv1, wait_event_info); ConditionVariableCancelSleep(); ConditionVariableBroadcast(cv2); It would only be a problem if you are interested in broadcasting to cv2 when you've been woken up and the condition is *still not true*, that is, when you've been spuriously woken. But why would anyone want to forward spurious wakeups to another CV? But if that seems too arbitrary, one way to lift the restriction would be to teach ConditionVariableBroadcast() to call ConditionVariableCancelSleep() if cv_sleep_target is non-NULL where you have the current assertion. Code that is still waiting for a CV must be in a loop that will eventually re-add it in ConditionVariableSleep(), and it won't miss any signals that it can't afford to miss because the first call to ConditionVariableSleep() will return immediately so the caller will recheck its condition. -- Thomas Munro http://www.enterprisedb.com -
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-05T06:10:57Z
Thomas Munro <thomas.munro@enterprisedb.com> writes: > On Fri, Jan 5, 2018 at 5:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Now, the limitation with this is that we can't be waiting for any *other* >> condition variable, because then we'd be trashing our state about that >> variable. As coded, we can't be waiting for the target CV either, but >> that case could actually be handled if we needed to, as per the comment. >> I do not know if this is likely to be a problematic limitation >> ... discuss. (The patch does survive check-world, FWIW.) > ... one way to lift the restriction would > be to teach ConditionVariableBroadcast() to call > ConditionVariableCancelSleep() if cv_sleep_target is non-NULL where > you have the current assertion. Code that is still waiting for a CV > must be in a loop that will eventually re-add it in > ConditionVariableSleep(), and it won't miss any signals that it can't > afford to miss because the first call to ConditionVariableSleep() will > return immediately so the caller will recheck its condition. Oh, of course, very simple. I thought of another possible issue, though. In the situation where someone else has removed our sentinel (presumably, by issuing ConditionVariableSignal just before we were about to remove the sentinel), my patch assumes we can just do nothing. But it seems like that amounts to losing one signal. Whoever the someone else was probably expected to awaken a waiter, and now that won't happen. Should we rejigger the logic so that it awakens one additional waiter (if there is one) after detecting that someone else has removed the sentinel? Obviously, this trades a risk of loss of wakeup for a risk of spurious wakeup, but presumably the latter is something we can cope with. regards, tom lane
-
Re: Condition variable live lock
Thomas Munro <thomas.munro@enterprisedb.com> — 2018-01-05T06:33:10Z
On Fri, Jan 5, 2018 at 7:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > I thought of another possible issue, though. In the situation where > someone else has removed our sentinel (presumably, by issuing > ConditionVariableSignal just before we were about to remove the > sentinel), my patch assumes we can just do nothing. But it seems > like that amounts to losing one signal. Whoever the someone else > was probably expected to awaken a waiter, and now that won't happen. Yeah, that's bad. > Should we rejigger the logic so that it awakens one additional waiter > (if there is one) after detecting that someone else has removed the > sentinel? Obviously, this trades a risk of loss of wakeup for a risk > of spurious wakeup, but presumably the latter is something we can > cope with. One detail is that the caller of ConditionVariableSignal() got a true return value when it took out the sentinel (indicating that someone received the signal), and now when you call ConditionVariableSignal() because !aminlist there may be no one there. I'm not sure if that's a problem. For comparison, pthread_cond_signal() doesn't tell you if you actually signalled anyone. Maybe the only reason we have that return code is so that ConditionVariableBroadcast() can use it the way it does in master... An alternative would be to mark sentinel entries somehow so that signallers can detect them and signal again, but that's not backpatchable. -- Thomas Munro http://www.enterprisedb.com
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-05T06:42:55Z
Thomas Munro <thomas.munro@enterprisedb.com> writes: > On Fri, Jan 5, 2018 at 7:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Should we rejigger the logic so that it awakens one additional waiter >> (if there is one) after detecting that someone else has removed the >> sentinel? Obviously, this trades a risk of loss of wakeup for a risk >> of spurious wakeup, but presumably the latter is something we can >> cope with. > One detail is that the caller of ConditionVariableSignal() got a true > return value when it took out the sentinel (indicating that someone > received the signal), and now when you call ConditionVariableSignal() > because !aminlist there may be no one there. I'm not sure if that's a > problem. For comparison, pthread_cond_signal() doesn't tell you if > you actually signalled anyone. Maybe the only reason we have that > return code is so that ConditionVariableBroadcast() can use it the way > it does in master... Indeed, it looks like no other caller is paying attention to the result. We could live with the uncertainty in the back branches, and redefine ConditionVariableSignal as returning void in master. regards, tom lane
-
Re: Condition variable live lock
Thomas Munro <thomas.munro@enterprisedb.com> — 2018-01-05T10:14:25Z
On Fri, Jan 5, 2018 at 7:42 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > Indeed, it looks like no other caller is paying attention to the result. > We could live with the uncertainty in the back branches, and redefine > ConditionVariableSignal as returning void in master. +1 Could we install the sentinel and pop the first entry at the same time, so that we're not adding an extra spinlock acquire/release? -- Thomas Munro http://www.enterprisedb.com
-
Re: Condition variable live lock
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2018-01-05T12:05:42Z
Tom Lane wrote: > Obviously, this trades a risk of loss of wakeup for a risk > of spurious wakeup, but presumably the latter is something we can > cope with. I wonder if it'd be useful to have a test mode for condition variables that spurious wakups happen randomly, to verify that every user is prepared to cope correctly. -- Álvaro Herrera https://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-05T15:28:10Z
Thomas Munro <thomas.munro@enterprisedb.com> writes: > Could we install the sentinel and pop the first entry at the same > time, so that we're not adding an extra spinlock acquire/release? Hm, maybe. Other ideas in that space: * if queue is empty when we first acquire the spinlock, we don't have to do anything at all. * if queue is empty after we pop the first entry, we needn't bother installing our sentinel, just signal that proc and we're done. It's a question of how complicated you're willing to make this logic, and whether you trust that we'll be able to test all the code paths. regards, tom lane
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-05T17:33:13Z
I wrote: > It's a question of how complicated you're willing to make this > logic, and whether you trust that we'll be able to test all the > code paths. Attached is a patch incorporating all the ideas mentioned in this thread, except that I think in HEAD we should change both ConditionVariableSignal and ConditionVariableBroadcast to return void rather than a possibly misleading wakeup count. This could be back-patched as is, though. As I feared, the existing regression tests are not really adequate for this: gcov testing shows that the sentinel-inserting code path is never entered, meaning ConditionVariableBroadcast never sees more than one waiter. What's more, it's now also apparent that no outside caller of ConditionVariableSignal ever actually awakens anything. So I think it'd be a good idea to expand the regression tests if we can do so cheaply. Anybody have ideas about that? Perhaps a new module under src/test/modules would be the best way? Alternatively, we could drop some of the optimization ideas. BTW, at least on gaur, this does nothing for the runtime of the join test, meaning I'd still like to see some effort put into reducing that. regards, tom lane
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-05T19:38:37Z
I wrote: > As I feared, the existing regression tests are not really adequate for > this: gcov testing shows that the sentinel-inserting code path is > never entered, meaning ConditionVariableBroadcast never sees more > than one waiter. Hmm ... adding tracing log printouts in BarrierArriveAndWait disproves that: the regression tests do, repeatedly, call ConditionVariableBroadcast when there are two waiters to be woken (and you can get it to be more if you raise the number of parallel workers specified in the PHJ tests). It just doesn't happen with --enable-coverage. Heisenberg wins again! I am not sure why coverage tracking changes the behavior so much, but it's clear from my results that if you change all the PHJ test cases in join.sql to use 4 parallel workers, then you get plenty of barrier release events with 4 or 5 barrier participants --- but running the identical test under --enable-coverage results in only a very small number of releases with even as many as 2 participants, let alone more. Perhaps the PHJ test cases don't run long enough to let slow-starting workers join in? Anyway, that may or may not indicate something we should tune at a higher level, but I'm now satisfied that the patch as presented works and does get tested by our existing tests. So barring objection I'll commit that shortly. There are some other things I don't like about condition_variable.c: * I think the Asserts in ConditionVariablePrepareToSleep and ConditionVariableSleep ought to be replaced by full-fledged test and elog(ERROR), so that they are enforced even in non-assert builds. I don't have a lot of confidence that corner cases that could violate those usage restrictions would get caught during developer testing. Nor do I see an argument that we can't afford the cycles to check. * ConditionVariablePrepareToSleep needs to be rearranged so that failure to create the WaitEventSet doesn't leave us in an invalid state. * A lot of the comments could be improved, IMHO. Barring objection I'll go deal with those things, too. regards, tom lane
-
Re: Condition variable live lock
Robert Haas <robertmhaas@gmail.com> — 2018-01-05T19:52:38Z
On Fri, Jan 5, 2018 at 2:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > * I think the Asserts in ConditionVariablePrepareToSleep and > ConditionVariableSleep ought to be replaced by full-fledged test and > elog(ERROR), so that they are enforced even in non-assert builds. > I don't have a lot of confidence that corner cases that could violate > those usage restrictions would get caught during developer testing. > Nor do I see an argument that we can't afford the cycles to check. I think those usage restrictions are pretty basic, and I think this might be used in some places where performance does matter. So -1 from me for this change. > * ConditionVariablePrepareToSleep needs to be rearranged so that failure > to create the WaitEventSet doesn't leave us in an invalid state. +1. > * A lot of the comments could be improved, IMHO. No opinion without seeing what you propose to change. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-05T20:03:19Z
Robert Haas <robertmhaas@gmail.com> writes: > On Fri, Jan 5, 2018 at 2:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> * I think the Asserts in ConditionVariablePrepareToSleep and >> ConditionVariableSleep ought to be replaced by full-fledged test and >> elog(ERROR), so that they are enforced even in non-assert builds. >> I don't have a lot of confidence that corner cases that could violate >> those usage restrictions would get caught during developer testing. >> Nor do I see an argument that we can't afford the cycles to check. > I think those usage restrictions are pretty basic, and I think this > might be used in some places where performance does matter. So -1 > from me for this change. Really? We're about to do a process sleep, and we can't afford a single test and branch to make sure we're doing it sanely? >> * A lot of the comments could be improved, IMHO. > No opinion without seeing what you propose to change. OK, will put out a proposal. regards, tom lane
-
Re: Condition variable live lock
Thomas Munro <thomas.munro@enterprisedb.com> — 2018-01-05T21:25:27Z
On Sat, Jan 6, 2018 at 6:33 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > As I feared, the existing regression tests are not really adequate for > this: gcov testing shows that the sentinel-inserting code path is > never entered, meaning ConditionVariableBroadcast never sees more > than one waiter. What's more, it's now also apparent that no outside > caller of ConditionVariableSignal ever actually awakens anything. > So I think it'd be a good idea to expand the regression tests if we > can do so cheaply. Anybody have ideas about that? Perhaps a new > module under src/test/modules would be the best way? Alternatively, > we could drop some of the optimization ideas. I think I might have a suitable test module already. I'll tidy it up and propose it in a few days. > BTW, at least on gaur, this does nothing for the runtime of the join > test, meaning I'd still like to see some effort put into reducing that. Will do. -- Thomas Munro http://www.enterprisedb.com
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-06T21:00:24Z
I wrote: > Robert Haas <robertmhaas@gmail.com> writes: >> No opinion without seeing what you propose to change. > OK, will put out a proposal. I began with the intention of making no non-cosmetic changes, but then I started to wonder why ConditionVariablePrepareToSleep bothers with a !proclist_contains test, when the calling process surely ought not be in the list -- or any other list -- since it wasn't prepared to sleep. And that led me down a different rabbit hole ending in the conclusion that proclist.h could stand some improvements too. I do not like the fact that it's impossible to tell whether a proclist_node is in any proclist or not. Initially, a proclist_node contains zeroes which is a distinguishable state, but proclist_delete_offset resets it to next = prev = INVALID_PGPROCNO which looks the same as a node that's in a singleton list. We should have it reset to the initial state of zeroes instead, and then we can add assertions to proclist_push_xxx that the supplied node is not already in a list. Hence, I propose the first attached patch which tightens things up in proclist.h and then removes the !proclist_contains test in ConditionVariablePrepareToSleep; the assertion in proclist_push_tail supersedes that. The second attached patch is the cosmetic changes I want to make in condition_variable.c/.h. I still think that we ought to change the Asserts on cv_sleep_target in ConditionVariablePrepareToSleep and ConditionVariableSleep to be full test-and-elog tests. Those are checking a global correctness property ("global" meaning "interactions between completely unrelated modules can break this"), and they'd be extremely cheap compared to the rest of what those functions are doing, so I think insisting that they be Asserts is penny wise and pound foolish. Anybody besides Robert want to vote on that? Another loose end that I'm seeing here is that while a process waiting on a condition variable will respond to a cancel or die interrupt, it will not notice postmaster death. This seems unwise to me. I think we should adjust the WaitLatch call to include WL_POSTMASTER_DEATH as a wake condition and just do a summary proc_exit(1) if it sees that. I'd even argue that that is a back-patchable bug fix. regards, tom lane -
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-06T21:43:58Z
I wrote: > I still think that we ought to change the Asserts on cv_sleep_target in > ConditionVariablePrepareToSleep and ConditionVariableSleep to be full > test-and-elog tests. Those are checking a global correctness property > ("global" meaning "interactions between completely unrelated modules can > break this"), and they'd be extremely cheap compared to the rest of what > those functions are doing, so I think insisting that they be Asserts is > penny wise and pound foolish. Actually ... perhaps a better design would be to have ConditionVariable[PrepareTo]Sleep auto-cancel any prepared sleep for a different condition variable, analogously to what we just did in ConditionVariableBroadcast, on the same theory that whenever control returns to the other CV wait loop it can re-establish the relevant state easily enough. I have to think that if the use of CVs grows much, the existing restriction is going to become untenable anyway, so why not just get rid of it? regards, tom lane -
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-07T23:38:36Z
I wrote: > Actually ... perhaps a better design would be to have > ConditionVariable[PrepareTo]Sleep auto-cancel any prepared sleep for > a different condition variable, analogously to what we just did in > ConditionVariableBroadcast, on the same theory that whenever control > returns to the other CV wait loop it can re-establish the relevant > state easily enough. I have to think that if the use of CVs grows > much, the existing restriction is going to become untenable anyway, > so why not just get rid of it? Concretely, as per attached. regards, tom lane
-
Re: Condition variable live lock
Thomas Munro <thomas.munro@enterprisedb.com> — 2018-01-08T02:21:30Z
On Mon, Jan 8, 2018 at 12:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > I wrote: >> Actually ... perhaps a better design would be to have >> ConditionVariable[PrepareTo]Sleep auto-cancel any prepared sleep for >> a different condition variable, analogously to what we just did in >> ConditionVariableBroadcast, on the same theory that whenever control >> returns to the other CV wait loop it can re-establish the relevant >> state easily enough. I have to think that if the use of CVs grows >> much, the existing restriction is going to become untenable anyway, >> so why not just get rid of it? > > Concretely, as per attached. +1 for the idea. Haven't looked at the code yet but I'll review this and the proclist patch shortly. -- Thomas Munro http://www.enterprisedb.com
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-08T02:34:11Z
Thomas Munro <thomas.munro@enterprisedb.com> writes: > On Mon, Jan 8, 2018 at 12:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Concretely, as per attached. > +1 for the idea. Haven't looked at the code yet but I'll review this > and the proclist patch shortly. Thanks. BTW, I realized that there is a second (and perhaps more important) reason why we can only prepare one CV sleep at a time: we only have one cvWaitLink in our PGPROC. So I'm now inclined to word the revised comment in ConditionVariablePrepareToSleep as /* * If some other sleep is already prepared, cancel it; this is necessary * because we have just one static variable tracking the prepared sleep, * and also only one cvWaitLink in our PGPROC. It's okay to do this * because whenever control does return to the other test-and-sleep loop, * its ConditionVariableSleep call will just re-establish that sleep as * the prepared one. */ regards, tom lane -
Re: Condition variable live lock
Thomas Munro <thomas.munro@enterprisedb.com> — 2018-01-08T04:25:53Z
On Sun, Jan 7, 2018 at 10:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > I began with the intention of making no non-cosmetic changes, but then > I started to wonder why ConditionVariablePrepareToSleep bothers with a > !proclist_contains test, when the calling process surely ought not be > in the list -- or any other list -- since it wasn't prepared to sleep. > And that led me down a different rabbit hole ending in the conclusion > that proclist.h could stand some improvements too. I do not like the > fact that it's impossible to tell whether a proclist_node is in any > proclist or not. Initially, a proclist_node contains zeroes which is > a distinguishable state, but proclist_delete_offset resets it to > next = prev = INVALID_PGPROCNO which looks the same as a node that's in a > singleton list. We should have it reset to the initial state of zeroes > instead, and then we can add assertions to proclist_push_xxx that the > supplied node is not already in a list. Hence, I propose the first > attached patch which tightens things up in proclist.h and then removes > the !proclist_contains test in ConditionVariablePrepareToSleep; the > assertion in proclist_push_tail supersedes that. +1 > The second attached patch is the cosmetic changes I want to make in > condition_variable.c/.h. +1 > I still think that we ought to change the Asserts on cv_sleep_target in > ConditionVariablePrepareToSleep and ConditionVariableSleep to be full > test-and-elog tests. Those are checking a global correctness property > ("global" meaning "interactions between completely unrelated modules can > break this"), and they'd be extremely cheap compared to the rest of what > those functions are doing, so I think insisting that they be Asserts is > penny wise and pound foolish. Anybody besides Robert want to vote on > that? I liked your follow-up idea better (see below). > Another loose end that I'm seeing here is that while a process waiting on > a condition variable will respond to a cancel or die interrupt, it will > not notice postmaster death. This seems unwise to me. I think we should > adjust the WaitLatch call to include WL_POSTMASTER_DEATH as a wake > condition and just do a summary proc_exit(1) if it sees that. I'd even > argue that that is a back-patchable bug fix. Yeah. As far as I know so far, every place where we wait on a WaitEventSet falls into one of 4 categories when it comes to postmaster death: 1. We proc_exit(1) if WL_POSTMASTER_DEATH is returned. 2. We ereport(FATAL) if WL_POSTMASTER_DEATH is returned. 3. We asked for WL_POSTMASTER_DEATH in the WaitEventSet, but we don't actually bother checking for it in the returned value. Instead we call PostmasterIsAlive() every time through the loop (pgarch.c, syncrep.c, walsender.c and walreceiver.c). 4. We didn't ask for WL_POSTMASTER_DEATH. Do I have that right? I guess category 3 is suboptimal especially on some clunky but loveable kernels[1] and all cases of 4 including this one are probably bugs. That makes me wonder why we don't change the WaitEventSet API so that it calls proc_exit(1) for you by default if you didn't ask to receive WL_POSTMASTER_DEATH explicitly, to handle category 1 for free. If you asked for WL_POSTMASTER_DEATH explicitly then we could return it, to support category 2 callers that want to do something different. Just a thought. Another possibility for this particular case would be that the client of ConditionVariable would like to be able to chose how to handle postmaster death, and in turn the client of Barrier (a client) might like to be able to choose too. But in every case I'm aware of today proc_exit(1) is the right thing to do, so teaching ConditionVariableSleep() to do that seems OK to me. On Sun, Jan 7, 2018 at 10:43 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > Actually ... perhaps a better design would be to have > ConditionVariable[PrepareTo]Sleep auto-cancel any prepared sleep for > a different condition variable, analogously to what we just did in > ConditionVariableBroadcast, on the same theory that whenever control > returns to the other CV wait loop it can re-establish the relevant > state easily enough. I have to think that if the use of CVs grows > much, the existing restriction is going to become untenable anyway, > so why not just get rid of it? +1 It's a more robust API this way. On Mon, Jan 8, 2018 at 3:34 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > Thanks. BTW, I realized that there is a second (and perhaps more > important) reason why we can only prepare one CV sleep at a time: > we only have one cvWaitLink in our PGPROC. So I'm now inclined > to word the revised comment in ConditionVariablePrepareToSleep as > > /* > * If some other sleep is already prepared, cancel it; this is necessary > * because we have just one static variable tracking the prepared sleep, > * and also only one cvWaitLink in our PGPROC. It's okay to do this > * because whenever control does return to the other test-and-sleep loop, > * its ConditionVariableSleep call will just re-establish that sleep as > * the prepared one. > */ +1 [1] https://www.postgresql.org/message-id/flat/CAEepm%3D0qQ6DO-u%3D25ny5EJAUbWeHbAQgJj1UJFAL1NWJNxC%2Bgg%40mail.gmail.com -- Thomas Munro http://www.enterprisedb.com -
Re: Condition variable live lock
Thomas Munro <thomas.munro@enterprisedb.com> — 2018-01-08T11:41:36Z
On Mon, Jan 8, 2018 at 5:25 PM, Thomas Munro <thomas.munro@enterprisedb.com> wrote: > On Sun, Jan 7, 2018 at 10:43 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Actually ... perhaps a better design would be to have >> ConditionVariable[PrepareTo]Sleep auto-cancel any prepared sleep for >> a different condition variable, analogously to what we just did in >> ConditionVariableBroadcast, on the same theory that whenever control >> returns to the other CV wait loop it can re-establish the relevant >> state easily enough. I have to think that if the use of CVs grows >> much, the existing restriction is going to become untenable anyway, >> so why not just get rid of it? > > +1 > > It's a more robust API this way. One very small thing after another look: - Assert(cv_sleep_target == NULL); + if (cv_sleep_target != NULL) + ConditionVariableCancelSleep(); The test for cv_sleep_target != NULL is redundant since ConditionVariableCancelSleep() would return early. ConditionVariableBroadcast() doesn't do that. -- Thomas Munro http://www.enterprisedb.com
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-08T14:57:05Z
Thomas Munro <thomas.munro@enterprisedb.com> writes: > One very small thing after another look: > - Assert(cv_sleep_target == NULL); > + if (cv_sleep_target != NULL) > + ConditionVariableCancelSleep(); > The test for cv_sleep_target != NULL is redundant since > ConditionVariableCancelSleep() would return early. True. I did that because Robert was already objecting to the cost of an added test-and-branch here, so I figured he'd be really unhappy with the cost of a function call plus test-and-branch ;-) > ConditionVariableBroadcast() doesn't do that. Yup. I considered removing the discrepancy by adding a similar if-guard in ConditionVariableBroadcast(). The internal test in ConditionVariableCancelSleep would then be only for the benefit of outside callers such as AbortTransaction, but that seems fine and per its documentation. Or we could remove those if's and save a few bytes at the cost of some cycles. I don't care much. regards, tom lane
-
Re: Condition variable live lock
Robert Haas <robertmhaas@gmail.com> — 2018-01-08T20:27:35Z
On Sun, Jan 7, 2018 at 6:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Actually ... perhaps a better design would be to have >> ConditionVariable[PrepareTo]Sleep auto-cancel any prepared sleep for >> a different condition variable, analogously to what we just did in >> ConditionVariableBroadcast, on the same theory that whenever control >> returns to the other CV wait loop it can re-establish the relevant >> state easily enough. I have to think that if the use of CVs grows >> much, the existing restriction is going to become untenable anyway, >> so why not just get rid of it? > > Concretely, as per attached. I guess my aversion to converting the existing Assert-test into an elog test was really a concern that we'd be countenancing the use of CVs in any coding pattern more complicated than a very simple test-and-wait loop. Suppose someone were to propose adding runtime checks that when we release a spinlock, it is held by the process that tried to release it. Someone might reply that such a check ought to be unnecessary because the code protected by a spinlock ought to be a short, straight-line critical section and therefore we should be able to spot any such coding error by inspection. And I wonder if the same isn't true here. Is it really sensible, within a CV-wait loop, to call some other function that contains its own CV-wait loop? Is that really a use case we want to support? If you do have such a thing, with the present coding, you don't *need* an Assert() at runtime; you just need to run the code with assertions enabled AT LEAST ONCE. If the code flow is so complex that it doesn't reliably fail an assertion in test environments, maybe it's just too complex. Perhaps our answer to that, or so my thinking went, ought to be "don't write code like that in the first place". Now, that may be myopic on my part. If we want to support complex control flows involving CVs, the approach here has a lot to recommend it. Instead of making it the caller's problem to work it out, we do it automatically. There is some loss of efficiency, perhaps, since when control returns to the outer CV-wait loop it will have to recheck the condition twice before potentially waiting, but maybe that doesn't matter. It's often the case that mechanisms like this end up getting used profitably in a lot of places not imagined by their original creator, and that might be the case here. I think an extremely *likely* programming error when programming with CVs is to have a code path where ConditionVariableCancelSleep() does not get called. The proposed change could make such mistakes much less noticeable. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-08T20:45:02Z
Robert Haas <robertmhaas@gmail.com> writes: > I guess my aversion to converting the existing Assert-test into an > elog test was really a concern that we'd be countenancing the use of > CVs in any coding pattern more complicated than a very simple > test-and-wait loop. Suppose someone were to propose adding runtime > checks that when we release a spinlock, it is held by the process that > tried to release it. Someone might reply that such a check ought to > be unnecessary because the code protected by a spinlock ought to be a > short, straight-line critical section and therefore we should be able > to spot any such coding error by inspection. Right, *because* we have the rule that spinlock-protected code must be short straight-line segments. There is certainly no such rule for LWLocks, and I'm not sure why you think that CVs have more restricted use-cases than LWLocks. There is a potential issue, if you call random code inside the wait loop, that that code might reset the process latch and thereby lose a signal for the CV. As far as I can see at the moment, that'd only be a hazard for code executed before the loop's ConditionVariableSleep, not after, so even that can be avoided if you code properly. In any case, the proposed generalization of CVs poses no such hazard: yes, we might reset the latch, but if so the outer CV loop will see that it's lost its prepared sleep state and will be forced to recheck its exit condition. > It's often the case that mechanisms like this end up getting > used profitably in a lot of places not imagined by their original > creator, and that might be the case here. Yeah, that's exactly my expectation. If I thought we were only going to have five uses of condition variables forevermore, I wouldn't be putting much time into them. > I think an extremely *likely* programming error when programming with > CVs is to have a code path where ConditionVariableCancelSleep() does > not get called. The proposed change could make such mistakes much > less noticeable. Actually, the proposed change would turn it into barely an error at all. The only advantage of cancelling a sleep immediately is that you avoid possibly getting a no-longer-useful latch event later. regards, tom lane
-
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-08T23:50:33Z
Thomas Munro <thomas.munro@enterprisedb.com> writes: > On Sun, Jan 7, 2018 at 10:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> I began with the intention of making no non-cosmetic changes, but then >> I started to wonder why ConditionVariablePrepareToSleep bothers with a >> !proclist_contains test, when the calling process surely ought not be >> in the list -- or any other list -- since it wasn't prepared to sleep. >> And that led me down a different rabbit hole ending in the conclusion >> that proclist.h could stand some improvements too. > +1 >> The second attached patch is the cosmetic changes I want to make in >> condition_variable.c/.h. > +1 I pushed these, with an addition to ConditionVariablePrepareToSleep's comment emphasizing that you're supposed to call it before the loop test; this because some desultory digging found that one of the existing callers is violating that rule. replorigin_drop() in replication/logical/origin.c has cv = &state->origin_cv; LWLockRelease(ReplicationOriginLock); ConditionVariablePrepareToSleep(cv); ConditionVariableSleep(cv, WAIT_EVENT_REPLICATION_ORIGIN_DROP); ConditionVariableCancelSleep(); goto restart; and unless I'm much mistaken, that is flat broken. Somebody could change the ReplicationState's acquired_by before we reach ConditionVariablePrepareToSleep, in which case we won't get any signal for that and will just sleep here indefinitely. It would still be broken if we removed the PrepareToSleep call, but at least it'd be a busy-wait not a hang. Not sure about a convenient fix. One idea is to move the PrepareToSleep call inside the hold on ReplicationOriginLock, which would fix things only if the various signallers of origin_cv did the signalling while holding that lock, which they do not. It's not clear to me whether making them do so inside that lock would be problematic for performance. We can't just move the prepare/cancel sleep calls to around this whole loop, because we do not know outside the loop which CV is to be waited on. So another idea is to get rid of that and have just one CV for all ReplicationStates. Or (and I'm sure Robert sees this coming), if we applied my proposed patch to let ConditionVariableSleep auto-switch to a different CV, then we could fix this code by simply removing the PrepareToSleep and moving the ConditionVariableCancelSleep call to below the loop. This would work even in the perhaps-unlikely case where the slot as identified by roident changed positions, though you'd get an additional trip through the loop (a/k/a test of the exit condition) if that happened. >> Another loose end that I'm seeing here is that while a process waiting on >> a condition variable will respond to a cancel or die interrupt, it will >> not notice postmaster death. > Yeah. I'll respond to that separately to keep it from getting confused with this replorigin_drop() bug. regards, tom lane -
Re: Condition variable live lock
Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-09T00:02:23Z
Thomas Munro <thomas.munro@enterprisedb.com> writes: > On Sun, Jan 7, 2018 at 10:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Another loose end that I'm seeing here is that while a process waiting on >> a condition variable will respond to a cancel or die interrupt, it will >> not notice postmaster death. This seems unwise to me. I think we should >> adjust the WaitLatch call to include WL_POSTMASTER_DEATH as a wake >> condition and just do a summary proc_exit(1) if it sees that. I'd even >> argue that that is a back-patchable bug fix. > Yeah. As far as I know so far, every place where we wait on a > WaitEventSet falls into one of 4 categories when it comes to > postmaster death: > 1. We proc_exit(1) if WL_POSTMASTER_DEATH is returned. > 2. We ereport(FATAL) if WL_POSTMASTER_DEATH is returned. > 3. We asked for WL_POSTMASTER_DEATH in the WaitEventSet, but we don't > actually bother checking for it in the returned value. Instead we > call PostmasterIsAlive() every time through the loop (pgarch.c, > syncrep.c, walsender.c and walreceiver.c). > 4. We didn't ask for WL_POSTMASTER_DEATH. > Do I have that right? I guess category 3 is suboptimal especially on > some clunky but loveable kernels[1] and all cases of 4 including this > one are probably bugs. That makes me wonder why we don't change the > WaitEventSet API so that it calls proc_exit(1) for you by default if > you didn't ask to receive WL_POSTMASTER_DEATH explicitly, to handle > category 1 for free. If you asked for WL_POSTMASTER_DEATH explicitly > then we could return it, to support category 2 callers that want to do > something different. Just a thought. Yeah, that's worth thinking about, because I'm pretty sure that we have had this type of bug before. We have to be a bit careful because Andres is thinking of using the WaitEventState support in the postmaster loop, where it definitely shouldn't check WL_POSTMASTER_DEATH, and it is probably also possible to reach some of those calls in standalone backends, which shouldn't either. So I'm imagining: 1. The WaitEventSet code always includes WL_POSTMASTER_DEATH checking if IsUnderPostmaster --- and conversely, if !IsUnderPostmaster, it should ignore any caller request for that. 2. If caller specifies WL_POSTMASTER_DEATH then we'll return that flag bit, otherwise just exit(1) --- or should the default be ereport(FATAL)? 3. Remove explicit specification of WL_POSTMASTER_DEATH anywhere that the default handling is OK, which is probably almost everywhere. Get rid of those now-redundant PostmasterIsAlive checks, too. I'm not real sure BTW why we have some callers that ereport and some that just exit(1). Seems like it would be better to be consistent, though I'm not entirely sure which behavior to standardize on. (Of course, this would only be an appropriate thing to do in HEAD. In v10 I think we should do the narrow fix I suggested up top.) regards, tom lane
-
Re: Condition variable live lock
Robert Haas <robertmhaas@gmail.com> — 2018-01-09T04:02:32Z
On Mon, Jan 8, 2018 at 7:02 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > I'm not real sure BTW why we have some callers that ereport and some > that just exit(1). Seems like it would be better to be consistent, > though I'm not entirely sure which behavior to standardize on. I think at one point we had an idea that regular backends would FATAL if the postmaster fell over and other processes (e.g. checkpointer, bgwriter) would exit silently. Whether that was the right idea, and whether it's still is/was ever what the code did, I'm not sure. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company