diff --git a/src/python/build_pyrte3.com b/src/python/build_pyrte3.com
index 9f21a62c1055b5b818c32c9850f57633a0ec207d_c3JjL3B5dGhvbi9idWlsZF9weXJ0ZTMuY29t..aff157d9025310d1eee56d29d81fd9d7ee58a922_c3JjL3B5dGhvbi9idWlsZF9weXJ0ZTMuY29t 100644
--- a/src/python/build_pyrte3.com
+++ b/src/python/build_pyrte3.com
@@ -64,7 +64,7 @@
 $!
 $    SET NOON
 $    SET VERIFY
-$ LINK /THREADS_ENABLE/EXECUTABLE=WASD_EXE:PYRTE.EXE -
+$ LINK /THREADS_ENABLE/EXECUTABLE=WASD_EXE:PYRTE3.EXE -
 [.OBJ_'ARCH_NAME']PYRTE.OBJ, -
 SYS$INPUT:/OPTIONS 
 IDENTIFICATION="PYRTE 3.0.0"
diff --git a/src/python/pyrte.c b/src/python/pyrte.c
index 9f21a62c1055b5b818c32c9850f57633a0ec207d_c3JjL3B5dGhvbi9weXJ0ZS5j..aff157d9025310d1eee56d29d81fd9d7ee58a922_c3JjL3B5dGhvbi9weXJ0ZS5j 100644
--- a/src/python/pyrte.c
+++ b/src/python/pyrte.c
@@ -27,9 +27,9 @@
 
   import wasd
   while wasd.cgiplus_begin():
-     print 'Content-type: text/html'
-     print ''
-     print '<h1><i>hello world!</i></h1>'
+     print ('Content-type: text/html')
+     print ('')
+     print ('<h1><i>hello world!</i></h1>')
 
 The cgiplus_begin() method accepts zero, one or two positional parameters.
 
@@ -50,5 +50,44 @@
 activated as a CGIplus script.
 
 
+DEBUGGING SCRIPTS
+-----------------
+Always fun :-/   PyRTE v2.0/3.0 provides some execution data via the WATCH
+[x]Script report.  For example (note the SCRIPT items):
+
+|00:30:50.08 SERVICE  1735 093001 CONNECT    VIRTUAL klaatu.local:443|
+|00:30:50.08 REQUEST  4318 093001 REQUEST    GET /py-bin/wsgi_test3.py|
+|00:30:50.09 CACHE    0604 093001 RESPONSE   CACHE search path 5F1A99EFCB98C60126B8F244110E5BDD|
+|00:30:50.09 DCL      1572 093001 RESPONSE   SCRIPT as HTTP$NOBODY RTE /py-bin/wsgi_test3.py wasd_root:[src.python.scripts]wsgi_test3.py ($cgi_exe:pyrte.exe)|
+|00:30:50.10 DCL      7962 125001 SCRIPT     PYRTE AXP-2.0.0 Python 2.7.18 (default, Jul 23 2020, 17:40:05) [DECC]|
+|00:30:50.10 DCL      7945 093001 SCRIPT     RTE caching /py-bin/wsgi_test3.py /WASD_ROOT/src/PYTHON/SCRIPTS/wsgi_test3.py|
+|00:30:50.10 DCL      7945 093001 SCRIPT     CACHE new 7/7|
+|00:30:50.10 DCL      7945 093001 SCRIPT     LOAD /py-bin/wsgi_test3.py /WASD_ROOT/src/PYTHON/SCRIPTS/wsgi_test3.py|
+|00:30:50.10 DCL      7945 093001 SCRIPT     CODE /WASD_ROOT/src/PYTHON/SCRIPTS/wsgi_test3.py|
+|00:30:50.11 DCL      7945 093001 SCRIPT     EVAL /py-bin/wsgi_test3.py|
+|00:30:50.12 DCL      7945 093001 SCRIPT     PYRTE AXP-2.0.0 USAGE:1/19 REAL:00:00:00.02 CPU:0.02 DIO:3 BIO:36 FAULTS:0 PGFL:463040/8%|
+|00:30:50.12 GZIP     0608 093001 RESPONSE   DEFLATE 1289->507 bytes, 39% (261kB)|
+|00:30:50.12 REQUEST  1438 093001 REQUEST    STATUS 200 (OK) rx:78 tx:2415 bytes 1.045832 seconds 2,383 B/s|
+
+Script errors are flagged in the report.
+
+|00:33:50.17 DCL      7962 265001 SCRIPT     EVAL /py-bin/pyrte_test3.py|
+|00:33:56.82 DCL      7962 265001 SCRIPT     ERROR <type 'exceptions.RuntimeError'> 'logical name PYRTE_CACHE_ENTRY not defined'|
+
+It is also possible to add items from within the script Python code using the
+wasd.WATCH() function.  The example /py-bin/pyrte_test1.py script contains the
+statement
+
+   wasd.WATCH('and hello [x]Script')
+
+|00:35:15.05 DCL      7945 057001 SCRIPT     RTE caching /py-bin/pyrte_test1.py /WASD_ROOT/src/PYTHON/SCRIPTS/pyrte_test1.py|
+|00:35:15.05 DCL      7945 057001 SCRIPT     CACHE new 1/1|
+|00:35:15.05 DCL      7945 057001 SCRIPT     LOAD /py-bin/pyrte_test1.py /WASD_ROOT/src/PYTHON/SCRIPTS/pyrte_test1.py|
+|00:35:15.05 DCL      7945 057001 SCRIPT     CODE /WASD_ROOT/src/PYTHON/SCRIPTS/pyrte_test1.py|
+|00:35:15.06 DCL      7945 057001 SCRIPT     EVAL /py-bin/pyrte_test1.py|
+|00:35:15.07 DCL      7945 057001 SCRIPT     WATCH and hello [x]Script|
+|00:35:15.07 DCL      7945 057001 SCRIPT     PYRTE AXP-2.0.0 USAGE:1/1 REAL:00:00:05.10 CPU:5.06 DIO:65 BIO:1060 FAULTS:870 PGFL:476448/5%|
+
+
 LATENCIES
 ---------
@@ -53,16 +92,9 @@
 LATENCIES
 ---------
-Establishing a Python interpreter is quite expensive in terms of CPU cycles and
-latency.  When pyRTE creates a new interpreter for each request it means that
-script name-spaces are completely isolated.  On my test-bench PWS500 this adds
-approximately 250mS to each request.  pyRTE attempts to ameliorate the effects
-of this overhead by creating the interpreter prior to accepting the next
-request.  For back-to-back requests this gains nothing but where there is at
-least 250mS between requests (minimum, and for example) reduces response time
-latency by that amount.  Hence the simple "hello world" test example for
-back-to-back requests (generated using ApacheBench or WASD-bench) each is
-reported by HTTPDMON as having a duration of approximately 280mS, while
-one-at-a-time using a browser each is reported at between 10 and 15 mS.
+pyRTE version prior to 2.0/3.0 used subinterpreters to process independent
+requests.  Post 2.0/3.0 pyRTE uses the one interpreter and threads.  With the
+suggested and default caching RTE these threads are cleared and remain with
+loaded code between requests.
 
 BY DEFAULT, pyRTE takes AN AGGRESSIVE POSITION ON REDUCING LATENCY.  This
 leaves the interpreter intact for each unique script.  The standard CGI "hello
@@ -122,6 +154,7 @@
 
     Read a record from the CGIPLUSIN stream (caution!)
 
+  wasd.reuse_thread()
   wasd.reuse_interpreter()
 
     See discussion of 'latencies' above.  Takes one arugment; True to reuse
@@ -148,6 +181,7 @@
     Returns an integer representing the number of times the cached byte-code
     for this particular script has been (re)used.
 
+  wasd.usage_thread()
   wasd.usage_interpreter()
 
     Returns an integer representing the number of times this (sub)interpreter
@@ -157,6 +191,10 @@
 
     Returns an integer representing the number of times this RTE has been used.
 
+  wasd.watch()
+
+    Writes a message to WATCH [x]Script (useful for debugging).
+
   wasd.write()
 
     Writes directly to <stdout> with a following fflush().
@@ -195,8 +233,8 @@
 
 REFERENCES
 ----------
-Python/C API Reference Manual, Release 2.4.3
-Extending and Embedding the Python Interpreter, Release 2.4.3
+Python/C API Reference Manual, Release 2.7.18 and 3.8
+Extending and Embedding the Python Interpreter, Release 2.7.18 and 3.8
 Lots of Googling with many and varied query strings.
 http://python3porting.com/cextensions.html
 
@@ -280,8 +318,8 @@
 therefore per-script) basis using mapping rules.
 
   script=param=PYRTE=/HEAD=GET     same as logical PYRTE_HEAD_CVT_GET
-  script=param=PYRTE=/REUSE        reuse this Python interpreter
-  script=param=PYRTE=/NOREUSE      do not reuse this Python interpreter
+  script=param=PYRTE=/REUSE        reuse this Python thread
+  script=param=PYRTE=/NOREUSE      do not reuse this Python thread
   script=param=PYRTE=/NOSTREAM     do not issue the stream-mode header
   script=param=PYRTE=/WSGI=BUFFER  force WSGI buffering mode
 
@@ -292,9 +330,10 @@
                      if some unique part of the script debugs just that script
                      if defined to something like 'c' or '/' or '-' debugs all
                      (this script-name check is CASE-SENSITIVE)
+PYRTE_ARGV         multi-valued logical name containing Python CLI switches
 PYRTE_BASIC_RTE    full interpreter cycle request processing (for comparison)
 PYRTE_HEAD_CVT_GET   if a HEAD method convert to a GET method
                      as per Apache mod_wsgi behaviour
 PYRTE_CACHE_ENTRY  enables function RtePyMethRteCache()
 PYRTE_CACHE_MAX    integer size of code cache (zero to compare uncached)
 PYRTE_METRICS      enable metric collection and report via "?$metrics$" query
@@ -295,8 +334,9 @@
 PYRTE_BASIC_RTE    full interpreter cycle request processing (for comparison)
 PYRTE_HEAD_CVT_GET   if a HEAD method convert to a GET method
                      as per Apache mod_wsgi behaviour
 PYRTE_CACHE_ENTRY  enables function RtePyMethRteCache()
 PYRTE_CACHE_MAX    integer size of code cache (zero to compare uncached)
 PYRTE_METRICS      enable metric collection and report via "?$metrics$" query
-PYRTE_NOREUSE_INTERPRETER   do not reuse interpreter for each script instance
+PYRTE_NOREUSE_THREAD   do not reuse thread for each script instance
+PYRTE_NOREUSE_INTERPRETER   backward compatibility with PYRTE_NOREUSE_THREAD
 PYRTE_ONESHOT      voluntary exit after a single use (see PYRTE_USAGE_LIMIT) 
@@ -302,5 +342,4 @@
 PYRTE_ONESHOT      voluntary exit after a single use (see PYRTE_USAGE_LIMIT) 
-PYRTE_STAT_TIMER   incorporate basic stats in HTML comment
 PYRTE_USAGE_LIMIT  integer number of requests before voluntary exit
 PYRTE_WSGI_FORCE_BUFFERING  enable buffering with WASD, this breaks the WSGI
                             specification but may give a performance boost.
@@ -314,7 +353,7 @@
 
 VERSION HISTORY (update SOFTWAREVN as well!)
 ---------------
-20-AUG-2020  MGD  v3.0.0,  Python 3 (via JFP Python 3.10.0a0)
+08-SEP-2020  MGD  v3.0.0,  Python 3 (via JFP Python 3.10.0a0)
                   v2.0.0,  Python 2 equivalent
                            Python 2.7.18 build kludge for
                              VSI C V7.4-001 on OpenVMS IA64 V8.4-2L1
@@ -318,6 +357,8 @@
                   v2.0.0,  Python 2 equivalent
                            Python 2.7.18 build kludge for
                              VSI C V7.4-001 on OpenVMS IA64 V8.4-2L1
+                           subinterpreters are out and threads in!
+                           add WATCH [x]Script watch-points and wasd.WATCH()
 22-JUL-2017  MGD  v1.1.12, wasd.cgi_callout and RtePyMethCgiCallout()
                              provides callout environment regardless of WSGI
                            bugfix; stderr = freopen(stderr);
@@ -349,7 +390,7 @@
 /*****************************************************************************/
 
 #define SOFTWARECR "Copyright (C) 2007-2020 Mark G.Daniel"
-#define SOFTWAREVN "x.0.0"  /* just the minor and tweak versions here! */
+#define SOFTWAREVN "x.0.0"  /** 'x' will be updated with '2' or '3' below **/
 /** ----------------^^^^^ and IDENTIFICATION=x.n.n in BUILD_PYRTE[n].COM **/
 #define SOFTWARENM "PYRTE"
 #ifdef __ALPHA
@@ -441,6 +482,11 @@
 /* macros */
 /**********/
 
+/* mainly to allow easy use of the __unaligned directive */
+#define ULONGPTR __unaligned unsigned long*
+#define USHORTPTR __unaligned unsigned short*
+#define INT64PTR __unaligned __int64*
+
 /** INTERIM **/
 #if PY_MAJOR_VERSION >= 3
 #define PyArg_ParseTuple                _PyArg_ParseTuple_SizeT
@@ -469,12 +515,7 @@
 
 #define FI_LI __FILE__, __LINE__
 
-/* mainly to allow easy use of the __unaligned directive */
-#define ULONGPTR __unaligned unsigned long*
-#define USHORTPTR __unaligned unsigned short*
-#define INT64PTR __unaligned __int64*
-
 /******************/
 /* global storage */
 /******************/
 
@@ -477,6 +518,6 @@
 /******************/
 /* global storage */
 /******************/
 
-int  dbug,
+int  dbug, watches,
      AppOutputCount,
@@ -482,3 +523,3 @@
      AppOutputCount,
-     CgiPlusPythonEnd,
+     CgiPlusState,
      CodeMetricEnabled,
@@ -484,5 +525,5 @@
      CodeMetricEnabled,
-     GlobalDebug,
+     CurrentTimeSecs,
      HeadCvtGet,
      IsCgiPlusMode,
      IsCgiPlusScript,
@@ -486,4 +527,6 @@
      HeadCvtGet,
      IsCgiPlusMode,
      IsCgiPlusScript,
+     IsScriptRte,
+     MainArgc,
      NoStreamMode,
@@ -489,3 +532,4 @@
      NoStreamMode,
-     ReuseInterpreter,
+     PyrteUsageCount,
+     ReuseThread,
      RunStarted,
@@ -491,5 +535,4 @@
      RunStarted,
-     UsageCount,
      UsageLimit,
      WasdHeadersSent,
      WsgiHeadersSent,
@@ -511,6 +554,8 @@
       *CgiPlusEotPtr,
       *CgiPlusEscPtr;
 
+char  **MainArgv;
+
 char  CgiPlusEof [64],
       CgiPlusEot [64],
       CgiPlusEsc [64];
@@ -532,8 +577,9 @@
           *pOsDict,
           *pOsEnvironDict,
           *pOsModule,
+          *pStdout,
           *pWasdModule,
           *pWsgiExcInfo,
           *pWsgiHeaders,
           *pWsgiStatus;
 
@@ -535,8 +581,10 @@
           *pWasdModule,
           *pWsgiExcInfo,
           *pWsgiHeaders,
           *pWsgiStatus;
 
+PyInterpreterState  *pInterpState;
+
 struct AnExitHandler
 {
    unsigned long  Flink;
@@ -579,7 +627,7 @@
 struct CodeCacheStruct
 {
    int  CodeUsageCount,
-        InterpreterUsageCount;
+        ThreadUsageCount;
    unsigned long  LastUsedSecs,
                   ScriptMtimeSecs;
    char *FileNamePtr,
@@ -583,8 +631,10 @@
    unsigned long  LastUsedSecs,
                   ScriptMtimeSecs;
    char *FileNamePtr,
-        *FileNameByteCodePtr;
-   PyThreadState  *pInterpState;
+        *FileNameByteCodePtr,
+        *ScriptNamePtr;
+   PyThreadState  *pThreadState;
+   PyObject  *pMainDict2;
 #if PY_MAJOR_VERSION >= 3
    PyObject  *pByteCode;
 #else
@@ -635,9 +685,10 @@
 PyDoc_STRVAR (rte_read_cgiplusin__doc__,
 "Retuns a string containing a record read from the CGIPLUSIN stream.");
 
-static PyObject* RtePyMethReuseInterpreter ();
-
-PyDoc_STRVAR (rte_reuse_interpreter__doc__,
+static PyObject* RtePyMethReuseThread ();
+static PyObject* RtePyMethReuseInterpreter ();  /* backward compatibility */
+
+PyDoc_STRVAR (rte_reuse_thread__doc__,
 "Enables (True) or disables (False) interpreter reuse for the next request \
 received by pyRTE.");
 
@@ -666,5 +717,6 @@
 "Returns an integer value for how many times the cached byte-code \
 has been used.");
 
+static PyObject* RtePyMethUsageThread ();
 static PyObject* RtePyMethUsageInterpreter ();
 
@@ -669,4 +721,4 @@
 static PyObject* RtePyMethUsageInterpreter ();
 
-PyDoc_STRVAR (rte_usage_interpreter__doc__,
+PyDoc_STRVAR (rte_usage_thread__doc__,
 "Returns an integer value for how many times the current Python \
@@ -672,8 +724,8 @@
 "Returns an integer value for how many times the current Python \
-(sub)interpreter has been used.");
+thread has been used.");
 
 static PyObject* RtePyMethUsageRte ();
 
 PyDoc_STRVAR (rte_usage_rte__doc__,
 "Returns an integer value for how many times the RTE has been used.");
 
@@ -674,9 +726,14 @@
 
 static PyObject* RtePyMethUsageRte ();
 
 PyDoc_STRVAR (rte_usage_rte__doc__,
 "Returns an integer value for how many times the RTE has been used.");
 
+static PyObject* RteWATCH (PyObject*, PyObject*);
+
+PyDoc_STRVAR (wsgi_watch__doc__,
+"Write to WATCH [x]Script (useful for debugging).");
+
 /* WSGI methods */
 
 static PyObject* WsgiRun (PyObject*, PyObject*);
@@ -715,8 +772,11 @@
                 rte_getvar__doc__ },
    { "read_cgiplusin", (PyCFunction)RtePyMethReadCgiPlusIn, METH_VARARGS,
                 rte_read_cgiplusin__doc__ },
-   { "reuse_interpreter", (PyCFunction)RtePyMethReuseInterpreter, METH_VARARGS,
-                           rte_reuse_interpreter__doc__ },
+   { "reuse_thread", (PyCFunction)RtePyMethReuseThread, METH_VARARGS,
+                     rte_reuse_thread__doc__ },
+   /* and backward compatibility */
+   { "reuse_interpreter", (PyCFunction)RtePyMethReuseThread, METH_VARARGS,
+                           rte_reuse_thread__doc__ },
    { "rte_cache_entry", (PyCFunction)RtePyMethRteCacheEntry, METH_VARARGS,
                         rte_cache_entry__doc__ },
    { "rte_id", (PyCFunction)RtePyMethRteId, METH_VARARGS,
@@ -725,7 +785,10 @@
                     rte_stat_timer__doc__ },
    { "usage_code", (PyCFunction)RtePyMethUsageCode, METH_NOARGS,
                     rte_usage_code__doc__ },
-   { "usage_interpreter", (PyCFunction)RtePyMethUsageInterpreter, METH_NOARGS,
-                           rte_usage_interpreter__doc__ },
+   { "usage_thread", (PyCFunction)RtePyMethUsageThread, METH_NOARGS,
+                     rte_usage_thread__doc__ },
+   /* and backward compatibility */
+   { "usage_interpreter", (PyCFunction)RtePyMethUsageThread, METH_NOARGS,
+                           rte_usage_thread__doc__ },
    { "usage_rte", (PyCFunction)RtePyMethUsageRte, METH_NOARGS,
                    rte_usage_rte__doc__ },
@@ -730,3 +793,7 @@
    { "usage_rte", (PyCFunction)RtePyMethUsageRte, METH_NOARGS,
                    rte_usage_rte__doc__ },
+   { "WATCH", (PyCFunction)RteWATCH, METH_VARARGS,
+               wsgi_watch__doc__ },
+   { "watch", (PyCFunction)RteWATCH, METH_VARARGS,
+               wsgi_watch__doc__ },
    { "write", (PyCFunction)RteStdoutWrite, METH_VARARGS,
@@ -732,5 +799,5 @@
    { "write", (PyCFunction)RteStdoutWrite, METH_VARARGS,
-              wsgi_write__doc__ },
+               wsgi_write__doc__ },
 
    /* WSGI methods */
 
@@ -776,6 +843,8 @@
 void at_Exit ();
 void AddMetrics (struct CodeCacheStruct*);
 int ByteCodeUpToDate (struct CodeCacheStruct*);
+void CgiPlusBegin ();
+void CgiPlusEnd ();
 char* CgiVar (char*);
 char* CgiVarDclSymbol (char*);
 void CollectMetrics (struct CodeCacheStruct*, int);
@@ -779,6 +848,6 @@
 char* CgiVar (char*);
 char* CgiVarDclSymbol (char*);
 void CollectMetrics (struct CodeCacheStruct*, int);
-void DbugCheck ();
-int EmptyScriptName (char*, char*);
+int DbugCheck (int);
+char* EmptyScriptName (char*);
 void EnsureExit (unsigned long*);
@@ -784,2 +853,3 @@
 void EnsureExit (unsigned long*);
+void EvalByteCode (struct CodeCacheStruct*);
 void FlushCacheEntry (struct CodeCacheStruct*);
@@ -785,6 +855,7 @@
 void FlushCacheEntry (struct CodeCacheStruct*);
+PyThreadState* FreshThread (PyThreadState*, char*);
 int lib$stop (__unknown_params);
 int LibVmReport (void*);
 void LoadByteCode (char*, char*, struct CodeCacheStruct*);
 int MetricsReport ();
 int ModuleCgiibLoaded ();
@@ -786,10 +857,11 @@
 int lib$stop (__unknown_params);
 int LibVmReport (void*);
 void LoadByteCode (char*, char*, struct CodeCacheStruct*);
 int MetricsReport ();
 int ModuleCgiibLoaded ();
-void ProcessCLI (char*);
-void ProcessCGI (int, char**);
-void ProcessCGIplus (int, char**);
+int ProcessArgcArgv ();
+void ProcessCLI ();
+void ProcessCGI ();
+void ProcessCGIplus ();
 void ProcessBasicRte ();
 void ProcessCachingRte ();
@@ -794,4 +866,5 @@
 void ProcessBasicRte ();
 void ProcessCachingRte ();
+void ProcessPyRun ();
 int ProctorDetect ();
 int ReadFileIntoMemory (char*, char**, int*);
@@ -796,4 +869,4 @@
 int ProctorDetect ();
 int ReadFileIntoMemory (char*, char**, int*);
-void ReportError (int, int, int, char*, ...);
+int ReportError (int, int, int, char*, ...);
 void RteScriptParam ();
@@ -799,4 +872,6 @@
 void RteScriptParam ();
+int ServerSoftware (void);
+void SetArgv (char*);
 void SetProgramName (char*);
 int SetupOsEnvCgiVar (int);
 char* StatTimer (int);
@@ -800,5 +875,7 @@
 void SetProgramName (char*);
 int SetupOsEnvCgiVar (int);
 char* StatTimer (int);
-char* TrnLnm (char*, char*);
+void StatTimerCallout (int);
+char* TrnLnm (char*);
+char* Trn2Lnm (char*, char*, int);
 int WasdModuleInit (void);
@@ -804,4 +881,6 @@
 int WasdModuleInit (void);
+void WatchCallout (char*, ...);
+void WatchErrorCallout ();
 int WsgiSendHeaders ();
 int WsgiSendResponse (PyObject*);
 int WsgiOsEnvVar ();
@@ -805,11 +884,6 @@
 int WsgiSendHeaders ();
 int WsgiSendResponse (PyObject*);
 int WsgiOsEnvVar ();
-#if PY_MAJOR_VERSION >= 3
-wchar_t** MungeArgv (int, char*[]);
-#else
-char** MungeArgv (int, char*[]);
-#endif
 
 #if PY_MAJOR_VERSION >= 3
 PyAPI_FUNC(void) PyNode_Free (struct _mod*);
@@ -852,8 +926,11 @@
     *strchr(SoftwareId,'x') = '2';
 #endif
 
+   MainArgc = argc;
+   MainArgv = argv;
+
    /* set the global pointer used by some functions */
    CodeCachePtr = &CodeCache[0];
 
    if (argc == 2)
    {
@@ -855,7 +932,14 @@
    /* set the global pointer used by some functions */
    CodeCachePtr = &CodeCache[0];
 
    if (argc == 2)
    {
+      if (!strncasecmp(argv[1], "/run=", 5))
+      {
+         DbugCheck (1);
+         argv[1] += 5;
+         ProcessPyRun ();
+         exit (SS$_NORMAL);
+      }
       if (!strncasecmp(argv[1], "/cli=", 5))
       {
@@ -860,8 +944,9 @@
       if (!strncasecmp(argv[1], "/cli=", 5))
       {
-         DbugCheck();
-         ProcessCLI (argv[1]+5);
+         DbugCheck (1);
+         argv[1] += 5;
+         ProcessCLI ();
          exit (SS$_NORMAL);
       }
       if (!strcasecmp(argv[1], "/version"))
       {
@@ -864,10 +949,11 @@
          exit (SS$_NORMAL);
       }
       if (!strcasecmp(argv[1], "/version"))
       {
-         fprintf (stdout, "%%PYRTE-I-VERSION, %s (%s)\n%s\n%s",
-                  SoftwareId, BUILD_DATETIME, SOFTWARECR, SOFTWAREGPL);
+         fprintf (stdout, "%%PYRTE-I-VERSION, %s (%s) Python %s\n%s\n%s",
+                  SoftwareId, BUILD_DATETIME, Py_GetVersion(),
+                  SOFTWARECR, SOFTWAREGPL);
          exit (SS$_NORMAL);
       }
    }
 
@@ -870,7 +956,15 @@
          exit (SS$_NORMAL);
       }
    }
 
+#if PY_MAJOR_VERSION >= 3
+   if (argc >= 2)
+   {
+      ProcessArgcArgv ();
+      exit (SS$_NORMAL);
+   }
+#endif
+
    status = sys$getjpiw (0, 0, 0, &JpiItems, 0, 0, 0);
    if (!(status & 1)) exit (status);
 
@@ -874,9 +968,4 @@
    status = sys$getjpiw (0, 0, 0, &JpiItems, 0, 0, 0);
    if (!(status & 1)) exit (status);
 
-   DbugCheck();
-
-   WsgiForceBuffering = 
-         (TrnLnm ("PYRTE_WSGI_FORCE_BUFFERING", NULL) != NULL);
-
    /* if it doesn't look like CGI environment then forget it */
@@ -882,5 +971,16 @@
    /* if it doesn't look like CGI environment then forget it */
-   if (!TrnLnm ("HTTP$INPUT", NULL)) exit (SS$_ABORT);
-
-   if (!dbug)
+   if (!TrnLnm ("HTTP$INPUT")) exit (SS$_ABORT);
+
+   WsgiForceBuffering = (TrnLnm ("PYRTE_WSGI_FORCE_BUFFERING") != NULL);
+
+   cptr = TrnLnm ("PYRTE_USAGE_LIMIT");
+   if (cptr && isdigit(*cptr)) UsageLimit = atoi(cptr);
+
+   if (TrnLnm ("PYRTE_ONESHOT")) UsageLimit = 1;
+
+   /* binary mode to eliminate carriage-control */
+   if (!(stdin = freopen ("HTTP$INPUT:", "r", stdin, "ctx=bin")))
+      exit (vaxc$errno);
+
+   if (!DbugCheck(1))
    {
@@ -886,8 +986,4 @@
    {
-      /* binary mode to eliminate carriage-control */
-      if (!(stdin = freopen ("HTTP$INPUT:", "r", stdin,
-                             "ctx=bin", "ctx=xplct")))
-         exit (vaxc$errno);
       if (!(stdout = freopen ("SYS$OUTPUT:", "w", stdout,
                               "ctx=bin", "ctx=xplct")))
          exit (vaxc$errno);
@@ -891,7 +987,7 @@
       if (!(stdout = freopen ("SYS$OUTPUT:", "w", stdout,
                               "ctx=bin", "ctx=xplct")))
          exit (vaxc$errno);
-      if (!(stderr = freopen ("SYS$ERROR:", "w", stderr,
-                              "ctx=bin", "ctx=xplct")))
-         exit (vaxc$errno);
+      /* PY_MAJOR_VERSION >= 3 required interleaving output and errors */
+      fclose (stderr);
+      stderr = stdout;
    }
@@ -897,8 +993,4 @@
    }
-
-   cptr = TrnLnm ("PYRTE_USAGE_LIMIT", NULL);
-   if (cptr && isdigit(*cptr)) UsageLimit = atoi(cptr);
-
-   if (TrnLnm ("PYRTE_ONESHOT", NULL)) UsageLimit = 1;
+   dbug = 0;
 
    /* need differentiate between RTE/CGIplus and CGIplus script */
@@ -903,12 +995,14 @@
 
    /* need differentiate between RTE/CGIplus and CGIplus script */
-   IsCgiPlusMode = (TrnLnm ("CGIPLUSEOF", NULL) != NULL);
-   CgiPlusEofPtr = TrnLnm ("CGIPLUSEOF", CgiPlusEof);
-   CgiPlusEotPtr = TrnLnm ("CGIPLUSEOT", CgiPlusEot);
-   CgiPlusEscPtr = TrnLnm ("CGIPLUSESC", CgiPlusEsc);
+   CgiPlusEofPtr = strdup(TrnLnm ("CGIPLUSEOF"));
+   CgiPlusEotPtr = strdup(TrnLnm ("CGIPLUSEOT"));
+   CgiPlusEscPtr = strdup(TrnLnm ("CGIPLUSESC"));
+   IsCgiPlusMode = (CgiPlusEofPtr != NULL);
+
+   WasdModuleInit ();
 
    /* set up and declare the exit handler */
    ExitHandler.HandlerAddress = (unsigned long)&EnsureExit;
    ExitHandler.ArgCount = 1;
    ExitHandler.ExitStatusPtr = (unsigned long)&ExitStatus;
    status = sys$dclexh (&ExitHandler);
@@ -909,10 +1003,59 @@
 
    /* set up and declare the exit handler */
    ExitHandler.HandlerAddress = (unsigned long)&EnsureExit;
    ExitHandler.ArgCount = 1;
    ExitHandler.ExitStatusPtr = (unsigned long)&ExitStatus;
    status = sys$dclexh (&ExitHandler);
-   if (!(status & 0x00000001)) exit (status);
+   if (!(status & 1)) exit (status);
+
+   Py_Initialize();
+   PyEval_InitThreads ();
+#if PY_MAJOR_VERSION >= 3
+   pInterpState = PyInterpreterState_Main ();
+#else
+   pInterpState = PyInterpreterState_Head ();
+#endif
+
+   /* get handle to python stdout file ready for explicit flushing */
+   pStdout = PySys_GetObject ("stdout");
+   if (!pStdout)
+   {
+      ReportError (__LINE__, 0, 1, ErrorPython);
+      exit (SS$_NORMAL);
+   }
+
+   /* borrowed reference */
+   pMainModule = PyImport_AddModule ("__main__");
+   if (!pMainModule)
+   {
+      ReportError (__LINE__, 0, 1, ErrorPython);
+      return;
+   }
+
+   /* borrowed reference */
+   pMainDict = PyModule_GetDict (pMainModule);
+   if (!pMainDict)
+   {
+      ReportError (__LINE__, 0, 1, ErrorPython);
+      return;
+   }
+   DEBUGPYOBJ("pMainDict",pMainDict)
+
+   /* borrowed reference */
+   pOsModule = PyImport_AddModule ("os");
+   if (!pOsModule)
+   {
+      ReportError (__LINE__, 0, 1, ErrorPython);
+      return;
+   }
+   /* borrowed reference */
+   pOsDict = PyModule_GetDict (pOsModule);
+   if (!pOsDict)
+   {
+      ReportError (__LINE__, 0, 1, ErrorPython);
+      return;
+   }
+   DEBUGPYOBJ("pOsDict",pOsDict)
 
    if (IsCgiPlusMode)
    {
@@ -916,10 +1059,16 @@
 
    if (IsCgiPlusMode)
    {
-      /* for pyRTE an RTE will only have one parameter, CGIplus two! */
-      if (argc == 1)
+      /* get the first request (which is immediately available) */
+      CgiVar ("");
+
+      if (CgiVar("WATCH_SCRIPT") != NULL)
+         WatchCallout ("%s Python %s", SoftwareId, Py_GetVersion());
+
+      /* only an RTE will have this variable */
+      if (IsScriptRte = (CgiVar ("SCRIPT_RTE") != NULL))
       {
          /*******/
          /* RTE */
          /*******/
 
@@ -921,9 +1070,9 @@
       {
          /*******/
          /* RTE */
          /*******/
 
-         if (TrnLnm ("PYRTE_BASIC_RTE", NULL))
+         if (TrnLnm ("PYRTE_BASIC_RTE"))
             ProcessBasicRte ();
          else
             ProcessCachingRte ();
@@ -935,7 +1084,7 @@
          /***********/
 
          /* supply the command-line script file name */
-         ProcessCGIplus (argc, argv);
+         ProcessCGIplus ();
       }
    }
    else
@@ -944,6 +1093,6 @@
       /* CGI */
       /*******/
 
-      ProcessCGI (argc, argv);
+      ProcessCGI ();
    }
 
@@ -948,5 +1097,7 @@
    }
 
+   Py_Finalize ();
+
    exit (SS$_NORMAL);
 }
 
@@ -973,7 +1124,47 @@
 
 /*****************************************************************************/
 /*
+Process using standard Python arguments at the command-line.
+For base-line comparison purposes only.
+*/
+
+#if PY_MAJOR_VERSION >= 3
+
+int ProcessArgcArgv ()
+
+{
+   PyConfig  config;
+   PyStatus  status;
+
+   /*********/
+   /* begin */
+   /*********/
+
+   if (DbugCheck(1)) fprintf (stdout, "ProcessArgcArgv()\n");
+
+   PyConfig_InitPythonConfig (&config);
+   config.isolated = 1;
+
+   status = PyConfig_SetBytesArgv (&config, MainArgc, MainArgv);
+   if (PyStatus_Exception(status)) goto fail;
+
+   status = Py_InitializeFromConfig (&config);
+   if (PyStatus_Exception (status)) goto fail;
+   PyConfig_Clear (&config);
+
+   return (Py_RunMain ());
+
+fail:
+   PyConfig_Clear (&config);
+   if (PyStatus_IsExit(status)) return (status.exitcode);
+   Py_ExitStatusException(status);
+}
+
+#endif
+
+/*****************************************************************************/
+/*
 Process from the command-line.
 For base-line comparison purposes only.
 */
 
@@ -976,7 +1167,7 @@
 Process from the command-line.
 For base-line comparison purposes only.
 */
 
-void ProcessCLI (char *ScriptName)
+void ProcessPyRun ()
 
 {
@@ -981,6 +1172,6 @@
 
 {
-   int  status;
+   int  argc, status;
    char  *cptr, *sptr;
    FILE  *fp;
 
@@ -988,7 +1179,6 @@
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "ProcessCLI()\n");
-
-   SetProgramName (ScriptName);
+   if (DbugCheck(1)) fprintf (stdout, "ProcessPyRun()\n");
+
    WasdModuleInit ();
@@ -994,7 +1184,8 @@
    WasdModuleInit ();
-   Py_Initialize();
-
-   fp = fopen (ScriptName, "r");
+   Py_Initialize ();
+   SetArgv (NULL);
+
+   fp = fopen (MainArgv[1], "r");
    if (fp)
    {
       if (dbug) fprintf (stdout, "fp:%u\n", fp);
@@ -998,9 +1189,9 @@
    if (fp)
    {
       if (dbug) fprintf (stdout, "fp:%u\n", fp);
-      PyRun_AnyFileExFlags (fp, ScriptName, 0, NULL);
+      PyRun_AnyFileExFlags (fp, MainArgv[1], 0, NULL);
       fclose (fp);
    }
    else
    {
       status = vaxc$errno;
@@ -1002,10 +1193,10 @@
       fclose (fp);
    }
    else
    {
       status = vaxc$errno;
-      ReportError (__LINE__, status, 0, "Error opening !AZ", ScriptName);
+      ReportError (__LINE__, status, 0, "Error opening !AZ", MainArgv[1]);
    }
 
    Py_Finalize ();
 
@@ -1008,9 +1199,50 @@
    }
 
    Py_Finalize ();
 
-   if (dbug || TrnLnm ("PYRTE_STAT_TIMER",NULL))
-      fprintf (stdout, "<!-- %s -->\n", StatTimer(TRUE));
+   if (dbug || TrnLnm ("PYRTE_STAT_TIMER"))
+      fprintf (stdout, "%s\n", StatTimer(TRUE));
+}
+
+/*****************************************************************************/
+/*
+Process from the command-line.
+For base-line comparison purposes only.
+*/
+
+void ProcessCLI ()
+
+{
+   int  argc;
+   struct CodeCacheStruct  *ccptr;
+
+   /*********/
+   /* begin */
+   /*********/
+
+   if (DbugCheck(1)) fprintf (stdout, "ProcessCLI()\n");
+
+   WasdModuleInit ();
+   Py_Initialize();
+   PyEval_InitThreads ();
+   SetArgv (MainArgv[1]);
+
+#if PY_MAJOR_VERSION >= 3
+   pInterpState = PyInterpreterState_Main ();
+#else
+   pInterpState = PyInterpreterState_Head ();
+#endif
+
+   ccptr = &CodeCache[0];
+   ccptr->FileNamePtr = ccptr->ScriptNamePtr = MainArgv[1];
+   ccptr->pThreadState = PyThreadState_New (pInterpState);
+   LoadByteCode (MainArgv[1], MainArgv[1], ccptr);
+   if (ccptr->pByteCode) EvalByteCode (ccptr);
+
+   Py_Finalize ();
+
+   if (dbug || TrnLnm ("PYRTE_STAT_TIMER"))
+      fprintf (stdout, "%s\n", StatTimer(TRUE));
 }
 
 /*****************************************************************************/
@@ -1019,9 +1251,6 @@
 For base-line comparison purposes only.
 */
 
-void ProcessCGI
-(
-int argc,
-char **argv
-)
+void ProcessCGI ()
+
 {
@@ -1027,9 +1256,9 @@
 {
-   int  status;
-   char  *cptr, *sptr;
-   FILE  *fp;
+   char  *fnptr, *snptr;
+   struct CodeCacheStruct  *ccptr;
+   PyThreadState  *pStateBuffer;
 
    /*********/
    /* begin */
    /*********/
 
@@ -1031,16 +1260,19 @@
 
    /*********/
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "ProcessCGI()\n");
-
-   cptr = sptr = CgiVar ("PATH_INFO");
-   fp = fopen (cptr, "r");
-   if (!fp)
-   {
-      sptr = CgiVar ("PATH_TRANSLATED");
-      fp = fopen (sptr, "r");
-   }
-   if (fp)
+   if (DbugCheck(1)) fprintf (stdout, "ProcessCGI()\n");
+
+   watches = (CgiVar("WATCH_SCRIPT") != NULL);
+
+   fnptr = CgiVar ("PATH_TRANSLATED");
+   snptr = CgiVar ("PATH_INFO");
+
+   if (watches) WatchCallout ("CGI %s %s", snptr, fnptr);
+
+   StatTimer(FALSE);
+
+   LoadByteCode (fnptr, snptr, (ccptr = &CodeCache[0]));
+   if (ccptr->pByteCode)
    {
@@ -1046,18 +1278,8 @@
    {
-      StatTimer(FALSE);
-
-      SetProgramName (cptr);
-      WasdModuleInit ();
-      Py_Initialize();
-
-      PySys_SetArgv (argc, MungeArgv(argc,argv));
-
-      PyRun_AnyFileExFlags (fp, cptr, 0, NULL);
-
-      if (dbug || TrnLnm ("PYRTE_STAT_TIMER",NULL))
-         fprintf (stdout, "<!-- %s -->\n", StatTimer(TRUE));
-
-      Py_Finalize();
-
-      fclose (fp);
+      ccptr->pThreadState = FreshThread (NULL, snptr);
+      pStateBuffer = PyThreadState_Swap (ccptr->pThreadState);
+      EvalByteCode (ccptr);
+      if (ReportError (0, 0, 0, NULL)) FlushCacheEntry (ccptr);
+      PyThreadState_Swap (pStateBuffer);
+      FreshThread (ccptr->pThreadState, NULL);
    }
@@ -1063,9 +1285,6 @@
    }
-   else
-   {
-      status = vaxc$errno;
-      ReportError (__LINE__, status, 0, "Error opening !AZ", sptr);
-   }
+
+   if (watches) StatTimerCallout (1);
 }
 
 /*****************************************************************************/
@@ -1073,9 +1292,6 @@
 For a Python script that implements CGIplus itself.
 */
 
-void ProcessCGIplus
-(
-int argc,
-char **argv
-)
+void ProcessCGIplus ()
+
 {
@@ -1081,4 +1297,3 @@
 {
-   int  status;
-   char  *cptr, *sptr;
+   char  *fnptr, *snptr;
    struct CodeCacheStruct  *ccptr;
@@ -1084,7 +1299,7 @@
    struct CodeCacheStruct  *ccptr;
-   PyObject  *pResult;
+   PyThreadState  *pStateBuffer;
 
    /*********/
    /* begin */
    /*********/
 
@@ -1086,9 +1301,11 @@
 
    /*********/
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "ProcessCGIplus() %d\n", argc);
+   if (DbugCheck(1)) fprintf (stdout, "ProcessCgiPlus()\n");
+
+   watches = (CgiVar("WATCH_SCRIPT") != NULL);
 
    /* code behaviours are different for an explicitly CGIplus script */
    IsCgiPlusScript = 1;
@@ -1092,42 +1309,15 @@
 
    /* code behaviours are different for an explicitly CGIplus script */
    IsCgiPlusScript = 1;
-
-   SetProgramName (argv[1]);
-   Py_Initialize();
-
-   /* just use the first element of the code cache */
-   ccptr = &CodeCache[0];
-
-   /* it will have been used the once! */
-   ccptr->InterpreterUsageCount++;
-
-   /* first is RTE image, second CGIplus script, then optional parameters */
-   PySys_SetArgv (argc-1, MungeArgv(argc,&argv[1]));
-
-   LoadByteCode (argv[1], argv[1], ccptr);
-   if (!ccptr->pByteCode) return;
-
-   pMainModule = PyImport_AddModule ("__main__");
-   if (!pMainModule)
-   {
-      ReportError (__LINE__, 0, 1, ErrorPython);
-      return;
-   }
-   pMainDict = PyModule_GetDict (pMainModule);
-   if (!pMainDict)
-   {
-      ReportError (__LINE__, 0, 1, ErrorPython);
-      return;
-   }
-   DEBUGPYOBJ("pMainDict",pMainDict)
-
-   pOsModule = PyImport_AddModule ("os");
-   if (!pOsModule)
-   {
-      ReportError (__LINE__, 0, 1, ErrorPython);
-      return;
-   }
-   pOsDict = PyModule_GetDict (pOsModule);
-   if (!pOsDict)
+   PyrteUsageCount++;
+
+   StatTimer(FALSE);
+
+   fnptr = CgiVar ("SCRIPT_FILENAME");
+   snptr = CgiVar ("SCRIPT_NAME");
+
+   if (watches) WatchCallout ("CGIplus %s %s", snptr, fnptr);
+
+   LoadByteCode (fnptr, snptr, (ccptr = &CodeCache[0]));
+   if (ccptr->pByteCode)
    {
@@ -1133,4 +1323,8 @@
    {
-      ReportError (__LINE__, 0, 1, ErrorPython);
-      return;
+      ccptr->pThreadState = FreshThread (NULL, snptr);
+      pStateBuffer = PyThreadState_Swap (ccptr->pThreadState);
+      EvalByteCode (ccptr);
+      if (ReportError (0, 0, 0, NULL)) FlushCacheEntry (ccptr);
+      PyThreadState_Swap (pStateBuffer);
+      FreshThread (ccptr->pThreadState, NULL);
    }
@@ -1136,40 +1330,6 @@
    }
-   DEBUGPYOBJ("pOsDict",pOsDict)
-
-   if (!WasdModuleInit ()) return;
-
-   /* get the first request (which is already available and won't block) */
-   CgiVar ("");
-
-   DbugCheck();
-
-   RteScriptParam ();
-
-   if (!NoStreamMode) fprintf (stdout, "Script-Control: X-stream-mode\n");
-
-   if (!SetupOsEnvCgiVar (1)) return;
-
-   PyErr_Clear ();
-
-   pResult = PyEval_EvalCode (ccptr->pByteCode, pMainDict, pMainDict);
-   if (pError = PyErr_Occurred())
-   {
-      ReportError (__LINE__, 0, 1, NULL);
-      FlushCacheEntry (ccptr);
-   }
-   DEBUGPYOBJ("PyEval_EvalCode",pResult)
-   Py_XDECREF (pResult);
-
-   fflush (stdout);
-   fputs (CgiPlusEofPtr, stdout);
-   fflush (stdout);
-
-   if (!GlobalDebug) dbug = 0;
-
-   Py_Finalize();
-
-   if (dbug || TrnLnm ("PYRTE_STAT_TIMER",NULL))
-      fprintf (stdout, "<!-- %s -->\n", StatTimer(TRUE));
+
+   DbugCheck (0);
 }
 
 /*****************************************************************************/
@@ -1182,10 +1342,9 @@
 void ProcessBasicRte ()
        
 {
-   int  status;
-   char  *cptr, *sptr;
-   char  *argv [2] = { NULL, NULL };
-   FILE  *fp;
+   char  *fnptr, *snptr;
+   struct CodeCacheStruct  *ccptr;
+   PyThreadState  *pStateBuffer;
 
    /*********/
    /* begin */
@@ -1195,32 +1354,18 @@
 
    for (;;)
    {
-      AppOutputCount = 0;
-
-      /* initialise this for special CGI-and-CGIplus script cases */
-      RtePyMethCgiPlusBegin (NULL, NULL);
-
-      SetProgramName ("ProcessBasicRte()");
-      if (!WasdModuleInit()) break;
-      Py_Initialize();
-
-      /* block waiting for the first/next request */
-      CgiVar ("");
-      UsageCount++;
-
-      DbugCheck();
-
-      cptr = sptr = CgiVar ("SCRIPT_FILENAME");
-      SetProgramName (cptr);
-
-      /* fudge these */
-      argv[0] = cptr;
-      PySys_SetArgv (1, MungeArgv(1,argv));
-
-      RteScriptParam ();
-
-      if (!NoStreamMode) fprintf (stdout, "Script-Control: X-stream-mode\n");
-
-      fp = fopen (sptr, "r", "shr=get");
-      if (fp)
+      CgiPlusBegin ();
+
+      if (dbug) fprintf (stdout, "ProcessBasicRte()\n");
+
+      watches = (CgiVar("WATCH_SCRIPT") != NULL);
+
+      fnptr = CgiVar ("SCRIPT_FILENAME");
+      snptr = CgiVar ("SCRIPT_NAME");
+      if (watches) WatchCallout ("RTE basic %s %s", snptr, fnptr);
+
+      SetProgramName (snptr);
+
+      LoadByteCode (fnptr, snptr, (ccptr = &CodeCache[0]));
+      if (ccptr->pByteCode)
       {
@@ -1226,11 +1371,11 @@
       {
-         PyRun_AnyFileExFlags (fp, sptr, 0, NULL);
-         fclose (fp);
-      }
-      else
-      if (!ProctorDetect ())
-      {
-         status = vaxc$errno;
-         ReportError (__LINE__, status, 0, "Error opening !AZ", sptr);
+         ccptr->pThreadState = FreshThread (NULL, snptr);
+         pStateBuffer = PyThreadState_Swap (ccptr->pThreadState);
+         EvalByteCode (ccptr);
+         if (ReportError (0, 0, 0, NULL)) FlushCacheEntry (ccptr);
+         RtePyMethCgiPlusEnd (NULL, NULL);
+         PyThreadState_Swap (pStateBuffer);
+         /* always destroy the just-used thread */
+         ccptr->pThreadState = FreshThread (ccptr->pThreadState, NULL);
       }
 
@@ -1235,17 +1380,10 @@
       }
 
-      Py_Finalize();
-
-      if (dbug || TrnLnm ("PYRTE_STAT_TIMER",NULL))
-         fprintf (stdout, "<!-- %s -->\n", StatTimer(TRUE));
-
-      fflush (stdout);
-      fputs (CgiPlusEofPtr, stdout);
-      fflush (stdout);
-
-      if (!GlobalDebug) dbug = 0;
-
-      if (UsageLimit && UsageCount >= UsageLimit) break;
+      if (watches) StatTimerCallout (1);
+
+      CgiPlusEnd ();
+
+      if (UsageLimit && PyrteUsageCount >= UsageLimit) break;
    }
 }
 
@@ -1262,6 +1400,5 @@
        
 {
    int  idx, tidx, retval, status;
-   unsigned long  mtime,
-                  CurrentTimeSecs;
+   unsigned long  mtime;
    char  *cptr, *fnptr, *sptr, *snptr;
@@ -1267,5 +1404,4 @@
    char  *cptr, *fnptr, *sptr, *snptr;
-   char  *argv [2] = { NULL, NULL };
    struct CodeCacheStruct  *ccptr;
    PyObject  *pObject,
              *pResult;
@@ -1276,5 +1412,5 @@
 
    if (dbug) fprintf (stdout, "ProcessCachingRte()\n");
 
-   cptr = TrnLnm ("PYRTE_CACHE_MAX", NULL);
+   cptr = TrnLnm ("PYRTE_CACHE_MAX");
    if (cptr && isdigit(*cptr)) CodeCacheMax = atoi(cptr);
@@ -1280,5 +1416,5 @@
    if (cptr && isdigit(*cptr)) CodeCacheMax = atoi(cptr);
-   if (CodeCacheMax > CACHE_MAX) CodeCacheMax = CACHE_MAX;
+   if (CodeCacheMax <= 0 || CodeCacheMax > CACHE_MAX) CodeCacheMax = CACHE_MAX;
 
    for (;;)
    {
@@ -1282,7 +1418,10 @@
 
    for (;;)
    {
+      /* reduce latency by always keeping a fresh thread on hand */
+      FreshThread (NULL, NULL);
+
       /********/
       /* wait */
       /********/
 
@@ -1285,23 +1424,15 @@
       /********/
       /* wait */
       /********/
 
-      /* initialise this for special CGI-and-CGIplus script cases */
-      RtePyMethCgiPlusBegin (NULL, NULL);
-
-      SetProgramName ("ProcessBasicRte()");
-      if (!WasdModuleInit()) break;
-      Py_Initialize();
-
-      /* block waiting for the first/next request */
-      CgiVar ("");
-
-      sys$gettim (&CurrentBinTime);
-      CurrentTimeSecs = decc$fix_time (&CurrentBinTime);
-      DbugCheck();
-
-      if (TrnLnm ("PYRTE_METRICS", NULL))
+      CgiPlusBegin ();
+
+      if (DbugCheck(1)) fprintf (stdout, "ProcessCachingRte()\n");
+
+      watches = (CgiVar("WATCH_SCRIPT") != NULL);
+
+      if (TrnLnm ("PYRTE_METRICS"))
          CodeMetricEnabled = 1;
       else
          CodeMetricEnabled = 0;
 
@@ -1304,7 +1435,8 @@
          CodeMetricEnabled = 1;
       else
          CodeMetricEnabled = 0;
 
-      if (TrnLnm ("PYRTE_NOREUSE_INTERPRETER", NULL))
-         ReuseInterpreter = 0;
+      if (TrnLnm ("PYRTE_NOREUSE_THREAD") ||
+          TrnLnm ("PYRTE_NOREUSE_INTERPRETER"))
+         ReuseThread = 0;
       else
@@ -1310,7 +1442,5 @@
       else
-         ReuseInterpreter = 1;
-
-      AppOutputCount = 0;
+         ReuseThread = 1;
 
       RteScriptParam ();
 
@@ -1321,14 +1451,11 @@
       /***********/
 
       fnptr = CgiVar ("SCRIPT_FILENAME");
-      if (!fnptr)
-      {
-         ReportError (__LINE__, 0, 0, "Error getting SCRIPT_FILENAME.");
-         break;
-      }
+      snptr = CgiVar ("SCRIPT_NAME");
+      if (watches) WatchCallout ("RTE caching %s %s", snptr, fnptr);
 
       sptr = CgiVar ("QUERY_STRING");
       if (*sptr == '$' &&
           !strncmp (sptr, "$metrics$", 9) &&
           MetricsReport()) continue;
 
@@ -1329,12 +1456,9 @@
 
       sptr = CgiVar ("QUERY_STRING");
       if (*sptr == '$' &&
           !strncmp (sptr, "$metrics$", 9) &&
           MetricsReport()) continue;
 
-      /* delay to here so that metrics report is excluded */
-      UsageCount++;
-
       /*******************/
       /* byte-code cache */
       /*******************/
@@ -1347,7 +1471,7 @@
          ccptr = &CodeCache[idx];
          if (!ccptr->FileNamePtr) continue;
          if (dbug) fprintf (stdout, "%d %d |%s|%s|\n",
-                             idx, ccptr->InterpreterUsageCount,
+                             idx, ccptr->ThreadUsageCount,
                              cptr, ccptr->FileNamePtr);
          if (!strcasecmp (fnptr, ccptr->FileNamePtr)) break;
          ccptr = NULL;
@@ -1359,12 +1483,8 @@
          /* found cache */
          /***************/
 
-         if (dbug) fprintf (stdout, "hit:%d\n", idx);
-         if (!ByteCodeUpToDate (ccptr))
-         {
-            /* apparently not */
-            FlushCacheEntry (ccptr);
-         }
+         if (watches) WatchCallout ("CACHE hit %d/%d", idx+1, CodeCacheCount);
+         if (!ByteCodeUpToDate (ccptr)) FlushCacheEntry (ccptr);
       }
       else
       if (CodeCacheMax)
@@ -1377,9 +1497,13 @@
          for (tidx = 0; tidx < CodeCacheCount; tidx++)
             if (!CodeCache[tidx].FileNamePtr) break;
          if (tidx < CodeCacheCount)
-            ccptr = &CodeCache[tidx];
+         {
+            ccptr = &CodeCache[idx = tidx];
+            if (watches) WatchCallout ("CACHE unused %d/%d",
+                                       idx+1, CodeCacheCount);
+         }
          else
          if (tidx < CodeCacheMax)
          {
             /* didn't find an unused entry */
             CodeCacheCount++;
@@ -1381,9 +1505,11 @@
          else
          if (tidx < CodeCacheMax)
          {
             /* didn't find an unused entry */
             CodeCacheCount++;
-            ccptr = &CodeCache[tidx];
+            ccptr = &CodeCache[idx = tidx];
+            if (watches) WatchCallout ("CACHE new %d/%d",
+                                       idx+1, CodeCacheCount);
          }
          else
          {
@@ -1391,5 +1517,5 @@
             for (idx = tidx = 0; idx < CodeCacheMax; idx++)
                if (CodeCache[idx].LastUsedSecs < CodeCache[tidx].LastUsedSecs)
                   tidx = idx;
-            ccptr = &CodeCache[tidx];
+            ccptr = &CodeCache[idx];
             FlushCacheEntry (ccptr);
@@ -1395,2 +1521,4 @@
             FlushCacheEntry (ccptr);
+            if (watches) WatchCallout ("CACHE lru %d/%d",
+                                       idx+1, CodeCacheCount);
          }
@@ -1396,6 +1524,5 @@
          }
-         idx = tidx;
       }
       else
       {
          /* code caching is disabled */
@@ -1398,8 +1525,9 @@
       }
       else
       {
          /* code caching is disabled */
-         ccptr = &CodeCache[0];
+         if (watches) WatchCallout ("CACHE disabled");
+         ccptr = &CodeCache[idx = 0];
          if (ccptr->FileNamePtr) FlushCacheEntry (ccptr);
       }
       if (dbug) fprintf (stdout, "CodeCacheCount:%d idx:%d usage:%d\n",
@@ -1403,7 +1531,7 @@
          if (ccptr->FileNamePtr) FlushCacheEntry (ccptr);
       }
       if (dbug) fprintf (stdout, "CodeCacheCount:%d idx:%d usage:%d\n",
-                         CodeCacheCount, idx, ccptr->CodeUsageCount);
+                         CodeCacheCount, idx+1, ccptr->CodeUsageCount);
 
       /* set the global pointer used by some functions */
       CodeCachePtr = ccptr;
@@ -1414,13 +1542,6 @@
          /* load script */
          /***************/
 
-         snptr = CgiVar ("SCRIPT_NAME");
-         if (!snptr)
-         {
-            ReportError (__LINE__, 0, 0, "Error getting SCRIPT_NAME.");
-            break;
-         }
-
          LoadByteCode (fnptr, snptr, ccptr);
 
          AddMetrics (ccptr);
@@ -1428,76 +1549,20 @@
 
       CollectMetrics (ccptr, 0);
 
-      if (ccptr->pByteCode)
-      {
-         /*********************/
-         /* execute byte-code */
-         /*********************/
-
-         ccptr->CodeUsageCount++;
-         ccptr->LastUsedSecs = CurrentTimeSecs;
-
-         /* borrowed reference */
-         pMainModule = PyImport_AddModule ("__main__");
-         if (!pMainModule)
-         {
-            ReportError (__LINE__, 0, 1, ErrorPython);
-            return;
-         }
-         /* borrowed reference */
-         pMainDict = PyModule_GetDict (pMainModule);
-         if (!pMainDict)
-         {
-            ReportError (__LINE__, 0, 1, ErrorPython);
-            return;
-         }
-         DEBUGPYOBJ("pMainDict",pMainDict)
-
-         /* borrowed reference */
-         pOsModule = PyImport_AddModule ("os");
-         if (!pOsModule)
-         {
-            ReportError (__LINE__, 0, 1, ErrorPython);
-            return;
-         }
-         /* borrowed reference */
-         pOsDict = PyModule_GetDict (pOsModule);
-         if (!pOsDict)
-         {
-            ReportError (__LINE__, 0, 1, ErrorPython);
-            return;
-         }
-         DEBUGPYOBJ("pOsDict",pOsDict)
-
-         if (!SetupOsEnvCgiVar (1)) return;
-
-         SetProgramName (snptr);
-
-         PyErr_Clear ();
-
-         pResult = PyEval_EvalCode (ccptr->pByteCode, pMainDict, pMainDict);
-         /* new reference */
-         if (pError = PyErr_Occurred())
-         {
-            ReportError (__LINE__, 0, 1, NULL);
-            FlushCacheEntry (ccptr);
-         }
-         DEBUGPYOBJ("PyEval_EvalCode",pResult)
-
-         if (!SetupOsEnvCgiVar (0)) return;
-
-         /* new reference, can be NULL */
-         Py_XDECREF (pResult);
-      }
-      else
-      {
-         /* no code - no cache! */
-         FlushCacheEntry (ccptr);
-      }
-
-      Py_Finalize();
+      /******************/
+      /* execute script */
+      /******************/
+
+      EvalByteCode (ccptr);
+
+      if (watches) StatTimerCallout (ccptr->ThreadUsageCount);
+
+      /* after the flush to accumulate any outstanding BIO */
+      CollectMetrics (ccptr, 1);
+
+      if (ReportError (0, 0, 0, NULL)) FlushCacheEntry (ccptr);
 
       /******************/
       /* end of request */
       /******************/
 
@@ -1499,23 +1564,13 @@
 
       /******************/
       /* end of request */
       /******************/
 
-      /* after the flush to accumulate any outstanding BIO */
-      CollectMetrics (ccptr, 1);
-
-      if (dbug || TrnLnm ("PYRTE_STAT_TIMER",NULL))
-         fprintf (stdout, "<!-- %s -->\n", StatTimer(TRUE));
-
-      fflush (stdout);
-      fputs (CgiPlusEofPtr, stdout);
-      fflush (stdout);
-
-      if (!GlobalDebug) dbug = 0;
-
-      if (UsageLimit && UsageCount >= UsageLimit) break;
+      CgiPlusEnd ();
+
+      if (UsageLimit && PyrteUsageCount >= UsageLimit) break;
    }
 }
 
 /*****************************************************************************/
 /*
@@ -1517,9 +1572,382 @@
    }
 }
 
 /*****************************************************************************/
 /*
-Free dynamic components and NULL related pointers.
+If both parameters are NULL then create a new thread in advance.
+If |pStaleState| is non-NULL remove that thread.
+If |ScriptName| is non-NULL use the fresh thread and return it.
+*/
+
+PyThreadState* FreshThread
+(
+PyThreadState *pStaleState,
+char *ScriptName
+)
+{
+   static PyThreadState  *pFreshState = NULL;
+
+   int  argc;
+   PyThreadState  *pStateBuffer;
+
+   /*********/
+   /* begin */
+   /*********/
+
+   if (dbug) fprintf (stdout, "FreshThread() %u |%s|\n",
+                      (unsigned int)pStaleState, ScriptName);
+
+   if (pStaleState)
+   {
+      /* finished with this thread */
+      PyThreadState_Clear (pStaleState);
+      PyThreadState_Delete (pStaleState);
+   }
+   if (!pFreshState)
+   {
+      /* proactively create a thread (even if it's then used immediately) */
+      pFreshState = PyThreadState_New (pInterpState);
+      if (!pFreshState)
+      {
+         ReportError (__LINE__, 0, 0, "Error initialising thread.");
+         exit (SS$_ABORT);
+      }
+   }
+   if (ScriptName)
+   {
+      /* return the fresh thread */
+      pStateBuffer = PyThreadState_Swap (pFreshState);
+      SetProgramName (ScriptName);
+      SetArgv (ScriptName);
+      PyThreadState_Swap (pStateBuffer);
+      pStateBuffer = pFreshState;
+      pFreshState = NULL;
+      return (pStateBuffer);
+   }
+   return (NULL);
+}
+
+/*****************************************************************************/
+/*
+Execute the Python script (the loaded bytecode).
+*/
+
+void EvalByteCode (struct CodeCacheStruct *ccptr)
+
+{
+   int  error = 0;
+   char  *argv [2] = { NULL, NULL };
+   PyObject  *pObject,
+             *pResult;
+   PyThreadState  *pStateBuffer;
+
+   /*********/
+   /* begin */
+   /*********/
+
+   if (dbug) fprintf (stdout, "EvalByteCode()\n");
+
+   if (watches) WatchCallout ("EVAL %s", ccptr->ScriptNamePtr);
+
+   if (!ccptr->pByteCode)
+   {
+      if (ccptr->pThreadState)
+      {
+         /* destroy the current thread */
+         ccptr->pThreadState = FreshThread (ccptr->pThreadState, NULL);
+         ccptr->ThreadUsageCount = 0;
+      }
+      FlushCacheEntry (ccptr);
+      return;
+   }
+
+   ccptr->CodeUsageCount++;
+   ccptr->LastUsedSecs = CurrentTimeSecs;
+
+#if PY_MAJOR_VERSION >= 3
+   if (dbug) fprintf (stdout, "PyGILState_Check() %d\n", PyGILState_Check());
+#endif
+
+   if (!ccptr->pThreadState)
+   {
+      /* allocate a fresh thread to this entry */
+      ccptr->pThreadState = FreshThread (NULL, ccptr->ScriptNamePtr);
+      ccptr->ThreadUsageCount = 0;
+   }
+
+   /* use the associated thread */
+   pStateBuffer = PyThreadState_Swap (ccptr->pThreadState);
+   ccptr->ThreadUsageCount++;
+
+   /* borrowed reference */
+#if PY_MAJOR_VERSION >= 3
+   pWasdModule = PyModule_Create (&RtePyModuleDef);
+#else
+   pWasdModule = Py_InitModule ("wasd", RtePyMethods);
+#endif
+   if (!pWasdModule)
+   {
+      ReportError (__LINE__, 0, 1, ErrorPython);
+      return;
+   }
+
+   if (!SetupOsEnvCgiVar (1)) return;
+
+   PyErr_Clear ();
+
+#if PY_MAJOR_VERSION >= 3
+   DEBUGPYOBJ("->pByteCode", ccptr->pByteCode)
+#else
+   DEBUGPYOBJ("->pByteCode", (struct _object*)ccptr->pByteCode)
+#endif
+
+   if (!ccptr->pMainDict2)
+   {
+      ccptr->pMainDict2 = PyObject_CallMethod(pMainDict, "copy", "()");
+      if (!ccptr->pMainDict2)
+      {
+         ReportError (__LINE__, 0, 1, ErrorPython);
+         return;
+      }
+      DEBUGPYOBJ("ccptr->pMainDict2",ccptr->pMainDict2)
+   }
+
+   pResult = PyEval_EvalCode (ccptr->pByteCode, ccptr->pMainDict2,
+                                                ccptr->pMainDict2);
+   DEBUGPYOBJ("pResult",pResult)
+   Py_XDECREF (pResult);
+
+   /* new reference */
+   if (pError = PyErr_Occurred())
+   {
+      DEBUGPYOBJ("pError",pError)
+      Py_XDECREF (pError);
+      if (watches) WatchErrorCallout ();
+      error = ReportError (__LINE__, 0, 1, NULL);
+   }
+
+   /* explicitly flush the buffer */
+   pResult = PyObject_CallMethod (pStdout, "flush", NULL);
+   Py_XDECREF (pResult);
+
+#if PY_MAJOR_VERSION >= 3
+   if (dbug) fprintf (stdout, "PyGILState_Check() %d\n", PyGILState_Check());
+#endif
+
+   /* revert to the main thread */
+   PyThreadState_Swap (pStateBuffer);
+
+   if (error || !ReuseThread || !ccptr->FileNamePtr)
+   {
+      /* destroy the current thread */
+      ccptr->pThreadState = FreshThread (ccptr->pThreadState, NULL);
+      ccptr->ThreadUsageCount = 0;
+   }
+
+
+   if (!SetupOsEnvCgiVar (0)) return;
+}
+
+/*****************************************************************************/
+/*
+Create the script byte-code by either loading the source code (.PY) and
+compiling, or loading the pre-compiled byte code (.PYC) or optimised,
+pre-compiled byte-code (.PYO).  Reports it's own errors.  Success is indicated
+by '->pByteCode' containing a pointer to the code object, and failing by it
+being NULL.
+*/
+
+void LoadByteCode
+(
+char *FileName,
+char *ScriptName,
+struct CodeCacheStruct *ccptr
+)
+{
+   int  fclen, status,
+        FileNameLength;
+   unsigned long  magic, mtime;
+   char  *cptr, *fcptr, *sptr, *zptr,
+         *CharPtr;
+   char  FileNameByteCode [256];
+   FILE  *fp;
+   stat_t  FstatBuffer;
+   struct CodeMetricStruct  *cmptr;
+
+   /*********/
+   /* begin */
+   /*********/
+
+   if (dbug) fprintf (stdout, "LoadByteCode() |%s|%s|\n", FileName, ScriptName);
+
+   if (watches) WatchCallout ("LOAD %s %s", ScriptName, FileName);
+
+   ccptr->LastUsedSecs = ccptr->ScriptMtimeSecs = ccptr->CodeUsageCount = 0;
+
+   if (ccptr->pByteCode)
+   {
+      /* remove reference to any cached code object */
+      Py_DECREF (ccptr->pByteCode);
+      ccptr->pByteCode = NULL;
+   }
+
+   FlushCacheEntry (ccptr);
+
+   zptr = (sptr = FileNameByteCode) + sizeof(FileNameByteCode);
+   for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++);
+   FileNameLength = cptr - FileName;
+   CharPtr = sptr;
+   /* first; try to open a '.pyo' (optimised byte-code) */
+   *sptr++ = 'o';
+   if (sptr > zptr)
+   {
+      ReportError (__LINE__, 0, 0, ErrorOverflow);
+      return;
+   }
+   *sptr = '\0';
+
+   fp = NULL;
+   for (;;)
+   {
+      if (dbug) fprintf (stdout, "|%s|\n", FileNameByteCode);
+      /* note; opened in 'binary' mode */
+      fp = fopen (FileNameByteCode, "rb", "shr=get");
+      if (fp && watches) WatchCallout ("BYTECODE %s", FileNameByteCode);
+      if (fp) break;
+      /* third; try to open a '.py' (source) */
+      if (*CharPtr == 'c') break;
+      /* second; try to open a '.pyc' (compiled byte-code) */
+      *CharPtr = 'c';
+   }
+   if (!fp)
+   {
+      /* well, it's certainly not pre-compiled! */
+      FileNameByteCode[0] = '\0';
+      if (dbug) fprintf (stdout, "|%s|\n", FileName);
+      /* note; opened in 'record' mode */
+      fp = fopen (FileName, "r", "ctx=rec", "shr=get");
+      if (fp && watches) WatchCallout ("CODE %s", FileName);
+      if (!fp)
+      {
+         if (ProctorDetect ()) return;
+         status = vaxc$errno;
+         ReportError (__LINE__, status, 0, "Error opening !AZ", ScriptName);
+         return;
+      }
+   }
+   if (fstat (fileno(fp), &FstatBuffer) < 0)
+   {
+      status = vaxc$errno;
+      fclose (fp);
+      ReportError (__LINE__, status, 0, "Error fstat() !AZ", ScriptName);
+      return;
+   }
+
+   PyErr_Clear ();
+
+   if (FileNameByteCode[0])
+   {
+      /* byte-code file; discard these first two longwords */
+      magic = PyMarshal_ReadLongFromFile (fp);
+      mtime = PyMarshal_ReadLongFromFile (fp);
+      /* what's left is the byte-code */
+#if PY_MAJOR_VERSION >= 3
+      ccptr->pByteCode = PyMarshal_ReadObjectFromFile (fp);
+#else
+      ccptr->pByteCode = (PyCodeObject*)PyMarshal_ReadObjectFromFile (fp);
+#endif
+      fclose (fp);
+      if (!ccptr->pByteCode)
+      {
+         ReportError (__LINE__, 0, 1, NULL);
+         return;
+      }
+   }
+   else
+   {
+      /* source code file */
+      status = ReadFileIntoMemory (FileName, &fcptr, &fclen);
+      if (!(status & 1))
+      {
+         ReportError (__LINE__, status, 0, NULL);
+         return;
+      }
+#if PY_MAJOR_VERSION >= 3
+      ccptr->pByteCode = Py_CompileString (fcptr, ScriptName, Py_file_input);
+#else
+      ccptr->pByteCode = (PyCodeObject*)Py_CompileString (fcptr, ScriptName,
+                                                          Py_file_input);
+#endif
+      free (fcptr);
+      fclose (fp);
+      if (!ccptr->pByteCode)
+      {
+         ReportError (__LINE__, 0, 1, NULL);
+         return;
+      }
+   }
+
+   /* successful load */
+   ccptr->ScriptMtimeSecs = FstatBuffer.st_mtime;
+   ccptr->FileNamePtr = calloc (1, FileNameLength+1);
+   if (!ccptr->FileNamePtr) exit (vaxc$errno);
+   strcpy (ccptr->FileNamePtr, FileName);
+   ccptr->ScriptNamePtr = calloc (1, strlen(ScriptName)+1);
+   if (!ccptr->ScriptNamePtr) exit (vaxc$errno);
+   strcpy (ccptr->ScriptNamePtr, ScriptName);
+   if (FileNameByteCode[0])
+   {
+      ccptr->FileNameByteCodePtr = calloc (1, FileNameLength+2);
+      if (!ccptr->FileNameByteCodePtr) exit (vaxc$errno);
+      strcpy (ccptr->FileNameByteCodePtr, FileNameByteCode);
+   }
+   if (dbug)
+   {
+      fprintf (stdout, "|%s|%s| %u", ccptr->FileNamePtr,
+               ccptr->FileNameByteCodePtr, ccptr->pByteCode);
+#if PY_MAJOR_VERSION >= 3
+      DEBUGPYOBJ("->pByteCode", ccptr->pByteCode)
+#else
+      DEBUGPYOBJ("->pByteCode", (struct _object*)ccptr->pByteCode)
+#endif
+   }
+}
+
+/*****************************************************************************/
+/*
+Check if this script (source or byte) code has been modified.
+*/
+
+int ByteCodeUpToDate (struct CodeCacheStruct *ccptr)
+
+{
+   int  retval;
+   stat_t  StatBuffer;
+
+   /*********/
+   /* begin */
+   /*********/
+
+   if (dbug) fprintf (stdout, "ByteCodeUpToDate()\n");
+
+   if (ccptr->FileNameByteCodePtr)
+      retval = stat (ccptr->FileNameByteCodePtr, &StatBuffer);
+   else
+      retval = stat (ccptr->FileNamePtr, &StatBuffer);
+   if (retval) return (0);
+   /* if the script file is more recent than the cached byte-code */
+   if (dbug) fprintf (stdout, "%d - %d %d secs\n",
+                      StatBuffer.st_mtime, ccptr->ScriptMtimeSecs,
+                      StatBuffer.st_mtime - ccptr->ScriptMtimeSecs);
+   if (watches) WatchCallout ("MODIFIED %d secs",
+                              StatBuffer.st_mtime - ccptr->ScriptMtimeSecs);
+   if (StatBuffer.st_mtime != ccptr->ScriptMtimeSecs) return (0);
+   return (1);
+}
+
+/*****************************************************************************/
+/*
+Free dynamic components, the thread, and reset the structure.
 */
 
 void FlushCacheEntry (struct CodeCacheStruct *ccptr)
@@ -1532,10 +1960,12 @@
 
    if (dbug) fprintf (stdout, "FlushCacheEntry()\n");
 
-   if (ccptr->FileNamePtr)
-   {
-      free (ccptr->FileNamePtr);
-      ccptr->FileNamePtr = NULL;
-   }
-   if (ccptr->FileNameByteCodePtr)
+   if (ccptr->FileNamePtr) free (ccptr->FileNamePtr);
+   if (ccptr->FileNameByteCodePtr) free (ccptr->FileNameByteCodePtr);
+   if (ccptr->ScriptNamePtr) free (ccptr->ScriptNamePtr);
+   if (ccptr->CodeMetricPtr) free (ccptr->CodeMetricPtr);
+   ccptr->FileNamePtr = ccptr->FileNameByteCodePtr =
+      ccptr->ScriptNamePtr = NULL;
+   ccptr->CodeMetricPtr = NULL;
+   if (ccptr->pByteCode)
    {
@@ -1541,4 +1971,4 @@
    {
-      free (ccptr->FileNameByteCodePtr);
-      ccptr->FileNameByteCodePtr = NULL;
+      Py_DECREF (ccptr->pByteCode); 
+      ccptr->pByteCode = NULL;
    }
@@ -1544,3 +1974,3 @@
    }
-   if (ccptr->CodeMetricPtr)
+   if (ccptr->pMainDict2)
    {
@@ -1546,6 +1976,11 @@
    {
-      free (ccptr->CodeMetricPtr);
-      ccptr->CodeMetricPtr = NULL;
+      Py_XDECREF (ccptr->pMainDict2);
+      ccptr->pMainDict2 = NULL;
+   }
+   if (ccptr->pThreadState)
+   {
+      ccptr->pThreadState = FreshThread (ccptr->pThreadState, NULL);
+      ccptr->ThreadUsageCount = 0;
    }
 }
 
@@ -1637,7 +2072,6 @@
 
 /*****************************************************************************/
 /*
-For Python 3 covert an array of pointers to char to an array of pointers to
-wide char.  For Python 2 just return the original array of pointers to char.
+Set the Python argv.
 */
 
@@ -1642,7 +2076,7 @@
 */
 
-#if PY_MAJOR_VERSION >= 3
-
-wchar_t** MungeArgv (int argc, char *argv[])
+#define VARRAY_SIZE 32
+
+void SetArgv (char *ScriptName)
 
 {
@@ -1647,10 +2081,11 @@
 
 {
-   static wchar_t*  wargv [32];
-
-   int  idx;
+   int  cnt, idx;
+   char  *cptr;
+   char  *pargv [VARRAY_SIZE];
+   wchar_t*  wargv [VARRAY_SIZE];
 
    /*********/
    /* begin */
    /*********/
 
@@ -1652,18 +2087,40 @@
 
    /*********/
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "MungeArgv()\n");
-
-   /* completely arbitrary */
-   if (argc >= 32) exit (SS$_BUGCHECK);
-
-   /* free any existing array of wide strings */
-   for (idx = 0; idx < 32; idx++)
+   if (dbug) fprintf (stdout, "SetArgv() %d\n", MainArgc);
+
+#if PY_MAJOR_VERSION >= 3
+
+   if (MainArgc >= VARRAY_SIZE) exit (SS$_BUGCHECK);
+
+   memset (wargv, 0, sizeof(wargv));
+
+   idx = 0;
+   if (ScriptName) wargv[idx++] = Py_DecodeLocale (ScriptName, NULL);
+   for (cnt = 1; cnt < MainArgc && idx < VARRAY_SIZE; cnt++) 
+   {
+      if (dbug) fprintf (stdout, "%d |%s|\n", idx, MainArgv[cnt]);
+      if (!MainArgv[cnt]) break;
+      wargv[idx++] = Py_DecodeLocale (MainArgv[cnt], NULL);
+   }
+
+   for (cnt = 0;
+        (cptr = Trn2Lnm ("PYRTE_ARGV", NULL, cnt)) && idx < VARRAY_SIZE;
+        cnt++)
+   {
+      if (dbug) fprintf (stdout, "%d |%s|\n", idx, cptr);
+      wargv[idx++] = Py_DecodeLocale (cptr, NULL);
+   }
+
+   PySys_SetArgv (idx, wargv);
+
+   /* free array of wide strings */
+   for (idx = 0; idx < VARRAY_SIZE; idx++)
    {
       if (!wargv[idx]) continue;
       if (wargv[idx]) PyMem_RawFree (wargv[idx]);
       wargv[idx] = NULL;
    }
 
@@ -1664,8 +2121,14 @@
    {
       if (!wargv[idx]) continue;
       if (wargv[idx]) PyMem_RawFree (wargv[idx]);
       wargv[idx] = NULL;
    }
 
-   for (idx = 0; idx < argc; idx++)
+#else /* PY_MAJOR_VERSION < 3 */
+
+   memset (pargv, 0, sizeof(pargv));
+
+   idx = 0;
+   if (ScriptName) pargv[idx++] = strdup (ScriptName);
+   for (cnt = 1; cnt < MainArgc && idx < VARRAY_SIZE; cnt++) 
    {
@@ -1671,6 +2134,6 @@
    {
-      if (dbug) fprintf (stdout, "%d |%s|\n", idx, argv[idx]);
-      if (!argv[idx]) return (NULL);
-      wargv[idx] = Py_DecodeLocale (argv[idx], NULL);
+      if (dbug) fprintf (stdout, "%d |%s|\n", idx, MainArgv[cnt]);
+      if (!MainArgv[cnt]) break;
+      pargv[idx] = strdup (MainArgv[cnt]);
    }
 
@@ -1675,26 +2138,21 @@
    }
 
-   return (wargv);
-}
-
-#else /* PY_MAJOR_VERSION < 3 */
-
-char** MungeArgv (int argc, char *argv[])
-
-{
-   int  idx;
-
-   /*********/
-   /* begin */
-   /*********/
-
-   if (dbug) fprintf (stdout, "MungeArgv()\n");
-
-   if (dbug)
-      for (idx = 0; idx < argc; idx++)
-         fprintf (stdout, "%d |%s|\n", idx, argv[idx]);
-
-   return (argv);
-}
+   for (cnt = 0;
+        (cptr = Trn2Lnm ("PYRTE_ARGV", NULL, cnt)) && idx < VARRAY_SIZE;
+        cnt++)
+   {
+      if (dbug) fprintf (stdout, "%d |%s|\n", idx, cptr);
+      pargv[idx++] = strdup (cptr);
+   }
+
+   PySys_SetArgv (idx, pargv);
+
+   /* free array of strings */
+   for (idx = 0; idx < VARRAY_SIZE; idx++)
+   {
+      if (!pargv[idx]) continue;
+      if (pargv[idx]) free (pargv[idx]);
+      pargv[idx] = NULL;
+   }
 
 #endif /* PY_MAJOR_VERSION >= 3 */
@@ -1699,5 +2157,6 @@
 
 #endif /* PY_MAJOR_VERSION >= 3 */
+}
 
 /*****************************************************************************/
 /*
@@ -1701,10 +2160,6 @@
 
 /*****************************************************************************/
 /*
-Create the script byte-code by either loading the source code (.PY) and
-compiling, or loading the pre-compiled byte code (.PYC) or optimised,
-pre-compiled byte-code (.PYO).  Reports it's own errors.  Success is indicated
-by '->pByteCode' containing a pointer to the code object, and failing by it
-being NULL.
+Write data to WATCH [x]Script (useful for debugging).
 */
 
@@ -1709,4 +2164,4 @@
 */
 
-void LoadByteCode
+static PyObject* RteWATCH
 (
@@ -1712,6 +2167,5 @@
 (
-char *FileName,
-char *ScriptName,
-struct CodeCacheStruct *ccptr
+PyObject *self,
+PyObject *args
 )
 {
@@ -1716,11 +2170,4 @@
 )
 {
-   int  flen, status,
-        FileNameLength;
-   unsigned long  magic, mtime;
-   char  *cptr, *fptr, *sptr, *zptr,
-         *CharPtr;
-   char  FileNameByteCode [256];
-   FILE  *fp;
-   stat_t  FstatBuffer;
+   int  DataLength;
 #if PY_MAJOR_VERSION >= 3
@@ -1726,3 +2173,3 @@
 #if PY_MAJOR_VERSION >= 3
-   struct _mod  *pNode;
+   const char  *DataPtr;
 #else
@@ -1728,3 +2175,3 @@
 #else
-   struct _node  *pNode;
+   char  *DataPtr;
 #endif
@@ -1730,7 +2177,7 @@
 #endif
-   struct CodeMetricStruct  *cmptr;
+   PyObject  *pValue;
 
    /*********/
    /* begin */
    /*********/
 
@@ -1732,34 +2179,13 @@
 
    /*********/
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "LoadByteCode() |%s|\n", FileName);
-
-   ccptr->LastUsedSecs = ccptr->ScriptMtimeSecs = ccptr->CodeUsageCount = 0;
-
-   if (ccptr->pByteCode)
-   {
-      /* remove reference to any cached code object */
-      Py_DECREF (ccptr->pByteCode);
-      ccptr->pByteCode = NULL;
-   }
-
-   FlushCacheEntry (ccptr);
-
-   zptr = (sptr = FileNameByteCode) + sizeof(FileNameByteCode);
-   for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++);
-   FileNameLength = cptr - FileName;
-   CharPtr = sptr;
-   /* first; try to open a '.pyo' (optimised byte-code) */
-   *sptr++ = 'o';
-   if (sptr > zptr)
-   {
-      ReportError (__LINE__, 0, 0, ErrorOverflow);
-      return;
-   }
-   *sptr = '\0';
-
-   fp = NULL;
-   for (;;)
+/**
+   if (dbug) fprintf (stdout, "RteWATCH()\n");
+   DEBUGPYOBJ("self",self)
+   DEBUGPYOBJ("args",args)
+**/
+
+   if (!CgiPlusEscPtr)
    {
@@ -1765,10 +2191,4 @@
    {
-      if (dbug) fprintf (stdout, "|%s|\n", FileNameByteCode);
-      /* note; opened in 'binary' mode */
-      fp = fopen (FileNameByteCode, "rb", "shr=get");
-      if (fp) break;
-      /* third; try to open a '.py' (source) */
-      if (*CharPtr == 'c') break;
-      /* second; try to open a '.pyc' (compiled byte-code) */
-      *CharPtr = 'c';
+      Py_INCREF (Py_None);
+      return (Py_None);
    }
@@ -1774,3 +2194,6 @@
    }
-   if (!fp)
+
+   if (!PyArg_ParseTuple (args, "O", &pValue)) return (NULL);
+
+   if (pValue == Py_None)
    {
@@ -1776,14 +2199,6 @@
    {
-      /* well, it's certainly not pre-compiled! */
-      FileNameByteCode[0] = '\0';
-      if (dbug) fprintf (stdout, "|%s|\n", FileName);
-      /* note; opened in 'record' mode */
-      fp = fopen (FileName, "r", "ctx=rec", "shr=get");
-      if (!fp)
-      {
-         if (ProctorDetect ()) return;
-         status = vaxc$errno;
-         ReportError (__LINE__, status, 0, "Error opening !AZ", ScriptName);
-         return;
-      }
+      AppOutputCount++;
+      fflush (stdout);
+      Py_INCREF (Py_None);
+      return (Py_None);
    }
@@ -1789,16 +2204,3 @@
    }
-   if (fstat (fileno(fp), &FstatBuffer) < 0)
-   {
-      status = vaxc$errno;
-      fclose (fp);
-      ReportError (__LINE__, status, 0, "Error fstat() !AZ", ScriptName);
-      return;
-   }
-
-   if (FileNameByteCode[0])
-   {
-      /* byte-code file; discard these first two longwords */
-      magic = PyMarshal_ReadLongFromFile (fp);
-      mtime = PyMarshal_ReadLongFromFile (fp);
-      /* what's left is the byte-code */
+
 #if PY_MAJOR_VERSION >= 3
@@ -1804,3 +2206,3 @@
 #if PY_MAJOR_VERSION >= 3
-      ccptr->pByteCode = PyMarshal_ReadObjectFromFile (fp);
+   if (PyUnicode_Check (pValue))
 #else
@@ -1806,3 +2208,3 @@
 #else
-      ccptr->pByteCode = (PyCodeObject*)PyMarshal_ReadObjectFromFile (fp);
+   if (PyString_Check (pValue))
 #endif
@@ -1808,10 +2210,2 @@
 #endif
-      fclose (fp);
-      if (!ccptr->pByteCode)
-      {
-         ReportError (__LINE__, 0, 1, NULL);
-         return;
-      }
-   }
-   else
    {
@@ -1817,9 +2211,2 @@
    {
-      /* source code file */
-      status = ReadFileIntoMemory (FileName, &fptr, &flen);
-      if (!(status & 1))
-      {
-         ReportError (__LINE__, 0, 1, NULL);
-         return;
-      }
 #if PY_MAJOR_VERSION >= 3
@@ -1825,3 +2212,3 @@
 #if PY_MAJOR_VERSION >= 3
-      ccptr->pByteCode = Py_CompileString (fptr, ScriptName, Py_file_input);
+      DataPtr = PyUnicode_AsUTF8AndSize (pValue, &DataLength);
 #else
@@ -1827,4 +2214,3 @@
 #else
-      ccptr->pByteCode = (PyCodeObject*)Py_CompileString (fptr, ScriptName,
-                                                          Py_file_input);
+      PyString_AsStringAndSize (pValue, &DataPtr, &DataLength);
 #endif
@@ -1830,10 +2216,17 @@
 #endif
-      free (fptr);
-      fclose (fp);
-      if (!ccptr->pByteCode)
-      {
-         ReportError (__LINE__, 0, 1, NULL);
-         return;
-      }
+      if (dbug) fprintf (stdout, "%d |%s|\n", DataLength, DataPtr);
+
+      fflush (stdout);
+      fputs (CgiPlusEscPtr, stdout);
+      fflush (stdout);
+      /* the leading '!' indicates we're not going to read the response */
+      fputs ("!WATCH: WATCH ", stdout);
+      fwrite (DataPtr, DataLength, 1, stdout);
+      fflush (stdout);
+      fputs (CgiPlusEotPtr, stdout);
+      fflush (stdout);
+
+      Py_INCREF (Py_None);
+      return (Py_None);
    }
 
@@ -1838,56 +2231,7 @@
    }
 
-   /* successful load */
-   ccptr->ScriptMtimeSecs = FstatBuffer.st_mtime;
-   ccptr->FileNamePtr = calloc (1, FileNameLength+1);
-   if (!ccptr->FileNamePtr) exit (vaxc$errno);
-   strcpy (ccptr->FileNamePtr, FileName);
-   if (FileNameByteCode[0])
-   {
-      ccptr->FileNameByteCodePtr = calloc (1, FileNameLength+2);
-      if (!ccptr->FileNameByteCodePtr) exit (vaxc$errno);
-      strcpy (ccptr->FileNameByteCodePtr, FileNameByteCode);
-   }
-   if (dbug)
-   {
-      fprintf (stdout, "|%s|%s| %u", ccptr->FileNamePtr,
-               ccptr->FileNameByteCodePtr, ccptr->pByteCode);
-#if PY_MAJOR_VERSION >= 3
-      DEBUGPYOBJ("->pByteCode", ccptr->pByteCode)
-#else
-      DEBUGPYOBJ("->pByteCode", (struct _object*)ccptr->pByteCode)
-#endif
-   }
-}
-
-/*****************************************************************************/
-/*
-Check if this script (source or byte) code has been modified.
-*/
-
-int ByteCodeUpToDate (struct CodeCacheStruct *ccptr)
-
-{
-   int  retval;
-   stat_t  StatBuffer;
-
-   /*********/
-   /* begin */
-   /*********/
-
-   if (dbug) fprintf (stdout, "ByteCodeUpToDate()\n");
-
-   if (ccptr->FileNameByteCodePtr)
-      retval = stat (ccptr->FileNameByteCodePtr, &StatBuffer);
-   else
-      retval = stat (ccptr->FileNamePtr, &StatBuffer);
-   if (retval) return (0);
-   /* if the script file is more recent than the cached byte-code */
-   if (dbug) fprintf (stdout, "|%d|%d|%s\n",
-                       StatBuffer.st_mtime, ccptr->ScriptMtimeSecs,
-                 StatBuffer.st_mtime == ccptr->ScriptMtimeSecs ? "YES" : "NO");
-   if (StatBuffer.st_mtime != ccptr->ScriptMtimeSecs) return (0);
-   return (1);
+   PyErr_SetString (PyExc_TypeError, "type must be String or None");
+   return (NULL);
 }
 
 /*****************************************************************************/
@@ -1903,4 +2247,7 @@
 )
 {
    int  DataLength;
+#if PY_MAJOR_VERSION >= 3
+   const char  *DataPtr;
+#else
    char  *DataPtr;
@@ -1906,4 +2253,5 @@
    char  *DataPtr;
+#endif
    PyObject  *pValue;
 
    /*********/
@@ -1927,9 +2275,9 @@
    }
 
 #if PY_MAJOR_VERSION >= 3
-   if (PyBytes_Check (pValue))
+   if (PyUnicode_Check (pValue))
 #else
    if (PyString_Check (pValue))
 #endif
    {
 #if PY_MAJOR_VERSION >= 3
@@ -1931,9 +2279,9 @@
 #else
    if (PyString_Check (pValue))
 #endif
    {
 #if PY_MAJOR_VERSION >= 3
-      PyBytes_AsStringAndSize (pValue, &DataPtr, &DataLength);
+      DataPtr = PyUnicode_AsUTF8AndSize (pValue, &DataLength);
 #else
       PyString_AsStringAndSize (pValue, &DataPtr, &DataLength);
 #endif
@@ -1966,6 +2314,7 @@
 
 {
    static char  RequestMethodGet[] = "REQUEST_METHOD=GET";
+   static PyObject  *pMainEnvironDict = NULL;
 
    int  retval;
    char  *cptr, *sptr,
@@ -1977,10 +2326,9 @@
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "SetupOsEnvCgiVar() %d\n", InitEnv);
-
-   if (!HeadCvtGet)
-      HeadCvtGet = (TrnLnm ("PYRTE_HEAD_CVT_GET", NULL) != NULL);
-
-   if (InitEnv)
+   if (dbug) fprintf (stdout, "SetupOsEnvCgiVar(%d)\n", InitEnv);
+
+   if (!HeadCvtGet) HeadCvtGet = (TrnLnm ("PYRTE_HEAD_CVT_GET") != NULL);
+
+   if (!pMainEnvironDict)
    {
@@ -1986,8 +2334,8 @@
    {
-      /* borrowed reference */
-      pOsEnvironDict = PyDict_GetItemString (pOsDict, "environ");
-      if (!pOsEnvironDict)
+      /* get the "original" environment dictionary */
+      pMainEnvironDict = PyDict_GetItemString (pOsDict, "environ");
+      if (!pMainEnvironDict)
       {
          ReportError (__LINE__, 0, 1, ErrorCgiVarEnv);
          return (0);
       }
@@ -1990,7 +2338,12 @@
       {
          ReportError (__LINE__, 0, 1, ErrorCgiVarEnv);
          return (0);
       }
-      DEBUGPYOBJ("pOsEnvironDict",pOsEnvironDict)
-
+      /* borrowed reference */
+      Py_INCREF (pMainEnvironDict);
+      DEBUGPYOBJ("pMainEnvironDict",pMainEnvironDict)
+   }
+
+   if (InitEnv)
+   {
       /* new reference */
@@ -1996,3 +2349,3 @@
       /* new reference */
-      pEnvironDict = PyObject_CallMethod(pOsEnvironDict, "copy", "()");
+      pEnvironDict = PyObject_CallMethod(pMainEnvironDict, "copy", "()");
       DEBUGPYOBJ("pEnvironDict",pEnvironDict)
@@ -1998,4 +2351,3 @@
       DEBUGPYOBJ("pEnvironDict",pEnvironDict)
-
       while (cptr = CgiVar ("*"))
       {
@@ -2000,20 +2352,5 @@
       while (cptr = CgiVar ("*"))
       {
-         /** if (dbug) fprintf (stdout, "|%s|\n", cptr); **/
-
-         if ((toupper(*cptr) == 'F' &&
-              !strncmp (cptr, "FORM_", 5)) ||
-             (toupper(*cptr) == 'K' &&
-              !strncmp (cptr, "KEY_", 4)) ||
-             (toupper(*cptr) == 'P' &&
-              !strcmp (cptr, "PYRTE")) ||
-             (toupper(*cptr) == 'G' &&
-              (!strncmp (cptr, "GATEWAY_EOF", 11) ||
-               !strncmp (cptr, "GATEWAY_EOT", 11) ||
-               !strncmp (cptr, "GATEWAY_ESC", 11) ||
-               !strncmp (cptr, "GATEWAY_BG", 10))))
-            continue;
-
          if (HeadCvtGet &&
              toupper(*cptr) == 'R' &&
              !strcmp (cptr, "REQUEST_METHOD=HEAD"))
@@ -2022,6 +2359,7 @@
          for (sptr = cptr; *sptr && *sptr != '='; sptr++);
          if (!*sptr) continue;
          *sptr = '\0';
+         if (dbug) fprintf (stdout, "|%s|\n", cptr);
 #if PY_MAJOR_VERSION >= 3
          pValue = PyUnicode_FromString (sptr+1);
 #else
@@ -2025,6 +2363,7 @@
 #if PY_MAJOR_VERSION >= 3
          pValue = PyUnicode_FromString (sptr+1);
 #else
+         /* wsgi complained it was not a string! */
          pValue = PyString_FromString (sptr+1);
 #endif
          retval = PyMapping_SetItemString (pEnvironDict, cptr, pValue);
@@ -2045,5 +2384,6 @@
          return (0);
       }
       if (dbug) fprintf (stdout, "PyMapping_SetItemString() %d\n", retval);
+      DEBUGPYOBJ("pOsDict",pOsDict)
    }
    else
@@ -2048,5 +2388,6 @@
    }
    else
+   if (pEnvironDict)
       Py_DECREF (pEnvironDict);
 
    return (1);
@@ -2114,10 +2455,10 @@
 Python method to return the number of requests processed by this cached code.
 */
 
-static PyObject* RtePyMethUsageInterpreter ()
+static PyObject* RtePyMethUsageThread ()
 
 {
    /*********/
    /* begin */
    /*********/
 
@@ -2118,12 +2459,12 @@
 
 {
    /*********/
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "RtePyMethUsageInterpreter()\n");
-
-   return (Py_BuildValue ("i", CodeCachePtr->InterpreterUsageCount));
+   if (dbug) fprintf (stdout, "RtePyMethUsageThread()\n");
+
+   return (Py_BuildValue ("i", CodeCachePtr->ThreadUsageCount));
 }
 
 /*****************************************************************************/
@@ -2140,7 +2481,7 @@
 
    if (dbug) fprintf (stdout, "RtePyMethUsageRte()\n");
 
-   return (Py_BuildValue ("i", UsageCount));
+   return (Py_BuildValue ("i", PyrteUsageCount));
 }
 
 /*****************************************************************************/
@@ -2162,4 +2503,43 @@
 
 /*****************************************************************************/
 /*
+Wait for next request.
+*/
+
+void CgiPlusBegin ()
+
+{
+   /*********/
+   /* begin */
+   /*********/
+
+   if (dbug) fprintf (stdout, "CgiPlusBegin()\n");
+
+   if (CgiPlusState)
+   {
+      ReportError (__LINE__, SS$_BUGCHECK, 0, NULL);
+      exit (SS$_BUGCHECK);
+   }
+
+   /* first one (zeroeth) is always available */
+   if (PyrteUsageCount++) CgiVar ("");
+
+   CgiPlusState++;
+   DbugCheck (1);
+
+   AppOutputCount = 0;
+   sys$gettim (&CurrentBinTime);
+   CurrentTimeSecs = decc$fix_time (&CurrentBinTime);
+
+   RteScriptParam ();
+
+   if (!SetupOsEnvCgiVar (1))
+   {
+      ReportError (__LINE__, SS$_BUGCHECK, 0, NULL);
+      exit (SS$_BUGCHECK);
+   }
+}
+
+/*****************************************************************************/
+/*
 Python method to wait for the next CGIplus request.
@@ -2165,3 +2545,5 @@
 Python method to wait for the next CGIplus request.
-If not CGIplus (i.e. standard CGI) the function returns immediately.
+
+If an RTE caching or non-caching RTE execution just return True.
+
 If a CGIplus request has been begun but not explicitly ended this function
@@ -2167,12 +2549,17 @@
 If a CGIplus request has been begun but not explicitly ended this function
-provides the WASD script EOF sentinal.
-It then blocks waiting for the next request.
-
-It accepts zero, one or two parameters.  The first is a boolean (integer 0 or
-1) that if True allows a standard CGI script to call this once before
-returning False.  The second parameter is also a boolean (integer 0 or 1).  If
-True it gets the script code modification time to check for changes.  If
-modified it returns False.
+provides the WASD script EOF sentinal.  It then blocks waiting for the next
+request.
+
+If not CGIplus (i.e. standard CGI or RTE) the function returns True immediately
+and if called again False.  That is, a one-shot execution.  The function then
+returns to the caller which with RTE waits for the next request.  With CGI
+that's all folks.
+
+The function usage accepts zero, one or two parameters.  The first is a boolean
+(integer 0 or 1) that if True allows a standard CGI script to call this once
+before returning False.  The second parameter is also a boolean (integer 0 or
+1).  If True it gets the script code modification time to check for changes. 
+If modified it returns False.
 */
 
 static PyObject* RtePyMethCgiPlusBegin
@@ -2181,7 +2568,7 @@
 PyObject *args
 )
 {
-   static int  CallCount = 0;
+   static int  OneShot;
 
    int  AllowSingleCGI = 0,
         CheckCodeMtime = 0;
@@ -2191,14 +2578,9 @@
    /* begin */
    /*********/
 
-   if (!self && !args)
-   {
-      /* special case (not Python) reset the call count */
-      CallCount = 0;
-      return (NULL);
-   }
-
-   if (dbug) fprintf (stdout, "RtePyMethCgiPlusBegin()\n");
+   if (dbug) fprintf (stdout, "RtePyMethCgiPlusBegin() %u %u %d %d %d %d %d\n",
+                      self, args, CgiPlusState, IsScriptRte,
+                      IsCgiPlusScript, PyrteUsageCount, WsgiRunApp);
 
    if (WsgiRunApp)
    {
@@ -2202,8 +2584,7 @@
 
    if (WsgiRunApp)
    {
-      /* not available to WSGI applications */
       Py_INCREF (Py_None);
       return (Py_None);
    }
 
@@ -2206,6 +2587,6 @@
       Py_INCREF (Py_None);
       return (Py_None);
    }
 
-   if (!IsCgiPlusScript)
+   if (!(IsScriptRte || IsCgiPlusScript))
    {
@@ -2211,2 +2592,3 @@
    {
+      /* call from Python script inside standard CGI; allow one off */
       if (!PyArg_ParseTuple (args, "|i", &AllowSingleCGI)) return (NULL);
@@ -2212,4 +2594,5 @@
       if (!PyArg_ParseTuple (args, "|i", &AllowSingleCGI)) return (NULL);
-
-      if (AllowSingleCGI)
+      if (dbug) fprintf (stdout, "AllowSingleCGI:%d\n", AllowSingleCGI);
+
+      if (!OneShot && AllowSingleCGI)
       {
@@ -2215,8 +2598,6 @@
       {
-         if (!CallCount++)
-         {
-            Py_INCREF(Py_True);
-            return (Py_True);
-         }
+         OneShot = 1;
+         Py_INCREF(Py_True);
+         return (Py_True);
       }
 
@@ -2221,6 +2602,7 @@
       }
 
+      OneShot = 0;
       Py_INCREF(Py_False);
       return (Py_False);
    }
 
@@ -2223,6 +2605,6 @@
       Py_INCREF(Py_False);
       return (Py_False);
    }
 
-   if (CgiPlusPythonEnd)
+   if (IsCgiPlusScript)
    {
@@ -2228,27 +2610,10 @@
    {
-      if (!IsCgiPlusScript)
-      {
-         Py_INCREF(Py_False);
-         return (Py_False);
-      }
-
-      fflush (stdout);
-      fputs (CgiPlusEofPtr, stdout);
-      fflush (stdout);
-
-      if (!PyArg_ParseTuple (args, "|ii", &AllowSingleCGI, &CheckCodeMtime))
-         return (NULL);
-
-      if (CheckCodeMtime)
-      {
-         if (!ByteCodeUpToDate (CodeCachePtr))
-         {
-            Py_INCREF(Py_False);
-            return (Py_False);
-         }
-      }
-
-      CgiPlusPythonEnd = 0;
-      SetupOsEnvCgiVar (0);
+      if (!CgiPlusState)
+         /* wait for next request */
+         CgiPlusBegin ();
+      else
+         CgiPlusEnd ();
+      Py_INCREF(Py_True);
+      return (Py_True);
    }
 
@@ -2253,18 +2618,5 @@
    }
 
-   /* block waiting for the next request (if not the first usage) */
-   if (CallCount++) CgiVar ("");
-
-   UsageCount++;
-   CodeCachePtr->CodeUsageCount++;
-   CgiPlusPythonEnd = 1;
-   AppOutputCount = 0;
-
-   DbugCheck();
-
-   RteScriptParam ();
-
-   if (!NoStreamMode) fprintf (stdout, "Script-Control: X-stream-mode\n");
-
-   if (!SetupOsEnvCgiVar (1))
+   /* while wasd.cgiplus_begin(True): first is True then False */
+   if (CgiPlusState++ == 1)
    {
@@ -2270,4 +2622,11 @@
    {
+      /* first True */
+      Py_INCREF(Py_True);
+      return (Py_True);
+   }
+   else
+   {
+      /* then False */
       Py_INCREF(Py_False);
       return (Py_False);
    }
@@ -2271,9 +2630,36 @@
       Py_INCREF(Py_False);
       return (Py_False);
    }
-
-   Py_INCREF(Py_True);
-   return (Py_True);
+}
+
+/*****************************************************************************/
+/*
+Provide the end-of-request sentinal.
+*/
+
+void CgiPlusEnd ()
+
+{
+   /*********/
+   /* begin */
+   /*********/
+
+   if (dbug) fprintf (stdout, "CgiPlusEnd() %d\n", CgiPlusState);
+
+   if (!CgiPlusState)
+   {
+      ReportError (__LINE__, SS$_BUGCHECK, 0, NULL);
+      exit (SS$_BUGCHECK);
+   }
+
+   DbugCheck (0);
+   CgiPlusState = 0;
+
+   fflush (stdout);
+   fputs (CgiPlusEofPtr, stdout);
+   fflush (stdout);
+
+   SetupOsEnvCgiVar (0);
 }
 
 /*****************************************************************************/
@@ -2299,11 +2685,19 @@
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "RtePyMethCgiPlusEnd()\n");
-
-   if (!IsCgiPlusScript)
+   if (dbug) fprintf (stdout, "RtePyMethCgiPlusEnd() %u %u %d\n",
+                      self, args, CgiPlusState);
+
+   if (WsgiRunApp)
+   {
+      /* not available to WSGI applications */
+      Py_INCREF (Py_None);
+      return (Py_None);
+   }
+
+   if (!IsCgiPlusScript || !CgiPlusState)
    {
       Py_INCREF(Py_False);
       return (Py_False);
    }
 
@@ -2305,14 +2699,15 @@
    {
       Py_INCREF(Py_False);
       return (Py_False);
    }
 
-   fflush (stdout);
-   fputs (CgiPlusEofPtr, stdout);
-   fflush (stdout);
-
-   CgiPlusPythonEnd = 0;
-   SetupOsEnvCgiVar (0);
+   if (!CgiPlusState)
+   {
+      Py_INCREF(Py_False);
+      return (Py_False);
+   }
+
+   CgiPlusEnd ();
 
    if (!PyArg_ParseTuple (args, "|i", &CheckCodeMtime)) return (NULL);
 
@@ -2362,7 +2757,7 @@
    {
       /* with plain old CGI might not already be open */
       if (!CgiPlusInFp)
-         if (!(CgiPlusInFp = fopen (TrnLnm("CGIPLUSIN", NULL), "r")))
+         if (!(CgiPlusInFp = fopen (TrnLnm("CGIPLUSIN"), "r")))
             exit (vaxc$errno);
 
       rewind (CgiPlusInFp);
@@ -2376,5 +2771,4 @@
       {
          /* excise the (CRTL) trailing newline */
          if (retval) retval--;
-#if PY_MAJOR_VERSION >= 3
          pReturn = PyUnicode_FromStringAndSize (buf, retval);
@@ -2380,7 +2774,4 @@
          pReturn = PyUnicode_FromStringAndSize (buf, retval);
-#else
-         pReturn = PyString_FromStringAndSize (buf, retval);
-#endif
       }
    }
 
@@ -2422,5 +2813,4 @@
    {
       /* excise the (CRTL) trailing newline */
       if (retval) retval--;
-#if PY_MAJOR_VERSION >= 3
       pReturn = PyUnicode_FromStringAndSize (bptr, retval);
@@ -2426,7 +2816,4 @@
       pReturn = PyUnicode_FromStringAndSize (bptr, retval);
-#else
-      pReturn = PyString_FromStringAndSize (bptr, retval);
-#endif
    }
 
    free (bptr);
@@ -2436,7 +2823,7 @@
 
 /*****************************************************************************/
 /*
-Set whether the Python interpreter being used is disposed of at the end of the
+Set whether the Python thread being used is disposed of at the end of the
 current request (0 or False) or resued for the next request (1 or True).
 */
 
@@ -2440,7 +2827,7 @@
 current request (0 or False) or resued for the next request (1 or True).
 */
 
-static PyObject* RtePyMethReuseInterpreter
+static PyObject* RtePyMethReuseThread
 (
 PyObject *self,
 PyObject *args
@@ -2450,9 +2837,9 @@
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "RtePyMethReuseInterpreter()\n");
-
-   if (!PyArg_ParseTuple (args, "i", &ReuseInterpreter)) return (NULL);
+   if (dbug) fprintf (stdout, "RtePyMethReuseThread()\n");
+
+   if (!PyArg_ParseTuple (args, "i", &ReuseThread)) return (NULL);
    Py_INCREF(Py_None);
    return (Py_None);
 }
@@ -2501,7 +2888,7 @@
 
    if (dbug) fprintf (stdout, "RtePyMethRteCacheEntry()\n");
 
-   if (!TrnLnm ("PYRTE_CACHE_ENTRY", NULL))
+   if (!TrnLnm ("PYRTE_CACHE_ENTRY"))
    {
       PyErr_SetString (PyExc_RuntimeError,
                        "logical name PYRTE_CACHE_ENTRY not defined");
@@ -2524,9 +2911,9 @@
 
    sprintf (StringBuf, "%u,%u,%u,%u,%s,%s,%u,%u",
             CodeCache[idx].CodeUsageCount, 
-            CodeCache[idx].InterpreterUsageCount, 
+            CodeCache[idx].ThreadUsageCount, 
             CodeCache[idx].LastUsedSecs, 
             CodeCache[idx].ScriptMtimeSecs, 
             CodeCache[idx].FileNamePtr, 
             CodeCache[idx].FileNameByteCodePtr ? 
                CodeCache[idx].FileNameByteCodePtr : "(null)", 
@@ -2528,9 +2915,9 @@
             CodeCache[idx].LastUsedSecs, 
             CodeCache[idx].ScriptMtimeSecs, 
             CodeCache[idx].FileNamePtr, 
             CodeCache[idx].FileNameByteCodePtr ? 
                CodeCache[idx].FileNameByteCodePtr : "(null)", 
-            (unsigned long)CodeCache[idx].pInterpState, 
+            (unsigned long)CodeCache[idx].pThreadState, 
             (unsigned long)CodeCache[idx].pByteCode); 
 
    idx++;
@@ -2852,5 +3239,4 @@
 
    cptr = CgiVar ("REQUEST_SCHEME");
    if (!*cptr || !strcmp (cptr, "http:"))
-#if PY_MAJOR_VERSION >= 3
       pValue = PyUnicode_FromString ("http");
@@ -2856,5 +3242,2 @@
       pValue = PyUnicode_FromString ("http");
-#else
-      pValue = PyString_FromString ("http");
-#endif
    else
@@ -2860,3 +3243,2 @@
    else
-#if PY_MAJOR_VERSION >= 3
       pValue = PyUnicode_FromString ("https");
@@ -2862,7 +3244,4 @@
       pValue = PyUnicode_FromString ("https");
-#else
-      pValue = PyString_FromString ("https");
-#endif
    PyMapping_SetItemString (pEnvironDict, "wsgi.url_scheme", pValue);
    Py_DECREF(pValue);
 
@@ -3022,5 +3401,4 @@
    BodyLength = cptr - BodyPtr;
 
    if (BodyLength == 0)
-#if PY_MAJOR_VERSION >= 3
       pBody = PyUnicode_FromString("");
@@ -3026,6 +3404,3 @@
       pBody = PyUnicode_FromString("");
-#else
-      pBody = PyString_FromString("");
-#endif
    else
    if (BodyLength == strlen (BodyPtr))
@@ -3030,4 +3405,3 @@
    else
    if (BodyLength == strlen (BodyPtr))
-#if PY_MAJOR_VERSION >= 3
       pBody = PyUnicode_FromString (BodyPtr);
@@ -3033,5 +3407,2 @@
       pBody = PyUnicode_FromString (BodyPtr);
-#else
-      pBody = PyString_FromString (BodyPtr);
-#endif
    else
@@ -3037,3 +3408,2 @@
    else
-#if PY_MAJOR_VERSION >= 3
       pBody = PyUnicode_FromStringAndSize (BodyPtr, BodyLength);
@@ -3039,7 +3409,4 @@
       pBody = PyUnicode_FromStringAndSize (BodyPtr, BodyLength);
-#else
-      pBody = PyString_FromStringAndSize (BodyPtr, BodyLength);
-#endif
 
    if (BodyPtr) free (BodyPtr);
 
@@ -3072,6 +3439,6 @@
          WsgiForceBuffering = 1;
       else
       if (!strncasecmp (cptr, "/reuse", 6)) 
-         ReuseInterpreter = 1;
+         ReuseThread = 1;
       else
       if (!strncasecmp (cptr, "/noreuse", 8)) 
@@ -3076,6 +3443,6 @@
       else
       if (!strncasecmp (cptr, "/noreuse", 8)) 
-         ReuseInterpreter = 0;
+         ReuseThread = 0;
       else
       if (!strncasecmp (cptr, "/nostream", 9)) 
          NoStreamMode = 1;
@@ -3093,7 +3460,7 @@
 /*
 */
 
-void DbugCheck ()
+int DbugCheck (int enable)
 {
    char  *cptr, *sptr;
 
@@ -3101,12 +3468,7 @@
    /* begin */
    /*********/
 
-   if (!GlobalDebug)
-      if (cptr = TrnLnm ("PYRTE$DBUG", NULL))
-         if (!strchr (cptr, '*'))
-            if (sptr = CgiVar ("SCRIPT_NAME"))
-               if (!strstr (sptr, cptr))
-                  cptr = NULL;
-
-   if ((dbug = GlobalDebug) || (dbug = (cptr != NULL)))
+   if (!enable) return (dbug = 0);
+
+   if (cptr = TrnLnm ("PYRTE$DBUG"))
    {
@@ -3112,6 +3474,15 @@
    {
-      fprintf (stdout,
+      dbug = 1;
+      if (!strchr (cptr, '*'))
+         if (sptr = CgiVar ("SCRIPT_NAME"))
+            if (!strstr (sptr, cptr))
+               dbug = 0;
+   }
+
+   if (!dbug) return (0);
+
+   fprintf (stdout,
 "Script-Control: X-record-mode\n\
 Content-Type: text/plain\n\
 \n\
 ***** %s (%s) Python %s *****\n",
@@ -3114,13 +3485,10 @@
 "Script-Control: X-record-mode\n\
 Content-Type: text/plain\n\
 \n\
 ***** %s (%s) Python %s *****\n",
-               SoftwareId, BUILD_DATETIME, Py_GetVersion());
-fprintf (stdout, "+++++ %d %d\n",cptr,sptr);
-      fflush (stdout);
-      NoStreamMode = 1;
-   }
-
+            SoftwareId, BUILD_DATETIME, Py_GetVersion());
+   fflush (stdout);
+   NoStreamMode = 1;
 }
 
 /****************************************************************************/
@@ -3226,10 +3594,7 @@
    }
 
    if (dbug)
-      if (BufferCount < 50000)
-         fprintf (stdout, "%d |%s|\n", BufferCount, BufferPtr);
-      else
-         fprintf (stdout, "%d || TOO LARGE!\n", BufferCount);
+      fprintf (stdout, "%d |%s|\n", BufferCount, BufferPtr);
 
    if (FileTextPtr != NULL) *FileTextPtr = BufferPtr;
    if (FileSizePtr != NULL) *FileSizePtr = BufferCount;
@@ -3281,7 +3646,7 @@
 Make it look a bit like the standard WASD error message.
 */
 
-void ReportError
+int ReportError
 (
 int SourceCodeLine,
 int VmsStatus,
@@ -3290,4 +3655,7 @@
 ...
 )
 {
+   static int  ErrorReported;
+   static FILE  *CgiPlusIn;
+
    int  argcnt, status,
@@ -3293,5 +3661,7 @@
    int  argcnt, status,
-        CgiTbLoaded;
+        CgiLoaded,
+        CgiTbLoaded,
+        HttpStatus;
    unsigned short  ShortLen;
    char  *cptr, *sptr;
    char  Buffer [2048],
@@ -3309,9 +3679,18 @@
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "ReportError() %d\n", SourceCodeLine);
-
-   CgiTbLoaded = 0;
+   if (dbug) fprintf (stdout, "ReportError() %d %%X%08.08X %d\n",
+                      SourceCodeLine, VmsStatus, PythonErrorPrint);
+
+   if (!SourceCodeLine)
+   {
+      /* return if reported error while resetting */
+      status = ErrorReported;
+      ErrorReported = 0;
+      return (status);
+   }
+
+   CgiLoaded = CgiTbLoaded = 0;
    if (Py_IsInitialized())
    {
       /* assume that if it's loaded it's also enabled */
@@ -3322,7 +3701,6 @@
             CgiTbLoaded = 1;
          Py_DECREF (pObject);
       }
-      if (dbug) fprintf (stdout, "pObject:%u CgiTbLoaded:%d\n",
-                         pObject, CgiTbLoaded);
-      if (CgiTbLoaded)
+      pObject = PyUnicode_FromString ("cgi");
+      if (pObject)
       {
@@ -3328,10 +3706,6 @@
       {
-         if (PythonErrorPrint)
-         {
-            PyErr_Print();
-            fprintf (stdout, "<!-- PyRTE %s LINE:%d -->\n",
-                     SoftwareId, SourceCodeLine);
-            return;
-         }
+         if (pMainDict && PyDict_Contains (pMainDict, pObject) == 1)
+            CgiLoaded = 1;
+         Py_DECREF (pObject);
       }
    }
@@ -3336,5 +3710,38 @@
       }
    }
+   if (dbug) fprintf (stdout, "CgiLoaded:%d CgiTbLoaded:%d\n",
+                      CgiLoaded, CgiTbLoaded);
+
+   ErrorReported = 1;
+
+   HttpStatus = 0;
+   if (ServerSoftware() >= 110502)
+   {
+      /* for WASD v11.5.2 and later detect if there has been a response */
+      if (!CgiPlusIn)
+         if (!(CgiPlusIn = fopen ("CGIPLUSIN:", "r")))
+            exit (vaxc$errno);
+
+      fflush (stdout);
+      fputs (CgiPlusEscPtr, stdout);
+      fflush (stdout);
+      fputs ("HTTP-STATUS:", stdout);
+      fflush (stdout);
+      fgets (Buffer, sizeof(Buffer), CgiPlusIn);
+      fputs (CgiPlusEotPtr, stdout);
+      fflush (stdout);
+      if (atoi(Buffer) == 200)
+         HttpStatus = atoi(Buffer+4);
+   }
+
+   if (CgiTbLoaded)
+   {
+      if (!HttpStatus)
+         fputs ("Status: 502\nContent-Type: text/html\n\n", stdout);
+      PyErr_Print();
+      fprintf (stdout, "<!-- PyRTE LINE:%d -->\n", SourceCodeLine);
+      return (ErrorReported);
+   }
 
    va_count (argcnt);
    vecptr = FaoVector;
@@ -3360,9 +3767,8 @@
 
    if (AppOutputCount)
    {
-      fprintf (stdout, "\n<!-- PyRTE %s LINE:%d -->\n",
-               SoftwareId, SourceCodeLine);
+      fprintf (stdout, "\n<!-- PyRTE LINE:%d -->\n", SourceCodeLine);
       if (VmsStatus) fprintf (stdout, "<!-- %%X%08.08X -->\n", VmsStatus);
       if (!CgiTbLoaded) fputs ("<pre>\n----------\n", stdout);
       PyErr_Print();
       if (!CgiTbLoaded) fputs ("----------\n</pre>\n", stdout);
@@ -3365,7 +3771,7 @@
       if (VmsStatus) fprintf (stdout, "<!-- %%X%08.08X -->\n", VmsStatus);
       if (!CgiTbLoaded) fputs ("<pre>\n----------\n", stdout);
       PyErr_Print();
       if (!CgiTbLoaded) fputs ("----------\n</pre>\n", stdout);
-      return;
+      return (ErrorReported);
    }
 
@@ -3370,8 +3776,10 @@
    }
 
-   fputs ("Status: 502\nContent-Type: text/html\n\n", stdout);
-
-   fprintf (stdout,
+   if (!HttpStatus)
+   {
+      fputs ("Status: 502\nContent-Type: text/html\n\n", stdout);
+
+      fprintf (stdout,
 "<!DOCTYPE html>\n\
 <html>\n\
 <head>\n\
@@ -3382,8 +3790,9 @@
 <body>\n\
 <font size=\"+1\"><b>ERROR 502</b> &nbsp;-&nbsp; \
 External agent did not respond (or not acceptably)</font>\n",
-           SoftwareId,
-           SourceCodeLine);
+               SoftwareId,
+               SourceCodeLine);
+   }
 
    if (VmsStatus) fprintf (stdout, "<!-- %%X%08.08X -->\n", VmsStatus);
 
@@ -3404,6 +3813,8 @@
 </body>\n\
 </html>\n",
             cptr ? cptr : "", cptr ? "\n" : "");
+
+   return (ErrorReported);
 }
 
 /*****************************************************************************/
@@ -3432,9 +3843,8 @@
 
    static int  CalloutDone,
                StructLength;
-   static char  *EmptyScriptNamePtr = NULL,
-                *NextVarNamePtr;
+   static char  *NextVarNamePtr;
    static char  StructBuffer [CGIVAR_STRUCT_SIZE];
    
    int  status;
    int  Length;
@@ -3437,8 +3847,8 @@
    static char  StructBuffer [CGIVAR_STRUCT_SIZE];
    
    int  status;
    int  Length;
-   char  *bptr, *cptr, *sptr;
+   char  *bptr, *cptr, *sptr, *vptr;
 
    /*********/
    /* begin */
@@ -3474,15 +3884,9 @@
             sptr = (NextVarNamePtr += SOUS);
             NextVarNamePtr += Length;
             /* by default CGI variable names are prefixed by "WWW_", ignore */
-            sptr += 4;
-            /* ensure an empty SCRIPT_NAME *is* empty */
-            if (toupper(*sptr) != 'S') return (sptr);
-            if (toupper(*(sptr+1)) != 'C') return (sptr); 
-            if (!EmptyScriptName (sptr, NULL)) return (sptr);
-            if (EmptyScriptNamePtr) return (EmptyScriptNamePtr);
-            EmptyScriptNamePtr = calloc (1, 13);
-            if (EmptyScriptNamePtr) strcpy (EmptyScriptNamePtr, "SCRIPT_NAME=");
-            return (EmptyScriptNamePtr);
+            if (*(unsigned long*)sptr == 'WWW_') sptr += 4;
+            sptr = EmptyScriptName (sptr);
+            return (sptr);
          }
 
          /* standard CGI */
@@ -3498,9 +3902,9 @@
          for (bptr=StructBuffer; Length=*(unsigned short*)bptr; bptr+=Length)
          {
             /* by default CGI variable names are prefixed by "WWW_", ignore */
-            sptr = (bptr += SOUS) + 4;
+            sptr = vptr = (bptr += SOUS) + 4;
             for (cptr = VarName; *cptr && *sptr && *sptr != '='; cptr++, sptr++)
                if (toupper(*cptr) != toupper(*sptr)) break;
             /* if found return a pointer to the value */
             if (!*cptr && *sptr == '=')
             {
@@ -3502,15 +3906,10 @@
             for (cptr = VarName; *cptr && *sptr && *sptr != '='; cptr++, sptr++)
                if (toupper(*cptr) != toupper(*sptr)) break;
             /* if found return a pointer to the value */
             if (!*cptr && *sptr == '=')
             {
-               sptr++;
-               /* ensure an empty SCRIPT_NAME *is* empty */
-               if (toupper(*sptr) != 'S') return (sptr);
-               if (toupper(*(sptr+1)) != 'C') return (sptr); 
-               if (!EmptyScriptName (VarName, sptr)) return (sptr);
-               while (*sptr) sptr++;
-               return (sptr);
+               sptr = EmptyScriptName (vptr);
+               return (sptr + ((cptr - VarName) + 1));
             }
          }
          /* not found */
@@ -3534,7 +3933,7 @@
 
    /* the CGIPLUSIN stream can be left open */
    if (!CgiPlusInFp)
-      if (!(CgiPlusInFp = fopen (TrnLnm("CGIPLUSIN", NULL), "r")))
+      if (!(CgiPlusInFp = fopen (TrnLnm("CGIPLUSIN"), "r")))
          exit (vaxc$errno);
 
    /* get the starting record (the essentially discardable one) */
@@ -3538,6 +3937,7 @@
          exit (vaxc$errno);
 
    /* get the starting record (the essentially discardable one) */
+   DbugCheck (0);
    for (;;)
    {
       cptr = fgets (StructBuffer, sizeof(StructBuffer), CgiPlusInFp);
@@ -3607,6 +4007,8 @@
       CalloutDone = 1;
    }
 
+   DbugCheck (1);
+
    return (NULL);
 
 #  undef SOUS
@@ -3678,6 +4080,7 @@
 NULL };
 
    static int  SymbolIndex = 0;
-   static char  WwwName [256] = "WWW_";
-   static $DESCRIPTOR (NameDsc, WwwName);
+   static char  *wptr = NULL;
+   static char SymbolBuffer [2048];
+   static $DESCRIPTOR (NameDsc, SymbolBuffer);
    static $DESCRIPTOR (ValueDsc, "");
@@ -3683,3 +4086,4 @@
    static $DESCRIPTOR (ValueDsc, "");
+   static $DESCRIPTOR (WwwServerDsc, "WWW_SERVER_SOFTWARE");
 
    int  status;
@@ -3684,10 +4088,9 @@
 
    int  status;
-   unsigned short  ShortLength;
-   char  *cptr;
-   char  Value [1024];
+   unsigned short  slen;
+   char  *cptr, *sptr, *vnptr, *zptr;
 
    /*********/
    /* begin */
    /*********/
 
@@ -3689,8 +4092,17 @@
 
    /*********/
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "CgiVarDclSymbol()\n");
+//   if (dbug) fprintf (stdout, "CgiVarDclSymbol() |%s|\n", VarName);
+
+   if (!wptr)
+   {
+      status = lib$get_symbol (&WwwServerDsc, &ValueDsc, &slen, NULL);
+      if (status & 1)
+         wptr = "WWW_";
+      else
+         wptr = "";
+   }
 
    if (VarName[0] == '*')
@@ -3695,5 +4107,5 @@
 
    if (VarName[0] == '*')
-      cptr = SymbolNames[SymbolIndex++];
+      vnptr = SymbolNames[SymbolIndex++];
    else
    {
@@ -3698,6 +4110,6 @@
    else
    {
-      cptr = VarName;
+      vnptr = VarName;
       SymbolIndex = 0;
    }
 
@@ -3701,5 +4113,5 @@
       SymbolIndex = 0;
    }
 
-   while (cptr)
+   while (vnptr)
    {
@@ -3705,7 +4117,11 @@
    {
-      /* by default WASD CGI variable names are prefixed by "WWW_", add */
-      strncpy (WwwName+4, cptr, sizeof(WwwName)-5);
-      NameDsc.dsc$w_length = strlen(WwwName);
-      ValueDsc.dsc$a_pointer = Value;
-      ValueDsc.dsc$w_length = sizeof(Value)-1;
+      zptr = (sptr = SymbolBuffer) + sizeof(SymbolBuffer)-1;
+      if (*wptr) for (cptr = wptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
+      for (cptr = vnptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
+      NameDsc.dsc$a_pointer = SymbolBuffer;
+      NameDsc.dsc$w_length = sptr - SymbolBuffer;
+      if (sptr < zptr) *sptr++ = '=';
+      *sptr = '\0';
+      ValueDsc.dsc$a_pointer = sptr;
+      ValueDsc.dsc$w_length = (sizeof(SymbolBuffer)-1) - (sptr - SymbolBuffer);
    
@@ -3711,4 +4127,4 @@
    
-      status = lib$get_symbol (&NameDsc, &ValueDsc, &ShortLength, NULL);
+      status = lib$get_symbol (&NameDsc, &ValueDsc, &slen, NULL);
       if (status & 1)
       {
@@ -3713,13 +4129,13 @@
       if (status & 1)
       {
-         /* ensure an empty SCRIPT_NAME *is* empty */
-         if (toupper(VarName[0]) != 'S' && EmptyScriptName (VarName, Value))
-            ShortLength = 0;
-         cptr = calloc (1, ShortLength+1);
-         memcpy (cptr, Value, ShortLength);
-         cptr[ShortLength] = '\0';
-         return (cptr);
+         sptr[slen] = '\0';
+         sptr = strdup (*wptr ? SymbolBuffer+4 : SymbolBuffer);
+         sptr = EmptyScriptName (sptr);
+         if (VarName[0] == '*') return (sptr);
+         while (*sptr && *sptr != '=') sptr++;
+         if (*sptr) sptr++;
+         return (sptr);
       }
 
       if (VarName[0] != '*') return (NULL);
       /* assume the failure was 'no such symbol' and look further */
@@ -3722,8 +4138,8 @@
       }
 
       if (VarName[0] != '*') return (NULL);
       /* assume the failure was 'no such symbol' and look further */
-      cptr = SymbolNames[SymbolIndex++];
+      vnptr = SymbolNames[SymbolIndex++];
    }
 
    SymbolIndex = 0;
@@ -3733,6 +4149,5 @@
 /*****************************************************************************/
 /*
 Suppress the WASD-ism of an empty or 'root' script name (i.e. "/").
-If 'VarValue' is none then 'VarName' is "NAME=<value>" string.
 */
 
@@ -3737,8 +4152,5 @@
 */
 
-int EmptyScriptName
-(
-char *VarName,
-char *VarValue
-)
+char* EmptyScriptName (char *sptr)
+
 {
@@ -3744,8 +4156,5 @@
 {
-   char  *cptr = "SCRIPT_NAME",
-         *sptr = VarName;
-
    /*********/
    /* begin */
    /*********/
 
@@ -3748,24 +4157,12 @@
    /*********/
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "EmptyScriptName() %s %s\n", VarName, VarValue);
-
-   while (*cptr && *sptr && *sptr != '=')
-   { 
-      if (*cptr != toupper(*sptr)) break;
-      cptr++;
-      sptr++;
-   }
-   if (*cptr || (*sptr && *sptr != '=')) return (0);
-   if (VarValue)
-   {
-      if (*(unsigned short*)VarValue != '/\0') return (0);
-      if (!*VarValue) return (0);
-      return (1);
-   }
-   if (*(unsigned short*)(sptr+1) != '/\0') return (0);
-   return (1);
+//   if (dbug) fprintf (stdout, "EmptyScriptName() |%s|\n", sptr);
+
+   if (*(unsigned long*)sptr != 'SCRI') return (sptr);
+   if (!strncmp (sptr, "SCRIPT_NAME=/\0", 14)) return ("SCRIPT_NAME=");
+   return (sptr);
 }
 
 /*****************************************************************************/
@@ -3855,7 +4252,7 @@
 <p>%d Item(s)\n",
            SoftwareId, BUILD_DATETIME,
            SoftwareId, pyver,
-           ProcessPid, UsageCount,
+           ProcessPid, PyrteUsageCount,
            CodeCacheCount);
 
    if (CodeCacheCount)
@@ -4175,7 +4572,7 @@
       cmptr->JpiCpuTim[midx] = JpiCpuTim;
       cmptr->JpiDirIO[midx] = JpiDirIO;
       cmptr->JpiPageFlts[midx] = JpiPageFlts;
-      cmptr->UsageNumber[midx] = UsageCount;
+      cmptr->UsageNumber[midx] = PyrteUsageCount;
 
       zptr = (sptr = cmptr->RequestPath[midx]) +
              sizeof(cmptr->RequestPath[midx])-1;
@@ -4255,7 +4652,7 @@
 
 {
    static $DESCRIPTOR (FaoDsc,
-"REAL:!%T CPU:!UL.!2ZL DIO:!UL BIO:!UL FAULTS:!UL\0");
+"REAL:!%T CPU:!UL.!2ZL DIO:!UL BIO:!UL FAULTS:!UL PGFL:!UL/!UL%\0");
 
    static char  StatString [96];
    static $DESCRIPTOR (StatStringDsc, StatString);
@@ -4266,7 +4663,22 @@
                          LibStatTimerDio = 4,
                          LibStatTimerFaults = 5;
 
-   int  status;
+   static unsigned long  JpiPagFilCnt,
+                         JpiPgFlQuo;
+   static struct
+   {
+      unsigned short  buf_len;
+      unsigned short  item;
+      void  *buf_addr;
+      void  *ret_len;
+   } JpiItems [] =
+   {
+      { sizeof(JpiPagFilCnt), JPI$_PAGFILCNT, &JpiPagFilCnt, 0 },
+      { sizeof(JpiPgFlQuo), JPI$_PGFLQUOTA, &JpiPgFlQuo, 0 },
+      { 0,0,0,0 }
+   };
+
+   int  percent, status;
    unsigned long  CpuBinTime,
                   CountBio,
                   CountDio,
@@ -4283,6 +4695,10 @@
       return (NULL);
    }
 
+   status = sys$getjpiw (0, 0, 0, &JpiItems, 0, 0, 0);
+   if (!(status & 1)) exit (status);
+   percent = 100 - ((JpiPagFilCnt * 100) / JpiPgFlQuo);
+
    /* post-processing reset */
    lib$stat_timer (&LibStatTimerReal, &RealBinTime, 0);
    lib$stat_timer (&LibStatTimerCpu, &CpuBinTime, 0);
@@ -4291,10 +4707,10 @@
    lib$stat_timer (&LibStatTimerFaults, &CountFaults, 0);
    sys$fao (&FaoDsc, 0, &StatStringDsc,
             &RealBinTime, CpuBinTime/100, CpuBinTime%100,
-            CountDio, CountBio, CountFaults);
+            CountDio, CountBio, CountFaults, JpiPagFilCnt, percent);
 
    return (StatString);
 }
 
 /*****************************************************************************/
 /*
@@ -4295,12 +4711,52 @@
 
    return (StatString);
 }
 
 /*****************************************************************************/
 /*
-Translate a logical name using LNM$FILE_DEV.  Returns a pointer to the value
-string, or NULL if the name does not exist.  If 'LogValue' is supplied the
-logical name is translated into that (assumed to be large enough), otherwise
-it's translated into an internal static buffer.
+Using the WATCH [x]Script or PYRTE_DBUG or PYRTE_STAT_TIMER logical names.
+*/
+
+void StatTimerCallout (int count)
+
+{
+   fflush (stdout);
+   fputs (CgiPlusEscPtr, stdout);
+   fflush (stdout);
+   /* the leading '!' indicates we're not going to read the response */
+   fprintf (stdout, "!WATCH: %s USAGE:%d/%d %s\n",
+            SoftwareId, count, PyrteUsageCount, StatTimer(TRUE));
+   fflush (stdout);
+   fputs (CgiPlusEotPtr, stdout);
+   fflush (stdout);
+}
+
+/*****************************************************************************/
+/*
+Using the WATCH [x]Script formatted debug output.
+*/
+
+void WatchCallout (char *fmt, ...)
+
+{
+   va_list args;
+
+   fflush (stdout);
+   fputs (CgiPlusEscPtr, stdout);
+   fflush (stdout);
+   /* the leading '!' indicates we're not going to read the response */
+   fputs ("!WATCH: ", stdout);
+   va_start (args, fmt);
+   vfprintf (stdout, fmt, args);
+   va_end(args);
+   fflush (stdout);
+   fputs (CgiPlusEotPtr, stdout);
+   fflush (stdout);
+}
+
+/*****************************************************************************/
+/*
+Return an integer representing the server sooftware version.
+"HTTPd-WASD/11.5.2" should become 110502.
 */
 
@@ -4305,5 +4761,69 @@
 */
 
-char* TrnLnm
+int ServerSoftware (void)
+
+{
+   static int  ServerSoftware;
+   char  *cptr;
+
+   if (ServerSoftware) return (ServerSoftware);
+   if (!(cptr = CgiVar ("SERVER_SOFTWARE")))
+      return (ServerSoftware = 0);
+   while (*cptr && !isdigit(*cptr)) cptr++;
+   ServerSoftware = atoi(cptr) * 10000;
+   while (*cptr && isdigit(*cptr)) cptr++;
+   if (*cptr == '.') cptr++;
+   ServerSoftware += atoi(cptr) * 100;
+   while (*cptr && isdigit(*cptr)) cptr++;
+   if (*cptr == '.') cptr++;
+   ServerSoftware += atoi(cptr);
+   return (ServerSoftware);
+}
+
+/*****************************************************************************/
+/*
+Using the WATCH [x]Script formatted debug output.
+*/
+
+void WatchErrorCallout ()
+
+{
+   char  *type, *value;
+   PyObject  *ptype, *pstype, *pvalue, *psvalue, *ptrace;
+
+   PyErr_Fetch (&ptype, &pvalue, &ptrace);
+   pstype = PyObject_Repr (ptype);
+   psvalue = PyObject_Repr (pvalue);
+#if PY_MAJOR_VERSION >= 3
+   type = PyBytes_AsString (pstype);
+   value = PyBytes_AsString (psvalue);
+#else
+   type = PyString_AsString (pstype);
+   value = PyString_AsString (psvalue);
+#endif
+   WatchCallout ("ERROR %s %s", type, value);
+   PyErr_Restore (ptype, pvalue, ptrace);
+}
+
+/*****************************************************************************/
+/*
+Just a wrapper.
+*/
+
+char* TrnLnm (char *lnm)
+{
+   return (Trn2Lnm (lnm, NULL, 0));
+}
+
+/*****************************************************************************/
+/*
+Translate a logical name using LNM$FILE_DEV by default or the specified name
+table.  Returns a pointer to the value string, or NULL if the name does not
+exist.  'IndexValue' should be zero for a 'flat' logical name, or 0..127 for
+interative translations.  Returns pointer to non-reentrant static buffer which
+must be strdup()ed if retained.
+*/
+
+char* Trn2Lnm
 (
 char *LogName,
@@ -4308,5 +4828,6 @@
 (
 char *LogName,
-char *LogValue
+char *LogTable,
+int IndexValue
 )
 {
@@ -4311,5 +4832,7 @@
 )
 {
-   static unsigned short  ShortLength;
-   static char  StaticLogValue [256];
+   static unsigned short  ValueLength;
+   static unsigned long  LnmAttributes,
+                         LnmIndex;
+   static char  LogValue [256];
    static $DESCRIPTOR (LogNameDsc, "");
@@ -4315,5 +4838,5 @@
    static $DESCRIPTOR (LogNameDsc, "");
-   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
+   static $DESCRIPTOR (LogTableDsc, "");
    static struct {
       short int  buf_len;
       short int  item;
@@ -4321,8 +4844,10 @@
       unsigned short  *ret_len;
    } LnmItems [] =
    {
-      { 255, LNM$_STRING, 0, &ShortLength },
+      { sizeof(LnmIndex), LNM$_INDEX, &LnmIndex, 0 },
+      { sizeof(LnmAttributes), LNM$_ATTRIBUTES, &LnmAttributes, 0 },
+      { sizeof(LogValue), LNM$_STRING, LogValue, &ValueLength },
       { 0,0,0,0 }
    };
 
    int  status;
@@ -4325,10 +4850,9 @@
       { 0,0,0,0 }
    };
 
    int  status;
-   char  *cptr;
 
    /*********/
    /* begin */
    /*********/
 
@@ -4330,9 +4854,20 @@
 
    /*********/
    /* begin */
    /*********/
 
-   if (dbug) fprintf (stdout, "TrnLnm() |%s|\n", LogName);
+   LnmIndex = IndexValue;
+
+   if (LogTable)
+   {
+      LogTableDsc.dsc$a_pointer = LogTable;
+      LogTableDsc.dsc$w_length = strlen(LogTable);
+   }
+   else
+   {
+      LogTableDsc.dsc$a_pointer = "LNM$FILE_DEV";
+      LogTableDsc.dsc$w_length = sizeof("LNM$FILE_DEV")-1;
+   }
 
    LogNameDsc.dsc$a_pointer = LogName;
    LogNameDsc.dsc$w_length = strlen(LogName);
@@ -4336,12 +4871,7 @@
 
    LogNameDsc.dsc$a_pointer = LogName;
    LogNameDsc.dsc$w_length = strlen(LogName);
-   if (LogValue)
-      cptr = LnmItems[0].buf_addr = LogValue;
-   else
-      cptr = LnmItems[0].buf_addr = StaticLogValue;
-
-   status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems);
-   if (dbug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status);
-   if (!(status & 1))
+
+   status = sys$trnlnm (0, &LogTableDsc, &LogNameDsc, 0, &LnmItems);
+   if (!(status & 1) || !(LnmAttributes & LNM$M_EXISTS))
    {
@@ -4347,5 +4877,5 @@
    {
-      if (dbug) fprintf (stdout, "|(null)|\n");
+      if (LogValue) LogValue[0] = '\0';
       return (NULL);
    }
 
@@ -4349,9 +4879,8 @@
       return (NULL);
    }
 
-   cptr[ShortLength] = '\0';
-   if (dbug) fprintf (stdout, "|%s|\n", cptr);
-   return (cptr);
+   LogValue[ValueLength] = '\0';
+   return (LogValue);
 }
 
 /*****************************************************************************/
diff --git a/src/python/readmore.html b/src/python/readmore.html
index 9f21a62c1055b5b818c32c9850f57633a0ec207d_c3JjL3B5dGhvbi9yZWFkbW9yZS5odG1s..aff157d9025310d1eee56d29d81fd9d7ee58a922_c3JjL3B5dGhvbi9yZWFkbW9yZS5odG1s 100644
--- a/src/python/readmore.html
+++ b/src/python/readmore.html
@@ -88,5 +88,7 @@
 <center>
 
 <h1>Python Run-Time Environment</h1>
-<h3 style="margin-bottom:0;">Version 1.1.12, 30th July 2017</h3>
+<h3>~~~ PRE-RELEASE ~~~</h3>
+<h3>Version 2.0.0, 8th September 2020
+<br>Version 3.0.0, 8th September 2020</h3>
 
@@ -92,5 +94,5 @@
 
-<p><b>Copyright &copy; 2007-2017 Mark G. Daniel</b>
+<p><b>Copyright &copy; 2007-2020 Mark G. Daniel</b>
 <br>This program, comes with ABSOLUTELY NO WARRANTY.
 <br>This is free software, and you are welcome to redistribute it under the
 <br>conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.
@@ -106,6 +108,7 @@
 <li><a href="#example">Example Scripts</a>
 <li><a href="#wsgi">Web Server Gateway Interface</a>
 <li><a href="#lever">Leveraging PyRTE</a>
+<li><a href="#WATCHing">WATCHing Scripts</a>
 <li><a href="#problems">Problems</a>
 <li><a href="#releases">Releases</a>
 <li><a href="#ackn">Acknowlegements</a>
@@ -128,10 +131,10 @@
 command-line OpenVMS Python.  Alpha (AXP) and Itanium object modules are
 provided for the VMS platforms available for Python.
 
-<p> <span class="pyrte">PyRTE</span> has been developed and tested on VMS V8.3
-and V8.4, Alpha and  Itanium, using WASD v10.<i>n</i> and v11.<i>n</i> and VMS
-Python kits PYTHON272 and PYTHON278 available from and documented at 
+<p> <span class="pyrte">PyRTE</span> has been developed and tested on
+V8.4, Alpha and Itanium, using WASD v11.<i>n</i> and VMS Python kits available
+from and documented at 
 <a target="_blank" href="https://www.vmspython.org/">https://www.vmspython.org/</a>.  JFP's
 Python kits are kept very up-to-date as Python updates or implementation
 problems arise.
 
@@ -134,7 +137,13 @@
 <a target="_blank" href="https://www.vmspython.org/">https://www.vmspython.org/</a>.  JFP's
 Python kits are kept very up-to-date as Python updates or implementation
 problems arise.
 
+<p> <b>FYI:</b> Beginning August 2020, PyRTE underwent significant rework
+prompted by the pre-release of JFP's Python v3.10 port.  The core PyRTE code
+now supports both Python 2.7 and 3.10 as independent builds and executables. 
+PyRTE reports itself as v2.<i>n</i>.<i>n</i> for the Python 2.7 build, and as
+v3.<i>n</i>.<i>n</i> for Python 3.10.
+
 <a name="install">
 <h2>Installation</h2>
 </a>
@@ -179,9 +188,13 @@
 $ SET DEFAULT WASD_ROOT:[SRC.PYTHON]
 $ @BUILD_PYRTE LINK
 </pre>
+For Python 3:
+<pre class="code">
+$ @BUILD_PYRTE3 LINK
+</pre>
 
 <li> Copy the <span class="pyrte">PyRTE</span> engine to the script executable location.
 
 <pre class="code">
 $ COPY WASD_EXE:PYRTE.EXE CGI_EXE:
 </pre>
@@ -182,9 +195,13 @@
 
 <li> Copy the <span class="pyrte">PyRTE</span> engine to the script executable location.
 
 <pre class="code">
 $ COPY WASD_EXE:PYRTE.EXE CGI_EXE:
 </pre>
+For Python 3:
+<pre class="code">
+$ COPY WASD_EXE:PYRTE3.EXE CGI_EXE:
+</pre>
 
 <li> <a href="#configure_wasd">Configure</a> the Web server.
 
@@ -188,7 +205,7 @@
 
 <li> <a href="#configure_wasd">Configure</a> the Web server.
 
-<p><li> Start scripting&nbsp; <tt>:^)</tt>
+<p><li> Start scripting&nbsp; <span style="font-size:130%;">&#x263a;</span>
 <br>Try the <a href="#example">example scripts</a>.
 
 </ul>
@@ -211,7 +228,7 @@
 
 <ul>
 
-<li> Server global configuration (HTTPD$CONFIG) can be used to indicate which
-file types should activate the Python engine and how perform non-script access.
+<li> Server global configuration can be used to indicate which file types
+should activate the Python engine and how perform non-script access.
 
 <pre class="code">
@@ -216,6 +233,6 @@
 
 <pre class="code">
-# HTTPD$CONFIG
+# WASD_CONFIG_GLOBAL
 [DclScriptRunTime]
 .PY $CGI-BIN:[000000]PYRTE
 [AddType]
@@ -224,7 +241,15 @@
 .PYO  application/octet-stream  Python optimised byte-code
 </pre>
 
+For Python 3:
+
+<pre class="code">
+# WASD_CONFIG_GLOBAL
+[DclScriptRunTime]
+.PY $CGI-BIN:[000000]PYRTE3
+</pre>
+
 <li> The following rules require the Python script files to be located in the
 site administrator controlled /cgi-bin/ path.
 
 <pre class="code">
@@ -227,9 +252,9 @@
 <li> The following rules require the Python script files to be located in the
 site administrator controlled /cgi-bin/ path.
 
 <pre class="code">
-# HTTPD$MAP
+# WASD_CONFIG_MAP 
 exec /py-bin/* (cgi_exe:pyrte)/cgi-bin/* \
      script=syntax=unix script=query=none map=once ods=5
 </pre>
 
@@ -232,8 +257,16 @@
 exec /py-bin/* (cgi_exe:pyrte)/cgi-bin/* \
      script=syntax=unix script=query=none map=once ods=5
 </pre>
 
+For Python 3:
+
+<pre class="code">
+# WASD_CONFIG_MAP 
+exec /py-bin/* (cgi_exe:pyrte3)/cgi-bin/* \
+     script=syntax=unix script=query=none map=once ods=5
+</pre>
+
 <li> Alternatively, to automatically map scripts ending in .py to the RTE
 engine. 
 
 <pre class="code">
@@ -236,10 +269,10 @@
 <li> Alternatively, to automatically map scripts ending in .py to the RTE
 engine. 
 
 <pre class="code">
-# HTTPD$MAP for automatic RTE usage
+# WASD_CONFIG_MAP  for automatic RTE usage
 map /cgi-bin/*.py* /py-bin/*.py*
 exec /py-bin/* (cgi_exe:pyrte)/cgi-bin/* \
        script=syntax=unix script=query=none map=once ods=5
 </pre>
 
@@ -241,10 +274,17 @@
 map /cgi-bin/*.py* /py-bin/*.py*
 exec /py-bin/* (cgi_exe:pyrte)/cgi-bin/* \
        script=syntax=unix script=query=none map=once ods=5
 </pre>
 
+For Python 3:
+
+<pre class="code">
+exec /py-bin/* (cgi_exe:pyrte3)/cgi-bin/* \
+       script=syntax=unix script=query=none map=once ods=5
+</pre>
+
 <li> The following rule would allow .py type files anywhere in the mapped
 directory structure to be executed. This may be what is desired but might be
 dangerous in some circumstances.
 
 <pre class="code">
@@ -246,9 +286,9 @@
 <li> The following rule would allow .py type files anywhere in the mapped
 directory structure to be executed. This may be what is desired but might be
 dangerous in some circumstances.
 
 <pre class="code">
-# HTTPD$MAP
+# WASD_CONFIG_MAP 
 exec /web/**.py* /web/*.py* \
      script=syntax=unix script=query=none map=once ods=5
 </pre>
@@ -260,7 +300,7 @@
 detail).
 
 <pre class="code">
-# HTTPD$MAP
+# WASD_CONFIG_MAP 
 exec /appx/**.py* /appx_root/*.py* \
      script=syntax=unix script=query=none map=once ods=5 \
      script=as=USERX
@@ -270,8 +310,8 @@
 following configuration.
 
 <pre class="code">
-# HTTPD$MAP
+# WASD_CONFIG_MAP 
 exec /py-bin/* (cgi_exe:pyrte)/wasd_root/src/python/scripts/* \
      script=syntax=unix script=query=none map=once ods=5
 </pre>
 
@@ -274,7 +314,14 @@
 exec /py-bin/* (cgi_exe:pyrte)/wasd_root/src/python/scripts/* \
      script=syntax=unix script=query=none map=once ods=5
 </pre>
 
+For Python 3:
+
+<pre class="code">
+exec /py-bin/* (cgi_exe:pyrte3)/wasd_root/src/python/scripts/* \
+     script=syntax=unix script=query=none map=once ods=5
+</pre>
+
 Note that the location of the above example scripts has changed to [.SCRIPTS]
 since the v1.0 release.
 
@@ -448,6 +495,51 @@
 $ HTTPD/DO=DELETE
 </pre>
 
+<a name="WATCHing">
+<h2>WATCHing Scripts</h2>
+</a>
+
+<p> Debugging is always fun&nbsp; <span style="font-size:130%;">&#x2639;</span>
+<p> PyRTE provides some execution data via the WATCH report [X]Script item. 
+For example (note the SCRIPTs):
+
+<pre class="code" style="width:100%;;padding-right:40%;">
+|00:30:50.08 SERVICE  1735 093001 CONNECT    VIRTUAL klaatu.local:443|
+|00:30:50.08 REQUEST  4318 093001 REQUEST    GET /py-bin/wsgi_test3.py|
+|00:30:50.09 CACHE    0604 093001 RESPONSE   CACHE search path 5F1A99EFCB98C60126B8F244110E5BDD|
+|00:30:50.09 DCL      1572 093001 RESPONSE   SCRIPT as HTTP$NOBODY RTE /py-bin/wsgi_test3.py wasd_root:[src.python.scripts]wsgi_test3.py ($cgi_exe:pyrte.exe)|
+|00:30:50.10 DCL      7962 125001 SCRIPT     PYRTE AXP-2.0.0 Python 2.7.18 (default, Jul 23 2020, 17:40:05) [DECC]|
+|00:30:50.10 DCL      7945 093001 SCRIPT     RTE caching /py-bin/wsgi_test3.py /WASD_ROOT/src/PYTHON/SCRIPTS/wsgi_test3.py|
+|00:30:50.10 DCL      7945 093001 SCRIPT     CACHE new 1/1|
+|00:30:50.10 DCL      7945 093001 SCRIPT     LOAD /py-bin/wsgi_test3.py /WASD_ROOT/src/PYTHON/SCRIPTS/wsgi_test3.py|
+|00:30:50.10 DCL      7945 093001 SCRIPT     CODE /WASD_ROOT/src/PYTHON/SCRIPTS/wsgi_test3.py|
+|00:30:50.11 DCL      7945 093001 SCRIPT     EVAL /py-bin/wsgi_test3.py|
+|00:30:50.12 DCL      7945 093001 SCRIPT     PYRTE AXP-2.0.0 USAGE:1/19 REAL:00:00:00.02 CPU:0.02 DIO:3 BIO:36 FAULTS:0 PGFL:463040/8%|
+|00:30:50.12 GZIP     0608 093001 RESPONSE   DEFLATE 1289->507 bytes, 39% (261kB)|
+|00:30:50.12 REQUEST  1438 093001 REQUEST    STATUS 200 (OK) rx:78 tx:2415 bytes 1.045832 seconds 2,383 B/s|
+</pre>
+
+<p> Any reportable script error is indicated.
+
+<pre class="code" style="width:100%;padding-right:20%;">
+|00:33:50.17 DCL      7962 265001 SCRIPT     EVAL /py-bin/pyrte_test3.py|
+|00:33:56.82 DCL      7962 265001 SCRIPT     ERROR &lt;type 'exceptions.RuntimeError'&gt; 'logical name PYRTE_CACHE_ENTRY not defined'|
+</pre>
+
+<p> It is also possible to add items from within the script Python code using
+the wasd.WATCH() function. The example /py-bin/pyrte_test1.py script contains
+the statement&nbsp; <tt>wasd.WATCH('and hello [x]Script')</tt>
+
+<pre class="code" style="width:100%;padding-right:25%;">
+|00:35:15.05 DCL      7945 057001 SCRIPT     RTE caching /py-bin/pyrte_test1.py /WASD_ROOT/src/PYTHON/SCRIPTS/pyrte_test1.py|
+|00:35:15.05 DCL      7945 057001 SCRIPT     CACHE new 1/1|
+|00:35:15.05 DCL      7945 057001 SCRIPT     LOAD /py-bin/pyrte_test1.py /WASD_ROOT/src/PYTHON/SCRIPTS/pyrte_test1.py|
+|00:35:15.05 DCL      7945 057001 SCRIPT     CODE /WASD_ROOT/src/PYTHON/SCRIPTS/pyrte_test1.py|
+|00:35:15.06 DCL      7945 057001 SCRIPT     EVAL /py-bin/pyrte_test1.py|
+|00:35:15.07 DCL      7945 057001 SCRIPT     WATCH and hello [x]Script|
+|00:35:15.07 DCL      7945 057001 SCRIPT     PYRTE AXP-2.0.0 USAGE:1/1 REAL:00:00:05.10 CPU:5.06 DIO:65 BIO:1060 FAULTS:870 PGFL:476448/5%|
+</pre>
+
 <a name="problems">
 <h2>Problems?</h2>
 </a>
@@ -481,8 +573,17 @@
 
 <dl>
 
+<dt>v3.0.0&nbsp; 08-SEP-2020 ~~~ PRE-RELEASE ~~~</dt>
+<dd>&bull;&nbsp; Python version 3.10.0a0</dd>
+</dd>
+
+<dt>v2.0.0&nbsp; 08-SEP-2020 ~~~ PRE-RELEASE ~~~</dt>
+<dd>&bull;&nbsp; Python version 2.7.18</dd>
+</dd>
+
+</dd>
 <dt>v1.1.12&nbsp; 30-JUL-2017</dt>
 <dd>&bull;&nbsp; wasd.cgi_callout()
 <dd>&bull;&nbsp; bugfix 
 </dd>
 
@@ -484,56 +585,9 @@
 <dt>v1.1.12&nbsp; 30-JUL-2017</dt>
 <dd>&bull;&nbsp; wasd.cgi_callout()
 <dd>&bull;&nbsp; bugfix 
 </dd>
 
-<dt>v1.1.11&nbsp; 20-JUN-2013</dt>
-<dd>&bull;&nbsp; bugfix
-</dd>
-
-<dt>v1.1.10&nbsp; 05-NOV-2011</dt>
-<dd>&bull;&nbsp; Python 2.7.2, see changes to includes
-</dd>
-
-<dt>v1.1.9&nbsp; 27-FEB-2011</dt>
-<dd>&bull;&nbsp; /CLI= to allow a command-line script activation
-<dd>&bull;&nbsp; logical PYRTE_METRICS enables "?$metrics$" report
-</dd>
-
-<dt>v1.1.8&nbsp; 20-JUL-2010</dt>
-<dd>&bull;&nbsp; WASD v10.1 proctor detect
-</dd>
-
-<dt>v1.1.7&nbsp; 17-MAY-2010</dt>
-<dd>&bull;&nbsp; bugfix
-</dd>
-
-<dt>v1.1.6&nbsp; 30-APR-2010</dt>
-<dd>&bull;&nbsp; bugfix
-</dd>
-
-<dt>v1.1.5&nbsp; 29-APR-2010</dt>
-<dd>&bull;&nbsp; PYRTE_HEAD_CVT_GET and script=param=PYRTE=/HEAD=GET
-</dd>
-
-<dt>v1.1.4&nbsp; 08-DEC-2009</dt>
-<dd>&bull;&nbsp; bugfix
-</dd>
-
-<dt>v1.1.3&nbsp; 07-DEC-2009</dt>
-<dd>&bull;&nbsp; bugfix
-</dd>
-
-<dt>v1.1.2&nbsp; 19-APR-2008</dt>
-<dd>&bull;&nbsp; bugfix
-</dd>
-
-<dt>v1.1.1&nbsp; 17-JAN-2008</dt>
-<dd>&bull;&nbsp; bugfix
-</dd>
-
-<dt>v1.1.0&nbsp; 05-JAN-2008</dt>
-<dd>&bull;&nbsp; WSGI support</dd>
-</dd>
+&vellip;
 
 <dt>v1.0.0&nbsp; 22-APR-2007</dt>
 <dd>&bull;&nbsp; initial</dd>