diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 2cd761496d0066ef29c7d5a2eeadc92bddc7182f..87113f0fb11a4e3e2b2a463597ad9967eb485f9c 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -98,17 +98,19 @@ typedef struct plperl_interp_desc /********************************************************************** * The information we cache about loaded procedures * - * The refcount field counts the struct's reference from the hash table shown - * below, plus one reference for each function call level that is using the - * struct. We can release the struct, and the associated Perl sub, when the - * refcount goes to zero. + * The fn_refcount field counts the struct's reference from the hash table + * shown below, plus one reference for each function call level that is using + * the struct. We can release the struct, and the associated Perl sub, when + * the fn_refcount goes to zero. Releasing the struct itself is done by + * deleting the fn_cxt, which also gets rid of all subsidiary data. **********************************************************************/ typedef struct plperl_proc_desc { char *proname; /* user name of procedure */ + MemoryContext fn_cxt; /* memory context for this procedure */ + unsigned long fn_refcount; /* number of active references */ TransactionId fn_xmin; /* xmin/TID of procedure's pg_proc tuple */ ItemPointerData fn_tid; - int refcount; /* reference count of this struct */ SV *reference; /* CODE reference for Perl sub */ plperl_interp_desc *interp; /* interpreter it's created in */ bool fn_readonly; /* is function readonly (not volatile)? */ @@ -122,18 +124,19 @@ typedef struct plperl_proc_desc Oid result_oid; /* Oid of result type */ FmgrInfo result_in_func; /* I/O function and arg for result type */ Oid result_typioparam; - /* Conversion info for function's argument types: */ + /* Per-argument info for function's argument types: */ int nargs; - FmgrInfo arg_out_func[FUNC_MAX_ARGS]; - bool arg_is_rowtype[FUNC_MAX_ARGS]; - Oid arg_arraytype[FUNC_MAX_ARGS]; /* InvalidOid if not an array */ + FmgrInfo *arg_out_func; /* output fns for arg types */ + bool *arg_is_rowtype; /* is each arg composite? */ + Oid *arg_arraytype; /* InvalidOid if not an array */ } plperl_proc_desc; #define increment_prodesc_refcount(prodesc) \ - ((prodesc)->refcount++) + ((prodesc)->fn_refcount++) #define decrement_prodesc_refcount(prodesc) \ do { \ - if (--((prodesc)->refcount) <= 0) \ + Assert((prodesc)->fn_refcount > 0); \ + if (--((prodesc)->fn_refcount) == 0) \ free_plperl_function(prodesc); \ } while(0) @@ -353,23 +356,6 @@ hek2cstr(HE *he) return ret; } -/* - * This routine is a crock, and so is everyplace that calls it. The problem - * is that the cached form of plperl functions/queries is allocated permanently - * (mostly via malloc()) and never released until backend exit. Subsidiary - * data structures such as fmgr info records therefore must live forever - * as well. A better implementation would store all this stuff in a per- - * function memory context that could be reclaimed at need. In the meantime, - * fmgr_info_cxt must be called specifying TopMemoryContext so that whatever - * it might allocate, and whatever the eventual function might allocate using - * fn_mcxt, will live forever too. - */ -static void -perm_fmgr_info(Oid functionId, FmgrInfo *finfo) -{ - fmgr_info_cxt(functionId, finfo, TopMemoryContext); -} - /* * _PG_init() - library load-time initialization @@ -1433,6 +1419,10 @@ plperl_ref_from_pg_array(Datum arg, Oid typid) SV *av; HV *hv; + /* + * Currently we make no effort to cache any of the stuff we look up here, + * which is bad. + */ info = palloc0(sizeof(plperl_array_info)); /* get element type information, including output conversion function */ @@ -1440,10 +1430,16 @@ plperl_ref_from_pg_array(Datum arg, Oid typid) &typlen, &typbyval, &typalign, &typdelim, &typioparam, &typoutputfunc); - if ((transform_funcid = get_transform_fromsql(elementtype, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes))) - perm_fmgr_info(transform_funcid, &info->transform_proc); + /* Check for a transform function */ + transform_funcid = get_transform_fromsql(elementtype, + current_call_data->prodesc->lang_oid, + current_call_data->prodesc->trftypes); + + /* Look up transform or output function as appropriate */ + if (OidIsValid(transform_funcid)) + fmgr_info(transform_funcid, &info->transform_proc); else - perm_fmgr_info(typoutputfunc, &info->proc); + fmgr_info(typoutputfunc, &info->proc); info->elem_is_rowtype = type_is_rowtype(elementtype); @@ -1791,18 +1787,18 @@ plperl_call_handler(PG_FUNCTION_ARGS) } PG_CATCH(); { - if (this_call_data.prodesc) - decrement_prodesc_refcount(this_call_data.prodesc); current_call_data = save_call_data; activate_interpreter(oldinterp); + if (this_call_data.prodesc) + decrement_prodesc_refcount(this_call_data.prodesc); PG_RE_THROW(); } PG_END_TRY(); - if (this_call_data.prodesc) - decrement_prodesc_refcount(this_call_data.prodesc); current_call_data = save_call_data; activate_interpreter(oldinterp); + if (this_call_data.prodesc) + decrement_prodesc_refcount(this_call_data.prodesc); return retval; } @@ -2616,7 +2612,7 @@ validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup) static void free_plperl_function(plperl_proc_desc *prodesc) { - Assert(prodesc->refcount <= 0); + Assert(prodesc->fn_refcount == 0); /* Release CODE reference, if we have one, from the appropriate interp */ if (prodesc->reference) { @@ -2626,12 +2622,8 @@ free_plperl_function(plperl_proc_desc *prodesc) SvREFCNT_dec(prodesc->reference); activate_interpreter(oldinterp); } - /* Get rid of what we conveniently can of our own structs */ - /* (FmgrInfo subsidiary info will get leaked ...) */ - if (prodesc->proname) - free(prodesc->proname); - list_free(prodesc->trftypes); - free(prodesc); + /* Release all PG-owned data for this proc */ + MemoryContextDelete(prodesc->fn_cxt); } @@ -2642,8 +2634,8 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) Form_pg_proc procStruct; plperl_proc_key proc_key; plperl_proc_ptr *proc_ptr; - plperl_proc_desc *prodesc = NULL; - int i; + plperl_proc_desc *volatile prodesc = NULL; + volatile MemoryContext proc_cxt = NULL; plperl_interp_desc *oldinterp = plperl_active_interp; ErrorContextCallback plperl_error_context; @@ -2653,41 +2645,50 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) elog(ERROR, "cache lookup failed for function %u", fn_oid); procStruct = (Form_pg_proc) GETSTRUCT(procTup); - /* Set a callback for reporting compilation errors */ - plperl_error_context.callback = plperl_compile_callback; - plperl_error_context.previous = error_context_stack; - plperl_error_context.arg = NameStr(procStruct->proname); - error_context_stack = &plperl_error_context; - - /* Try to find function in plperl_proc_hash */ + /* + * Try to find function in plperl_proc_hash. The reason for this + * overcomplicated-seeming lookup procedure is that we don't know whether + * it's plperl or plperlu, and don't want to spend a lookup in pg_language + * to find out. + */ proc_key.proc_id = fn_oid; proc_key.is_trigger = is_trigger; proc_key.user_id = GetUserId(); - proc_ptr = hash_search(plperl_proc_hash, &proc_key, HASH_FIND, NULL); + if (validate_plperl_function(proc_ptr, procTup)) + { + /* Found valid plperl entry */ + ReleaseSysCache(procTup); + return proc_ptr->proc_ptr; + } + /* If not found or obsolete, maybe it's plperlu */ + proc_key.user_id = InvalidOid; + proc_ptr = hash_search(plperl_proc_hash, &proc_key, + HASH_FIND, NULL); if (validate_plperl_function(proc_ptr, procTup)) - prodesc = proc_ptr->proc_ptr; - else { - /* If not found or obsolete, maybe it's plperlu */ - proc_key.user_id = InvalidOid; - proc_ptr = hash_search(plperl_proc_hash, &proc_key, - HASH_FIND, NULL); - if (validate_plperl_function(proc_ptr, procTup)) - prodesc = proc_ptr->proc_ptr; + /* Found valid plperlu entry */ + ReleaseSysCache(procTup); + return proc_ptr->proc_ptr; } /************************************************************ * If we haven't found it in the hashtable, we analyze * the function's arguments and return type and store - * the in-/out-functions in the prodesc block and create - * a new hashtable entry for it. - * - * Then we load the procedure into the Perl interpreter. + * the in-/out-functions in the prodesc block, + * then we load the procedure into the Perl interpreter, + * and last we create a new hashtable entry for it. ************************************************************/ - if (prodesc == NULL) + + /* Set a callback for reporting compilation errors */ + plperl_error_context.callback = plperl_compile_callback; + plperl_error_context.previous = error_context_stack; + plperl_error_context.arg = NameStr(procStruct->proname); + error_context_stack = &plperl_error_context; + + PG_TRY(); { HeapTuple langTup; HeapTuple typeTup; @@ -2697,42 +2698,42 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) Datum prosrcdatum; bool isnull; char *proc_source; + MemoryContext oldcontext; /************************************************************ - * Allocate a new procedure description block + * Allocate a context that will hold all PG data for the procedure. ************************************************************/ - prodesc = (plperl_proc_desc *) malloc(sizeof(plperl_proc_desc)); - if (prodesc == NULL) - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"))); - /* Initialize all fields to 0 so free_plperl_function is safe */ - MemSet(prodesc, 0, sizeof(plperl_proc_desc)); + proc_cxt = AllocSetContextCreate(TopMemoryContext, + NameStr(procStruct->proname), + ALLOCSET_SMALL_SIZES); - prodesc->proname = strdup(NameStr(procStruct->proname)); - if (prodesc->proname == NULL) - { - free_plperl_function(prodesc); - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"))); - } + /************************************************************ + * Allocate and fill a new procedure description block. + * struct prodesc and subsidiary data must all live in proc_cxt. + ************************************************************/ + oldcontext = MemoryContextSwitchTo(proc_cxt); + prodesc = (plperl_proc_desc *) palloc0(sizeof(plperl_proc_desc)); + prodesc->proname = pstrdup(NameStr(procStruct->proname)); + prodesc->fn_cxt = proc_cxt; + prodesc->fn_refcount = 0; prodesc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data); prodesc->fn_tid = procTup->t_self; + prodesc->nargs = procStruct->pronargs; + prodesc->arg_out_func = (FmgrInfo *) palloc0(prodesc->nargs * sizeof(FmgrInfo)); + prodesc->arg_is_rowtype = (bool *) palloc0(prodesc->nargs * sizeof(bool)); + prodesc->arg_arraytype = (Oid *) palloc0(prodesc->nargs * sizeof(Oid)); + MemoryContextSwitchTo(oldcontext); /* Remember if function is STABLE/IMMUTABLE */ prodesc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE); - { - MemoryContext oldcxt; - - protrftypes_datum = SysCacheGetAttr(PROCOID, procTup, + /* Fetch protrftypes */ + protrftypes_datum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_protrftypes, &isnull); - oldcxt = MemoryContextSwitchTo(TopMemoryContext); - prodesc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum); - MemoryContextSwitchTo(oldcxt); - } + MemoryContextSwitchTo(proc_cxt); + prodesc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum); + MemoryContextSwitchTo(oldcontext); /************************************************************ * Lookup the pg_language tuple by Oid @@ -2740,11 +2741,8 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) langTup = SearchSysCache1(LANGOID, ObjectIdGetDatum(procStruct->prolang)); if (!HeapTupleIsValid(langTup)) - { - free_plperl_function(prodesc); elog(ERROR, "cache lookup failed for language %u", procStruct->prolang); - } langStruct = (Form_pg_language) GETSTRUCT(langTup); prodesc->lang_oid = HeapTupleGetOid(langTup); prodesc->lanpltrusted = langStruct->lanpltrusted; @@ -2760,11 +2758,8 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) SearchSysCache1(TYPEOID, ObjectIdGetDatum(procStruct->prorettype)); if (!HeapTupleIsValid(typeTup)) - { - free_plperl_function(prodesc); elog(ERROR, "cache lookup failed for type %u", procStruct->prorettype); - } typeStruct = (Form_pg_type) GETSTRUCT(typeTup); /* Disallow pseudotype result, except VOID or RECORD */ @@ -2775,21 +2770,15 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) /* okay */ ; else if (procStruct->prorettype == TRIGGEROID || procStruct->prorettype == EVTTRIGGEROID) - { - free_plperl_function(prodesc); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called " "as triggers"))); - } else - { - free_plperl_function(prodesc); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Perl functions cannot return type %s", format_type_be(procStruct->prorettype)))); - } } prodesc->result_oid = procStruct->prorettype; @@ -2800,7 +2789,9 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) prodesc->fn_retisarray = (typeStruct->typlen == -1 && typeStruct->typelem); - perm_fmgr_info(typeStruct->typinput, &(prodesc->result_in_func)); + fmgr_info_cxt(typeStruct->typinput, + &(prodesc->result_in_func), + proc_cxt); prodesc->result_typioparam = getTypeIOParam(typeTup); ReleaseSysCache(typeTup); @@ -2812,29 +2803,24 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) ************************************************************/ if (!is_trigger && !is_event_trigger) { - prodesc->nargs = procStruct->pronargs; + int i; + for (i = 0; i < prodesc->nargs; i++) { typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(procStruct->proargtypes.values[i])); if (!HeapTupleIsValid(typeTup)) - { - free_plperl_function(prodesc); elog(ERROR, "cache lookup failed for type %u", procStruct->proargtypes.values[i]); - } typeStruct = (Form_pg_type) GETSTRUCT(typeTup); /* Disallow pseudotype argument */ if (typeStruct->typtype == TYPTYPE_PSEUDO && procStruct->proargtypes.values[i] != RECORDOID) - { - free_plperl_function(prodesc); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Perl functions cannot accept type %s", format_type_be(procStruct->proargtypes.values[i])))); - } if (typeStruct->typtype == TYPTYPE_COMPOSITE || procStruct->proargtypes.values[i] == RECORDOID) @@ -2842,8 +2828,9 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) else { prodesc->arg_is_rowtype[i] = false; - perm_fmgr_info(typeStruct->typoutput, - &(prodesc->arg_out_func[i])); + fmgr_info_cxt(typeStruct->typoutput, + &(prodesc->arg_out_func[i]), + proc_cxt); } /* Identify array attributes */ @@ -2880,22 +2867,42 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger) activate_interpreter(oldinterp); pfree(proc_source); + if (!prodesc->reference) /* can this happen? */ - { - free_plperl_function(prodesc); elog(ERROR, "could not create PL/Perl internal procedure"); - } /************************************************************ - * OK, link the procedure into the correct hashtable entry + * OK, link the procedure into the correct hashtable entry. + * Note we assume that the hashtable entry either doesn't exist yet, + * or we already cleared its proc_ptr during the validation attempts + * above. So no need to decrement an old refcount here. ************************************************************/ proc_key.user_id = prodesc->lanpltrusted ? GetUserId() : InvalidOid; proc_ptr = hash_search(plperl_proc_hash, &proc_key, HASH_ENTER, NULL); + /* We assume these two steps can't throw an error: */ proc_ptr->proc_ptr = prodesc; increment_prodesc_refcount(prodesc); } + PG_CATCH(); + { + /* + * If we got as far as creating a reference, we should be able to use + * free_plperl_function() to clean up. If not, then at most we have + * some PG memory resources in proc_cxt, which we can just delete. + */ + if (prodesc && prodesc->reference) + free_plperl_function(prodesc); + else if (proc_cxt) + MemoryContextDelete(proc_cxt); + + /* Be sure to restore the previous interpreter, too, for luck */ + activate_interpreter(oldinterp); + + PG_RE_THROW(); + } + PG_END_TRY(); /* restore previous error callback */ error_context_stack = plperl_error_context.previous;