diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index c7ba4248c72266685cae88f69da80fc417bc0ad8..8b963bf818d65d8157b1b039e9478a79cc6dc27c 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -2417,11 +2417,8 @@ delete_function(PLpgSQL_function *func) plpgsql_HashTableDelete(func); /* release the function's storage if safe and not done already */ - if (func->use_count == 0 && func->fn_cxt) - { - MemoryContextDelete(func->fn_cxt); - func->fn_cxt = NULL; - } + if (func->use_count == 0) + plpgsql_free_function_memory(func); } /* exported so we can call it from plpgsql_init() */ diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index f13e4c3db6367c47ff75445fcad4eb7937c835a3..1f83114d9b15cae9260fd30fb9ced487db8b9246 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -15,6 +15,8 @@ #include "plpgsql.h" +#include "utils/memutils.h" + /* ---------- * Local variables for namespace handling @@ -264,6 +266,402 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt) } +/********************************************************************** + * Release memory when a PL/pgSQL function is no longer needed + * + * The code for recursing through the function tree is really only + * needed to locate PLpgSQL_expr nodes, which may contain references + * to saved SPI Plans that must be freed. The function tree itself, + * along with subsidiary data, is freed in one swoop by freeing the + * function's permanent memory context. + **********************************************************************/ +static void free_stmt(PLpgSQL_stmt *stmt); +static void free_block(PLpgSQL_stmt_block *block); +static void free_assign(PLpgSQL_stmt_assign *stmt); +static void free_if(PLpgSQL_stmt_if *stmt); +static void free_case(PLpgSQL_stmt_case *stmt); +static void free_loop(PLpgSQL_stmt_loop *stmt); +static void free_while(PLpgSQL_stmt_while *stmt); +static void free_fori(PLpgSQL_stmt_fori *stmt); +static void free_fors(PLpgSQL_stmt_fors *stmt); +static void free_forc(PLpgSQL_stmt_forc *stmt); +static void free_foreach_a(PLpgSQL_stmt_foreach_a *stmt); +static void free_exit(PLpgSQL_stmt_exit *stmt); +static void free_return(PLpgSQL_stmt_return *stmt); +static void free_return_next(PLpgSQL_stmt_return_next *stmt); +static void free_return_query(PLpgSQL_stmt_return_query *stmt); +static void free_raise(PLpgSQL_stmt_raise *stmt); +static void free_execsql(PLpgSQL_stmt_execsql *stmt); +static void free_dynexecute(PLpgSQL_stmt_dynexecute *stmt); +static void free_dynfors(PLpgSQL_stmt_dynfors *stmt); +static void free_getdiag(PLpgSQL_stmt_getdiag *stmt); +static void free_open(PLpgSQL_stmt_open *stmt); +static void free_fetch(PLpgSQL_stmt_fetch *stmt); +static void free_close(PLpgSQL_stmt_close *stmt); +static void free_perform(PLpgSQL_stmt_perform *stmt); +static void free_expr(PLpgSQL_expr *expr); + + +static void +free_stmt(PLpgSQL_stmt *stmt) +{ + switch ((enum PLpgSQL_stmt_types) stmt->cmd_type) + { + case PLPGSQL_STMT_BLOCK: + free_block((PLpgSQL_stmt_block *) stmt); + break; + case PLPGSQL_STMT_ASSIGN: + free_assign((PLpgSQL_stmt_assign *) stmt); + break; + case PLPGSQL_STMT_IF: + free_if((PLpgSQL_stmt_if *) stmt); + break; + case PLPGSQL_STMT_CASE: + free_case((PLpgSQL_stmt_case *) stmt); + break; + case PLPGSQL_STMT_LOOP: + free_loop((PLpgSQL_stmt_loop *) stmt); + break; + case PLPGSQL_STMT_WHILE: + free_while((PLpgSQL_stmt_while *) stmt); + break; + case PLPGSQL_STMT_FORI: + free_fori((PLpgSQL_stmt_fori *) stmt); + break; + case PLPGSQL_STMT_FORS: + free_fors((PLpgSQL_stmt_fors *) stmt); + break; + case PLPGSQL_STMT_FORC: + free_forc((PLpgSQL_stmt_forc *) stmt); + break; + case PLPGSQL_STMT_FOREACH_A: + free_foreach_a((PLpgSQL_stmt_foreach_a *) stmt); + break; + case PLPGSQL_STMT_EXIT: + free_exit((PLpgSQL_stmt_exit *) stmt); + break; + case PLPGSQL_STMT_RETURN: + free_return((PLpgSQL_stmt_return *) stmt); + break; + case PLPGSQL_STMT_RETURN_NEXT: + free_return_next((PLpgSQL_stmt_return_next *) stmt); + break; + case PLPGSQL_STMT_RETURN_QUERY: + free_return_query((PLpgSQL_stmt_return_query *) stmt); + break; + case PLPGSQL_STMT_RAISE: + free_raise((PLpgSQL_stmt_raise *) stmt); + break; + case PLPGSQL_STMT_EXECSQL: + free_execsql((PLpgSQL_stmt_execsql *) stmt); + break; + case PLPGSQL_STMT_DYNEXECUTE: + free_dynexecute((PLpgSQL_stmt_dynexecute *) stmt); + break; + case PLPGSQL_STMT_DYNFORS: + free_dynfors((PLpgSQL_stmt_dynfors *) stmt); + break; + case PLPGSQL_STMT_GETDIAG: + free_getdiag((PLpgSQL_stmt_getdiag *) stmt); + break; + case PLPGSQL_STMT_OPEN: + free_open((PLpgSQL_stmt_open *) stmt); + break; + case PLPGSQL_STMT_FETCH: + free_fetch((PLpgSQL_stmt_fetch *) stmt); + break; + case PLPGSQL_STMT_CLOSE: + free_close((PLpgSQL_stmt_close *) stmt); + break; + case PLPGSQL_STMT_PERFORM: + free_perform((PLpgSQL_stmt_perform *) stmt); + break; + default: + elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type); + break; + } +} + +static void +free_stmts(List *stmts) +{ + ListCell *s; + + foreach(s, stmts) + { + free_stmt((PLpgSQL_stmt *) lfirst(s)); + } +} + +static void +free_block(PLpgSQL_stmt_block *block) +{ + free_stmts(block->body); + if (block->exceptions) + { + ListCell *e; + + foreach(e, block->exceptions->exc_list) + { + PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e); + + free_stmts(exc->action); + } + } +} + +static void +free_assign(PLpgSQL_stmt_assign *stmt) +{ + free_expr(stmt->expr); +} + +static void +free_if(PLpgSQL_stmt_if *stmt) +{ + free_expr(stmt->cond); + free_stmts(stmt->true_body); + free_stmts(stmt->false_body); +} + +static void +free_case(PLpgSQL_stmt_case *stmt) +{ + ListCell *l; + + free_expr(stmt->t_expr); + foreach(l, stmt->case_when_list) + { + PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l); + + free_expr(cwt->expr); + free_stmts(cwt->stmts); + } + free_stmts(stmt->else_stmts); +} + +static void +free_loop(PLpgSQL_stmt_loop *stmt) +{ + free_stmts(stmt->body); +} + +static void +free_while(PLpgSQL_stmt_while *stmt) +{ + free_expr(stmt->cond); + free_stmts(stmt->body); +} + +static void +free_fori(PLpgSQL_stmt_fori *stmt) +{ + free_expr(stmt->lower); + free_expr(stmt->upper); + free_expr(stmt->step); + free_stmts(stmt->body); +} + +static void +free_fors(PLpgSQL_stmt_fors *stmt) +{ + free_stmts(stmt->body); + free_expr(stmt->query); +} + +static void +free_forc(PLpgSQL_stmt_forc *stmt) +{ + free_stmts(stmt->body); + free_expr(stmt->argquery); +} + +static void +free_foreach_a(PLpgSQL_stmt_foreach_a *stmt) +{ + free_expr(stmt->expr); + free_stmts(stmt->body); +} + +static void +free_open(PLpgSQL_stmt_open *stmt) +{ + ListCell *lc; + + free_expr(stmt->argquery); + free_expr(stmt->query); + free_expr(stmt->dynquery); + foreach(lc, stmt->params) + { + free_expr((PLpgSQL_expr *) lfirst(lc)); + } +} + +static void +free_fetch(PLpgSQL_stmt_fetch *stmt) +{ + free_expr(stmt->expr); +} + +static void +free_close(PLpgSQL_stmt_close *stmt) +{ +} + +static void +free_perform(PLpgSQL_stmt_perform *stmt) +{ + free_expr(stmt->expr); +} + +static void +free_exit(PLpgSQL_stmt_exit *stmt) +{ + free_expr(stmt->cond); +} + +static void +free_return(PLpgSQL_stmt_return *stmt) +{ + free_expr(stmt->expr); +} + +static void +free_return_next(PLpgSQL_stmt_return_next *stmt) +{ + free_expr(stmt->expr); +} + +static void +free_return_query(PLpgSQL_stmt_return_query *stmt) +{ + ListCell *lc; + + free_expr(stmt->query); + free_expr(stmt->dynquery); + foreach(lc, stmt->params) + { + free_expr((PLpgSQL_expr *) lfirst(lc)); + } +} + +static void +free_raise(PLpgSQL_stmt_raise *stmt) +{ + ListCell *lc; + + foreach(lc, stmt->params) + { + free_expr((PLpgSQL_expr *) lfirst(lc)); + } + foreach(lc, stmt->options) + { + PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc); + + free_expr(opt->expr); + } +} + +static void +free_execsql(PLpgSQL_stmt_execsql *stmt) +{ + free_expr(stmt->sqlstmt); +} + +static void +free_dynexecute(PLpgSQL_stmt_dynexecute *stmt) +{ + ListCell *lc; + + free_expr(stmt->query); + foreach(lc, stmt->params) + { + free_expr((PLpgSQL_expr *) lfirst(lc)); + } +} + +static void +free_dynfors(PLpgSQL_stmt_dynfors *stmt) +{ + ListCell *lc; + + free_stmts(stmt->body); + free_expr(stmt->query); + foreach(lc, stmt->params) + { + free_expr((PLpgSQL_expr *) lfirst(lc)); + } +} + +static void +free_getdiag(PLpgSQL_stmt_getdiag *stmt) +{ +} + +static void +free_expr(PLpgSQL_expr *expr) +{ + if (expr && expr->plan) + { + SPI_freeplan(expr->plan); + expr->plan = NULL; + } +} + +void +plpgsql_free_function_memory(PLpgSQL_function *func) +{ + int i; + + /* Better not call this on an in-use function */ + Assert(func->use_count == 0); + + /* Release plans associated with variable declarations */ + for (i = 0; i < func->ndatums; i++) + { + PLpgSQL_datum *d = func->datums[i]; + + switch (d->dtype) + { + case PLPGSQL_DTYPE_VAR: + { + PLpgSQL_var *var = (PLpgSQL_var *) d; + + free_expr(var->default_val); + free_expr(var->cursor_explicit_expr); + } + break; + case PLPGSQL_DTYPE_ROW: + break; + case PLPGSQL_DTYPE_REC: + break; + case PLPGSQL_DTYPE_RECFIELD: + break; + case PLPGSQL_DTYPE_ARRAYELEM: + free_expr(((PLpgSQL_arrayelem *) d)->subscript); + break; + default: + elog(ERROR, "unrecognized data type: %d", d->dtype); + } + } + func->ndatums = 0; + + /* Release plans in statement tree */ + if (func->action) + free_block(func->action); + func->action = NULL; + + /* + * And finally, release all memory except the PLpgSQL_function struct + * itself (which has to be kept around because there may be multiple + * fn_extra pointers to it). + */ + if (func->fn_cxt) + MemoryContextDelete(func->fn_cxt); + func->fn_cxt = NULL; +} + + /********************************************************************** * Debug functions for analyzing the compiled code **********************************************************************/ diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index 8f7080e88bb10c7f07130566aaacabb419b0e80e..2389849a223195fb3e78e884d07e17b85f08fb00 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -172,6 +172,9 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) /* Compile the anonymous code block */ func = plpgsql_compile_inline(codeblock->source_text); + /* Mark the function as busy, just pro forma */ + func->use_count++; + /* * Set up a fake fcinfo with just enough info to satisfy * plpgsql_exec_function(). In particular note that this sets things up @@ -185,6 +188,13 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) retval = plpgsql_exec_function(func, &fake_fcinfo); + /* Function should now have no remaining use-counts ... */ + func->use_count--; + Assert(func->use_count == 0); + + /* ... so we can free subsidiary storage */ + plpgsql_free_function_memory(func); + /* * Disconnect from SPI manager */ diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 25689c78912bb07bd8aa4a311c85df4d7ac87816..0429f68d4c56c089cc4019149a3d28f5b07f4229 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -928,6 +928,7 @@ extern PLpgSQL_nsitem *plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur, * ---------- */ extern const char *plpgsql_stmt_typename(PLpgSQL_stmt *stmt); +extern void plpgsql_free_function_memory(PLpgSQL_function *func); extern void plpgsql_dumptree(PLpgSQL_function *func); /* ----------