Thread: plpython memory leak uppon empty resultsets in all versions

plpython memory leak uppon empty resultsets in all versions

From
Andres Freund
Date:
Hi all,

plpython[u] leaks memory in PLy_spi_execute_fetch_result in the following
snippet:

Py_DECREF(result->nrows);
result->nrows = PyInt_FromLong(rows);
PLy_typeinfo_init(&args);

oldcontext = CurrentMemoryContext;
PG_TRY();
{
    if (rows)
    {
        Py_DECREF(result->rows);
        result->rows = PyList_New(rows);

        PLy_input_tuple_funcs(&args, tuptable->tupdesc);
        for (i = 0; i < rows; i++)
        {
            PyObject   *row = PLyDict_FromTuple(&args, tuptable->vals[i],
                                              tuptable->tupdesc);

            PyList_SetItem(result->rows, i, row);
        }
        PLy_typeinfo_dealloc(&args);

        SPI_freetuptable(tuptable);
    }
}
PG_CATCH();
{
    MemoryContextSwitchTo(oldcontext);
    PLy_error_in_progress = CopyErrorData();
    FlushErrorState();
    if (!PyErr_Occurred())
        PyErr_SetString(PLy_exc_error,
                "Unknown error in PLy_spi_execute_fetch_result");
    Py_DECREF(result);
    PLy_typeinfo_dealloc(&args);
    return NULL;
}
PG_END_TRY();

if rows is 0 PLy_typeinfo_dealloc and SPI_freetuptable will not be called. An
easy example where this is easily leading to a fast growing memleak (1G in
5s):

DO LANGUAGE 'plpythonu' $$while True: plpy.execute("SELECT 1 WHERE false")$$;


The fix is simple. Just move those two outside the if block. As a slightly
alternative solution one could also remove the "return NULL" and move those
outside the PG_CATCH.

Found when investigating a problem of 'bag' on irc (bcc'ed, he had to go home
before I though of asking him whether its ok to publish his name/mail).

Andres

Re: plpython memory leak uppon empty resultsets in all versions

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> plpython[u] leaks memory in PLy_spi_execute_fetch_result in the following
> snippet:

Yeah.  There's a leak of the tuptable in the CATCH path, too.  Will fix.

            regards, tom lane

Re: plpython memory leak uppon empty resultsets in all versions

From
Andres Freund
Date:
Hi,

On Friday 30 April 2010 19:39:32 Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > plpython[u] leaks memory in PLy_spi_execute_fetch_result in the following
>
> > snippet:
> Yeah.  There's a leak of the tuptable in the CATCH path, too.  Will fix.
Yes, theres even more than that (measured it) in the error case. Will have a
look later today.

Andres

Re: plpython memory leak uppon empty resultsets in all versions

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> On Friday 30 April 2010 19:39:32 Tom Lane wrote:
>> Yeah.  There's a leak of the tuptable in the CATCH path, too.  Will fix.

> Yes, theres even more than that (measured it) in the error case. Will have a
> look later today.

Here's the patch I'm planning to apply --- working it back into the
back branches now.

            regards, tom lane


Index: plpython.c
===================================================================
RCS file: /cvsroot/pgsql/src/pl/plpython/plpython.c,v
retrieving revision 1.141
diff -c -r1.141 plpython.c
*** plpython.c    18 Mar 2010 19:43:03 -0000    1.141
--- plpython.c    30 Apr 2010 17:38:20 -0000
***************
*** 3147,3155 ****

                      PyList_SetItem(result->rows, i, row);
                  }
-                 PLy_typeinfo_dealloc(&args);
-
-                 SPI_freetuptable(tuptable);
              }
          }
          PG_CATCH();
--- 3147,3152 ----
***************
*** 3160,3170 ****
              if (!PyErr_Occurred())
                  PLy_exception_set(PLy_exc_error,
                         "unrecognized error in PLy_spi_execute_fetch_result");
-             Py_DECREF(result);
              PLy_typeinfo_dealloc(&args);
              return NULL;
          }
          PG_END_TRY();
      }

      return (PyObject *) result;
--- 3157,3171 ----
              if (!PyErr_Occurred())
                  PLy_exception_set(PLy_exc_error,
                         "unrecognized error in PLy_spi_execute_fetch_result");
              PLy_typeinfo_dealloc(&args);
+             SPI_freetuptable(tuptable);
+             Py_DECREF(result);
              return NULL;
          }
          PG_END_TRY();
+
+         PLy_typeinfo_dealloc(&args);
+         SPI_freetuptable(tuptable);
      }

      return (PyObject *) result;

Re: plpython memory leak uppon empty resultsets in all versions

From
Andres Freund
Date:
On Friday 30 April 2010 20:09:48 Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > On Friday 30 April 2010 19:39:32 Tom Lane wrote:
> >> Yeah.  There's a leak of the tuptable in the CATCH path, too.  Will fix.
> >
> > Yes, theres even more than that (measured it) in the error case. Will
> > have a look later today.
>
> Here's the patch I'm planning to apply --- working it back into the
> back branches now.
The one I measured was 9.0 only:

 diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
 index 6063628..a6dd9d0 100644
 *** a/src/pl/plpython/plpython.c
 --- b/src/pl/plpython/plpython.c
 *************** plpython_inline_handler(PG_FUNCTION_ARGS
 *** 538,546 ****
 --- 538,548 ----
                 PLy_procedure_compile(proc, codeblock->source_text);
                 PLy_curr_procedure = proc;
                 PLy_function_handler(&fake_fcinfo, proc);
 +               PLy_free(proc);
         }
         PG_CATCH();
         {
 +               PLy_free(proc);
                 PLy_curr_procedure = save_curr_proc;
                 PyErr_Clear();
                 PG_RE_THROW();


Found by running something like:

while true; do echo 'DO LANGUAGE plpythonu $$import
gc;gc.collect();plpy.execute("SELECT unknown"); $$;';done|psql -h /tmp -p 5433
postgres


The gc stuff is just to make real leaks way much easier to spot.

Andres

Re: plpython memory leak uppon empty resultsets in all versions

From
Andres Freund
Date:
On Friday 30 April 2010 23:54:16 Andres Freund wrote:
> On Friday 30 April 2010 20:09:48 Tom Lane wrote:
> > Andres Freund <andres@anarazel.de> writes:
> > > On Friday 30 April 2010 19:39:32 Tom Lane wrote:
> > >> Yeah.  There's a leak of the tuptable in the CATCH path, too.  Will
> > >> fix.
> > >
> > > Yes, theres even more than that (measured it) in the error case. Will
> > > have a look later today.
> >
> > Here's the patch I'm planning to apply --- working it back into the
> > back branches now.
>
> The one I measured was 9.0 only:

> Found by running something like:
>
> while true; do echo 'DO LANGUAGE plpythonu $$import
> gc;gc.collect();plpy.execute("SELECT unknown"); $$;';done|psql -h /tmp -p
> 5433 postgres

Actually this one is also visible by 'SELECT 1' which is kinda obivous with
the above diff I guess - but I wanted to mention it for completeness.

Andres

Re: plpython memory leak uppon empty resultsets in all versions

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> The one I measured was 9.0 only:

>  diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
>  index 6063628..a6dd9d0 100644
>  *** a/src/pl/plpython/plpython.c
>  --- b/src/pl/plpython/plpython.c
>  *************** plpython_inline_handler(PG_FUNCTION_ARGS
>  *** 538,546 ****
>  --- 538,548 ----
>                  PLy_procedure_compile(proc, codeblock->source_text);
>                  PLy_curr_procedure = proc;
>                  PLy_function_handler(&fake_fcinfo, proc);
>  +               PLy_free(proc);
>          }
>          PG_CATCH();
>          {
>  +               PLy_free(proc);
>                  PLy_curr_procedure = save_curr_proc;
>                  PyErr_Clear();
>                  PG_RE_THROW();


> Found by running something like:

> while true; do echo 'DO LANGUAGE plpythonu $$import
> gc;gc.collect();plpy.execute("SELECT unknown"); $$;';done|psql -h /tmp -p 5433
> postgres

I tried this and found there was still a leak after applying your patch.
What seems like the correct thing is to use PLy_procedure_delete(),
as in the attached applied patch.  With this, I see zero leak rate for
either this test or the no-error-thrown variant.

This shows that there is a pre-existing leak in PLy_procedure_delete(),
since it was failing to release the proc block itself.  I did not bother
to back-patch that, though, because the one pre-existing call was not in
a place where it'd be likely to get executed over and over.

Thanks for the report!

            regards, tom lane


Index: plpython.c
===================================================================
RCS file: /cvsroot/pgsql/src/pl/plpython/plpython.c,v
retrieving revision 1.142
diff -c -r1.142 plpython.c
*** plpython.c    30 Apr 2010 19:15:45 -0000    1.142
--- plpython.c    1 May 2010 17:03:35 -0000
***************
*** 541,552 ****
--- 541,555 ----
      }
      PG_CATCH();
      {
+         PLy_procedure_delete(proc);
          PLy_curr_procedure = save_curr_proc;
          PyErr_Clear();
          PG_RE_THROW();
      }
      PG_END_TRY();

+     PLy_procedure_delete(proc);
+
      /* Pop the error context stack */
      error_context_stack = plerrcontext.previous;

***************
*** 1664,1669 ****
--- 1667,1673 ----
      }
      if (proc->argnames)
          PLy_free(proc->argnames);
+     PLy_free(proc);
  }

  /*

Re: plpython memory leak uppon empty resultsets in all versions

From
Andres Freund
Date:
Hi,

On Saturday 01 May 2010 19:08:40 Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > while true; do echo 'DO LANGUAGE plpythonu $$import
> > gc;gc.collect();plpy.execute("SELECT unknown"); $$;';done|psql -h /tmp -p
> > 5433 postgres
>
> I tried this and found there was still a leak after applying your patch.
> What seems like the correct thing is to use PLy_procedure_delete(),
> as in the attached applied patch.  With this, I see zero leak rate for
> either this test or the no-error-thrown variant.
Yes, I am not surprised. I havent read enough of the code to really understand
its codepaths. I didnt find it exactly obvious what happens where and when
what happens...

Thanks for really fixing the issue.

At times a sql level function that returns the current heap size would be
rather neat for writing minimal regression tests for such things would be
kinda neat. But likely it would increase the runtime a bit too much and would
be hard to implement cross-platformish...

Andres

Re: plpython memory leak uppon empty resultsets in all versions

From
Björn Grüning
Date:
Hi!

I have tested the proposed patch and it works nicely. Let me know if
further testing is necessary or if i can help.

Thanks!
Björn

Am Freitag, den 30.04.2010, 01:14 +0200 schrieb Andres Freund:
> Hi all,
>
> plpython[u] leaks memory in PLy_spi_execute_fetch_result in the following
> snippet:
>
> Py_DECREF(result->nrows);
> result->nrows = PyInt_FromLong(rows);
> PLy_typeinfo_init(&args);
>
> oldcontext = CurrentMemoryContext;
> PG_TRY();
> {
>     if (rows)
>     {
>         Py_DECREF(result->rows);
>         result->rows = PyList_New(rows);
>
>         PLy_input_tuple_funcs(&args, tuptable->tupdesc);
>         for (i = 0; i < rows; i++)
>         {
>             PyObject   *row = PLyDict_FromTuple(&args, tuptable->vals[i],
>                                               tuptable->tupdesc);
>
>             PyList_SetItem(result->rows, i, row);
>         }
>         PLy_typeinfo_dealloc(&args);
>
>         SPI_freetuptable(tuptable);
>     }
> }
> PG_CATCH();
> {
>     MemoryContextSwitchTo(oldcontext);
>     PLy_error_in_progress = CopyErrorData();
>     FlushErrorState();
>     if (!PyErr_Occurred())
>         PyErr_SetString(PLy_exc_error,
>                 "iso-8859-1 error in PLy_spi_execute_fetch_result");
>     Py_DECREF(result);
>     PLy_typeinfo_dealloc(&args);
>     return NULL;
> }
> PG_END_TRY();
>
> if rows is 0 PLy_typeinfo_dealloc and SPI_freetuptable will not be called. An
> easy example where this is easily leading to a fast growing memleak (1G in
> 5s):
>
> DO LANGUAGE 'plpythonu' $$while True: plpy.execute("SELECT 1 WHERE false")$$;
>
>
> The fix is simple. Just move those two outside the if block. As a slightly
> alternative solution one could also remove the "return NULL" and move those
> outside the PG_CATCH.
>
> Found when investigating a problem of 'bag' on irc (bcc'ed, he had to go home
> before I though of asking him whether its ok to publish his name/mail).
>
> Andres


--
Björn Grüning
Albert-Ludwigs-Universität Freiburg
Institut für Pharmazeutische Wissenschaften
Pharmazeutische Bioinformatik
Hermann-Herder-Strasse 9
D-79104 Freiburg i. Br.

Tel.:  +49-761-203-4872
E-Mail: bjoern.gruening@pharmazie.uni-freiburg.de