Thread: Re: pgsql: Add function to get memory context stats for processes
On Tue, Apr 8, 2025 at 5:10 AM Daniel Gustafsson <dgustafsson@postgresql.org> wrote: > Add function to get memory context stats for processes Apologies if this has already been discussed, but what is the argument that it is safe to do everything in ProcessGetMemoryContextInterrupt() at an arbitrary CHECK_FOR_INTERRUPTS() call? We have CHECK_FOR_INTERRUPTS() calls in some quite low-level places, such as walkdir() and copydir(). I don't think there's any guarantee that it's safe to perform DSA operations at an arbitrary place where CHECK_FOR_INTERRUPTS() is called, and I'm not even quite sure that it's safe to assume that the local memory-context tree is in a consistent state when CHECK_FOR_INTERRUPTS() is called. If there is some existing discussion of this that I should read, please point me in the right direction; I didn't see anything in a quick look through the commit. Thanks, -- Robert Haas EDB: http://www.enterprisedb.com
Hi, On 2025-04-10 09:31:00 -0400, Robert Haas wrote: > On Tue, Apr 8, 2025 at 5:10 AM Daniel Gustafsson > <dgustafsson@postgresql.org> wrote: > > Add function to get memory context stats for processes > > Apologies if this has already been discussed, but what is the argument > that it is safe to do everything in ProcessGetMemoryContextInterrupt() > at an arbitrary CHECK_FOR_INTERRUPTS() call? We have > CHECK_FOR_INTERRUPTS() calls in some quite low-level places, such as > walkdir() and copydir(). I don't think there's any guarantee that it's > safe to perform DSA operations at an arbitrary place where > CHECK_FOR_INTERRUPTS() is called, and I'm not even quite sure that > it's safe to assume that the local memory-context tree is in a > consistent state when CHECK_FOR_INTERRUPTS() is called. I don't know of existing discussion, but it seems rather fundamental to me - if either DSA or memory contexts could be inconsistent at a CFI(), how could it possibly be safe to interrupt at that point? After all, after an error you need to be able to reset the memory contexts / release memory in a dsa/dshash/whatnot? Memory context reset requires walking over the allocations made in the context, similar releasing a dsa? Greetings, Andres Freund
On Thu, Apr 10, 2025 at 4:05 PM Andres Freund <andres@anarazel.de> wrote: > I don't know of existing discussion, but it seems rather fundamental to me - > if either DSA or memory contexts could be inconsistent at a CFI(), how could > it possibly be safe to interrupt at that point? After all, after an error you > need to be able to reset the memory contexts / release memory in a > dsa/dshash/whatnot? Memory context reset requires walking over the allocations > made in the context, similar releasing a dsa? I think it would be a bit surprising if somebody put a CHECK_FOR_INTERRUPTS() inside aset.c or similar, but I don't see a reason why we couldn't end up with one reachable via the DSA code. DSA calls DSM which depending on dynamic_shared_memory_type might involve filesystem operations. That's a fairly large amount of code. I admit I have no particular theory about how CFI could be reachable from there today, but even if it definitely isn't, I don't see why someone would hesitate to add one in the future. -- Robert Haas EDB: http://www.enterprisedb.com
Hi, On 2025-04-14 10:03:28 -0400, Robert Haas wrote: > On Thu, Apr 10, 2025 at 4:05 PM Andres Freund <andres@anarazel.de> wrote: > > I don't know of existing discussion, but it seems rather fundamental to me - > > if either DSA or memory contexts could be inconsistent at a CFI(), how could > > it possibly be safe to interrupt at that point? After all, after an error you > > need to be able to reset the memory contexts / release memory in a > > dsa/dshash/whatnot? Memory context reset requires walking over the allocations > > made in the context, similar releasing a dsa? > > I think it would be a bit surprising if somebody put a > CHECK_FOR_INTERRUPTS() inside aset.c or similar, but I don't see a > reason why we couldn't end up with one reachable via the DSA code. DSA > calls DSM which depending on dynamic_shared_memory_type might involve > filesystem operations. That's a fairly large amount of code. I admit I > have no particular theory about how CFI could be reachable from there > today, but even if it definitely isn't, I don't see why someone would > hesitate to add one in the future. There very well could be a CFI - but it better be somewhere where the in-memory state is consistent. Otherwise an error inside raised in the CFI would lead the in-memory state inconsistent which then would cause problems when cleaning up the dsa during resowner release or process exit. What am I missing here? Greetings, Andres Freund
On Tue, Apr 15, 2025 at 6:11 AM Andres Freund <andres@anarazel.de> wrote: > There very well could be a CFI - but it better be somewhere where the > in-memory state is consistent. Otherwise an error inside raised in the CFI > would lead the in-memory state inconsistent which then would cause problems > when cleaning up the dsa during resowner release or process exit. > > What am I missing here? I think maybe you're only thinking about gathering the data. What about publishing it? If the DSA code were interrupted at a CFI and the interrupting code went and tried to perform a DSA allocation to store the resulting data and then returned to the interrupted DSA operation, would you expect the code to cope with that? I do not believe we have anywhere enough guarantees about reentrancy for that to be safe. -- Robert Haas EDB: http://www.enterprisedb.com
> On 17 Apr 2025, at 16:42, Robert Haas <robertmhaas@gmail.com> wrote: > > On Tue, Apr 15, 2025 at 6:11 AM Andres Freund <andres@anarazel.de> wrote: >> There very well could be a CFI - but it better be somewhere where the >> in-memory state is consistent. Otherwise an error inside raised in the CFI >> would lead the in-memory state inconsistent which then would cause problems >> when cleaning up the dsa during resowner release or process exit. >> >> What am I missing here? > > I think maybe you're only thinking about gathering the data. What > about publishing it? If the DSA code were interrupted at a CFI and the > interrupting code went and tried to perform a DSA allocation to store > the resulting data and then returned to the interrupted DSA operation, > would you expect the code to cope with that? I do not believe we have > anywhere enough guarantees about reentrancy for that to be safe. Do you mean that an interrupt handler will make DSA allocations? I don't think that would be something we'd want to allow regardless of this patch. Or did I misunderstand the above? -- Daniel Gustafsson
Hi, On 2025-04-17 10:42:45 -0400, Robert Haas wrote: > On Tue, Apr 15, 2025 at 6:11 AM Andres Freund <andres@anarazel.de> wrote: > > There very well could be a CFI - but it better be somewhere where the > > in-memory state is consistent. Otherwise an error inside raised in the CFI > > would lead the in-memory state inconsistent which then would cause problems > > when cleaning up the dsa during resowner release or process exit. > > > > What am I missing here? > > I think maybe you're only thinking about gathering the data. What > about publishing it? If the DSA code were interrupted at a CFI and the > interrupting code went and tried to perform a DSA allocation to store > the resulting data and then returned to the interrupted DSA operation, > would you expect the code to cope with that? I would expect the DSA code to not call CFI() in such a place - afaict nearly all such cases would also not be safe against errors being raised in the CFI() and then later again allocating memory from the DSA (or resetting it). Similarly, aset.c better not accept interrupts in the middle of, e.g., AllocSetAllocFromNewBlock(), since we'd loose track of the allocation. Greetings, Andres Freund
On Tue, Apr 22, 2025 at 4:12 AM Daniel Gustafsson <dgustafsson@postgresql.org> wrote: > Do you mean that an interrupt handler will make DSA allocations? I don't think > that would be something we'd want to allow regardless of this patch. Or did I > misunderstand the above? My primary concern about the patch is that ProcessGetMemoryContextInterrupt() can be called from any CHECK_FOR_INTERRUPTS() and calls lots of DSA functions, including dsa_create() and, via PublishMemoryContext(), dsa_allocate0(). I'm shocked to hear that you and Andres think that's safe to do at any current or future CHECK_FOR_INTERRUPTS() anywhere in the code; but Andres seems very confident that it's fine, so perhaps I should just stop worrying and be happy that we have the feature. I thought that the issues here would be similar to https://commitfest.postgresql.org/patch/5473/ and https://commitfest.postgresql.org/patch/5330/ where it seems we need to go to fairly extraordinary lengths to try to make the operation safe -- the proposal there is essentially to have a CFI handler run around the executor state tree and replace every ExecProcNode pointer with a pointer to some new wrapper function which in turn does the actual query-plan printing. Even that requires some tricky footwork to get right, but the core idea is that you can't imagine that CHECK_FOR_INTERRUPTS() is a safe place to do arbitrary stuff, so you have to use it to trigger the actual work at some later point where it will be safe to do the stuff that you want to do. I suppose the difference in this case is that memory-allocation is a lower-level subsystem than query execution and therefore there are presumably fewer places where it's not safe to assume that you can do memory-allocation type operations. But I at least feel like DSA is a pretty high-level system that I wouldn't want to count on being able to access from a fairly-arbitrary point in the code, which is why I'm quite astonished to hear Andres basically saying "don't worry, it's all fine!". But my astonishment does not mean that he is wrong. -- Robert Haas EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes: > My primary concern about the patch is that > ProcessGetMemoryContextInterrupt() can be called from any > CHECK_FOR_INTERRUPTS() and calls lots of DSA functions, including > dsa_create() and, via PublishMemoryContext(), dsa_allocate0(). I'm > shocked to hear that you and Andres think that's safe to do at any > current or future CHECK_FOR_INTERRUPTS() anywhere in the code; but > Andres seems very confident that it's fine, so perhaps I should just > stop worrying and be happy that we have the feature. Just for the record, it sounds quite unsafe to me too. I could credit it being all right to examine the process' MemoryContext data structures, but calling dsa_create() from CFI seems really insane. Way too many moving parts there. regards, tom lane
On 4/23/25 20:14, Tom Lane wrote: > Robert Haas <robertmhaas@gmail.com> writes: >> My primary concern about the patch is that >> ProcessGetMemoryContextInterrupt() can be called from any >> CHECK_FOR_INTERRUPTS() and calls lots of DSA functions, including >> dsa_create() and, via PublishMemoryContext(), dsa_allocate0(). I'm >> shocked to hear that you and Andres think that's safe to do at any >> current or future CHECK_FOR_INTERRUPTS() anywhere in the code; but >> Andres seems very confident that it's fine, so perhaps I should just >> stop worrying and be happy that we have the feature. > > Just for the record, it sounds quite unsafe to me too. I could > credit it being all right to examine the process' MemoryContext data > structures, but calling dsa_create() from CFI seems really insane. > Way too many moving parts there. > Maybe I'm oblivious to some very obvious issues, but why is this so different from everything else that is already called from ProcessInterrupts()? Perhaps dsa_create() is more complex compared to the other stuff (haven't checked), but why would it be unsafe? The one risk I can think of is a risk of recursion - if any of the functions called from ProcessGetMemoryContextInterrupt() does CFI, could that be a problem if there's another pending signal. I see some other handlers (e.g. ProcessParallelApplyMessages) handle this by explicitly holding interrupts. Should ProcessGetMemoryContextInterrupt() do the same thing? In any case, if DSA happens to not be the right way to transfer this, what should we use instead? The only thing I can think of is some sort of pre-allocated chunk of shared memory. regards -- Tomas Vondra
On Sat, Apr 26, 2025 at 5:07 PM Tomas Vondra <tomas@vondra.me> wrote: > > Just for the record, it sounds quite unsafe to me too. I could > > credit it being all right to examine the process' MemoryContext data > > structures, but calling dsa_create() from CFI seems really insane. > > Way too many moving parts there. > > Maybe I'm oblivious to some very obvious issues, but why is this so > different from everything else that is already called from > ProcessInterrupts()? Perhaps dsa_create() is more complex compared to > the other stuff (haven't checked), but why would it be unsafe? > > The one risk I can think of is a risk of recursion - if any of the > functions called from ProcessGetMemoryContextInterrupt() does CFI, could > that be a problem if there's another pending signal. I see some other > handlers (e.g. ProcessParallelApplyMessages) handle this by explicitly > holding interrupts. Should ProcessGetMemoryContextInterrupt() do the > same thing? > > In any case, if DSA happens to not be the right way to transfer this, > what should we use instead? The only thing I can think of is some sort > of pre-allocated chunk of shared memory. The big disadvantage of a pre-allocated chunk of shared memory is that it would necessarily be fixed size, and a memory context dump can be really big. Another alternative would be a temporary file. But none of that settles the question of safety. I think part of the reason why it's possible for us to have disagreements about whether this is safe is that we don't have any clear documentation of what you can assume to be true at a CHECK_FOR_INTERRUPTS(). It's not possible for there to be an LWLock held at that point, because we hold off interrupts when you acquire an LWLock and don't re-enable them until all LWLocks have been released. We can't be holding a spinlock, either, because we only insert CHECK_FOR_INTERRUPTS() at the top of loops and code that holds a spinlock is supposed to be straight-line code that never loops. But what else can you assume? Can you assume, for example, that there's a transaction? I doubt it. Can you assume that the transaction is non-aborted? I doubt that even more. There's no obvious-to-me reason why those things should be true. And in fact if you try this on a backend waiting in an aborted transaction, it breaks: robert.haas=# select pg_backend_pid(); pg_backend_pid ---------------- 19321 (1 row) robert.haas=# begin; BEGIN robert.haas=*# select 1/0; ERROR: division by zero And then from another session, run this command, using the PID from above: select * from pg_get_process_memory_contexts(19321, false, 1); Then you get: 2025-04-30 15:14:33.878 EDT [19321] ERROR: ResourceOwnerEnlarge called after release started 2025-04-30 15:14:33.878 EDT [19321] FATAL: terminating connection because protocol synchronization was lost I kind of doubt whether that's the only problem here, but it's *a* problem. -- Robert Haas EDB: http://www.enterprisedb.com
> On 30 Apr 2025, at 22:17, Robert Haas <robertmhaas@gmail.com> wrote: > I kind of doubt whether that's the only problem here, but it's *a* problem. This is indeed exposing a pre-existing issue, which was reported in [0] and a patch fixing it has been submitted. I have been testing and reworking the patch slightly but due to $life and $work events have yet to have time to push it. -- Daniel Gustafsson [0] 3eb40b3e-45c7-426a-b7f8-81f7d05a9b53@oss.nttdata.com
On Wed, Apr 30, 2025 at 4:29 PM Daniel Gustafsson <dgustafsson@postgresql.org> wrote: > This is indeed exposing a pre-existing issue, which was reported in [0] and a > patch fixing it has been submitted. I have been testing and reworking the > patch slightly but due to $life and $work events have yet to have time to push > it. Thanks for the pointer. I will reply to that thread. -- Robert Haas EDB: http://www.enterprisedb.com