| From: | Bryan Green <dbryan(dot)green(at)gmail(dot)com> |
|---|---|
| To: | Robert Haas <robertmhaas(at)gmail(dot)com> |
| Cc: | Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>, pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Subject: | Re: [PATCH] Allow complex data for GUC extra. |
| Date: | 2025-12-08 15:23:00 |
| Message-ID: | e4ee5574-d842-486a-b06f-3ccfd5c7a94a@gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
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(dot)green(at)gmail(dot)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
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Hannu Krosing | 2025-12-08 15:25:20 | Re: making tid and HOTness of UPDATE available to logical decoding plugins |
| Previous Message | Rafia Sabih | 2025-12-08 15:20:46 | Re: Add wait event for CommitDelay |