SRF memory mgmt patch (was [HACKERS] Concern about memory management with SRFs) - Mailing list pgsql-patches

From Joe Conway
Subject SRF memory mgmt patch (was [HACKERS] Concern about memory management with SRFs)
Date
Msg-id 3D6DC503.3060802@joeconway.com
Whole thread Raw
Responses Re: SRF memory mgmt patch (was [HACKERS] Concern about memory management with SRFs)
List pgsql-patches
Tom Lane wrote:
> Joe Conway <mail@joeconway.com> writes:
>>Is it OK to use fcinfo->flinfo->fn_mcxt as the long term memory
>>context or is there a better choice?
>
> That is the correct choice.
>
>>Is funcctx->multi_call_memory_ctx a
>>suitable name in place of funcctx->fmctx?
>
> No objection here.

Here's a patch to address Tom's SRF API memory management concerns, as
discussed earlier today on HACKERS.

Please note that, although this should apply cleanly on cvs tip, it will
have (two) failed hunks (nodeFunctionscan.c) *if* applied after Neil's
plpgsql SRF patch. Or it will cause a failure in Neil's patch if it is
applied first (I think). The fix in either case is to wrap the loop that
collects rows from the function and stashes them in the tuplestore as
follows:

do until no more tuples
+  ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;

   get one tuple
   put it in the tuplestore

+  ResetExprContext(econtext);
loop

Also note that contrib/dblink is intentionally missing, because I'm
still working on other aspects of it. I'll have an updated dblink in a
day or so.

If there are no objections, please apply.

Thanks,

Joe
Index: contrib/pgstattuple/pgstattuple.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/contrib/pgstattuple/pgstattuple.c,v
retrieving revision 1.7
diff -c -r1.7 pgstattuple.c
*** contrib/pgstattuple/pgstattuple.c    23 Aug 2002 08:19:49 -0000    1.7
--- contrib/pgstattuple/pgstattuple.c    29 Aug 2002 05:31:26 -0000
***************
*** 78,94 ****
      TupleDesc            tupdesc;
      TupleTableSlot       *slot;
      AttInMetadata       *attinmeta;
!
!     char **values;
!     int i;
!     Datum        result;

      /* stuff done only on the first call of the function */
      if(SRF_IS_FIRSTCALL())
      {
          /* create a function context for cross-call persistence */
!         funcctx = SRF_FIRSTCALL_INIT();
!
          /* total number of tuples to be returned */
          funcctx->max_calls = 1;

--- 78,97 ----
      TupleDesc            tupdesc;
      TupleTableSlot       *slot;
      AttInMetadata       *attinmeta;
!     char              **values;
!     int                    i;
!     Datum                result;
!     MemoryContext        oldcontext;

      /* stuff done only on the first call of the function */
      if(SRF_IS_FIRSTCALL())
      {
          /* create a function context for cross-call persistence */
!          funcctx = SRF_FIRSTCALL_INIT();
!
!         /* switch to memory context appropriate for multiple function calls */
!         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
!
          /* total number of tuples to be returned */
          funcctx->max_calls = 1;

***************
*** 109,114 ****
--- 112,119 ----
           */
          attinmeta = TupleDescGetAttInMetadata(tupdesc);
          funcctx->attinmeta = attinmeta;
+
+         MemoryContextSwitchTo(oldcontext);
      }

      /* stuff done on every call of the function */
Index: contrib/tablefunc/tablefunc.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/contrib/tablefunc/tablefunc.c,v
retrieving revision 1.2
diff -c -r1.2 tablefunc.c
*** contrib/tablefunc/tablefunc.c    15 Aug 2002 02:51:26 -0000    1.2
--- contrib/tablefunc/tablefunc.c    29 Aug 2002 05:37:30 -0000
***************
*** 87,92 ****
--- 87,93 ----
      float8                stddev;
      float8                carry_val;
      bool                use_carry;
+     MemoryContext        oldcontext;

      /* stuff done only on the first call of the function */
       if(SRF_IS_FIRSTCALL())
***************
*** 94,99 ****
--- 95,103 ----
          /* create a function context for cross-call persistence */
           funcctx = SRF_FIRSTCALL_INIT();

+         /* switch to memory context appropriate for multiple function calls */
+         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
          /* total number of tuples to be returned */
          funcctx->max_calls = PG_GETARG_UINT32(0);

***************
*** 119,124 ****
--- 123,130 ----
           * purpose it doesn't matter, just cast it as an unsigned value
           */
          srandom(PG_GETARG_UINT32(3));
+
+         MemoryContextSwitchTo(oldcontext);
      }

      /* stuff done on every call of the function */
***************
*** 260,269 ****
      AttInMetadata       *attinmeta;
      SPITupleTable       *spi_tuptable = NULL;
      TupleDesc            spi_tupdesc;
!     char               *lastrowid;
      crosstab_fctx       *fctx;
      int                    i;
      int                    num_categories;

      /* stuff done only on the first call of the function */
       if(SRF_IS_FIRSTCALL())
--- 266,276 ----
      AttInMetadata       *attinmeta;
      SPITupleTable       *spi_tuptable = NULL;
      TupleDesc            spi_tupdesc;
!     char               *lastrowid = NULL;
      crosstab_fctx       *fctx;
      int                    i;
      int                    num_categories;
+     MemoryContext        oldcontext;

      /* stuff done only on the first call of the function */
       if(SRF_IS_FIRSTCALL())
***************
*** 275,287 ****
          TupleDesc        tupdesc = NULL;
          int                ret;
          int                proc;
-         MemoryContext    oldcontext;

          /* create a function context for cross-call persistence */
           funcctx = SRF_FIRSTCALL_INIT();

!         /* SPI switches context on us, so save it first */
!         oldcontext = CurrentMemoryContext;

          /* Connect to SPI manager */
          if ((ret = SPI_connect()) < 0)
--- 282,293 ----
          TupleDesc        tupdesc = NULL;
          int                ret;
          int                proc;

          /* create a function context for cross-call persistence */
           funcctx = SRF_FIRSTCALL_INIT();

!         /* switch to memory context appropriate for multiple function calls */
!         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

          /* Connect to SPI manager */
          if ((ret = SPI_connect()) < 0)
***************
*** 317,324 ****
               SRF_RETURN_DONE(funcctx);
          }

!         /* back to the original memory context */
!         MemoryContextSwitchTo(oldcontext);

          /* get the typeid that represents our return type */
          functypeid = get_func_rettype(funcid);
--- 323,330 ----
               SRF_RETURN_DONE(funcctx);
          }

!         /* SPI switches context on us, so reset it */
!         MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

          /* get the typeid that represents our return type */
          functypeid = get_func_rettype(funcid);
***************
*** 381,386 ****
--- 387,394 ----

          /* total number of tuples to be returned */
          funcctx->max_calls = proc;
+
+         MemoryContextSwitchTo(oldcontext);
      }

      /* stuff done on every call of the function */
***************
*** 432,438 ****
              for (i = 0; i < num_categories; i++)
              {
                  HeapTuple    spi_tuple;
!                 char       *rowid;

                  /* see if we've gone too far already */
                  if (call_cntr >= max_calls)
--- 440,446 ----
              for (i = 0; i < num_categories; i++)
              {
                  HeapTuple    spi_tuple;
!                 char       *rowid = NULL;

                  /* see if we've gone too far already */
                  if (call_cntr >= max_calls)
***************
*** 496,502 ****
--- 504,516 ----
              xpfree(fctx->lastrowid);

              if (values[0] != NULL)
+             {
+                 /* switch to memory context appropriate for multiple function calls */
+                 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
                  lastrowid = fctx->lastrowid = pstrdup(values[0]);
+                 MemoryContextSwitchTo(oldcontext);
+             }

              if (!allnulls)
              {
Index: doc/src/sgml/xfunc.sgml
===================================================================
RCS file: /opt/src/cvs/pgsql-server/doc/src/sgml/xfunc.sgml,v
retrieving revision 1.57
diff -c -r1.57 xfunc.sgml
*** doc/src/sgml/xfunc.sgml    29 Aug 2002 00:17:02 -0000    1.57
--- doc/src/sgml/xfunc.sgml    29 Aug 2002 05:30:04 -0000
***************
*** 1670,1682 ****
      AttInMetadata       *attinmeta;

      /*
!      * memory context used to initialize structure
       *
!      * fmctx is set by SRF_FIRSTCALL_INIT() for you, and used by
!      * SRF_RETURN_DONE() for cleanup. It is primarily for internal use
!      * by the API.
       */
!     MemoryContext    fmctx;

  }    FuncCallContext;
  </programlisting>
--- 1670,1682 ----
      AttInMetadata       *attinmeta;

      /*
!      * memory context used for structures which must live for multiple calls
       *
!      * multi_call_memory_ctx is set by SRF_FIRSTCALL_INIT() for you, and used
!      * by SRF_RETURN_DONE() for cleanup. It is the most appropriate memory
!      * context for any memory that is allocated for use in multiple calls.
       */
!     MemoryContext    multi_call_memory_ctx;

  }    FuncCallContext;
  </programlisting>
***************
*** 1720,1740 ****
  Datum
  my_Set_Returning_Function(PG_FUNCTION_ARGS)
  {
!     FuncCallContext     *funcctx;
!     Datum                result;
      [user defined declarations]

      if (SRF_IS_FIRSTCALL())
      {
          /* one-time setup code appears here: */
          [user defined code]
-         funcctx = SRF_FIRSTCALL_INIT();
          [if returning composite]
              [build TupleDesc, and perhaps AttInMetadata]
              [obtain slot]
              funcctx->slot = slot;
          [endif returning composite]
          [user defined code]
      }

      /* each-time setup code appears here: */
--- 1720,1743 ----
  Datum
  my_Set_Returning_Function(PG_FUNCTION_ARGS)
  {
!     FuncCallContext  *funcctx;
!     Datum             result;
!     MemoryContext     oldcontext;
      [user defined declarations]

      if (SRF_IS_FIRSTCALL())
      {
+            funcctx = SRF_FIRSTCALL_INIT();
+         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
          /* one-time setup code appears here: */
          [user defined code]
          [if returning composite]
              [build TupleDesc, and perhaps AttInMetadata]
              [obtain slot]
              funcctx->slot = slot;
          [endif returning composite]
          [user defined code]
+         MemoryContextSwitchTo(oldcontext);
      }

      /* each-time setup code appears here: */
***************
*** 1747,1752 ****
--- 1750,1758 ----
      {
          /* here we want to return another item: */
          [user defined code]
+         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+         [user allocated memory needed in later function calls]
+         MemoryContextSwitchTo(oldcontext);
          [obtain result Datum]
          SRF_RETURN_NEXT(funcctx, result);
      }
***************
*** 1777,1784 ****
       /* stuff done only on the first call of the function */
       if (SRF_IS_FIRSTCALL())
       {
!         /* create a function context for cross-call persistence */
!          funcctx = SRF_FIRSTCALL_INIT();

          /* total number of tuples to be returned */
          funcctx->max_calls = PG_GETARG_UINT32(0);
--- 1783,1795 ----
       /* stuff done only on the first call of the function */
       if (SRF_IS_FIRSTCALL())
       {
!         MemoryContext    oldcontext;
!
!         /* create a function context for cross-call persistence */
!         funcctx = SRF_FIRSTCALL_INIT();
!
!         /* switch to memory context appropriate for multiple function calls */
!         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

          /* total number of tuples to be returned */
          funcctx->max_calls = PG_GETARG_UINT32(0);
***************
*** 1800,1805 ****
--- 1811,1818 ----
           */
          attinmeta = TupleDescGetAttInMetadata(tupdesc);
          funcctx->attinmeta = attinmeta;
+
+         MemoryContextSwitchTo(oldcontext);
      }

      /* stuff done on every call of the function */
Index: src/backend/executor/nodeFunctionscan.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/backend/executor/nodeFunctionscan.c,v
retrieving revision 1.6
diff -c -r1.6 nodeFunctionscan.c
*** src/backend/executor/nodeFunctionscan.c    29 Aug 2002 00:17:04 -0000    1.6
--- src/backend/executor/nodeFunctionscan.c    29 Aug 2002 04:38:01 -0000
***************
*** 96,101 ****
--- 96,102 ----
          {
              bool                isNull;
              ExprDoneCond        isDone;
+             ExprContext           *econtext = scanstate->csstate.cstate.cs_ExprContext;

              isNull = false;
              isDone = ExprSingleResult;
***************
*** 108,113 ****
--- 109,116 ----

              if (isDone != ExprMultipleResult)
                  break;
+
+             ResetExprContext(econtext);
          }

          /*
Index: src/backend/utils/adt/lockfuncs.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/backend/utils/adt/lockfuncs.c,v
retrieving revision 1.3
diff -c -r1.3 lockfuncs.c
*** src/backend/utils/adt/lockfuncs.c    29 Aug 2002 00:17:05 -0000    1.3
--- src/backend/utils/adt/lockfuncs.c    29 Aug 2002 05:37:32 -0000
***************
*** 24,38 ****
  Datum
  pg_lock_status(PG_FUNCTION_ARGS)
  {
!     FuncCallContext        *funccxt;
!     LockData            *lockData;

      if (SRF_IS_FIRSTCALL())
      {
-         MemoryContext    oldcxt;
          TupleDesc        tupdesc;

!         funccxt = SRF_FIRSTCALL_INIT();
          tupdesc = CreateTemplateTupleDesc(5, WITHOUTOID);
          TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relation",
                             OIDOID, -1, 0, false);
--- 24,43 ----
  Datum
  pg_lock_status(PG_FUNCTION_ARGS)
  {
!     FuncCallContext       *funcctx;
!     LockData           *lockData;
!     MemoryContext        oldcontext;

      if (SRF_IS_FIRSTCALL())
      {
          TupleDesc        tupdesc;

!         /* create a function context for cross-call persistence */
!         funcctx = SRF_FIRSTCALL_INIT();
!
!         /* switch to memory context appropriate for multiple function calls */
!         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
!
          tupdesc = CreateTemplateTupleDesc(5, WITHOUTOID);
          TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relation",
                             OIDOID, -1, 0, false);
***************
*** 45,54 ****
          TupleDescInitEntry(tupdesc, (AttrNumber) 5, "isgranted",
                             BOOLOID, -1, 0, false);

!         funccxt->slot = TupleDescGetSlot(tupdesc);
!         funccxt->attinmeta = TupleDescGetAttInMetadata(tupdesc);
!
!         oldcxt = MemoryContextSwitchTo(funccxt->fmctx);

          /*
           * Preload all the locking information that we will eventually format
--- 50,57 ----
          TupleDescInitEntry(tupdesc, (AttrNumber) 5, "isgranted",
                             BOOLOID, -1, 0, false);

!         funcctx->slot = TupleDescGetSlot(tupdesc);
!         funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);

          /*
           * Preload all the locking information that we will eventually format
***************
*** 56,70 ****
           * MemoryContext is reset when the SRF finishes, we don't need to
           * free it ourselves.
           */
!         funccxt->user_fctx = (LockData *) palloc(sizeof(LockData));

!         GetLockStatusData(funccxt->user_fctx);

!         MemoryContextSwitchTo(oldcxt);
      }

!     funccxt    = SRF_PERCALL_SETUP();
!     lockData = (LockData *) funccxt->user_fctx;

      while (lockData->currIdx < lockData->nelements)
      {
--- 59,73 ----
           * MemoryContext is reset when the SRF finishes, we don't need to
           * free it ourselves.
           */
!         funcctx->user_fctx = (LockData *) palloc(sizeof(LockData));

!         GetLockStatusData(funcctx->user_fctx);

!         MemoryContextSwitchTo(oldcontext);
      }

!     funcctx    = SRF_PERCALL_SETUP();
!     lockData = (LockData *) funcctx->user_fctx;

      while (lockData->currIdx < lockData->nelements)
      {
***************
*** 82,88 ****
          holder        = &(lockData->holders[currIdx]);
          lock        = &(lockData->locks[currIdx]);
          proc        = &(lockData->procs[currIdx]);
!         num_attrs    = funccxt->attinmeta->tupdesc->natts;

          values = (char **) palloc(sizeof(*values) * num_attrs);

--- 85,91 ----
          holder        = &(lockData->holders[currIdx]);
          lock        = &(lockData->locks[currIdx]);
          proc        = &(lockData->procs[currIdx]);
!         num_attrs    = funcctx->attinmeta->tupdesc->natts;

          values = (char **) palloc(sizeof(*values) * num_attrs);

***************
*** 133,144 ****

          strncpy(values[3], GetLockmodeName(mode), 32);

!         tuple = BuildTupleFromCStrings(funccxt->attinmeta, values);
!         result = TupleGetDatum(funccxt->slot, tuple);
!         SRF_RETURN_NEXT(funccxt, result);
      }

!     SRF_RETURN_DONE(funccxt);
  }

  static LOCKMODE
--- 136,147 ----

          strncpy(values[3], GetLockmodeName(mode), 32);

!         tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
!         result = TupleGetDatum(funcctx->slot, tuple);
!         SRF_RETURN_NEXT(funcctx, result);
      }

!     SRF_RETURN_DONE(funcctx);
  }

  static LOCKMODE
Index: src/backend/utils/fmgr/funcapi.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/backend/utils/fmgr/funcapi.c,v
retrieving revision 1.3
diff -c -r1.3 funcapi.c
*** src/backend/utils/fmgr/funcapi.c    29 Aug 2002 00:17:05 -0000    1.3
--- src/backend/utils/fmgr/funcapi.c    29 Aug 2002 04:32:33 -0000
***************
*** 59,72 ****
          retval->slot = NULL;
          retval->user_fctx = NULL;
          retval->attinmeta = NULL;
!         retval->fmctx = fcinfo->flinfo->fn_mcxt;

          /*
           * save the pointer for cross-call use
           */
          fcinfo->flinfo->fn_extra = retval;

-         /* back to the original memory context */
          MemoryContextSwitchTo(oldcontext);
      }
      else    /* second and subsequent calls */
--- 59,71 ----
          retval->slot = NULL;
          retval->user_fctx = NULL;
          retval->attinmeta = NULL;
!         retval->multi_call_memory_ctx = fcinfo->flinfo->fn_mcxt;

          /*
           * save the pointer for cross-call use
           */
          fcinfo->flinfo->fn_extra = retval;

          MemoryContextSwitchTo(oldcontext);
      }
      else    /* second and subsequent calls */
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/backend/utils/misc/guc.c,v
retrieving revision 1.85
diff -c -r1.85 guc.c
*** src/backend/utils/misc/guc.c    29 Aug 2002 00:17:05 -0000    1.85
--- src/backend/utils/misc/guc.c    29 Aug 2002 05:37:32 -0000
***************
*** 2421,2426 ****
--- 2421,2427 ----
      int                    max_calls;
      TupleTableSlot       *slot;
      AttInMetadata       *attinmeta;
+     MemoryContext        oldcontext;

      /* stuff done only on the first call of the function */
       if(SRF_IS_FIRSTCALL())
***************
*** 2428,2433 ****
--- 2429,2437 ----
          /* create a function context for cross-call persistence */
           funcctx = SRF_FIRSTCALL_INIT();

+         /* switch to memory context appropriate for multiple function calls */
+         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
          /* need a tuple descriptor representing two TEXT columns */
          tupdesc = CreateTemplateTupleDesc(2, WITHOUTOID);
          TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
***************
*** 2450,2455 ****
--- 2454,2461 ----

          /* total number of tuples to be returned */
          funcctx->max_calls = GetNumConfigOptions();
+
+         MemoryContextSwitchTo(oldcontext);
      }

      /* stuff done on every call of the function */
Index: src/include/funcapi.h
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/include/funcapi.h,v
retrieving revision 1.5
diff -c -r1.5 funcapi.h
*** src/include/funcapi.h    29 Aug 2002 00:17:06 -0000    1.5
--- src/include/funcapi.h    29 Aug 2002 05:37:32 -0000
***************
*** 101,113 ****
      AttInMetadata       *attinmeta;

      /*
!      * memory context used to initialize structure
       *
!      * fmctx is set by SRF_FIRSTCALL_INIT() for you, and used by
!      * SRF_RETURN_DONE() for cleanup. It is primarily for internal use
!      * by the API.
       */
!     MemoryContext    fmctx;

  }    FuncCallContext;

--- 101,113 ----
      AttInMetadata       *attinmeta;

      /*
!      * memory context used for structures which must live for multiple calls
       *
!      * multi_call_memory_ctx is set by SRF_FIRSTCALL_INIT() for you, and used
!      * by SRF_RETURN_DONE() for cleanup. It is the most appropriate memory
!      * context for any memory that is allocated for use in multiple calls.
       */
!     MemoryContext    multi_call_memory_ctx;

  }    FuncCallContext;

***************
*** 160,176 ****
   * {
   *     FuncCallContext       *funcctx;
   *     Datum                result;
   *     <user defined declarations>
   *
   *     if(SRF_IS_FIRSTCALL())
   *     {
-  *         <user defined code>
   *         funcctx = SRF_FIRSTCALL_INIT();
   *         <if returning composite>
   *             <obtain slot>
   *             funcctx->slot = slot;
   *         <endif returning composite>
   *         <user defined code>
   *  }
   *     <user defined code>
   *     funcctx = SRF_PERCALL_SETUP();
--- 160,179 ----
   * {
   *     FuncCallContext       *funcctx;
   *     Datum                result;
+  *  MemoryContext        oldcontext;
   *     <user defined declarations>
   *
   *     if(SRF_IS_FIRSTCALL())
   *     {
   *         funcctx = SRF_FIRSTCALL_INIT();
+  *        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+  *         <user defined code>
   *         <if returning composite>
   *             <obtain slot>
   *             funcctx->slot = slot;
   *         <endif returning composite>
   *         <user defined code>
+  *        MemoryContextSwitchTo(oldcontext);
   *  }
   *     <user defined code>
   *     funcctx = SRF_PERCALL_SETUP();
***************
*** 179,184 ****
--- 182,190 ----
   *     if (funcctx->call_cntr < funcctx->max_calls)
   *     {
   *         <user defined code>
+  *        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+  *      <user allocated memory needed in later function calls>
+  *        MemoryContextSwitchTo(oldcontext);
   *         <obtain result Datum>
   *         SRF_RETURN_NEXT(funcctx, result);
   *     }

pgsql-patches by date:

Previous
From: Tom Lane
Date:
Subject: Re: small mistakes in func.sgml
Next
From: Neil Conway
Date:
Subject: Re: revised patch for PL/PgSQL table functions