Re: WIP 2 interpreters for plperl - Mailing list pgsql-patches

From Andrew Dunstan
Subject Re: WIP 2 interpreters for plperl
Date
Msg-id 454E64ED.80500@dunslane.net
Whole thread Raw
In response to WIP 2 interpreters for plperl  (Andrew Dunstan <andrew@dunslane.net>)
List pgsql-patches

I wrote:
>
> I have made some progress with what I think is needed to have two
> interpreters for plperl. This is a lot harder than the pltcl case for
> two reasons: 1. there are no restrictions on having 2 tcl
> interpreters, and 2. tcl does not need to save and restore context as
> we have to do with perl. I think I have a conceptual siolution to
> these two problems, but what I have is currently segfaulting somewhat
> myteriously. Tracing a dynamically loaded library in a postgres
> backend with a debugger is less than fun, too. I am attaching what I
> currently have, liberally sprinkled with elog(NOTICE) calls as trace
> writes.
>
>

With a little more perseverance I found the problem. The attached patch
passes regression. But it now needs plenty of eyeballs and testing.

cheers

andrew
Index: plperl.c
===================================================================
RCS file: /cvsroot/pgsql/src/pl/plperl/plperl.c,v
retrieving revision 1.121
diff -c -r1.121 plperl.c
*** plperl.c    19 Oct 2006 18:32:47 -0000    1.121
--- plperl.c    5 Nov 2006 22:20:16 -0000
***************
*** 27,32 ****
--- 27,33 ----
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/typcache.h"
+ #include "utils/hsearch.h"

  /* perl stuff */
  #include "plperl.h"
***************
*** 55,60 ****
--- 56,69 ----
      SV           *reference;
  } plperl_proc_desc;

+ /* hash table entry for proc desc  */
+
+ typedef struct plperl_proc_entry
+ {
+     char proc_name[NAMEDATALEN];
+     plperl_proc_desc *proc_data;
+ } plperl_proc_entry;
+
  /*
   * The information we cache for the duration of a single call to a
   * function.
***************
*** 82,94 ****
      Oid           *argtypioparams;
  } plperl_query_desc;

  /**********************************************************************
   * Global data
   **********************************************************************/
  static bool plperl_safe_init_done = false;
! static PerlInterpreter *plperl_interp = NULL;
! static HV  *plperl_proc_hash = NULL;
! static HV  *plperl_query_hash = NULL;

  static bool plperl_use_strict = false;

--- 91,128 ----
      Oid           *argtypioparams;
  } plperl_query_desc;

+ /* hash table entry for query desc  */
+
+ typedef struct plperl_query_entry
+ {
+     char query_name[NAMEDATALEN];
+     plperl_query_desc *query_data;
+ } plperl_query_entry;
+
  /**********************************************************************
   * Global data
   **********************************************************************/
+
+ typedef enum
+ {
+     INTERP_NONE,
+     INTERP_HELD,
+     INTERP_TRUSTED,
+     INTERP_UNTRUSTED,
+     INTERP_BOTH
+ } InterpState;
+
+ static InterpState interp_state = INTERP_NONE;
+ static bool can_run_two = false;
+
  static bool plperl_safe_init_done = false;
! static PerlInterpreter *plperl_trusted_interp = NULL;
! static PerlInterpreter *plperl_untrusted_interp = NULL;
! static PerlInterpreter *plperl_held_interp = NULL;
! static bool can_run_two;
! static bool trusted_context;
! static HTAB  *plperl_proc_hash = NULL;
! static HTAB  *plperl_query_hash = NULL;

  static bool plperl_use_strict = false;

***************
*** 144,149 ****
--- 178,184 ----
  {
      /* Be sure we do initialization only once (should be redundant now) */
      static bool inited = false;
+     HASHCTL     hash_ctl;

      if (inited)
          return;
***************
*** 157,162 ****
--- 192,213 ----

      EmitWarningsOnPlaceholders("plperl");

+     MemSet(&hash_ctl, 0, sizeof(hash_ctl));
+
+     hash_ctl.keysize = NAMEDATALEN;
+     hash_ctl.entrysize = sizeof(plperl_proc_entry);
+
+     plperl_proc_hash = hash_create("PLPerl Procedures",
+                                    32,
+                                    &hash_ctl,
+                                    HASH_ELEM);
+
+     hash_ctl.entrysize = sizeof(plperl_query_entry);
+     plperl_query_hash = hash_create("PLPerl Queries",
+                                     32,
+                                     &hash_ctl,
+                                     HASH_ELEM);
+
      plperl_init_interp();

      inited = true;
***************
*** 235,240 ****
--- 286,375 ----
      "      elog(ERROR,'trusted Perl functions disabled - " \
      "      please upgrade Perl Safe module to version 2.09 or later');}]); }"

+ #define TEST_FOR_MULTI \
+     "use Config; " \
+     "$Config{usemultiplicity} eq 'define' or "  \
+     "($Config{usethreads} eq 'define' " \
+     " and $Config{useithreads} eq 'define')"
+
+
+ /********************************************************************
+  *
+  * We start out by creating a "held" interpreter that we can use in
+  * trusted or untrusted mode (but not both) as the need arises. Later, we
+  * assign that interpreter if it is available to either the trusted or
+  * untrusted interpreter. If it has already been assigned, and we need to
+  * create the other interpreter, we do that if we can, or error out.
+  * We detect if it is safe to run two interpreters during the setup of the
+  * dummy interpreter.
+  */
+
+
+ static void
+ check_interp(bool trusted)
+ {
+     if (interp_state == INTERP_HELD)
+     {
+         if (trusted)
+         {
+             plperl_trusted_interp = plperl_held_interp;
+             interp_state = INTERP_TRUSTED;
+         }
+         else
+         {
+             plperl_untrusted_interp = plperl_held_interp;
+             interp_state = INTERP_UNTRUSTED;
+         }
+         plperl_held_interp = NULL;
+         trusted_context = trusted;
+     }
+     else if (interp_state == INTERP_BOTH ||
+              (trusted && interp_state == INTERP_TRUSTED) ||
+              (!trusted && interp_state == INTERP_UNTRUSTED))
+     {
+         if (trusted_context != trusted)
+         {
+             if (trusted)
+                 PERL_SET_CONTEXT(plperl_trusted_interp);
+             else
+                 PERL_SET_CONTEXT(plperl_untrusted_interp);
+             trusted_context = trusted;
+         }
+     }
+     else if (can_run_two)
+     {
+         PERL_SET_CONTEXT(plperl_held_interp);
+         plperl_init_interp();
+         if (trusted)
+             plperl_trusted_interp = plperl_held_interp;
+         else
+             plperl_untrusted_interp = plperl_held_interp;
+         interp_state = INTERP_BOTH;
+         plperl_held_interp = NULL;
+         trusted_context = trusted;
+     }
+     else
+     {
+         elog(ERROR,
+              "can not allocate second Perl interpreter on this platform");
+
+     }
+
+ }
+
+
+ static void
+ restore_context (bool old_context)
+ {
+     if (trusted_context != old_context)
+     {
+         if (old_context)
+             PERL_SET_CONTEXT(plperl_trusted_interp);
+         else
+             PERL_SET_CONTEXT(plperl_untrusted_interp);
+         trusted_context = old_context;
+     }
+ }

  static void
  plperl_init_interp(void)
***************
*** 285,300 ****
      save_time = loc ? pstrdup(loc) : NULL;
  #endif

-     plperl_interp = perl_alloc();
-     if (!plperl_interp)
-         elog(ERROR, "could not allocate Perl interpreter");

!     perl_construct(plperl_interp);
!     perl_parse(plperl_interp, plperl_init_shared_libs, 3, embedding, NULL);
!     perl_run(plperl_interp);

!     plperl_proc_hash = newHV();
!     plperl_query_hash = newHV();

  #ifdef WIN32

--- 420,443 ----
      save_time = loc ? pstrdup(loc) : NULL;
  #endif


!     plperl_held_interp = perl_alloc();
!     if (!plperl_held_interp)
!         elog(ERROR, "could not allocate Perl interpreter");

!     perl_construct(plperl_held_interp);
!     perl_parse(plperl_held_interp, plperl_init_shared_libs,
!                3, embedding, NULL);
!     perl_run(plperl_held_interp);
!
!     if (interp_state == INTERP_NONE)
!     {
!         SV *res;
!
!         res = eval_pv(TEST_FOR_MULTI,TRUE);
!         can_run_two = SvIV(res);
!         interp_state = INTERP_HELD;
!     }

  #ifdef WIN32

***************
*** 1009,1014 ****
--- 1152,1158 ----
      Datum        retval;
      ReturnSetInfo *rsi;
      SV           *array_ret = NULL;
+     bool       oldcontext = trusted_context;

      /*
       * Create the call_data beforing connecting to SPI, so that it is not
***************
*** 1037,1042 ****
--- 1181,1188 ----
                              "cannot accept a set")));
      }

+     check_interp(prodesc->lanpltrusted);
+
      perlret = plperl_call_perl_func(prodesc, fcinfo);

      /************************************************************
***************
*** 1146,1151 ****
--- 1292,1299 ----
          SvREFCNT_dec(perlret);

      current_call_data = NULL;
+     restore_context(oldcontext);
+
      return retval;
  }

***************
*** 1158,1163 ****
--- 1306,1312 ----
      Datum        retval;
      SV           *svTD;
      HV           *hvTD;
+     bool       oldcontext = trusted_context;

      /*
       * Create the call_data beforing connecting to SPI, so that it is not
***************
*** 1174,1179 ****
--- 1323,1330 ----
      prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true);
      current_call_data->prodesc = prodesc;

+     check_interp(prodesc->lanpltrusted);
+
      svTD = plperl_trigger_build_args(fcinfo);
      perlret = plperl_call_perl_trigger_func(prodesc, fcinfo, svTD);
      hvTD = (HV *) SvRV(svTD);
***************
*** 1244,1249 ****
--- 1395,1401 ----
          SvREFCNT_dec(perlret);

      current_call_data = NULL;
+     restore_context(oldcontext);
      return retval;
  }

***************
*** 1256,1262 ****
      char        internal_proname[64];
      plperl_proc_desc *prodesc = NULL;
      int            i;
!     SV          **svp;

      /* We'll need the pg_proc tuple in any case... */
      procTup = SearchSysCache(PROCOID,
--- 1408,1416 ----
      char        internal_proname[64];
      plperl_proc_desc *prodesc = NULL;
      int            i;
!     plperl_proc_entry *hash_entry;
!     bool found;
!     bool oldcontext = trusted_context;

      /* We'll need the pg_proc tuple in any case... */
      procTup = SearchSysCache(PROCOID,
***************
*** 1277,1288 ****
      /************************************************************
       * Lookup the internal proc name in the hashtable
       ************************************************************/
!     svp = hv_fetch_string(plperl_proc_hash, internal_proname);
!     if (svp)
      {
          bool        uptodate;

!         prodesc = INT2PTR(plperl_proc_desc *, SvUV(*svp));

          /************************************************************
           * If it's present, must check whether it's still up to date.
--- 1431,1444 ----
      /************************************************************
       * Lookup the internal proc name in the hashtable
       ************************************************************/
!     hash_entry = hash_search(plperl_proc_hash, internal_proname,
!                              HASH_FIND, NULL);
!
!     if (hash_entry)
      {
          bool        uptodate;

!         prodesc = hash_entry->proc_data;

          /************************************************************
           * If it's present, must check whether it's still up to date.
***************
*** 1294,1301 ****

          if (!uptodate)
          {
!             /* need we delete old entry? */
              prodesc = NULL;
          }
      }

--- 1450,1459 ----

          if (!uptodate)
          {
!             free(prodesc); /* are we leaking memory here? */
              prodesc = NULL;
+             hash_search(plperl_proc_hash, internal_proname,
+                         HASH_REMOVE,NULL);
          }
      }

***************
*** 1469,1475 ****
--- 1627,1639 ----
          /************************************************************
           * Create the procedure in the interpreter
           ************************************************************/
+
+         check_interp(prodesc->lanpltrusted);
+
          prodesc->reference = plperl_create_sub(proc_source, prodesc->lanpltrusted);
+
+         restore_context(oldcontext);
+
          pfree(proc_source);
          if (!prodesc->reference)    /* can this happen? */
          {
***************
*** 1479,1486 ****
                   internal_proname);
          }

!         hv_store_string(plperl_proc_hash, internal_proname,
!                         newSVuv(PTR2UV(prodesc)));
      }

      ReleaseSysCache(procTup);
--- 1643,1651 ----
                   internal_proname);
          }

!         hash_entry = hash_search(plperl_proc_hash, internal_proname,
!                                  HASH_ENTER, &found);
!         hash_entry->proc_data = prodesc;
      }

      ReleaseSysCache(procTup);
***************
*** 1939,1944 ****
--- 2104,2111 ----
  plperl_spi_prepare(char *query, int argc, SV **argv)
  {
      plperl_query_desc *qdesc;
+     plperl_query_entry *hash_entry;
+     bool        found;
      void       *plan;
      int            i;

***************
*** 2051,2057 ****
       * Insert a hashtable entry for the plan and return
       * the key to the caller.
       ************************************************************/
!     hv_store_string(plperl_query_hash, qdesc->qname, newSVuv(PTR2UV(qdesc)));

      return newSVstring(qdesc->qname);
  }
--- 2218,2227 ----
       * Insert a hashtable entry for the plan and return
       * the key to the caller.
       ************************************************************/
!
!     hash_entry = hash_search(plperl_query_hash, qdesc->qname,
!                              HASH_ENTER,&found);
!     hash_entry->query_data = qdesc;

      return newSVstring(qdesc->qname);
  }
***************
*** 2067,2072 ****
--- 2237,2243 ----
      char       *nulls;
      Datum       *argvalues;
      plperl_query_desc *qdesc;
+     plperl_query_entry *hash_entry;

      /*
       * Execute the query inside a sub-transaction, so we can cope with errors
***************
*** 2084,2096 ****
          /************************************************************
           * Fetch the saved plan descriptor, see if it's o.k.
           ************************************************************/
!         sv = hv_fetch_string(plperl_query_hash, query);
!         if (sv == NULL)
              elog(ERROR, "spi_exec_prepared: Invalid prepared query passed");
-         if (*sv == NULL || !SvOK(*sv))
-             elog(ERROR, "spi_exec_prepared: panic - plperl_query_hash value corrupted");

!         qdesc = INT2PTR(plperl_query_desc *, SvUV(*sv));
          if (qdesc == NULL)
              elog(ERROR, "spi_exec_prepared: panic - plperl_query_hash value vanished");

--- 2255,2268 ----
          /************************************************************
           * Fetch the saved plan descriptor, see if it's o.k.
           ************************************************************/
!
!         hash_entry = hash_search(plperl_query_hash, query,
!                                          HASH_FIND,NULL);
!         if (hash_entry == NULL)
              elog(ERROR, "spi_exec_prepared: Invalid prepared query passed");

!         qdesc = hash_entry->query_data;
!
          if (qdesc == NULL)
              elog(ERROR, "spi_exec_prepared: panic - plperl_query_hash value vanished");

***************
*** 2201,2211 ****
  SV *
  plperl_spi_query_prepared(char *query, int argc, SV **argv)
  {
-     SV          **sv;
      int            i;
      char       *nulls;
      Datum       *argvalues;
      plperl_query_desc *qdesc;
      SV           *cursor;
      Portal        portal = NULL;

--- 2373,2383 ----
  SV *
  plperl_spi_query_prepared(char *query, int argc, SV **argv)
  {
      int            i;
      char       *nulls;
      Datum       *argvalues;
      plperl_query_desc *qdesc;
+     plperl_query_entry *hash_entry;
      SV           *cursor;
      Portal        portal = NULL;

***************
*** 2225,2237 ****
          /************************************************************
           * Fetch the saved plan descriptor, see if it's o.k.
           ************************************************************/
!         sv = hv_fetch_string(plperl_query_hash, query);
!         if (sv == NULL)
!             elog(ERROR, "spi_query_prepared: Invalid prepared query passed");
!         if (*sv == NULL || !SvOK(*sv))
!             elog(ERROR, "spi_query_prepared: panic - plperl_query_hash value corrupted");

-         qdesc = INT2PTR(plperl_query_desc *, SvUV(*sv));
          if (qdesc == NULL)
              elog(ERROR, "spi_query_prepared: panic - plperl_query_hash value vanished");

--- 2397,2409 ----
          /************************************************************
           * Fetch the saved plan descriptor, see if it's o.k.
           ************************************************************/
!         hash_entry = hash_search(plperl_query_hash, query,
!                                          HASH_FIND,NULL);
!         if (hash_entry == NULL)
!             elog(ERROR, "spi_exec_prepared: Invalid prepared query passed");
!
!         qdesc = hash_entry->query_data;

          if (qdesc == NULL)
              elog(ERROR, "spi_query_prepared: panic - plperl_query_hash value vanished");

***************
*** 2335,2351 ****
  void
  plperl_spi_freeplan(char *query)
  {
-     SV          **sv;
      void       *plan;
      plperl_query_desc *qdesc;

!     sv = hv_fetch_string(plperl_query_hash, query);
!     if (sv == NULL)
!         elog(ERROR, "spi_exec_freeplan: Invalid prepared query passed");
!     if (*sv == NULL || !SvOK(*sv))
!         elog(ERROR, "spi_exec_freeplan: panic - plperl_query_hash value corrupted");

-     qdesc = INT2PTR(plperl_query_desc *, SvUV(*sv));
      if (qdesc == NULL)
          elog(ERROR, "spi_exec_freeplan: panic - plperl_query_hash value vanished");

--- 2507,2523 ----
  void
  plperl_spi_freeplan(char *query)
  {
      void       *plan;
      plperl_query_desc *qdesc;
+     plperl_query_entry *hash_entry;

!     hash_entry = hash_search(plperl_query_hash, query,
!                                          HASH_FIND,NULL);
!     if (hash_entry == NULL)
!         elog(ERROR, "spi_exec_prepared: Invalid prepared query passed");
!
!     qdesc = hash_entry->query_data;

      if (qdesc == NULL)
          elog(ERROR, "spi_exec_freeplan: panic - plperl_query_hash value vanished");

***************
*** 2353,2359 ****
       * free all memory before SPI_freeplan, so if it dies, nothing will be
       * left over
       */
!     hv_delete(plperl_query_hash, query, strlen(query), G_DISCARD);
      plan = qdesc->plan;
      free(qdesc->argtypes);
      free(qdesc->arginfuncs);
--- 2525,2533 ----
       * free all memory before SPI_freeplan, so if it dies, nothing will be
       * left over
       */
!     hash_search(plperl_query_hash, query,
!                 HASH_REMOVE,NULL);
!
      plan = qdesc->plan;
      free(qdesc->argtypes);
      free(qdesc->arginfuncs);

pgsql-patches by date:

Previous
From: "Simon Riggs"
Date:
Subject: Re: Writing WAL for relcache invalidation:pg_internal.init
Next
From: Tom Lane
Date:
Subject: Re: Writing WAL for relcache invalidation:pg_internal.init