Re: plpgsql leaking memory when stringifying datums - Mailing list pgsql-hackers

From Tom Lane
Subject Re: plpgsql leaking memory when stringifying datums
Date
Msg-id 29668.1328925904@sss.pgh.pa.us
Whole thread Raw
In response to plpgsql leaking memory when stringifying datums  (Jan Urbański <wulczer@wulczer.org>)
Responses Re: plpgsql leaking memory when stringifying datums  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-hackers
=?UTF-8?B?SmFuIFVyYmHFhHNraQ==?= <wulczer@wulczer.org> writes:
> While chasing a PL/Python memory leak, I did a few tests with PL/pgSQL
> and I think there are places where memory is not freed sufficiently early.

I think the basic issue here is that the type output function might
generate (and not bother to free) additional cruft besides its output
string, so that pfree'ing the output alone is not sufficient to avoid
a memory leak if the call occurs in a long-lived context.

However, I don't much care for the details of the proposed patch: if
we're going to fix this by running the output function in the per-tuple
memory context, and expecting the caller to do exec_eval_cleanup later,
why should we add extra pstrdup/pfree overhead?  We can just leave the
result in the temp context in most cases, and thus get a net savings
rather than a net cost from fixing this.  The attached modified patch
does it like that.

BTW, it occurs to me to wonder whether we need to worry about such
subsidiary leaks in type input functions as well.  I see at least one
place where pl_exec.c is tediously freeing the result of
exec_simple_cast_value, but if there are secondary leaks that's not
going to be good enough.  Maybe we should switch over to a similar
definition where the cast result is in the per-tuple context, and you've
got to copy it if you want it to be long-lived.

            regards, tom lane


diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index bf952b62478792b5564fe2af744a318322ea197c..e93b7c63be5d8a385b420dc0d9afa04cb90174a7 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** static void exec_move_row(PLpgSQL_execst
*** 188,201 ****
  static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate,
                      PLpgSQL_row *row,
                      TupleDesc tupdesc);
! static char *convert_value_to_string(Datum value, Oid valtype);
! static Datum exec_cast_value(Datum value, Oid valtype,
                  Oid reqtype,
                  FmgrInfo *reqinput,
                  Oid reqtypioparam,
                  int32 reqtypmod,
                  bool isnull);
! static Datum exec_simple_cast_value(Datum value, Oid valtype,
                         Oid reqtype, int32 reqtypmod,
                         bool isnull);
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
--- 188,204 ----
  static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate,
                      PLpgSQL_row *row,
                      TupleDesc tupdesc);
! static char *convert_value_to_string(PLpgSQL_execstate *estate,
!                         Datum value, Oid valtype);
! static Datum exec_cast_value(PLpgSQL_execstate *estate,
!                 Datum value, Oid valtype,
                  Oid reqtype,
                  FmgrInfo *reqinput,
                  Oid reqtypioparam,
                  int32 reqtypmod,
                  bool isnull);
! static Datum exec_simple_cast_value(PLpgSQL_execstate *estate,
!                        Datum value, Oid valtype,
                         Oid reqtype, int32 reqtypmod,
                         bool isnull);
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
*************** plpgsql_exec_function(PLpgSQL_function *
*** 430,436 ****
          else
          {
              /* Cast value to proper type */
!             estate.retval = exec_cast_value(estate.retval, estate.rettype,
                                              func->fn_rettype,
                                              &(func->fn_retinput),
                                              func->fn_rettypioparam,
--- 433,440 ----
          else
          {
              /* Cast value to proper type */
!             estate.retval = exec_cast_value(&estate, estate.retval,
!                                             estate.rettype,
                                              func->fn_rettype,
                                              &(func->fn_retinput),
                                              func->fn_rettypioparam,
*************** exec_stmt_fori(PLpgSQL_execstate *estate
*** 1757,1763 ****
       * Get the value of the lower bound
       */
      value = exec_eval_expr(estate, stmt->lower, &isnull, &valtype);
!     value = exec_cast_value(value, valtype, var->datatype->typoid,
                              &(var->datatype->typinput),
                              var->datatype->typioparam,
                              var->datatype->atttypmod, isnull);
--- 1761,1767 ----
       * Get the value of the lower bound
       */
      value = exec_eval_expr(estate, stmt->lower, &isnull, &valtype);
!     value = exec_cast_value(estate, value, valtype, var->datatype->typoid,
                              &(var->datatype->typinput),
                              var->datatype->typioparam,
                              var->datatype->atttypmod, isnull);
*************** exec_stmt_fori(PLpgSQL_execstate *estate
*** 1772,1778 ****
       * Get the value of the upper bound
       */
      value = exec_eval_expr(estate, stmt->upper, &isnull, &valtype);
!     value = exec_cast_value(value, valtype, var->datatype->typoid,
                              &(var->datatype->typinput),
                              var->datatype->typioparam,
                              var->datatype->atttypmod, isnull);
--- 1776,1782 ----
       * Get the value of the upper bound
       */
      value = exec_eval_expr(estate, stmt->upper, &isnull, &valtype);
!     value = exec_cast_value(estate, value, valtype, var->datatype->typoid,
                              &(var->datatype->typinput),
                              var->datatype->typioparam,
                              var->datatype->atttypmod, isnull);
*************** exec_stmt_fori(PLpgSQL_execstate *estate
*** 1789,1795 ****
      if (stmt->step)
      {
          value = exec_eval_expr(estate, stmt->step, &isnull, &valtype);
!         value = exec_cast_value(value, valtype, var->datatype->typoid,
                                  &(var->datatype->typinput),
                                  var->datatype->typioparam,
                                  var->datatype->atttypmod, isnull);
--- 1793,1799 ----
      if (stmt->step)
      {
          value = exec_eval_expr(estate, stmt->step, &isnull, &valtype);
!         value = exec_cast_value(estate, value, valtype, var->datatype->typoid,
                                  &(var->datatype->typinput),
                                  var->datatype->typioparam,
                                  var->datatype->atttypmod, isnull);
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2440,2446 ****
                          errmsg("wrong result type supplied in RETURN NEXT")));

                      /* coerce type if needed */
!                     retval = exec_simple_cast_value(retval,
                                                      var->datatype->typoid,
                                                   tupdesc->attrs[0]->atttypid,
                                                  tupdesc->attrs[0]->atttypmod,
--- 2444,2450 ----
                          errmsg("wrong result type supplied in RETURN NEXT")));

                      /* coerce type if needed */
!                     retval = exec_simple_cast_value(estate, retval,
                                                      var->datatype->typoid,
                                                   tupdesc->attrs[0]->atttypid,
                                                  tupdesc->attrs[0]->atttypmod,
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2511,2517 ****
                                  &rettype);

          /* coerce type if needed */
!         retval = exec_simple_cast_value(retval,
                                          rettype,
                                          tupdesc->attrs[0]->atttypid,
                                          tupdesc->attrs[0]->atttypmod,
--- 2515,2522 ----
                                  &rettype);

          /* coerce type if needed */
!         retval = exec_simple_cast_value(estate,
!                                         retval,
                                          rettype,
                                          tupdesc->attrs[0]->atttypid,
                                          tupdesc->attrs[0]->atttypmod,
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2519,2526 ****

          tuplestore_putvalues(estate->tuple_store, tupdesc,
                               &retval, &isNull);
-
-         exec_eval_cleanup(estate);
      }
      else
      {
--- 2524,2529 ----
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2537,2542 ****
--- 2540,2547 ----
              heap_freetuple(tuple);
      }

+     exec_eval_cleanup(estate);
+
      return PLPGSQL_RC_OK;
  }

*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 2726,2732 ****
                  if (paramisnull)
                      extval = "<NULL>";
                  else
!                     extval = convert_value_to_string(paramvalue, paramtypeid);
                  appendStringInfoString(&ds, extval);
                  current_param = lnext(current_param);
                  exec_eval_cleanup(estate);
--- 2731,2738 ----
                  if (paramisnull)
                      extval = "<NULL>";
                  else
!                     extval = convert_value_to_string(estate,
!                                                      paramvalue, paramtypeid);
                  appendStringInfoString(&ds, extval);
                  current_param = lnext(current_param);
                  exec_eval_cleanup(estate);
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 2764,2770 ****
                      (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                       errmsg("RAISE statement option cannot be null")));

!         extval = convert_value_to_string(optionvalue, optiontypeid);

          switch (opt->opt_type)
          {
--- 2770,2776 ----
                      (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                       errmsg("RAISE statement option cannot be null")));

!         extval = convert_value_to_string(estate, optionvalue, optiontypeid);

          switch (opt->opt_type)
          {
*************** exec_stmt_dynexecute(PLpgSQL_execstate *
*** 3228,3234 ****
                   errmsg("query string argument of EXECUTE is null")));

      /* Get the C-String representation */
!     querystr = convert_value_to_string(query, restype);

      exec_eval_cleanup(estate);

--- 3234,3243 ----
                   errmsg("query string argument of EXECUTE is null")));

      /* Get the C-String representation */
!     querystr = convert_value_to_string(estate, query, restype);
!
!     /* copy it out of the temporary context before we clean up */
!     querystr = pstrdup(querystr);

      exec_eval_cleanup(estate);

*************** exec_assign_value(PLpgSQL_execstate *est
*** 3753,3759 ****
                  PLpgSQL_var *var = (PLpgSQL_var *) target;
                  Datum        newvalue;

!                 newvalue = exec_cast_value(value, valtype, var->datatype->typoid,
                                             &(var->datatype->typinput),
                                             var->datatype->typioparam,
                                             var->datatype->atttypmod,
--- 3762,3769 ----
                  PLpgSQL_var *var = (PLpgSQL_var *) target;
                  Datum        newvalue;

!                 newvalue = exec_cast_value(estate, value, valtype,
!                                            var->datatype->typoid,
                                             &(var->datatype->typinput),
                                             var->datatype->typioparam,
                                             var->datatype->atttypmod,
*************** exec_assign_value(PLpgSQL_execstate *est
*** 3946,3952 ****
                  atttype = SPI_gettypeid(rec->tupdesc, fno + 1);
                  atttypmod = rec->tupdesc->attrs[fno]->atttypmod;
                  attisnull = *isNull;
!                 values[fno] = exec_simple_cast_value(value,
                                                       valtype,
                                                       atttype,
                                                       atttypmod,
--- 3956,3963 ----
                  atttype = SPI_gettypeid(rec->tupdesc, fno + 1);
                  atttypmod = rec->tupdesc->attrs[fno]->atttypmod;
                  attisnull = *isNull;
!                 values[fno] = exec_simple_cast_value(estate,
!                                                      value,
                                                       valtype,
                                                       atttype,
                                                       atttypmod,
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4118,4124 ****
                  estate->eval_tuptable = save_eval_tuptable;

                  /* Coerce source value to match array element type. */
!                 coerced_value = exec_simple_cast_value(value,
                                                         valtype,
                                                         arrayelem->elemtypoid,
                                                         arrayelem->arraytypmod,
--- 4129,4136 ----
                  estate->eval_tuptable = save_eval_tuptable;

                  /* Coerce source value to match array element type. */
!                 coerced_value = exec_simple_cast_value(estate,
!                                                        value,
                                                         valtype,
                                                         arrayelem->elemtypoid,
                                                         arrayelem->arraytypmod,
*************** exec_eval_integer(PLpgSQL_execstate *est
*** 4514,4520 ****
      Oid            exprtypeid;

      exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
!     exprdatum = exec_simple_cast_value(exprdatum, exprtypeid,
                                         INT4OID, -1,
                                         *isNull);
      return DatumGetInt32(exprdatum);
--- 4526,4532 ----
      Oid            exprtypeid;

      exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
!     exprdatum = exec_simple_cast_value(estate, exprdatum, exprtypeid,
                                         INT4OID, -1,
                                         *isNull);
      return DatumGetInt32(exprdatum);
*************** exec_eval_boolean(PLpgSQL_execstate *est
*** 4536,4542 ****
      Oid            exprtypeid;

      exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
!     exprdatum = exec_simple_cast_value(exprdatum, exprtypeid,
                                         BOOLOID, -1,
                                         *isNull);
      return DatumGetBool(exprdatum);
--- 4548,4554 ----
      Oid            exprtypeid;

      exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
!     exprdatum = exec_simple_cast_value(estate, exprdatum, exprtypeid,
                                         BOOLOID, -1,
                                         *isNull);
      return DatumGetBool(exprdatum);
*************** exec_move_row(PLpgSQL_execstate *estate,
*** 5277,5282 ****
--- 5289,5296 ----
                                value, valtype, &isnull);
          }

+         exec_eval_cleanup(estate);
+
          return;
      }

*************** make_tuple_from_row(PLpgSQL_execstate *e
*** 5338,5365 ****
  /* ----------
   * convert_value_to_string            Convert a non-null Datum to C string
   *
!  * Note: callers generally assume that the result is a palloc'd string and
!  * should be pfree'd.  This is not all that safe an assumption ...
   *
   * Note: not caching the conversion function lookup is bad for performance.
   * ----------
   */
  static char *
! convert_value_to_string(Datum value, Oid valtype)
  {
      Oid            typoutput;
      bool        typIsVarlena;

      getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
!     return OidOutputFunctionCall(typoutput, value);
  }

  /* ----------
   * exec_cast_value            Cast a value if required
   * ----------
   */
  static Datum
! exec_cast_value(Datum value, Oid valtype,
                  Oid reqtype,
                  FmgrInfo *reqinput,
                  Oid reqtypioparam,
--- 5352,5394 ----
  /* ----------
   * convert_value_to_string            Convert a non-null Datum to C string
   *
!  * Note: the result is in the estate's eval_econtext, and will be cleared
!  * by the next exec_eval_cleanup() call.  The invoked output function might
!  * leave additional cruft there as well, so just pfree'ing the result string
!  * would not be enough to avoid memory leaks if we did not do it like this.
!  * In most usages the Datum being passed in is also in that context (if
!  * pass-by-reference) and so an exec_eval_cleanup() call is needed anyway.
   *
   * Note: not caching the conversion function lookup is bad for performance.
   * ----------
   */
  static char *
! convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype)
  {
+     char       *result;
+     MemoryContext oldcontext;
      Oid            typoutput;
      bool        typIsVarlena;

+     oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
      getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
!     result = OidOutputFunctionCall(typoutput, value);
!     MemoryContextSwitchTo(oldcontext);
!
!     return result;
  }

  /* ----------
   * exec_cast_value            Cast a value if required
+  *
+  * Note: the estate's eval_econtext is used for temporary storage.
+  * If you use this in a loop, be sure there is an exec_eval_cleanup() call.
+  * The result Datum is in the caller's memory context, however.
   * ----------
   */
  static Datum
! exec_cast_value(PLpgSQL_execstate *estate,
!                 Datum value, Oid valtype,
                  Oid reqtype,
                  FmgrInfo *reqinput,
                  Oid reqtypioparam,
*************** exec_cast_value(Datum value, Oid valtype
*** 5376,5385 ****
          {
              char       *extval;

!             extval = convert_value_to_string(value, valtype);
              value = InputFunctionCall(reqinput, extval,
                                        reqtypioparam, reqtypmod);
-             pfree(extval);
          }
          else
          {
--- 5405,5413 ----
          {
              char       *extval;

!             extval = convert_value_to_string(estate, value, valtype);
              value = InputFunctionCall(reqinput, extval,
                                        reqtypioparam, reqtypmod);
          }
          else
          {
*************** exec_cast_value(Datum value, Oid valtype
*** 5400,5406 ****
   * ----------
   */
  static Datum
! exec_simple_cast_value(Datum value, Oid valtype,
                         Oid reqtype, int32 reqtypmod,
                         bool isnull)
  {
--- 5428,5434 ----
   * ----------
   */
  static Datum
! exec_simple_cast_value(PLpgSQL_execstate *estate, Datum value, Oid valtype,
                         Oid reqtype, int32 reqtypmod,
                         bool isnull)
  {
*************** exec_simple_cast_value(Datum value, Oid
*** 5414,5420 ****

          fmgr_info(typinput, &finfo_input);

!         value = exec_cast_value(value,
                                  valtype,
                                  reqtype,
                                  &finfo_input,
--- 5442,5449 ----

          fmgr_info(typinput, &finfo_input);

!         value = exec_cast_value(estate,
!                                 value,
                                  valtype,
                                  reqtype,
                                  &finfo_input,
*************** exec_dynquery_with_params(PLpgSQL_execst
*** 6137,6143 ****
                   errmsg("query string argument of EXECUTE is null")));

      /* Get the C-String representation */
!     querystr = convert_value_to_string(query, restype);

      exec_eval_cleanup(estate);

--- 6166,6175 ----
                   errmsg("query string argument of EXECUTE is null")));

      /* Get the C-String representation */
!     querystr = convert_value_to_string(estate, query, restype);
!
!     /* copy it out of the temporary context before we clean up */
!     querystr = pstrdup(querystr);

      exec_eval_cleanup(estate);


pgsql-hackers by date:

Previous
From: Gaetano Mendola
Date:
Subject: bitfield and gcc
Next
From: Noah Misch
Date:
Subject: Re: RFC: Making TRUNCATE more "MVCC-safe"