Thread
-
[PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-11-17T21:17:19Z
Following up on Tom Lane's suggestion to use MemoryContexts for GUC extra data [1], I've implemented a working solution that addresses the design issues Robert Haas identified with my initial approach. The original problem: GUC check hooks can only return a single chunk for extra data, making it awkward to use complex structures like Lists or hash tables. Tom suggested allowing the extra field to point to a MemoryContext instead, which would enable arbitrary nested structures with automatic cleanup via MemoryContextDelete(). My first implementation stored the context pointer directly as the extra data. Robert pointed out the fatal flaw: during transaction rollback, the assign hook needs to receive a data pointer (to update its global variable), but if extra contains a context pointer, there's no way to retrieve the actual data. A global mapping table would work but seemed unnecessarily complex. The solution uses a wrapper struct (GucContextExtra) containing both the MemoryContext and data pointers. Check hooks: 1. Create a context under CurrentMemoryContext (for error safety) 2. Allocate their data structures within it 3. Allocate the wrapper itself within the same context 4. On success, re-parent the context to TopMemoryContext 5. Return the wrapper as extra The GUC machinery manages wrapper pointers in its stack. On rollback, the assign hook receives the old wrapper and extracts the correct old data pointer, maintaining proper transaction semantics. When freeing extra data, we simply delete the context - since the wrapper lives inside it, everything is freed with one call. Error handling is automatic: if the check hook errors during parsing, the context is still under CurrentMemoryContext and gets cleaned up normally, preventing leaks. The attached patch adds: - GUC_EXTRA_IS_CONTEXT flag - GucContextExtra struct definition - Modified free_extra_value() to handle both paths - Test module (src/test/modules/test_guc) with simple counter (traditional path, no context) and server pool (context-based path with List) Regression tests validate that Lists survive transaction rollback and savepoint operations correctly. Thoughts? Patch attached. [1] https://discord.com/channels/1258108670710124574/1402360503036285130 -- Bryan Green EDB: https://www.enterprisedb.com
-
Re: [PATCH] Allow complex data for GUC extra.
Robert Haas <robertmhaas@gmail.com> — 2025-11-18T17:24:50Z
On Mon, Nov 17, 2025 at 4:17 PM Bryan Green <dbryan.green@gmail.com> wrote: > The solution uses a wrapper struct (GucContextExtra) containing both the > MemoryContext and data pointers. Check hooks: > 1. Create a context under CurrentMemoryContext (for error safety) > 2. Allocate their data structures within it > 3. Allocate the wrapper itself within the same context > 4. On success, re-parent the context to TopMemoryContext > 5. Return the wrapper as extra An alternative design would be to make the check hook simply return a chunk palloc'd from the new context, and the GUC machinery would use GetMemoryChunkContext() to recover the context pointer and then MemoryContextDelete that context. I'm not sure if that's better or worse. I think one of the big usability questions around this is how the check hook is supposed to avoid leaking if it errors out. The approach you've taken is to have the check hook create the context under CurrentMemoryContext and then reparent it just before returning, which may be fine, but is worth discussing. I'm not 100% sure that it's actually good enough for every case: is there no situation where a check hook can be called without a CurrentMemoryContext, or with a very long-lived memory context like TopMemoryContext set to current? Even if there's technically a leak here, maybe we don't care: it might be limited enough not to matter. A whole different way of doing this would be to make the GUC machinery responsible for spinning up and tearing down the contexts. Then, the check hook could just be called with CurrentMemoryContext already set to the new context, and the caller would know about it. Then, the check hook doesn't need any special precautions to make sure the context gets destroyed; instead, the GUC machinery takes care of that. Here again, I'm not sure if this is better or worse than what you have. Thanks for working on this. -- Robert Haas EDB: http://www.enterprisedb.com
-
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-11-18T17:47:17Z
On 11/18/2025 11:24 AM, Robert Haas wrote: > On Mon, Nov 17, 2025 at 4:17 PM Bryan Green <dbryan.green@gmail.com> wrote: >> The solution uses a wrapper struct (GucContextExtra) containing both the >> MemoryContext and data pointers. Check hooks: >> 1. Create a context under CurrentMemoryContext (for error safety) >> 2. Allocate their data structures within it >> 3. Allocate the wrapper itself within the same context >> 4. On success, re-parent the context to TopMemoryContext >> 5. Return the wrapper as extra > > An alternative design would be to make the check hook simply return a > chunk palloc'd from the new context, and the GUC machinery would use > GetMemoryChunkContext() to recover the context pointer and then > MemoryContextDelete that context. I'm not sure if that's better or > worse. > This one is better I believe, but it still requires the check hook to manage context. > I think one of the big usability questions around this is how the > check hook is supposed to avoid leaking if it errors out. The approach > you've taken is to have the check hook create the context under > CurrentMemoryContext and then reparent it just before returning, which > may be fine, but is worth discussing. I'm not 100% sure that it's > actually good enough for every case: is there no situation where a > check hook can be called without a CurrentMemoryContext, or with a > very long-lived memory context like TopMemoryContext set to current? > Even if there's technically a leak here, maybe we don't care: it might > be limited enough not to matter. > You are correct, the reparenting approach could still leak memory. I found examples where the current memory context is already TopMemoryContext. > A whole different way of doing this would be to make the GUC machinery > responsible for spinning up and tearing down the contexts. Then, the > check hook could just be called with CurrentMemoryContext already set > to the new context, and the caller would know about it. Then, the > check hook doesn't need any special precautions to make sure the > context gets destroyed; instead, the GUC machinery takes care of that. > Here again, I'm not sure if this is better or worse than what you > have. > At first blush, I am leaning towards this solution because it seems cleaner and not leaky. The GUC machinery would: 1. Create a temporary context (child of TopMemoryContext) 2. Set it as CurrentMemoryContext 3. Call check_hook (allocates freely with palloc) 4. Restore previous CurrentMemoryContext 5. On success: keep context, store pointer to data 6. On error: delete context automatically Check hooks then become trivial - you just palloc what you need, no context management at all. The machinery handles everything. The flag (GUC_EXTRA_IS_CONTEXT) handles that will still handle distinquishing between plain extra data and context as extra data. Combined with your GetMemoryChunkContext() idea, we could eliminate the wrapper entirely: In GUC machinery (set_config_option): if (gconf->flags & GUC_EXTRA_IS_CONTEXT) { extra_cxt = AllocSetContextCreate(TopMemoryContext, ...); old_context = MemoryContextSwitchTo(extra_cxt); } /* Call check hook - just pallocs what it needs */ if (!call_check_hook(..., &extra)) { if (gconf->flags & GUC_EXTRA_IS_CONTEXT) MemoryContextDelete(extra_cxt); return false; } if (gconf->flags & GUC_EXTRA_IS_CONTEXT) MemoryContextSwitchTo(old_context); In free_extra_value(): if (gconf->flags & GUC_EXTRA_IS_CONTEXT) MemoryContextDelete(GetMemoryChunkContext(extra)); else guc_free(extra); This is significantly cleaner. The downside is more complexity in the GUC machinery itself, but it makes check hooks much simpler to write and reduces the chances of getting them wrong. Thoughts? I'm happy to rework the patch along these lines if this approach seems better-- which it does to me. > Thanks for working on this. > -- Bryan Green EDB: https://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Tom Lane <tgl@sss.pgh.pa.us> — 2025-11-18T18:21:22Z
Bryan Green <dbryan.green@gmail.com> writes: > On 11/18/2025 11:24 AM, Robert Haas wrote: >> A whole different way of doing this would be to make the GUC machinery >> responsible for spinning up and tearing down the contexts. Then, the >> check hook could just be called with CurrentMemoryContext already set >> to the new context, and the caller would know about it. Then, the >> check hook doesn't need any special precautions to make sure the >> context gets destroyed; instead, the GUC machinery takes care of that. I like this in principle, but I don't think Bryan's implementation sketch is right: > 1. Create a temporary context (child of TopMemoryContext) If the check_hook throws an error, you'll have leaked a long-lived context. You must *not* make it a child of TopMemoryContext until after successful assignment. I take Robert's point that we don't know whether the GUC logic will be called in a context that is short-lived or long-lived, so maybe making the context transiently a child of CurrentMemoryContext isn't good enough ... but TopMemoryContext is most definitely not good enough. (Actually, these things should be children of GUCMemoryContext not directly of TopMemoryContext. But that doesn't affect this point, since those are equally long-lived.) I'm really still dubious that this entire project is worthwhile. I think it is basically building support for GUCs whose values are unreasonably complicated, and would be better off if they got redesigned. Also, right now might be a bad time to be adding complexity to guc.c, in view of discussions such as [1]. regards, tom lane [1] https://www.postgresql.org/message-id/flat/2ff46ac9-b46c-4210-8f0c-0f5365b36db9%40eisentraut.org
-
Re: [PATCH] Allow complex data for GUC extra.
Robert Haas <robertmhaas@gmail.com> — 2025-11-18T19:56:22Z
On Tue, Nov 18, 2025 at 1:21 PM Tom Lane <tgl@sss.pgh.pa.us> wrote: > I'm really still dubious that this entire project is worthwhile. > I think it is basically building support for GUCs whose values > are unreasonably complicated, and would be better off if they > got redesigned. Also, right now might be a bad time to be > adding complexity to guc.c, in view of discussions such as [1]. What motivated me to care about this was pg_plan_advice.advice, which is indeed arguably too complicated to be a GUC, but I don't have a better idea right now. I thought about using a pg_upgrade-support style thing, like SELECT pg_plan_advice.next_advice_is('text value') -- but this seems really awkward to me in the context of code running inside of a function, because we don't know whether the query we're about to see is going to get planned or not. And while someone may think "just pass it through via the SQL comments" is a solution to this problem, I find I cannot agree for a whole long list of reasons. It seems to most naturally fit as a GUC, even though that's not a great fit. Also, that's not the only case we have of something like this. check_synchronous_standby_names and check_synchronized_standby_slots are good, existing examples of where substantial parsing is required and then effort must be expended to get it back into a single palloc'd chunk. check_backtrace_functions is an interesting example too: would we really pick this particular representation if the GUC infrastructure didn't require it? I have my doubts. In general, I don't think that whether or not a GUC's parsed value can be serialized easily into a single palloc'd chunk is a good measure of whether it's too complicated. I agree, of course, that we shouldn't randomly sandwhich a bunch of disparate values into a single GUC -- several separate GUCs is better. However, what about a value that intrinsically has some internal structure? We originally thought that we wanted synchronous_standby_names to just be a list of standbys, which barely qualifies as internal structure and so fits with the idea of a single palloc'd chunk, but then we decided we wanted to allow prefixing that list stuff like ANY 2 or FIRST 3. Does that make it no longer suitable to be a GUC? What if we had instead decided to allow nested structure, like synchronous_standby_names = a, (b, c), d? That definitely isn't nice for a flat structure, but I doubt anyone would like it if that adjustment suddenly meant it had to be some other kind of thing rather than a GUC, and what would the other thing be, anyway? -- Robert Haas EDB: http://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Andrew Dunstan <andrew@dunslane.net> — 2025-11-21T22:26:52Z
On 2025-11-18 Tu 2:56 PM, Robert Haas wrote: > > In general, I don't think that whether or not a GUC's parsed value can > be serialized easily into a single palloc'd chunk is a good measure of > whether it's too complicated. +1 > I agree, of course, that we shouldn't > randomly sandwhich a bunch of disparate values into a single GUC -- > several separate GUCs is better. However, what about a value that > intrinsically has some internal structure? We originally thought that > we wanted synchronous_standby_names to just be a list of standbys, > which barely qualifies as internal structure and so fits with the idea > of a single palloc'd chunk, but then we decided we wanted to allow > prefixing that list stuff like ANY 2 or FIRST 3. Does that make it no > longer suitable to be a GUC? What if we had instead decided to allow > nested structure, like synchronous_standby_names = a, (b, c), d? That > definitely isn't nice for a flat structure, but I doubt anyone would > like it if that adjustment suddenly meant it had to be some other kind > of thing rather than a GUC, and what would the other thing be, anyway? > If GUC A depends for sanity on the value of GUC B, it seems rather odd to force them to be independent at the grammar level. A structured GUC would make more sense in such a case. One of the things that bothers me a bit here is that we seem to be inventing a bunch of micro-languages to deal with structured GUC data. <asbestos-mode> Maybe they could all be JSON?</> cheers andrew -- Andrew Dunstan EDB: https://www.enterprisedb.com
-
Re: [PATCH] Allow complex data for GUC extra.
Robert Haas <robertmhaas@gmail.com> — 2025-11-22T18:55:18Z
On Fri, Nov 21, 2025 at 5:26 PM Andrew Dunstan <andrew@dunslane.net> wrote: > One of the things that bothers me a bit here is that we seem to be > inventing a bunch of micro-languages to deal with structured GUC data. > <asbestos-mode> Maybe they could all be JSON?</> I can understand why you (or anyone) would suggest this, but I don't think it would actually be better, for three reasons. First, changing the format for something like synchronous_standby_names would be a backward compatibility break. Second, I suspect the resulting format would be more long-winded. Instead of ANY 2 (foo, bar, baz) you'd have to write something like { 'op' => 'ANY', 'num' => 2, 'servers' => [ 'foo', 'bar', 'baz' ]}. Third, if my experience with using JSON for backup manifests is any indication, it would actually add significantly more code. The JSON parser doesn't do all the work for you, because you have to do semantic validation of the JSON afterwards. See parse_manifest.c for an example of what I mean. I think the real strength of JSON is not that it's any easier to use or objectively better than a mini-language, but that it's a standard. If we threw out the entire postgresql.conf format and replaced it with a big JSON document whose keys were GUC names and whose values were all JSON values, then somebody could manipulate that whole document using any JSON tool that they like, and that would probably be handy for some people. Same if we made the whole thing XML or whatever. We would probably make life harder for ourselves, but if it had enough benefit for users, it might worth it. I don't think users would actually be happy about such a change and I'm not proposing it, but it's the kind of thing I can imagine making sense hypothetically. If we were starting from scratch rather than trying to maintain compatibility with our previous releases, it would be worth thinking about. Absent that, I think ad-hoc mini-languages are a pretty good idea. By designing something that does exactly what you need and nothing else, you can often create something that is clear, succinct, and easy for users to learn. synchronous_standby_names is a good example: it's not that hard to understand how it works, it does what we need, and it doesn't become any easier to use if you make it JSON or XML or whatever. The plan advice mini-language is a more arguable case, but I based that on existing mini-languages that do similar things and then adapted it for what I was trying to do, which means that people familiar with those other things might have an easier learning curve. I am of course open to alternate ideas of how that language should work, and there are definitely things about it that I don't like. But as far as I can see, none of the things I'm unhappy about would be fixed by using JSON. In response to your asbestos-mode tag, let me say that none of this is intended to flame you, or to express outrage. I know a lot of people would say that my appreciation for a well-chosen mini-language is wrong-headed, and you may be one of them, and that's fair enough. But I see it differently, so this is just to explain my view of it. -- Robert Haas EDB: http://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Bruce Momjian <bruce@momjian.us> — 2025-11-22T19:00:05Z
On Fri, Nov 21, 2025 at 05:26:52PM -0500, Andrew Dunstan wrote: > > I agree, of course, that we shouldn't > > randomly sandwhich a bunch of disparate values into a single GUC -- > > several separate GUCs is better. However, what about a value that > > intrinsically has some internal structure? We originally thought that > > we wanted synchronous_standby_names to just be a list of standbys, > > which barely qualifies as internal structure and so fits with the idea > > of a single palloc'd chunk, but then we decided we wanted to allow > > prefixing that list stuff like ANY 2 or FIRST 3. Does that make it no > > longer suitable to be a GUC? What if we had instead decided to allow > > nested structure, like synchronous_standby_names = a, (b, c), d? That > > definitely isn't nice for a flat structure, but I doubt anyone would > > like it if that adjustment suddenly meant it had to be some other kind > > of thing rather than a GUC, and what would the other thing be, anyway? > > > > If GUC A depends for sanity on the value of GUC B, it seems rather odd to > force them to be independent at the grammar level. A structured GUC would > make more sense in such a case. > > One of the things that bothers me a bit here is that we seem to be inventing > a bunch of micro-languages to deal with structured GUC data. <asbestos-mode> > Maybe they could all be JSON?</> As long as you didn't say XML, we are good. ;-) -- Bruce Momjian <bruce@momjian.us> https://momjian.us EDB https://enterprisedb.com Do not let urgent matters crowd out time for investment in the future.
-
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-12-05T05:45:16Z
On 11/18/2025 12:21 PM, Tom Lane wrote: > Bryan Green <dbryan.green@gmail.com> writes: >> On 11/18/2025 11:24 AM, Robert Haas wrote: >>> A whole different way of doing this would be to make the GUC machinery >>> responsible for spinning up and tearing down the contexts. Then, the >>> check hook could just be called with CurrentMemoryContext already set >>> to the new context, and the caller would know about it. Then, the >>> check hook doesn't need any special precautions to make sure the >>> context gets destroyed; instead, the GUC machinery takes care of that. > > I like this in principle, but I don't think Bryan's implementation > sketch is right: > >> 1. Create a temporary context (child of TopMemoryContext) > > If the check_hook throws an error, you'll have leaked a long-lived > context. You must *not* make it a child of TopMemoryContext until > after successful assignment. I take Robert's point that we don't > know whether the GUC logic will be called in a context that is > short-lived or long-lived, so maybe making the context transiently > a child of CurrentMemoryContext isn't good enough ... but > TopMemoryContext is most definitely not good enough. > > (Actually, these things should be children of GUCMemoryContext > not directly of TopMemoryContext. But that doesn't affect this > point, since those are equally long-lived.) > > I'm really still dubious that this entire project is worthwhile. > I think it is basically building support for GUCs whose values > are unreasonably complicated, and would be better off if they > got redesigned. Also, right now might be a bad time to be > adding complexity to guc.c, in view of discussions such as [1]. > > regards, tom lane > > [1] https://www.postgresql.org/message-id/flat/2ff46ac9-b46c-4210-8f0c-0f5365b36db9%40eisentraut.org I tried implementing a PG_TRY/PG_CATCH approach and it doesn't work. The switch statement in set_config_with_handle() has multiple early returns (parse failures, prohibitValueChange checks, etc.) that bypass both the success path and the PG_CATCH handler. If we've switched into extra_cxt before entering the switch, these early returns leave CurrentMemoryContext pointing at a temp context. If the GUC machinery switches contexts before calling the check hook, we have to switch back on every exit path, but the early returns make that difficult without refactoring the entire switch statement-- it's quite the switch statement. Maybe an alternative: create the temp context under CurrentMemoryContext, but don't switch into it. The check hook switches if it needs complex structures, and switches back before returning. On success, we reparent to GUCMemoryContext. On error or early return, the context is automatically deleted with its parent. I think there may still be a small leak in the case if CurrentMemoryContext is long-lived. The check hook API would be: MemoryContext oldcxt = MemoryContextSwitchTo(extra_cxt); /* allocate complex structures with palloc */ MemoryContextSwitchTo(oldcxt); *extra = my_data_pointer; Not as automatic as Robert's suggestion, but it avoids the early return problem entirely. Thoughts? -- Bryan Green EDB: https://www.enterprisedb.com
-
Re: [PATCH] Allow complex data for GUC extra.
Robert Haas <robertmhaas@gmail.com> — 2025-12-05T20:48:32Z
On Fri, Dec 5, 2025 at 12:45 AM Bryan Green <dbryan.green@gmail.com> wrote: > I tried implementing a PG_TRY/PG_CATCH approach and it doesn't work. > The switch statement in set_config_with_handle() has multiple early > returns (parse failures, prohibitValueChange checks, etc.) that bypass > both the success path and the PG_CATCH handler. If we've switched into > extra_cxt before entering the switch, these early returns leave > CurrentMemoryContext pointing at a temp context. I'm pretty sure it's not intended that you can return out of a PG_CATCH() block. You could, however, modify the control flow so that you stash the return value in a variable and the actual return happens after you exit the PG_CATCH() block. But I also don't understand why you want to use a PG_CATCH() block here in the first place. At first glance, I'm inclined to wonder why this wouldn't be a new wrinkle for the existing logic in call_string_check_hook. > The check hook API would be: > > MemoryContext oldcxt = MemoryContextSwitchTo(extra_cxt); > /* allocate complex structures with palloc */ > MemoryContextSwitchTo(oldcxt); > *extra = my_data_pointer; > > Not as automatic as Robert's suggestion, but it avoids the early return > problem entirely. This wouldn't be terrible or anything, and someone may prefer it on stylistic grounds, but I don't really think I believe your argument that this is the only way it can work. -- Robert Haas EDB: http://www.enterprisedb.com
-
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-12-05T21:24:46Z
On 12/5/2025 2:48 PM, Robert Haas wrote: > On Fri, Dec 5, 2025 at 12:45 AM Bryan Green <dbryan.green@gmail.com> wrote: >> I tried implementing a PG_TRY/PG_CATCH approach and it doesn't work. >> The switch statement in set_config_with_handle() has multiple early >> returns (parse failures, prohibitValueChange checks, etc.) that bypass >> both the success path and the PG_CATCH handler. If we've switched into >> extra_cxt before entering the switch, these early returns leave >> CurrentMemoryContext pointing at a temp context. > > I'm pretty sure it's not intended that you can return out of a > PG_CATCH() block. You could, however, modify the control flow so that > you stash the return value in a variable and the actual return happens > after you exit the PG_CATCH() block. > I should have been more clear, I was referring to trying the following: if (GUC_EXTRA_IS_CONTEXT && value != NULL) { extra_cxt = AllocSetContextCreate(CurrentMemoryContext, ...); old_context = MemoryContextSwitchTo(extra_cxt); } PG_TRY(); { switch (record->vartype) { ... } /* DIFFERENT RETURN PATHS */ /* Success path */ if (extra_cxt) { MemoryContextSwitchTo(old_context); MemoryContextSetParent(extra_cxt, GUCMemoryContext); } } PG_CATCH(); { if (extra_cxt) MemoryContextDelete(extra_cxt); PG_RE_THROW(); } PG_END_TRY(); The early returns are inside the PG_TRY block (in the switch statement), not in PG_CATCH. But I see your point - I could refactor to use a result variable and only return after PG_END_TRY. Some of the "return 0" paths happen after the check hook has already run and allocated into extra_cxt. If I just break out of the switch to avoid the return, I'd still need to distinguish "should I reparent this context (success) or delete it (failure)" before exiting PG_TRY. > But I also don't understand why you want to use a PG_CATCH() block > here in the first place. At first glance, I'm inclined to wonder why > this wouldn't be a new wrinkle for the existing logic in > call_string_check_hook. > I think I'm missing something obvious here. call_string_check_hook doesn't do any memory context management - it just calls the hook. Are you suggesting the context creation/switching should be factored into the call_*_check_hook functions themselves? That would keep it out of the main switch statement entirely. Something like: if (record->flags & GUC_EXTRA_IS_CONTEXT) return call_string_check_hook_with_context(...); else return call_string_check_hook(...); Where the _with_context version handles creating the temp context, switching into it, calling the hook, switching back, and cleaning up on failure? That would avoid touching the switch statement at all. Is that what you had in mind? >> The check hook API would be: >> >> MemoryContext oldcxt = MemoryContextSwitchTo(extra_cxt); >> /* allocate complex structures with palloc */ >> MemoryContextSwitchTo(oldcxt); >> *extra = my_data_pointer; >> >> Not as automatic as Robert's suggestion, but it avoids the early return >> problem entirely. > > This wouldn't be terrible or anything, and someone may prefer it on > stylistic grounds, but I don't really think I believe your argument > that this is the only way it can work. > I did not mean to imply that this is the ONLY way it could work-- it was just the solution that was in my mind currently. I always assume there are multiple ways. Thanks -- Bryan Green EDB: https://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-12-06T07:08:47Z
On 12/5/2025 3:24 PM, Bryan Green wrote: > On 12/5/2025 2:48 PM, Robert Haas wrote: >> On Fri, Dec 5, 2025 at 12:45 AM Bryan Green <dbryan.green@gmail.com> wrote: >>> I tried implementing a PG_TRY/PG_CATCH approach and it doesn't work. >>> The switch statement in set_config_with_handle() has multiple early >>> returns (parse failures, prohibitValueChange checks, etc.) that bypass >>> both the success path and the PG_CATCH handler. If we've switched into >>> extra_cxt before entering the switch, these early returns leave >>> CurrentMemoryContext pointing at a temp context. >> >> I'm pretty sure it's not intended that you can return out of a >> PG_CATCH() block. You could, however, modify the control flow so that >> you stash the return value in a variable and the actual return happens >> after you exit the PG_CATCH() block. >> > > I should have been more clear, I was referring to trying the following: > > > if (GUC_EXTRA_IS_CONTEXT && value != NULL) > { > extra_cxt = AllocSetContextCreate(CurrentMemoryContext, ...); > old_context = MemoryContextSwitchTo(extra_cxt); > } > > PG_TRY(); > { > switch (record->vartype) { ... } /* DIFFERENT RETURN PATHS */ > > /* Success path */ > if (extra_cxt) > { > MemoryContextSwitchTo(old_context); > MemoryContextSetParent(extra_cxt, GUCMemoryContext); > } > } > PG_CATCH(); > { > if (extra_cxt) > MemoryContextDelete(extra_cxt); > PG_RE_THROW(); > } > PG_END_TRY(); > > The early returns are inside the PG_TRY block (in the switch > statement), not in PG_CATCH. But I see your point - I could refactor > to use a result variable and only return after PG_END_TRY. > > Some of the "return 0" paths happen after the check hook has already > run and allocated into extra_cxt. If I just break out of the switch > to avoid the return, I'd still need to distinguish "should I reparent > this context (success) or delete it (failure)" before exiting PG_TRY. > >> But I also don't understand why you want to use a PG_CATCH() block >> here in the first place. At first glance, I'm inclined to wonder why >> this wouldn't be a new wrinkle for the existing logic in >> call_string_check_hook. >> > > I think I'm missing something obvious here. call_string_check_hook > doesn't do any memory context management - it just calls the hook. > > Are you suggesting the context creation/switching should be factored > into the call_*_check_hook functions themselves? That would keep it > out of the main switch statement entirely. Something like: > > if (record->flags & GUC_EXTRA_IS_CONTEXT) > return call_string_check_hook_with_context(...); > else > return call_string_check_hook(...); > > Where the _with_context version handles creating the temp context, > switching into it, calling the hook, switching back, and cleaning up > on failure? > > That would avoid touching the switch statement at all. Is that what > you had in mind? > >>> The check hook API would be: >>> >>> MemoryContext oldcxt = MemoryContextSwitchTo(extra_cxt); >>> /* allocate complex structures with palloc */ >>> MemoryContextSwitchTo(oldcxt); >>> *extra = my_data_pointer; >>> >>> Not as automatic as Robert's suggestion, but it avoids the early return >>> problem entirely. >> >> This wouldn't be terrible or anything, and someone may prefer it on >> stylistic grounds, but I don't really think I believe your argument >> that this is the only way it can work. >> > > I did not mean to imply that this is the ONLY way it could work-- it was > just the solution that was in my mind currently. I always assume there > are multiple ways. > > Thanks > Robert, I've implemented the GUC_EXTRA_IS_CONTEXT approach I believe you were suggesting. The basic idea is straightforward: the check hook wrapper creates a temporary AllocSetContext, switches to it before calling the hook, then either reparents the context to GUCMemoryContext on success or deletes it on failure. Cleanup in set_extra_field() uses GetMemoryChunkContext() to locate and delete the old context. This required modifications to all five call_*_check_hook() functions (bool, int, real, string, enum) to follow the same pattern. I also had to keep the context operations outside the PG_TRY block. One additional fix: if a check hook succeeds but returns NULL for extra, we delete the empty context rather than reparenting it to avoid leaking contexts that would never be cleaned up. Does this match what you had in mind? Patch attached. -- Bryan Green EDB: https://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-12-08T15:23:00Z
On 12/6/2025 1:08 AM, Bryan Green wrote: > On 12/5/2025 3:24 PM, Bryan Green wrote: >> On 12/5/2025 2:48 PM, Robert Haas wrote: >>> On Fri, Dec 5, 2025 at 12:45 AM Bryan Green <dbryan.green@gmail.com> wrote: >>>> I tried implementing a PG_TRY/PG_CATCH approach and it doesn't work. >>>> The switch statement in set_config_with_handle() has multiple early >>>> returns (parse failures, prohibitValueChange checks, etc.) that bypass >>>> both the success path and the PG_CATCH handler. If we've switched into >>>> extra_cxt before entering the switch, these early returns leave >>>> CurrentMemoryContext pointing at a temp context. >>> >>> I'm pretty sure it's not intended that you can return out of a >>> PG_CATCH() block. You could, however, modify the control flow so that >>> you stash the return value in a variable and the actual return happens >>> after you exit the PG_CATCH() block. >>> >> >> I should have been more clear, I was referring to trying the following: >> >> >> if (GUC_EXTRA_IS_CONTEXT && value != NULL) >> { >> extra_cxt = AllocSetContextCreate(CurrentMemoryContext, ...); >> old_context = MemoryContextSwitchTo(extra_cxt); >> } >> >> PG_TRY(); >> { >> switch (record->vartype) { ... } /* DIFFERENT RETURN PATHS */ >> >> /* Success path */ >> if (extra_cxt) >> { >> MemoryContextSwitchTo(old_context); >> MemoryContextSetParent(extra_cxt, GUCMemoryContext); >> } >> } >> PG_CATCH(); >> { >> if (extra_cxt) >> MemoryContextDelete(extra_cxt); >> PG_RE_THROW(); >> } >> PG_END_TRY(); >> >> The early returns are inside the PG_TRY block (in the switch >> statement), not in PG_CATCH. But I see your point - I could refactor >> to use a result variable and only return after PG_END_TRY. >> >> Some of the "return 0" paths happen after the check hook has already >> run and allocated into extra_cxt. If I just break out of the switch >> to avoid the return, I'd still need to distinguish "should I reparent >> this context (success) or delete it (failure)" before exiting PG_TRY. >> >>> But I also don't understand why you want to use a PG_CATCH() block >>> here in the first place. At first glance, I'm inclined to wonder why >>> this wouldn't be a new wrinkle for the existing logic in >>> call_string_check_hook. >>> >> >> I think I'm missing something obvious here. call_string_check_hook >> doesn't do any memory context management - it just calls the hook. >> >> Are you suggesting the context creation/switching should be factored >> into the call_*_check_hook functions themselves? That would keep it >> out of the main switch statement entirely. Something like: >> >> if (record->flags & GUC_EXTRA_IS_CONTEXT) >> return call_string_check_hook_with_context(...); >> else >> return call_string_check_hook(...); >> >> Where the _with_context version handles creating the temp context, >> switching into it, calling the hook, switching back, and cleaning up >> on failure? >> >> That would avoid touching the switch statement at all. Is that what >> you had in mind? >> >>>> The check hook API would be: >>>> >>>> MemoryContext oldcxt = MemoryContextSwitchTo(extra_cxt); >>>> /* allocate complex structures with palloc */ >>>> MemoryContextSwitchTo(oldcxt); >>>> *extra = my_data_pointer; >>>> >>>> Not as automatic as Robert's suggestion, but it avoids the early return >>>> problem entirely. >>> >>> This wouldn't be terrible or anything, and someone may prefer it on >>> stylistic grounds, but I don't really think I believe your argument >>> that this is the only way it can work. >>> >> >> I did not mean to imply that this is the ONLY way it could work-- it was >> just the solution that was in my mind currently. I always assume there >> are multiple ways. >> >> Thanks >> > Robert, > > I've implemented the GUC_EXTRA_IS_CONTEXT approach I believe you were > suggesting. The basic idea is straightforward: the check hook wrapper > creates a temporary AllocSetContext, switches to it before calling the > hook, then either reparents the context to GUCMemoryContext on success > or deletes it on failure. Cleanup in set_extra_field() uses > GetMemoryChunkContext() to locate and delete the old context. > This required modifications to all five call_*_check_hook() functions > (bool, int, real, string, enum) to follow the same pattern. I also had > to keep the context operations outside the PG_TRY block. > One additional fix: if a check hook succeeds but returns NULL for extra, > we delete the empty context rather than reparenting it to avoid leaking > contexts that would never be cleaned up. > > Does this match what you had in mind? > > Patch attached. > Actually, I realized I still allocated in CurrentMemoryContext-- I think instead I should just allocate the extra_cxt under GUCMemoryContext and then there is no need to reparent. if (conf->flags & GUC_EXTRA_IS_CONTEXT) { /* Create directly under GUCMemoryContext - it's already where we want it */ extra_cxt = AllocSetContextCreate(GUCMemoryContext, "GUC check_hook extra context", ALLOCSET_DEFAULT_SIZES); old_cxt = MemoryContextSwitchTo(extra_cxt); } // ... if (result) { /* Already under GUCMemoryContext, just leave it there */ /* Delete if unused */ if (*extra == NULL) MemoryContextDelete(extra_cxt); } else { MemoryContextDelete(extra_cxt); } -- Bryan Green EDB: https://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-12-08T15:55:41Z
On 12/8/2025 9:23 AM, Bryan Green wrote: > On 12/6/2025 1:08 AM, Bryan Green wrote: ... > Actually, I realized I still allocated in CurrentMemoryContext-- I think > instead I should just allocate the extra_cxt under GUCMemoryContext and > then there is no need to reparent. > > if (conf->flags & GUC_EXTRA_IS_CONTEXT) > { > /* Create directly under GUCMemoryContext - it's already where we want > it */ > extra_cxt = AllocSetContextCreate(GUCMemoryContext, > "GUC check_hook extra context", > ALLOCSET_DEFAULT_SIZES); > old_cxt = MemoryContextSwitchTo(extra_cxt); > } > > // ... > > if (result) > { > /* Already under GUCMemoryContext, just leave it there */ > /* Delete if unused */ > if (*extra == NULL) > MemoryContextDelete(extra_cxt); > } > else > { > MemoryContextDelete(extra_cxt); > } > Apologies for the unneeded email above. Upon more reflection, I need to walk back my previous change from CurrentMemoryContext to GUCMemoryContext. I think CurrentMemoryContext was correct. The issue is the ERROR path. If a check hook throws ERROR, we longjmp out without hitting the cleanup code, leaving extra_cxt orphaned under whatever parent we gave it. With CurrentMemoryContext as parent (typically MessageContext or similar), error recovery resets those contexts, and MemoryContextReset() deletes all children via MemoryContextDeleteChildren(). So the orphaned context gets cleaned up automatically. With GUCMemoryContext as parent, it never gets reset during normal error recovery, so the orphaned context just sits there leaking memory. So the original code was actually relying on PostgreSQL's error recovery to handle the ERROR case, which is the right approach here. Hopefully my understanding of this is correct. The last attached patches are still the correct ones. -- Bryan Green EDB: https://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Robert Haas <robertmhaas@gmail.com> — 2025-12-08T16:48:35Z
On Sat, Dec 6, 2025 at 2:08 AM Bryan Green <dbryan.green@gmail.com> wrote: > > I think I'm missing something obvious here. call_string_check_hook > > doesn't do any memory context management - it just calls the hook. No, it does do memory management. It has a PG_TRY()/PG_CATCH() block to ensure that we don't forget to GUC_free(*newval) in case of error. I was trying to figure out where we were doing the relevant memory management today, and then extend that to handle the new thing. But I am guilty of fuzzy thinking here, because we're talking about where the "extra" memory is managed, not the memory for "newval". So the logic we care about is in set_config_with_handle() just as you said: /* Perhaps we didn't install newextra anywhere */ if (newextra && !extra_field_used(record, newextra)) guc_free(newextra); What I hadn't quite internalized previously was that there's no PG_TRY/PG_CATCH block here right now because we assume that (1) we assume the check hook won't allocate the extra value until it's ready to return, so it will never leak a value by allocating it and then erroring out and (2) we take care to ensure that no errors can happen in the GUC code itself after the extra value has been returned and before we either free it or save a pointer to it someplace. But having said that, I'm inclined to think that handling the memory management concerns inside call_WHATEVER_check_hook() still makes some sense. It seems to me that if we do that, set_config_with_handle() needs very little change. All it needs to do differently is: wherever it would guc_free(newextra), it can call some new helper function that will either just guc_free() or alternatively MemoryContextDelete(GetMemoryChunkContext()) depending on flags. I think this is good, because set_config_with_handle() is already pretty complicated, and I'd rather not inject more complexity into that function. For this to work, each call_WHATEVER_check_hook() function would need a PG_TRY()/PG_CATCH() block, rather than only call_string_check_hook() as currently. Or alternatively, and I think this might be an appealing option, we could say that this feature is only available for string values, and the other call_WHATEVER_check_hook() functions just assert that the GUC_EXTRA_IS_CONTEXT flag is not set. I don't see why you'd need a complex "extra" value for a bool or int or enum or real-valued GUC -- how much complex parsing can you need to do on a non-string value? I think the call_string_check_hook logic in the v2 patch is approximately correct. This can be tightened up: if (result) { if (*extra != NULL) MemoryContextSetParent(extra_cxt, GUCMemoryContext); else MemoryContextDelete(extra_cxt); } else { MemoryContextDelete(extra_cxt); } You can instead write: if (result != NULL && *extra != NULL) MemoryContextSetParent(extra_cxt, GUCMemoryContext); else MemoryContextDelete(extra_cxt); > One additional fix: if a check hook succeeds but returns NULL for extra, > we delete the empty context rather than reparenting it to avoid leaking > contexts that would never be cleaned up. Yeah, avoiding leaking contexts seems like one of the key challenges here. I'm not sure whether we would ever have a check hook that either returns a null or non-null *extra depending on the situation, but it seems good to be prepared for that case. I notice that guc_free() silently accepts a null pointer, so presumably a similar case with a "flat" GUC extra could exist and work today. Also, to respond to your later emails, I agree that the new context shouldn't be created under GUCMemoryContext. As discussed with Tom earlier, we don't want it to be a long-lived context. I think CurrentMemoryContext is OK provided that CurrentMemoryContext is always a child of TopTransactionContext, because even if we leak something, it will only survive until the end of the transaction at latest. However, if CurrentMemoryContext can be something like TopMemoryContext or CacheMemoryContext, then we might want to think a little harder. I'm not sure whether that's possible -- perhaps you would like to investigate? Think particularly about GUCs set during server startup -- maybe in the postmaster, maybe in a backend very early during initialization. Also maybe configuration reloads while the backend is idle. I think we ought to make this patch use MemoryContextSetIdentifier() to make any leaks easier to debug. If a memory context dump shows that you've got a whole bunch of contexts floating around, or one really big one, and they're all just named "GUC extra context" or whatever, that's going to be pretty unhelpful. If the patch does MemoryContextSetIdentifier(extra_cxt, conf->name), you'll be able to see which GUC is responsible. I think you should port a couple of the core GUCs use this new mechanism. I suggest specifically check_synchronous_standby_names and check_synchronized_standby_slots. That should give us some better insight into how well this mechanism really works and whether it is more convenient in practice than what we're making check hooks do today. I thought about proposing that if you do that, you might be able to just drop this test module, but both of those GUCs are PGC_SIGHUP, so they wouldn't be good for testing the behavior with SET, SET LOCAL, RESET, etc. So we might need to either find a case that can benefit from this mechanism that is PGC_USERSET or PGC_SUSET, or keep the test module in some form. backtrace_functions is a possibility, but it's not altogether clear that a non-flat representation is better in that case, and it doesn't seem great in terms of being able to write simple tests, either. -- Robert Haas EDB: http://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-12-10T21:06:36Z
On 12/8/2025 10:48 AM, Robert Haas wrote: > On Sat, Dec 6, 2025 at 2:08 AM Bryan Green <dbryan.green@gmail.com> wrote: >>> I think I'm missing something obvious here. call_string_check_hook >>> doesn't do any memory context management - it just calls the hook. > > No, it does do memory management. It has a PG_TRY()/PG_CATCH() block > to ensure that we don't forget to GUC_free(*newval) in case of error. > I was trying to figure out where we were doing the relevant memory > management today, and then extend that to handle the new thing. But I > am guilty of fuzzy thinking here, because we're talking about where > the "extra" memory is managed, not the memory for "newval". So the > logic we care about is in set_config_with_handle() just as you said: > > /* Perhaps we didn't install newextra anywhere */ > if (newextra && !extra_field_used(record, newextra)) > guc_free(newextra); > > What I hadn't quite internalized previously was that there's no > PG_TRY/PG_CATCH block here right now because we assume that (1) we > assume the check hook won't allocate the extra value until it's ready > to return, so it will never leak a value by allocating it and then > erroring out and (2) we take care to ensure that no errors can happen > in the GUC code itself after the extra value has been returned and > before we either free it or save a pointer to it someplace. > > But having said that, I'm inclined to think that handling the memory > management concerns inside call_WHATEVER_check_hook() still makes some > sense. It seems to me that if we do that, set_config_with_handle() > needs very little change. All it needs to do differently is: wherever > it would guc_free(newextra), it can call some new helper function that > will either just guc_free() or alternatively > MemoryContextDelete(GetMemoryChunkContext()) depending on flags. I > think this is good, because set_config_with_handle() is already pretty > complicated, and I'd rather not inject more complexity into that > function. > > For this to work, each call_WHATEVER_check_hook() function would need > a PG_TRY()/PG_CATCH() block, rather than only call_string_check_hook() > as currently. Or alternatively, and I think this might be an appealing > option, we could say that this feature is only available for string > values, and the other call_WHATEVER_check_hook() functions just assert > that the GUC_EXTRA_IS_CONTEXT flag is not set. I don't see why you'd > need a complex "extra" value for a bool or int or enum or real-valued > GUC -- how much complex parsing can you need to do on a non-string > value? I agree. I was just thinking there might be edge cases I had not thought of-- such as using an enum to indicate a sorting algorithm that has some setup based on which one you choose (maybe locale characteristics) and the result of that setup is needed in the assign hook. That is somewhat of a contrived example and just underscores you comment about not ever really needing that. I will gladly just add the asserts and focus on the call_string_check_hook(...). > > I think the call_string_check_hook logic in the v2 patch is > approximately correct. This can be tightened up: > > if (result) > { > if (*extra != NULL) > MemoryContextSetParent(extra_cxt, GUCMemoryContext); > else > MemoryContextDelete(extra_cxt); > } > else > { > MemoryContextDelete(extra_cxt); > } > > You can instead write: > > if (result != NULL && *extra != NULL) > MemoryContextSetParent(extra_cxt, GUCMemoryContext); > else > MemoryContextDelete(extra_cxt); > Agreed. >> One additional fix: if a check hook succeeds but returns NULL for extra, >> we delete the empty context rather than reparenting it to avoid leaking >> contexts that would never be cleaned up. > > Yeah, avoiding leaking contexts seems like one of the key challenges > here. I'm not sure whether we would ever have a check hook that either > returns a null or non-null *extra depending on the situation, but it > seems good to be prepared for that case. I notice that guc_free() > silently accepts a null pointer, so presumably a similar case with a > "flat" GUC extra could exist and work today. > > Also, to respond to your later emails, I agree that the new context > shouldn't be created under GUCMemoryContext. As discussed with Tom > earlier, we don't want it to be a long-lived context. I think > CurrentMemoryContext is OK provided that CurrentMemoryContext is > always a child of TopTransactionContext, because even if we leak > something, it will only survive until the end of the transaction at > latest. However, if CurrentMemoryContext can be something like > TopMemoryContext or CacheMemoryContext, then we might want to think a > little harder. I'm not sure whether that's possible -- perhaps you > would like to investigate? Think particularly about GUCs set during > server startup -- maybe in the postmaster, maybe in a backend very > early during initialization. Also maybe configuration reloads while > the backend is idle. > Will investigate this. > I think we ought to make this patch use MemoryContextSetIdentifier() > to make any leaks easier to debug. If a memory context dump shows that > you've got a whole bunch of contexts floating around, or one really > big one, and they're all just named "GUC extra context" or whatever, > that's going to be pretty unhelpful. If the patch does > MemoryContextSetIdentifier(extra_cxt, conf->name), you'll be able to > see which GUC is responsible. > Agreed. > I think you should port a couple of the core GUCs use this new > mechanism. I suggest specifically check_synchronous_standby_names and > check_synchronized_standby_slots. That should give us some better > insight into how well this mechanism really works and whether it is > more convenient in practice than what we're making check hooks do > today. I thought about proposing that if you do that, you might be > able to just drop this test module, but both of those GUCs are > PGC_SIGHUP, so they wouldn't be good for testing the behavior with > SET, SET LOCAL, RESET, etc. So we might need to either find a case > that can benefit from this mechanism that is PGC_USERSET or PGC_SUSET, > or keep the test module in some form. I agree it would be nice to drop the test module. I will port the ones you suggested and search for a PGC_USERSET or PGC_SUSET to port. backtrace_functions is a > possibility, but it's not altogether clear that a non-flat > representation is better in that case, and it doesn't seem great in > terms of being able to write simple tests, either. > > -- > Robert Haas > EDB: http://www.enterprisedb.com Thank you for your continued time and help on this. -- Bryan Green EDB: https://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-12-15T06:03:44Z
On 12/8/2025 10:48 AM, Robert Haas wrote: > On Sat, Dec 6, 2025 at 2:08 AM Bryan Green <dbryan.green@gmail.com> wrote: >>> I think I'm missing something obvious here. call_string_check_hook >>> doesn't do any memory context management - it just calls the hook. > > No, it does do memory management. It has a PG_TRY()/PG_CATCH() block > to ensure that we don't forget to GUC_free(*newval) in case of error. > I was trying to figure out where we were doing the relevant memory > management today, and then extend that to handle the new thing. But I > am guilty of fuzzy thinking here, because we're talking about where > the "extra" memory is managed, not the memory for "newval". So the > logic we care about is in set_config_with_handle() just as you said: > > /* Perhaps we didn't install newextra anywhere */ > if (newextra && !extra_field_used(record, newextra)) > guc_free(newextra); > > What I hadn't quite internalized previously was that there's no > PG_TRY/PG_CATCH block here right now because we assume that (1) we > assume the check hook won't allocate the extra value until it's ready > to return, so it will never leak a value by allocating it and then > erroring out and (2) we take care to ensure that no errors can happen > in the GUC code itself after the extra value has been returned and > before we either free it or save a pointer to it someplace. > > But having said that, I'm inclined to think that handling the memory > management concerns inside call_WHATEVER_check_hook() still makes some > sense. It seems to me that if we do that, set_config_with_handle() > needs very little change. All it needs to do differently is: wherever > it would guc_free(newextra), it can call some new helper function that > will either just guc_free() or alternatively > MemoryContextDelete(GetMemoryChunkContext()) depending on flags. I > think this is good, because set_config_with_handle() is already pretty > complicated, and I'd rather not inject more complexity into that > function. > > For this to work, each call_WHATEVER_check_hook() function would need > a PG_TRY()/PG_CATCH() block, rather than only call_string_check_hook() > as currently. Or alternatively, and I think this might be an appealing > option, we could say that this feature is only available for string > values, and the other call_WHATEVER_check_hook() functions just assert > that the GUC_EXTRA_IS_CONTEXT flag is not set. I don't see why you'd > need a complex "extra" value for a bool or int or enum or real-valued > GUC -- how much complex parsing can you need to do on a non-string > value? > > I think the call_string_check_hook logic in the v2 patch is > approximately correct. This can be tightened up: > > if (result) > { > if (*extra != NULL) > MemoryContextSetParent(extra_cxt, GUCMemoryContext); > else > MemoryContextDelete(extra_cxt); > } > else > { > MemoryContextDelete(extra_cxt); > } > > You can instead write: > > if (result != NULL && *extra != NULL) > MemoryContextSetParent(extra_cxt, GUCMemoryContext); > else > MemoryContextDelete(extra_cxt); > >> One additional fix: if a check hook succeeds but returns NULL for extra, >> we delete the empty context rather than reparenting it to avoid leaking >> contexts that would never be cleaned up. > > Yeah, avoiding leaking contexts seems like one of the key challenges > here. I'm not sure whether we would ever have a check hook that either > returns a null or non-null *extra depending on the situation, but it > seems good to be prepared for that case. I notice that guc_free() > silently accepts a null pointer, so presumably a similar case with a > "flat" GUC extra could exist and work today. > > Also, to respond to your later emails, I agree that the new context > shouldn't be created under GUCMemoryContext. As discussed with Tom > earlier, we don't want it to be a long-lived context. I think > CurrentMemoryContext is OK provided that CurrentMemoryContext is > always a child of TopTransactionContext, because even if we leak > something, it will only survive until the end of the transaction at > latest. However, if CurrentMemoryContext can be something like > TopMemoryContext or CacheMemoryContext, then we might want to think a > little harder. I'm not sure whether that's possible -- perhaps you > would like to investigate? Think particularly about GUCs set during > server startup -- maybe in the postmaster, maybe in a backend very > early during initialization. Also maybe configuration reloads while > the backend is idle. > > I think we ought to make this patch use MemoryContextSetIdentifier() > to make any leaks easier to debug. If a memory context dump shows that > you've got a whole bunch of contexts floating around, or one really > big one, and they're all just named "GUC extra context" or whatever, > that's going to be pretty unhelpful. If the patch does > MemoryContextSetIdentifier(extra_cxt, conf->name), you'll be able to > see which GUC is responsible. > > I think you should port a couple of the core GUCs use this new > mechanism. I suggest specifically check_synchronous_standby_names and > check_synchronized_standby_slots. That should give us some better > insight into how well this mechanism really works and whether it is > more convenient in practice than what we're making check hooks do > today. I thought about proposing that if you do that, you might be > able to just drop this test module, but both of those GUCs are > PGC_SIGHUP, so they wouldn't be good for testing the behavior with > SET, SET LOCAL, RESET, etc. So we might need to either find a case > that can benefit from this mechanism that is PGC_USERSET or PGC_SUSET, > or keep the test module in some form. backtrace_functions is a > possibility, but it's not altogether clear that a non-flat > representation is better in that case, and it doesn't seem great in > terms of being able to write simple tests, either. > > -- > Robert Haas > EDB: http://www.enterprisedb.com Robert, TLDR; There is a possibility of GUCs using contexts that do not reset; combined with a small window of opportunity this would lead to a leak given correct timing. We could accept the leak risk, only allow extra data as a context for PGC_USERSET and PGC_SUSET GUCs, create a new context that is global but resets, or look for a different solution. I've incorporated your suggestions and investigated the memory context safety concerns you raised. I selected check_temp_tablespaces (PGC_USERSET) for testing SET and SET LOCAL behavior, and ported check_synchronous_standby_names and check_synchronized_standby_slots as you suggested. With working examples in core, I removed the test module from this patch. I traced through the code paths to determine which memory context is active when check hooks execute. PGC_POSTMASTER uses PostmasterContext during postmaster startup. PGC_SIGHUP uses PostmasterContext during postmaster startup or TopMemoryContext during backend init, though interestingly it uses MessageContext during actual SIGHUP reload in a running backend. PGC_BACKEND uses TopMemoryContext during backend init. PGC_USERSET and PGC_SUSET use MessageContext during interactive SET commands. The key distinction is whether these contexts are reset during error recovery. PostmasterContext and TopMemoryContext are never reset, while MessageContext is reset at the command loop and TopTransactionContext is reset during transaction abort. There's a small window after PG_END_TRY where we've completed the check hook successfully but haven't yet reparented the context to GUCMemoryContext or deleted it. For PGC_USERSET and PGC_SUSET, this isn't a problem. Even if an error occurs in that window, the context is a child of MessageContext, which gets reset at the command loop, cleaning up the context automatically. No leak occurs. For PGC_POSTMASTER, PGC_SIGHUP (during startup), and PGC_BACKEND, an error in that window would orphan a context under PostmasterContext or TopMemoryContext, which are never reset. This would leak memory for the backend's lifetime. The probability of hitting this timing is extremely low, perhaps 1 in 100 million, but it's not zero. We could restrict the feature to PGC_USERSET and PGC_SUSET only with a simple assertion, which eliminates the leak possibility entirely. Alternatively, we could create a dedicated GUCCheckContext that's explicitly reset during error recovery, allowing all GUC contexts to use the feature. Finally, we could document the limitation and accept the extremely low probability of a leak for GUCs using contexts that don't reset-- which seems...unseemly. The attached patch works as described but would require either restricting to specific GUC contexts, accepting the leak, or implementing a more invasive solution. I'd appreciate your thoughts on the appropriate path forward. -- Bryan Green EDB: https://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Robert Haas <robertmhaas@gmail.com> — 2025-12-16T21:12:35Z
On Mon, Dec 15, 2025 at 1:03 AM Bryan Green <dbryan.green@gmail.com> wrote: > TLDR; There is a possibility of GUCs using contexts that do not reset; > combined with a small window of opportunity this would lead to a leak > given correct timing. We could accept the leak risk, only allow extra > data as a context for PGC_USERSET and PGC_SUSET GUCs, create a new > context that is global but resets, or look for a different solution. I think the last two solutions are best. One idea is to create a context, maybe a child of GUCMemoryContext, that is the initial parent for all temporary check-hook contexts. When the check-hook completes, the context is reparented to GUCMemoryContext. At some appropriate point in the code, we delete any leftover children of that context. "Some appropriate point in the code" could mean "right before we create a new temporary check-hook context" if it's the case that one check-hook can never be reached from another check-hook, or it could be elsewhere if reentrancy is a problem. Another idea is to just keep track of which contexts need to be deleted if we error out. Let's say that when we create a temporary check-hook context, we add it to a linked list, similar to guc_stack_list, but each element of the list is just a memory context pointer and the current GUCNestLevel. When AtEOXact_GUC is called, after doing the stuff it does today, it can iterate over this new list and filter on nestLevel. Entries that need to get blown away are subjected to MemoryContextDelete() and also deleted from the list. -- Robert Haas EDB: http://www.enterprisedb.com
-
Re: [PATCH] Allow complex data for GUC extra.
Tom Lane <tgl@sss.pgh.pa.us> — 2025-12-16T21:49:44Z
Robert Haas <robertmhaas@gmail.com> writes: > Another idea is to just keep track of which contexts need to be > deleted if we error out. Let's say that when we create a temporary > check-hook context, we add it to a linked list, similar to > guc_stack_list, but each element of the list is just a memory context > pointer and the current GUCNestLevel. When AtEOXact_GUC is called, > after doing the stuff it does today, it can iterate over this new list > and filter on nestLevel. Entries that need to get blown away are > subjected to MemoryContextDelete() and also deleted from the list. I think something like this could work, but you'd have to be careful about how you manage the list: you can't just palloc something, nor lappend it to a list, for fear of OOM right there. An slist or dlist removes the lappend hazard, but you still need storage to hold the list entry. One way could be to allocate the list entry inside the new as-yet-transient context, expecting that there's guaranteed to be enough space in it for that to work. (If guc.c controls the initial parameters for the new context, that's a safe assumption.) The downside to this is that the check_hook could not be allowed to reset the context ... but it's hard to see why it'd need to. Alternatively: I don't see any really good reason for a check_hook to be setting other GUCs, in fact it's probably a seriously bad idea. (It's a *check* hook, it's not supposed to be causing any side-effects.) Therefore, there can be at most one of these operations in flight at a time, so you don't need any dynamic data structure. A simple static variable remembering a not-yet-reparented context would do it. regards, tom lane
-
Re: [PATCH] Allow complex data for GUC extra.
Robert Haas <robertmhaas@gmail.com> — 2025-12-17T01:38:01Z
On Tue, Dec 16, 2025 at 4:49 PM Tom Lane <tgl@sss.pgh.pa.us> wrote: > Alternatively: I don't see any really good reason for a check_hook > to be setting other GUCs, in fact it's probably a seriously bad idea. > (It's a *check* hook, it's not supposed to be causing any > side-effects.) Therefore, there can be at most one of these > operations in flight at a time, so you don't need any dynamic data > structure. A simple static variable remembering a not-yet-reparented > context would do it. Oh, yeah, I actually wondered if that would be an acceptable restriction and had it in an earlier version of the email, but it got lost in the final draft. Maybe with this design you just do something like: if (TempCheckHookConteck != NULL) MemoryContextReset(TempCheckHookConteck); else TempCheckHookConteck = AllocSetContextCreate(...); So then if the context survives, you just reset and reuse it, but if it gets reparented, you set the variable to NULL and create a new context the next time. Then you don't need any integration with (sub)transaction abort at all, which seems nice. -- Robert Haas EDB: http://www.enterprisedb.com -
Re: [PATCH] Allow complex data for GUC extra.
Tom Lane <tgl@sss.pgh.pa.us> — 2025-12-17T03:04:53Z
Robert Haas <robertmhaas@gmail.com> writes: > On Tue, Dec 16, 2025 at 4:49 PM Tom Lane <tgl@sss.pgh.pa.us> wrote: >> ... Therefore, there can be at most one of these >> operations in flight at a time, so you don't need any dynamic data >> structure. A simple static variable remembering a not-yet-reparented >> context would do it. > Oh, yeah, I actually wondered if that would be an acceptable > restriction and had it in an earlier version of the email, but it got > lost in the final draft. Maybe with this design you just do something > like: > if (TempCheckHookConteck != NULL) > MemoryContextReset(TempCheckHookConteck); > else > TempCheckHookConteck = AllocSetContextCreate(...); > So then if the context survives, you just reset and reuse it, but if > it gets reparented, you set the variable to NULL and create a new > context the next time. Then you don't need any integration with > (sub)transaction abort at all, which seems nice. You could do it like that, but I'd prefer a setup that would give an assertion failure if someone did try to invoke it recursively. So I'd opt for allocation like Assert(TempCheckHookContext == NULL); TempCheckHookContext = AllocSetContextCreate(...); and then you would need cleanup in AtEOXact_GUC, but that's hardly complicated. regards, tom lane
-
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-12-30T01:05:53Z
On 12/16/2025 9:04 PM, Tom Lane wrote: > Robert Haas <robertmhaas@gmail.com> writes: >> On Tue, Dec 16, 2025 at 4:49 PM Tom Lane <tgl@sss.pgh.pa.us> wrote: >>> ... Therefore, there can be at most one of these >>> operations in flight at a time, so you don't need any dynamic data >>> structure. A simple static variable remembering a not-yet-reparented >>> context would do it. > >> Oh, yeah, I actually wondered if that would be an acceptable >> restriction and had it in an earlier version of the email, but it got >> lost in the final draft. Maybe with this design you just do something >> like: >> if (TempCheckHookConteck != NULL) >> MemoryContextReset(TempCheckHookConteck); >> else >> TempCheckHookConteck = AllocSetContextCreate(...); >> So then if the context survives, you just reset and reuse it, but if >> it gets reparented, you set the variable to NULL and create a new >> context the next time. Then you don't need any integration with >> (sub)transaction abort at all, which seems nice. > > You could do it like that, but I'd prefer a setup that would give > an assertion failure if someone did try to invoke it recursively. > So I'd opt for allocation like > > Assert(TempCheckHookContext == NULL); > TempCheckHookContext = AllocSetContextCreate(...); > > and then you would need cleanup in AtEOXact_GUC, but that's > hardly complicated. > > regards, tom lane Robert, Tom, Following your feedback, I've implemented the static context variable approach. The attached v3 patch uses a single TempCheckHookContext that gets created on first use and cleaned up during error recovery. To catch recursive use, I've added Assert(TempCheckHookContext == NULL) before creating the context. One notable behavioral change: check hooks using GUC_EXTRA_IS_CONTEXT now use palloc() instead of guc_malloc(). The old approach with guc_malloc() allowed check hooks to return false on OOM, letting the caller handle it at the appropriate error level. With palloc() an OOM throws an immediate ERROR. This seemed like an acceptable tradeoff - if we can't allocate memory for a small temporary context, we're likely in dire straits anyway. However, this does mean check hooks lose the ability to gracefully handle OOM by returning false. Not all code paths flow through AtEOXact_GUC(). To handle orphaned contexts in these cases, I've added CleanupTempCheckHookContext() as a public function. AtEOXact_GUC() calls it at the end, and other contexts can call it as needed to ensure cleanup happens regardless of the execution path. I've restricted GUC_EXTRA_IS_CONTEXT to string GUCs only, since that's where complex parsing actually makes sense. The other check hook types now assert the flag isn't set. To demonstrate the mechanism works in practice, I've ported three GUCs: check_synchronous_standby_names and check_synchronized_standby_slots (both PGC_SIGHUP), plus check_temp_tablespaces (PGC_USERSET) for testing SET and SET LOCAL behavior. -- Bryan Green EDB: https://www.enterprisedb.com
-
Re: [PATCH] Allow complex data for GUC extra.
Tom Lane <tgl@sss.pgh.pa.us> — 2025-12-30T01:44:45Z
Bryan Green <dbryan.green@gmail.com> writes: > One notable behavioral change: check hooks using GUC_EXTRA_IS_CONTEXT > now use palloc() instead of guc_malloc(). The old approach with > guc_malloc() allowed check hooks to return false on OOM, letting the > caller handle it at the appropriate error level. With palloc() an OOM > throws an immediate ERROR. This seemed like an acceptable tradeoff Why? It seems both inconsistent and unsafe. regards, tom lane
-
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-12-30T02:06:30Z
On 12/29/2025 7:44 PM, Tom Lane wrote: > Bryan Green <dbryan.green@gmail.com> writes: >> One notable behavioral change: check hooks using GUC_EXTRA_IS_CONTEXT >> now use palloc() instead of guc_malloc(). The old approach with >> guc_malloc() allowed check hooks to return false on OOM, letting the >> caller handle it at the appropriate error level. With palloc() an OOM >> throws an immediate ERROR. This seemed like an acceptable tradeoff > > Why? It seems both inconsistent and unsafe. > > regards, tom lane Fair enough to call me on that. I mainly thought that if we are having problems allocating what is usually a few bytes then throwing an error would have been acceptable. Based on your comment about unsafe and a bit deeper thinking I can see where this is probably not a welcome change in behavior. I suppose we could catch the error and convert it to a false return. -- Bryan Green EDB: https://www.enterprisedb.com
-
Re: [PATCH] Allow complex data for GUC extra.
Tom Lane <tgl@sss.pgh.pa.us> — 2025-12-30T02:24:20Z
Bryan Green <dbryan.green@gmail.com> writes: > On 12/29/2025 7:44 PM, Tom Lane wrote: >> Bryan Green <dbryan.green@gmail.com> writes: >>> One notable behavioral change: check hooks using GUC_EXTRA_IS_CONTEXT >>> now use palloc() instead of guc_malloc(). >> Why? It seems both inconsistent and unsafe. > Fair enough to call me on that. I mainly thought that if we are having > problems allocating what is usually a few bytes then throwing an error > would have been acceptable. The key reason I'm allergic to this is that throwing elog(ERROR) in the postmaster process will take down the postmaster. So we really do not want code that will execute during SIGHUP configuration reloads to be doing that. I grant that there will probably always be edge cases where that happens, but I'm not okay with building such a hazard into the GUC APIs. > Based on your comment about unsafe and a > bit deeper thinking I can see where this is probably not a welcome > change in behavior. I suppose we could catch the error and convert it > to a false return. Does palloc_extended(..., MCXT_ALLOC_NO_OOM) help? regards, tom lane
-
Re: [PATCH] Allow complex data for GUC extra.
Bryan Green <dbryan.green@gmail.com> — 2025-12-30T05:07:58Z
On 12/29/2025 8:24 PM, Tom Lane wrote: > Bryan Green <dbryan.green@gmail.com> writes: >> On 12/29/2025 7:44 PM, Tom Lane wrote: >>> Bryan Green <dbryan.green@gmail.com> writes: >>>> One notable behavioral change: check hooks using GUC_EXTRA_IS_CONTEXT >>>> now use palloc() instead of guc_malloc(). > >>> Why? It seems both inconsistent and unsafe. > >> Fair enough to call me on that. I mainly thought that if we are having >> problems allocating what is usually a few bytes then throwing an error >> would have been acceptable. > > The key reason I'm allergic to this is that throwing elog(ERROR) in > the postmaster process will take down the postmaster. So we really > do not want code that will execute during SIGHUP configuration > reloads to be doing that. I grant that there will probably always > be edge cases where that happens, but I'm not okay with building > such a hazard into the GUC APIs. > >> Based on your comment about unsafe and a >> bit deeper thinking I can see where this is probably not a welcome >> change in behavior. I suppose we could catch the error and convert it >> to a false return. > > Does > > palloc_extended(..., MCXT_ALLOC_NO_OOM) > > help? > > regards, tom lane I think it does. We could write a wrapper to make it a bit more obvious that you should use this instead of palloc for GUC hooks. It could be modeled after guc_malloc. void * guc_palloc(int elevel, size_t size) { void *data = palloc_extended(size, MCXT_ALLOC_NO_OOM); if (unlikely(data == NULL)) ereport(elevel, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); return data; } ....check hook code .... data = guc_palloc(LOG, sizeof...); if (data == NULL) return false; .... Given the use case for guc_palloc...should elevel just be LOG with no option to change? Thoughts? -- Bryan Green EDB: https://www.enterprisedb.com