Re: [HACKERS] plpgsql leaking memory when stringifying datums
I wrote: BTW, it occurs to me to wonder whether we need to worry about such subsidiary leaks in type input functions as well. Sure enough, once you find an input function that leaks memory, there's trouble: create type myrow as (f1 text, f2 text, f3 text); create or replace function leak_assign() returns void as $$ declare t myrow[]; i int; begin for i in 1..1000 loop t := '{(abcd,efg' || ',hij), (a,b,c)}'; end loop; end; $$ language plpgsql; So the attached third try also moves the input function calls in exec_cast_value into the short-lived context, and rejiggers callers as necessary to deal with that. This actually ends up simpler and probably faster than the original coding, because we are able to get rid of some ad-hoc data copying and pfree'ing, and most of the performance-critical code paths already had exec_eval_cleanup calls anyway. Also, you wrote: With that I was still left with a leak in the typecast() test function and it turns out that sticking a exec_eval_cleanup into exec_move_row fixed it. The regression tests pass, but I'm not 100% sure if it's actually safe. After some study I felt pretty nervous about that too. It's safe enough with the statement-level callers of exec_move_row, but there are several calls from exec_assign_value, whose API contract says specifically that it *won't* call exec_eval_cleanup. Even if it works today, that's a bug waiting to happen. So I took the exec_eval_cleanup back out of exec_move_row, and instead made all the statement-level callers do it. I think this version is ready to go, so barring objections I'll set to work on back-patching it. regards, tom lane diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index bf952b62478792b5564fe2af744a318322ea197c..0b126a3f4ac0e16e4ae19b8507c5c1352842a7e2 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 * *** 295,300 --- 298,305 /* If arg is null, treat it as an empty row */ exec_move_row(estate, NULL, row, NULL, NULL); } + /* clean up after exec_move_row() */ + exec_eval_cleanup(estate); } break; *** 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, --- 435,443 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); --- 1764,1770 * 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,
Re: [HACKERS] plpgsql leaking memory when stringifying datums
=?UTF-8?B?SmFuIFVyYmHFhHNraQ==?= wulc...@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); !
[HACKERS] plpgsql leaking memory when stringifying datums
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. Attached are two functions that when run will make the backend's memory consumption increase until they finish. With both, the cause is convert_value_to_string that calls a datum's output function, which for toasted data results in an allocation. The proposed patch changes convert_value_to_string to call the output function in the per-tuple memory context and then copy the result string back to the original context. The comment in that function says that callers generally pfree its result, but that wasn't the case with exec_stmt_raise, so I added a pfree() there as well. With that I was still left with a leak in the typecast() test function and it turns out that sticking a exec_eval_cleanup into exec_move_row fixed it. The regression tests pass, but I'm not 100% sure if it's actually safe. Since convert_value_to_string needed to access the PL/pgSQL's execution state to get its hands on the per-tuple context, I needed to pass it to every caller that didn't have it already, which means exec_cast_value and exec_simple_cast_value. Anyone has a better idea? The initial diagnosis and proposed solution are by Andres Freund - thanks! Cheers, Jan diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index bf952b6..a691905 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,