Thread

  1. 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