You are viewing a plain text version of this content. The canonical link for it is here.
Posted to site-cvs@tcl.apache.org by mx...@apache.org on 2015/10/04 01:05:14 UTC
svn commit: r1706645 - in /tcl/rivet/trunk: ./ src/mod_rivet/
Author: mxmanghi
Date: Sat Oct 3 23:05:14 2015
New Revision: 1706645
URL: http://svn.apache.org/viewvc?rev=1706645&view=rev
Log:
* src/mod_rivet/mod_rivet.c: moving Rivet_ProcessorCleanup in
mod_rivet_common.c
* src/mod_rivet/mod_rivet.h: add exit status code to
thread private data
* src/mod_rivet/mod_rivet_common.c: Rivet_ProcessorCleanup now
in this file
* src/mod_rivet/rivet_worker_mpm.c: thread exit handling
now calling the finalize handling function. Functions naming
scheme reformed. Since the exit_thread command forces
the whole child process to exit the check on the thread
counter in the supervisor thread was changed as the thread
executing thread_exit brings about can't exit before
the supervisor itself terminates
* src/mod_rivet/rivet_prefork_mpm.c: function naming scheme
changed.
Modified:
tcl/rivet/trunk/ChangeLog
tcl/rivet/trunk/src/mod_rivet/mod_rivet.c
tcl/rivet/trunk/src/mod_rivet/mod_rivet.h
tcl/rivet/trunk/src/mod_rivet/mod_rivet_common.c
tcl/rivet/trunk/src/mod_rivet/mod_rivet_common.h
tcl/rivet/trunk/src/mod_rivet/rivetCore.c
tcl/rivet/trunk/src/mod_rivet/rivet_prefork_mpm.c
tcl/rivet/trunk/src/mod_rivet/rivet_worker_mpm.c
Modified: tcl/rivet/trunk/ChangeLog
URL: http://svn.apache.org/viewvc/tcl/rivet/trunk/ChangeLog?rev=1706645&r1=1706644&r2=1706645&view=diff
==============================================================================
--- tcl/rivet/trunk/ChangeLog (original)
+++ tcl/rivet/trunk/ChangeLog Sat Oct 3 23:05:14 2015
@@ -1,3 +1,20 @@
+2015-10-04 Massimo Manghi <mx...@apache.org>
+ * src/mod_rivet/mod_rivet.c: moving Rivet_ProcessorCleanup in
+ mod_rivet_common.c
+ * src/mod_rivet/mod_rivet.h: add exit status code to
+ thread private data
+ * src/mod_rivet/mod_rivet_common.c: Rivet_ProcessorCleanup now
+ in this file
+ * src/mod_rivet/rivet_worker_mpm.c: thread exit handling
+ now calling the finalize handling function. Functions naming
+ scheme reformed. Since the exit_thread command forces
+ the whole child process to exit the check on the thread
+ counter in the supervisor thread was changed as the thread
+ executing thread_exit brings about can't exit before
+ the supervisor itself terminates
+ * src/mod_rivet/rivet_prefork_mpm.c: function naming scheme
+ changed.
+
2015-09-04 Massimo Manghi <mx...@apache.org>
* src/mod_rivet/rivet_prefork_mpm.c: fix message severity
in finalize method
Modified: tcl/rivet/trunk/src/mod_rivet/mod_rivet.c
URL: http://svn.apache.org/viewvc/tcl/rivet/trunk/src/mod_rivet/mod_rivet.c?rev=1706645&r1=1706644&r2=1706645&view=diff
==============================================================================
--- tcl/rivet/trunk/src/mod_rivet/mod_rivet.c (original)
+++ tcl/rivet/trunk/src/mod_rivet/mod_rivet.c Sat Oct 3 23:05:14 2015
@@ -66,21 +66,24 @@ extern Tcl_ChannelType RivetChan;
apr_threadkey_t* rivet_thread_key = NULL;
mod_rivet_globals* module_globals = NULL;
-void Rivet_PerInterpInit ( rivet_thread_interp* interp,
- rivet_thread_private* private,
- server_rec *s, apr_pool_t *p );
-static int Rivet_ExecuteAndCheck (rivet_thread_private *private, Tcl_Obj *tcl_script_obj);
-int Rivet_InitCore (Tcl_Interp *interp,rivet_thread_private* p);
+void Rivet_PerInterpInit (rivet_thread_interp* interp,
+ rivet_thread_private* private,
+ server_rec *s, apr_pool_t *p );
+static int Rivet_ExecuteAndCheck (rivet_thread_private *private, Tcl_Obj *tcl_script_obj);
+int Rivet_InitCore (Tcl_Interp *interp,rivet_thread_private* p);
#define ERRORBUF_SZ 256
/*
+ * -- Rivet_Exit_Handler
+ *
+ *
*
*/
int Rivet_Exit_Handler(int code)
{
- Tcl_Exit(code);
+ //Tcl_Exit(code);
/*NOTREACHED*/
return TCL_OK; /* Better not ever reach this! */
}
@@ -430,90 +433,6 @@ rivet_thread_private* Rivet_VirtualHosts
}
-/*
- * -- Rivet_ProcessorCleanup
- *
- * Thread private data cleanup. This function is called by MPM bridges to
- * release data owned by private and pointed in the array of rivet_thread_interp
- * objects. It has to be called just before an agent, either thread or
- * process, exits.
- *
- * Arguments:
- *
- * data: pointer to a rivet_thread_private data structure.
- *
- * Returned value:
- *
- * none
- *
- * Side effects:
- *
- * resources stored in the array of rivet_thread_interp objects are released
- * and interpreters are deleted.
- *
- */
-
-void Rivet_ProcessorCleanup (void *data)
-{
- rivet_thread_private* private = (rivet_thread_private *) data;
- Tcl_HashSearch* searchCtx;
- Tcl_HashEntry* entry;
- int i;
- rivet_server_conf* rsc = RIVET_SERVER_CONF(module_globals->server->module_config);
-
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, module_globals->server,
- "Thread exiting after %d requests served (%d vhosts)",
- private->req_cnt,module_globals->vhosts_count);
-
- /* We are deleting the interpreters and release the thread channel.
- * Rivet channel is set as stdout channel of Tcl and as such is treated
- * by Tcl_UnregisterChannel is a special way. When its refCount reaches 1
- * the channel is released immediately by forcing the refCount to 0
- * (see Tcl source code: generic/TclIO.c). Unregistering for each interpreter
- * causes the process to segfault at least for certain Tcl versions.
- * We unset the channel as stdout to avoid this
- */
-
- Tcl_SetStdChannel(NULL,TCL_STDOUT);
-
- /* there must be always a root interpreter in the slot 0 of private->interps,
- so there is always need to run at least one cycle here */
-
- i = 0;
- do
- {
-
- /* cleaning the cache contents and deleting it */
-
- searchCtx = apr_pcalloc(private->pool,sizeof(Tcl_HashSearch));
- entry = Tcl_FirstHashEntry(private->interps[i]->objCache,searchCtx);
- while (entry)
- {
- Tcl_DecrRefCount(Tcl_GetHashValue(entry)); /* Let Tcl clear the mem allocated */
- Tcl_DeleteHashEntry(entry);
-
- entry = Tcl_NextHashEntry(searchCtx);
- }
-
- if ((i > 0) && rsc->separate_channels)
- Rivet_ReleaseRivetChannel(private->interps[i]->interp,private->channel);
-
- Tcl_DeleteInterp(private->interps[i]->interp);
-
- /* if separate_virtual_interps == 0 we are running the same interpreter
- * instance for each vhost, thus we can jump out of this loop after
- * the first cycle as the only real intepreter object we have is stored
- * in private->interps[0]
- */
-
- } while ((++i < module_globals->vhosts_count) && rsc->separate_virtual_interps);
-
- Tcl_DecrRefCount(private->request_init);
- Tcl_DecrRefCount(private->request_cleanup);
- apr_pool_destroy(private->pool);
-
-}
-
/* ----------------------------------------------------------------------------
* -- Rivet_SendContent
*
@@ -766,22 +685,29 @@ Rivet_SendContent(rivet_thread_private *
retval = OK;
sendcleanup:
- /* Let's set the charset in the headers if one was set in the configuration */
+ /* Request processing final stage */
+
+ /* A new big catch is the handling of exit commands that are treated
+ * as ::rivet::abort_page. After the AbortScript has been evaluated
+ * the exit condition is checked and in case the exit handler
+ * of the bridge module is called before terminating the whole process
+ */
+
+ if (private->thread_exit)
+ {
+ RIVET_MPM_BRIDGE_CALL(mpm_exit_handler,private->exit_status);
+ Tcl_Exit(private->exit_status);
+ }
+
+ /* We now reset the status to prepare the child process for another request */
- //if (!globals->req->headers_set && (globals->req->charset != NULL)) {
- // TclWeb_SetHeaderType (apr_pstrcat(globals->req->req->pool,"text/html;",
- // globals->req->charset,NULL),globals->req);
- //}
-
- private->req->content_sent = 0;
- private->page_aborting = 0;
- private->abort_code = NULL;
- private->page_aborting = 0;
+ private->req->content_sent = 0;
if (private->abort_code != NULL)
{
Tcl_DecrRefCount(private->abort_code);
private->abort_code = NULL;
}
+ private->page_aborting = 0;
/* We reset this pointer to signal we have terminated the request processing */
@@ -836,6 +762,7 @@ Rivet_ExecuteErrorHandler (Tcl_Interp* i
/* This shouldn't make the default_error_script go away,
* because it gets a Tcl_IncrRefCount when it is created.
*/
+
Tcl_DecrRefCount(errscript);
/* In case we are handling an error occurring after an abort_page call (for
@@ -848,6 +775,48 @@ Rivet_ExecuteErrorHandler (Tcl_Interp* i
return result;
}
+/*
+ * -- Rivet_RunAbortScript
+ *
+ *
+ */
+
+static int
+Rivet_RunAbortScript (rivet_thread_private *private)
+{
+ int retcode = TCL_OK;
+ Tcl_Interp* interp = private->interps[private->running_conf->idx]->interp;
+
+ if (private->running->rivet_abort_script)
+ {
+
+ /* Ideally an AbortScript should be fail safe, but in case
+ * it fails we give a chance to the subsequent ErrorScript
+ * to catch this error.
+ */
+
+ retcode = Tcl_EvalObjEx(interp,private->running->rivet_abort_script,0);
+
+ if (retcode == TCL_ERROR)
+ {
+ /* This is not elegant, but we want to avoid to print
+ * this error message if an ErrorScript will handle this error.
+ * Thus we print the usual error message only if we are running the
+ * default error handler
+ */
+
+ if (private->running->rivet_error_script == NULL) {
+
+ Rivet_PrintErrorMessage(interp,"<b>Rivet AbortScript failed</b>");
+
+ }
+ Rivet_ExecuteErrorHandler(interp,private->running->rivet_abort_script,private);
+ }
+
+ }
+ return retcode;
+}
+
/* -- Rivet_ExecuteAndCheck
*
@@ -887,16 +856,10 @@ Rivet_ExecuteAndCheck(rivet_thread_priva
Tcl_Preserve (interp_obj->interp);
- /* before execution we reset the thread_exit flag. It will in case set if
- * ::rivet::thread_exit gets called
- */
-
- private->thread_exit = 0;
tcl_result = Tcl_EvalObjEx(interp_obj->interp, tcl_script_obj, 0);
if (tcl_result == TCL_ERROR) {
Tcl_Obj* errorCodeListObj;
Tcl_Obj* errorCodeElementObj;
- char* errorCodeSubString;
/* There was an error, see if it's from Rivet and it was caused
* by abort_page.
@@ -920,54 +883,27 @@ Rivet_ExecuteAndCheck(rivet_thread_priva
* a rivet_abort_script is defined, otherwise the page emits
* as normal
*/
- if (strcmp (Tcl_GetString (errorCodeElementObj), "RIVET") == 0) {
+
+ // if (strcmp (Tcl_GetString (errorCodeElementObj), "RIVET") == 0)
+ if (private->page_aborting)
+ {
+ //char* errorCodeSubString;
/* dig the second element out of the errorCode list, make sure
* it succeeds -- it should always
*/
- ap_assert (Tcl_ListObjIndex (interp_obj->interp, errorCodeListObj, 1, &errorCodeElementObj) == TCL_OK);
+ //ap_assert (Tcl_ListObjIndex (interp_obj->interp, errorCodeListObj, 1, &errorCodeElementObj) == TCL_OK);
- errorCodeSubString = Tcl_GetString (errorCodeElementObj);
- if (strcmp (errorCodeSubString, ABORTPAGE_CODE) == 0)
- {
- if (private->running->rivet_abort_script)
- {
- Tcl_Interp* interp = interp_obj->interp;
-
- /* Ideally an AbortScript should be fail safe, but in case
- * it fails we give a chance to the subsequent ErrorScript
- * to catch this error.
- */
-
- if (Tcl_EvalObjEx(interp,private->running->rivet_abort_script,0) == TCL_ERROR)
- {
- /* This is not elegant, but we want to avoid to print
- * this error message if an ErrorScript will handle this error.
- * Thus we print the usual error message only if we are running the
- * default error handler
- */
-
- if (private->running->rivet_error_script == NULL) {
-
- Rivet_PrintErrorMessage(interp,"<b>Rivet AbortScript failed</b>");
- }
- Rivet_ExecuteErrorHandler(interp,private->running->rivet_abort_script,private);
- }
- }
- }
- else if (strcmp(errorCodeSubString, THREAD_EXIT_CODE) == 0)
- {
-
- /* we simply set the thread_exit flag and finish with this
- * request and proceed checking whether an after_every_script
- * is defined
- */
-
- private->thread_exit = 1;
- }
+ //errorCodeSubString = Tcl_GetString (errorCodeElementObj);
+ //if ((strcmp(errorCodeSubString, ABORTPAGE_CODE) == 0) ||
+ // (strcmp(errorCodeSubString, THREAD_EXIT_CODE) == 0))
+ //
+ //{
+ Rivet_RunAbortScript(private);
+ //}
}
- else if (!private->page_aborting)
+ else
{
Rivet_ExecuteErrorHandler (interp_obj->interp,tcl_script_obj,private);
}
@@ -1016,9 +952,6 @@ Rivet_ParseExecFile(rivet_thread_private
time_t ctime;
time_t mtime;
int res = 0;
- //rivet_server_conf *rsc;
-
- //rsc = Rivet_GetConf( private->r );
/* We have to fetch the interpreter data from the thread private environment */
@@ -1470,8 +1403,6 @@ Rivet_RunServerInit (apr_pool_t *pPool,
static int
Rivet_ServerInit (apr_pool_t *pPool, apr_pool_t *pLog, apr_pool_t *pTemp, server_rec *server)
{
- apr_status_t aprrv;
- char errorbuf[ERRORBUF_SZ];
char* mpm_prefork_bridge = "rivet_prefork_mpm.so";
char* mpm_worker_bridge = "rivet_worker_mpm.so";
char* mpm_model_path;
@@ -1479,9 +1410,9 @@ Rivet_ServerInit (apr_pool_t *pPool, apr
apr_dso_handle_t* dso_handle;
#if RIVET_DISPLAY_VERSION
- ap_add_version_component(pPool, RIVET_PACKAGE_NAME"/"RIVET_VERSION);
+ ap_add_version_component(pPool,RIVET_PACKAGE_NAME"/"RIVET_VERSION);
#else
- ap_add_version_component(pPool, RIVET_PACKAGE_NAME);
+ ap_add_version_component(pPool,RIVET_PACKAGE_NAME);
#endif
/* Everything revolves around this structure */
@@ -1532,8 +1463,7 @@ Rivet_ServerInit (apr_pool_t *pPool, apr
mpm_model_path = apr_pstrcat(pTemp,RIVET_DIR,"/mpm/",module_globals->rivet_mpm_bridge,NULL);
}
- aprrv = apr_dso_load(&dso_handle,mpm_model_path,pPool);
- if (aprrv == APR_SUCCESS)
+ if (apr_dso_load(&dso_handle,mpm_model_path,pPool) == APR_SUCCESS)
{
apr_status_t rv;
apr_dso_handle_sym_t func = NULL;
@@ -1547,6 +1477,8 @@ Rivet_ServerInit (apr_pool_t *pPool, apr
}
else
{
+ char errorbuf[ERRORBUF_SZ];
+
ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, server,
MODNAME ": Error loading symbol bridge_jump_table: %s",
apr_dso_error(dso_handle,errorbuf,ERRORBUF_SZ));
@@ -1558,11 +1490,6 @@ Rivet_ServerInit (apr_pool_t *pPool, apr
ap_assert(RIVET_MPM_BRIDGE_FUNCTION(mpm_request) != NULL);
ap_assert(RIVET_MPM_BRIDGE_FUNCTION(mpm_master_interp) != NULL);
- if (RIVET_MPM_BRIDGE_FUNCTION(mpm_exit_handler) == NULL) {
- RIVET_MPM_BRIDGE_FUNCTION(mpm_exit_handler) = Rivet_Exit_Handler;
- }
-
- apr_atomic_init(pPool);
apr_thread_mutex_create(&module_globals->pool_mutex, APR_THREAD_MUTEX_UNNESTED, pPool);
/* Another crucial point: we are storing in the globals a reference to the
@@ -1580,12 +1507,13 @@ Rivet_ServerInit (apr_pool_t *pPool, apr
}
else
{
+ char errorbuf[ERRORBUF_SZ];
/* If we don't find the mpm handler module we give up and exit */
ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, server,
MODNAME " Error loading MPM manager: %s",
- apr_dso_error(dso_handle,errorbuf,1024));
+ apr_dso_error(dso_handle,errorbuf,ERRORBUF_SZ));
exit(1);
}
@@ -1671,6 +1599,7 @@ static int Rivet_Handler (request_rec *r
if (ctype == CTYPE_NOT_HANDLED) {
return DECLINED;
}
+
return (*RIVET_MPM_BRIDGE_FUNCTION(mpm_request))(r,ctype);
}
Modified: tcl/rivet/trunk/src/mod_rivet/mod_rivet.h
URL: http://svn.apache.org/viewvc/tcl/rivet/trunk/src/mod_rivet/mod_rivet.h?rev=1706645&r1=1706644&r2=1706645&view=diff
==============================================================================
--- tcl/rivet/trunk/src/mod_rivet/mod_rivet.h (original)
+++ tcl/rivet/trunk/src/mod_rivet/mod_rivet.h Sat Oct 3 23:05:14 2015
@@ -28,7 +28,6 @@
#include <apr_tables.h>
#include <apr_thread_proc.h>
#include <apr_thread_cond.h>
-#include <apr_atomic.h>
#include <tcl.h>
#include "rivet.h"
#include "apache_request.h"
@@ -210,8 +209,8 @@ typedef struct _thread_worker_private {
rivet_server_conf* running_conf; /* running configuration */
running_scripts* running; /* (per request) running conf scripts */
- int thread_exit; /* Flag signalling thread_exit call */
-
+ int thread_exit; /* Thread exit code */
+ int exit_status; /* status code to be passed to exit() */
int page_aborting; /* abort_page flag */
Tcl_Obj* abort_code; /* To be reset by before request *
@@ -286,7 +285,6 @@ EXTERN Tcl_Interp* Rivet_CreateTclInterp
#define ABORTPAGE_CODE "ABORTPAGE"
#define THREAD_EXIT_CODE "THREAD_EXIT"
-#define MOD_RIVET_QUEUE_SIZE 100
#define TCL_MAX_CHANNEL_BUFFER_SIZE (1024*1024)
#define MODNAME "mod_rivet"
Modified: tcl/rivet/trunk/src/mod_rivet/mod_rivet_common.c
URL: http://svn.apache.org/viewvc/tcl/rivet/trunk/src/mod_rivet/mod_rivet_common.c?rev=1706645&r1=1706644&r2=1706645&view=diff
==============================================================================
--- tcl/rivet/trunk/src/mod_rivet/mod_rivet_common.c (original)
+++ tcl/rivet/trunk/src/mod_rivet/mod_rivet_common.c Sat Oct 3 23:05:14 2015
@@ -38,6 +38,7 @@
extern apr_threadkey_t* rivet_thread_key;
extern mod_rivet_globals* module_globals;
+
/*
*-----------------------------------------------------------------------------
*
@@ -150,6 +151,8 @@ rivet_thread_private* Rivet_CreatePrivat
private->r = NULL;
private->req = TclWeb_NewRequestObject (private->pool);
private->page_aborting = 0;
+ private->thread_exit = 0;
+ private->exit_status = 0;
private->abort_code = NULL;
private->request_init = Tcl_NewStringObj("::Rivet::initialize_request\n", -1);
private->request_cleanup = Tcl_NewStringObj("::Rivet::cleanup_request\n", -1);
@@ -481,4 +484,87 @@ Rivet_CheckType (request_rec *req)
return ctype;
}
+/*
+ * -- Rivet_ProcessorCleanup
+ *
+ * Thread private data cleanup. This function is called by MPM bridges to
+ * release data owned by private and pointed in the array of rivet_thread_interp
+ * objects. It has to be called just before an agent, either thread or
+ * process, exits.
+ *
+ * Arguments:
+ *
+ * data: pointer to a rivet_thread_private data structure.
+ *
+ * Returned value:
+ *
+ * none
+ *
+ * Side effects:
+ *
+ * resources stored in the array of rivet_thread_interp objects are released
+ * and interpreters are deleted.
+ *
+ */
+
+void Rivet_ProcessorCleanup (void *data)
+{
+ rivet_thread_private* private = (rivet_thread_private *) data;
+ Tcl_HashSearch* searchCtx;
+ Tcl_HashEntry* entry;
+ int i;
+ rivet_server_conf* rsc = RIVET_SERVER_CONF(module_globals->server->module_config);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, module_globals->server,
+ "Thread exiting after %d requests served (%d vhosts)",
+ private->req_cnt,module_globals->vhosts_count);
+
+ /* We are deleting the interpreters and release the thread channel.
+ * Rivet channel is set as stdout channel of Tcl and as such is treated
+ * by Tcl_UnregisterChannel is a special way. When its refCount reaches 1
+ * the channel is released immediately by forcing the refCount to 0
+ * (see Tcl source code: generic/TclIO.c). Unregistering for each interpreter
+ * causes the process to segfault at least for certain Tcl versions.
+ * We unset the channel as stdout to avoid this
+ */
+
+ Tcl_SetStdChannel(NULL,TCL_STDOUT);
+
+ /* there must be always a root interpreter in the slot 0 of private->interps,
+ so there is always need to run at least one cycle here */
+
+ i = 0;
+ do
+ {
+
+ /* cleaning the cache contents and deleting it */
+
+ searchCtx = apr_pcalloc(private->pool,sizeof(Tcl_HashSearch));
+ entry = Tcl_FirstHashEntry(private->interps[i]->objCache,searchCtx);
+ while (entry)
+ {
+ Tcl_DecrRefCount(Tcl_GetHashValue(entry)); /* Let Tcl clear the mem allocated */
+ Tcl_DeleteHashEntry(entry);
+
+ entry = Tcl_NextHashEntry(searchCtx);
+ }
+
+ if ((i > 0) && rsc->separate_channels)
+ Rivet_ReleaseRivetChannel(private->interps[i]->interp,private->channel);
+
+ Tcl_DeleteInterp(private->interps[i]->interp);
+
+ /* if separate_virtual_interps == 0 we are running the same interpreter
+ * instance for each vhost, thus we can jump out of this loop after
+ * the first cycle as the only real intepreter object we have is stored
+ * in private->interps[0]
+ */
+
+ } while ((++i < module_globals->vhosts_count) && rsc->separate_virtual_interps);
+
+ Tcl_DecrRefCount(private->request_init);
+ Tcl_DecrRefCount(private->request_cleanup);
+ apr_pool_destroy(private->pool);
+
+}
Modified: tcl/rivet/trunk/src/mod_rivet/mod_rivet_common.h
URL: http://svn.apache.org/viewvc/tcl/rivet/trunk/src/mod_rivet/mod_rivet_common.h?rev=1706645&r1=1706644&r2=1706645&view=diff
==============================================================================
--- tcl/rivet/trunk/src/mod_rivet/mod_rivet_common.h (original)
+++ tcl/rivet/trunk/src/mod_rivet/mod_rivet_common.h Sat Oct 3 23:05:14 2015
@@ -22,6 +22,7 @@
#ifndef _MOD_RIVET_COMMON_
#define _MOD_RIVET_COMMON_
+EXTERN void Rivet_ProcessorCleanup (void *data);
EXTERN int Rivet_chdir_file (const char *file);
EXTERN int Rivet_CheckType (request_rec* r);
EXTERN void Rivet_CleanupRequest(request_rec *r);
@@ -31,4 +32,5 @@ EXTERN Tcl_Channel* Rivet_CreateRivetCha
EXTERN rivet_thread_private* Rivet_CreatePrivateData (void);
EXTERN rivet_thread_private* Rivet_SetupTclPanicProc (void);
EXTERN void Rivet_ReleaseRivetChannel (Tcl_Interp* interp, Tcl_Channel* channel);
+
#endif
Modified: tcl/rivet/trunk/src/mod_rivet/rivetCore.c
URL: http://svn.apache.org/viewvc/tcl/rivet/trunk/src/mod_rivet/rivetCore.c?rev=1706645&r1=1706644&r2=1706645&view=diff
==============================================================================
--- tcl/rivet/trunk/src/mod_rivet/rivetCore.c (original)
+++ tcl/rivet/trunk/src/mod_rivet/rivetCore.c Sat Oct 3 23:05:14 2015
@@ -1234,6 +1234,11 @@ TCL_CMD_HEADER( Rivet_AbortPageCmd )
/* this is the first (and supposedly unique) abort_page call during this request */
+ /* we eleveta the page_aborting flag to the actual flag controlling the page abort execution.
+ * We still return the RIVET and ABORTPAGE_CODE, but internally
+ * its page_aborting that will drive the code execution after abort_page
+ */
+
private->page_aborting = 1;
Tcl_AddErrorInfo (interp, errorMessage);
@@ -1335,6 +1340,8 @@ TCL_CMD_HEADER( Rivet_EnvCmd )
TCL_CMD_HEADER( Rivet_ExitCmd )
{
int value;
+ rivet_thread_private* private;
+ char* errorMessage = "page generation interrupted by exit command";
if ((objc != 1) && (objc != 2)) {
Tcl_WrongNumArgs(interp, 1, objv, "?returnCode?");
@@ -1347,11 +1354,40 @@ TCL_CMD_HEADER( Rivet_ExitCmd )
return TCL_ERROR;
}
+ THREAD_PRIVATE_DATA(private)
+
+ private->page_aborting = 1;
+ private->abort_code = Tcl_NewDictObj();
+
+ /* The private->abort_code ref count is decremented before
+ * request processing terminates*/
+
+ Tcl_IncrRefCount(private->abort_code);
+
+ /*
+ * mod_rivet traps call to exit and offers a chance to handle them
+ * in the way we handle ::rivet::abort_page calls
+ */
+
+ Tcl_DictObjPut(interp,private->abort_code,
+ Tcl_NewStringObj("error_code",-1),
+ Tcl_NewStringObj("exit",-1));
+
+ Tcl_DictObjPut(interp,private->abort_code,
+ Tcl_NewStringObj("return_code",-1),
+ Tcl_NewIntObj(value));
+
+ private->thread_exit = 1;
+ private->exit_status = value;
+
/* this call actually could never return for a non-threaded MPM bridge
* as it eventually will call Tcl_Exit
*/
+ Tcl_AddErrorInfo (interp, errorMessage);
+ Tcl_SetErrorCode (interp, "RIVET", THREAD_EXIT_CODE, errorMessage, (char *)NULL);
- return (*RIVET_MPM_BRIDGE_FUNCTION(mpm_exit_handler))(value);
+ //return (*RIVET_MPM_BRIDGE_FUNCTION(mpm_exit_handler))(value);
+ return TCL_ERROR;
}
/*
*-----------------------------------------------------------------------------
@@ -1383,8 +1419,8 @@ TCL_CMD_HEADER( Rivet_VirtualFilenameCmd
return TCL_ERROR;
}
- virtual = Tcl_GetStringFromObj( objv[1], NULL );
- filename = TclWeb_GetVirtualFile( private->req, virtual );
+ virtual = Tcl_GetStringFromObj( objv[1], NULL );
+ filename = TclWeb_GetVirtualFile( private->req, virtual );
Tcl_SetObjResult(interp, Tcl_NewStringObj( filename, -1 ) );
return TCL_OK;
@@ -1728,7 +1764,7 @@ Rivet_InitCore(Tcl_Interp *interp,rivet_
RIVET_OBJ_CMD ("env",Rivet_EnvCmd,private);
RIVET_OBJ_CMD ("apache_log_error",Rivet_LogErrorCmd,private);
RIVET_OBJ_CMD ("inspect",Rivet_InspectCmd,private);
- RIVET_OBJ_CMD ("exit_thread",Rivet_ExitCmd,NULL);
+ RIVET_OBJ_CMD ("exit_thread",Rivet_ExitCmd,private);
#ifdef TESTPANIC
RIVET_OBJ_CMD ("testpanic",TestpanicCmd,private);
@@ -1738,6 +1774,5 @@ Rivet_InitCore(Tcl_Interp *interp,rivet_
Tcl_Export(interp,rivet_ns,"*",0);
#endif
-// return Tcl_PkgProvide( interp,RIVET_TCL_PACKAGE,"1.2");
return TCL_OK;
}
Modified: tcl/rivet/trunk/src/mod_rivet/rivet_prefork_mpm.c
URL: http://svn.apache.org/viewvc/tcl/rivet/trunk/src/mod_rivet/rivet_prefork_mpm.c?rev=1706645&r1=1706644&r2=1706645&view=diff
==============================================================================
--- tcl/rivet/trunk/src/mod_rivet/rivet_prefork_mpm.c (original)
+++ tcl/rivet/trunk/src/mod_rivet/rivet_prefork_mpm.c Sat Oct 3 23:05:14 2015
@@ -33,31 +33,31 @@
extern mod_rivet_globals* module_globals;
extern apr_threadkey_t* rivet_thread_key;
-void Rivet_ProcessorCleanup (void *data);
-int Rivet_InitCore (Tcl_Interp *interp,rivet_thread_private* p);
+int Rivet_InitCore (Tcl_Interp *interp,rivet_thread_private* p);
rivet_thread_private* Rivet_VirtualHostsInterps (rivet_thread_private* private);
rivet_thread_interp* Rivet_NewVHostInterp(apr_pool_t* pool);
-/* */
+/* -- Prefork_MPM_Finalize */
-apr_status_t Rivet_MPM_Finalize (void* data)
+apr_status_t Prefork_MPM_Finalize (void* data)
{
rivet_thread_private* private;
server_rec* s = (server_rec*) data;
- ap_assert (apr_threadkey_private_get ((void **)&private,rivet_thread_key) == APR_SUCCESS);
+ RIVET_PRIVATE_DATA_NOT_NULL(rivet_thread_key,private)
ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s, "Running prefork bridge finalize method");
- Rivet_ProcessorCleanup(private);
+ // No, we don't clean up anymore as we are just shutting this process down
+ // Rivet_ProcessorCleanup(private);
apr_threadkey_private_delete (rivet_thread_key);
return OK;
}
-//int Rivet_MPM_ServerInit (apr_pool_t *pPool, apr_pool_t *pLog, apr_pool_t *pTemp, server_rec *s) { return OK; }
+//int Prefork_MPM_ServerInit (apr_pool_t *pPool, apr_pool_t *pLog, apr_pool_t *pTemp, server_rec *s) { return OK; }
-void Rivet_MPM_ChildInit (apr_pool_t* pool, server_rec* server)
+void Prefork_MPM_ChildInit (apr_pool_t* pool, server_rec* server)
{
rivet_thread_private* private;
@@ -85,7 +85,7 @@ void Rivet_MPM_ChildInit (apr_pool_t* po
#ifdef RIVET_NAMESPACE_IMPORT
{
- char* tcl_import_cmd = "namespace eval :: { namespace eval :: { namespace import -force ::rivet::* }}\n";
+ char* tcl_import_cmd = "namespace eval :: { namespace import -force ::rivet::* }\n";
Tcl_Eval (module_globals->server_interp->interp,tcl_import_cmd);
}
@@ -104,7 +104,7 @@ void Rivet_MPM_ChildInit (apr_pool_t* po
}
/*
- * -- Rivet_MPM_Request
+ * -- Prefork_MPM_Request
*
* The prefork implementation of this function is basically a wrapper of
* Rivet_SendContent. The real job is fetching the thread private data
@@ -118,7 +118,7 @@ void Rivet_MPM_ChildInit (apr_pool_t* po
* HTTP status code (see the Apache HTTP web server documentation)
*/
-int Rivet_MPM_Request (request_rec* r,rivet_req_ctype ctype)
+int Prefork_MPM_Request (request_rec* r,rivet_req_ctype ctype)
{
rivet_thread_private* private;
@@ -131,7 +131,7 @@ int Rivet_MPM_Request (request_rec* r,ri
return Rivet_SendContent(private,r);
}
-rivet_thread_interp* Rivet_MPM_MasterInterp(void)
+rivet_thread_interp* Prefork_MPM_MasterInterp(void)
{
rivet_thread_private* private;
@@ -143,10 +143,10 @@ rivet_thread_interp* Rivet_MPM_MasterInt
rivet_bridge_table bridge_jump_table = {
NULL,
- Rivet_MPM_ChildInit,
- Rivet_MPM_Request,
- Rivet_MPM_Finalize,
- Rivet_MPM_MasterInterp,
+ Prefork_MPM_ChildInit,
+ Prefork_MPM_Request,
+ Prefork_MPM_Finalize,
+ Prefork_MPM_MasterInterp,
NULL
};
Modified: tcl/rivet/trunk/src/mod_rivet/rivet_worker_mpm.c
URL: http://svn.apache.org/viewvc/tcl/rivet/trunk/src/mod_rivet/rivet_worker_mpm.c?rev=1706645&r1=1706644&r2=1706645&view=diff
==============================================================================
--- tcl/rivet/trunk/src/mod_rivet/rivet_worker_mpm.c (original)
+++ tcl/rivet/trunk/src/mod_rivet/rivet_worker_mpm.c Sat Oct 3 23:05:14 2015
@@ -26,6 +26,7 @@
#include <tcl.h>
#include <ap_mpm.h>
#include <apr_strings.h>
+#include <apr_atomic.h>
#include "rivet.h"
#include "mod_rivet.h"
@@ -36,6 +37,7 @@
#include "rivet_config.h"
#define BRIDGE_SUPERVISOR_WAIT 1000000
+#define MOD_RIVET_QUEUE_SIZE 100
#ifdef RIVET_SERIALIZE_HTTP_REQUESTS
#define HTTP_REQUESTS_PROC(request_proc_call) \
@@ -51,7 +53,6 @@ extern apr_threadkey_t* rivet_thread_k
apr_threadkey_t* handler_thread_key;
-void Rivet_ProcessorCleanup (void *data);
rivet_thread_private* Rivet_VirtualHostsInterps (rivet_thread_private* private);
rivet_thread_interp* Rivet_NewVHostInterp(apr_pool_t* pool);
@@ -63,8 +64,8 @@ typedef struct mpm_bridge_status {
apr_array_header_t* exiting; /* */
apr_uint32_t* threads_count;
apr_uint32_t* running_threads_count;
- apr_queue_t* queue; /* jobs queue */
- void** workers; /* thread pool ids */
+ apr_queue_t* queue; /* jobs queue */
+ void** workers; /* thread pool ids */
int max_threads;
int min_spare_threads;
@@ -73,6 +74,7 @@ typedef struct mpm_bridge_status {
apr_thread_mutex_t* req_mutex;
#endif
} mpm_bridge_status;
+
/* data private to the Apache callback handling the request */
typedef struct _handler_private
@@ -92,10 +94,12 @@ enum
init,
idle,
request_processing,
- done
+ done,
+ child_exit
};
-/* Rivet_MPM_Shutdown --
+/*
+ * -- Worker_Bridge_Shutdown
*
* Child process shutdown. The thread count is read and
* on the job queue are places as many orderly exit commands
@@ -120,7 +124,7 @@ enum
*
*/
-void Rivet_MPM_Shutdown (void)
+void Worker_Bridge_Shutdown (void)
{
handler_private* job;
int count;
@@ -135,18 +139,26 @@ void Rivet_MPM_Shutdown (void)
count = (int) apr_atomic_read32(module_globals->mpm->threads_count);
for (i = 0; i < count; i++) { apr_queue_push(module_globals->mpm->queue,job); }
apr_sleep(500000);
+
do
{
+
count = (int) apr_atomic_read32(module_globals->mpm->threads_count);
- if (count == 0) break;
+
+ /* Actually, when Rivet exit command implementation shuts the bridge down
+ * the thread running the command is waiting for the supervisor to terminate
+ * therefore the 'count' variable can't drop to 0
+ */
+
+ if (count <= 1) break;
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_SUCCESS, module_globals->server,
+ "Sending %d more stop signals to threads",count);
apr_sleep(1000000);
- } while (waits-- > 0);
- if (count > 0) {
- ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, module_globals->server,
- "Unexpected %d threads still running after 5 seconds. Child process exits anyway",count);
- }
+ } while (waits-- > 0);
+ return;
}
@@ -227,9 +239,6 @@ static void* APR_THREAD_FUNC request_pro
apr_atomic_inc32(module_globals->mpm->running_threads_count);
- //server_conf = RIVET_SERVER_CONF(request_obj->r->server->module_config);
- //TclWeb_InitRequest(request_obj->req, private->interps[server_conf->idx]->interp, request_obj->r);
-
/* these assignements are crucial for both calling Rivet_SendContent and
* for telling the channel where stuff must be sent to */
@@ -238,7 +247,10 @@ static void* APR_THREAD_FUNC request_pro
HTTP_REQUESTS_PROC(request_obj->code = Rivet_SendContent(private,request_obj->r));
apr_thread_mutex_lock(request_obj->mutex);
+
request_obj->status = done;
+ if (private->thread_exit) request_obj->status = child_exit;
+
apr_thread_cond_signal(request_obj->cond);
apr_thread_mutex_unlock(request_obj->mutex);
@@ -249,7 +261,9 @@ static void* APR_THREAD_FUNC request_pro
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, module_globals->server, "processor thread orderly exit");
- Rivet_ProcessorCleanup(private);
+ // We don't clean up the thread resources anymore, if the thread exits the whole process terminates
+
+ // Rivet_ProcessorCleanup(private);
apr_thread_mutex_lock(module_globals->mpm->job_mutex);
*(apr_thread_t **) apr_array_push(module_globals->mpm->exiting) = thd;
@@ -283,7 +297,8 @@ static void start_thread_pool (int nthre
rv = create_worker_thread(&slot);
module_globals->mpm->workers[i] = (void *) slot;
- if (rv != APR_SUCCESS) {
+ if (rv != APR_SUCCESS)
+ {
char errorbuf[512];
apr_strerror(rv, errorbuf,200);
@@ -328,11 +343,13 @@ static void supervisor_housekeeping (voi
static void* APR_THREAD_FUNC threaded_bridge_supervisor (apr_thread_t *thd, void *data)
{
+ mpm_bridge_status* mpm = module_globals->mpm;
+
server_rec* s = (server_rec *)data;
#ifdef RIVET_MPM_SINGLE_TCL_THREAD
int thread_to_start = 1;
#else
- int thread_to_start = (int)round(module_globals->mpm->max_threads);
+ int thread_to_start = (int) round(mpm->max_threads);
#endif
ap_log_error(APLOG_MARK,APLOG_INFO,0,s,"starting %d Tcl threads",thread_to_start);
@@ -340,31 +357,32 @@ static void* APR_THREAD_FUNC threaded_br
do
{
- apr_thread_t* p;
+ apr_thread_t* p;
- apr_thread_mutex_lock(module_globals->mpm->job_mutex);
- while (apr_is_empty_array(module_globals->mpm->exiting) && !module_globals->mpm->server_shutdown)
+ apr_thread_mutex_lock(mpm->job_mutex);
+ while (apr_is_empty_array(mpm->exiting) && !mpm->server_shutdown)
{
- apr_thread_cond_wait ( module_globals->mpm->job_cond, module_globals->mpm->job_mutex );
+ apr_thread_cond_wait ( mpm->job_cond, mpm->job_mutex );
}
- while (!apr_is_empty_array(module_globals->mpm->exiting) && !module_globals->mpm->server_shutdown)
+ while (!apr_is_empty_array(mpm->exiting) && !mpm->server_shutdown)
{
int i;
- p = *(apr_thread_t **)apr_array_pop(module_globals->mpm->exiting);
+ p = *(apr_thread_t **)apr_array_pop(mpm->exiting);
- for (i = 0; (i < TCL_INTERPS) && !module_globals->mpm->server_shutdown; i++)
+ for (i = 0; (i < TCL_INTERPS) && !mpm->server_shutdown; i++)
{
- if (p == module_globals->mpm->workers[i])
+ if (p == mpm->workers[i])
{
apr_status_t rv;
ap_log_error(APLOG_MARK,APLOG_DEBUG,0,s,"thread %d notifies orderly exit",i);
- module_globals->mpm->workers[i] = NULL;
+ mpm->workers[i] = NULL;
/* terminated thread restart */
- rv = create_worker_thread (&((apr_thread_t **)module_globals->mpm->workers)[i]);
+
+ rv = create_worker_thread (&((apr_thread_t **)mpm->workers)[i]);
if (rv != APR_SUCCESS) {
char errorbuf[512];
@@ -384,19 +402,30 @@ static void* APR_THREAD_FUNC threaded_br
}
}
}
- apr_thread_mutex_unlock(module_globals->mpm->job_mutex);
- } while (!module_globals->mpm->server_shutdown);
+ apr_thread_mutex_unlock(mpm->job_mutex);
+ } while (!mpm->server_shutdown);
- Rivet_MPM_Shutdown();
+ Worker_Bridge_Shutdown();
+ {
+ int count = (int) apr_atomic_read32(mpm->threads_count);
+ if (count > 0)
+ {
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, module_globals->server,
+ "%d threads are still running after 5 attempts. Child process exits anyway",count);
+
+ }
+ }
apr_thread_exit(thd,APR_SUCCESS);
+
return NULL;
}
-// int Rivet_MPM_ServerInit (apr_pool_t *pPool, apr_pool_t *pLog, apr_pool_t *pTemp, server_rec *s) { return OK; }
+// int Worker_MPM_ServerInit (apr_pool_t *pPool, apr_pool_t *pLog, apr_pool_t *pTemp, server_rec *s) { return OK; }
/*
- * -- Rivet_MPM_ChildInit
+ * -- Worker_MPM_ChildInit
*
* Child initialization function called by the web server framework.
* For this bridge tasks are
@@ -405,10 +434,9 @@ static void* APR_THREAD_FUNC threaded_br
* + content generation callback private key creation
* + supervisor thread creation
*
- *
*/
-void Rivet_MPM_ChildInit (apr_pool_t* pool, server_rec* server)
+void Worker_MPM_ChildInit (apr_pool_t* pool, server_rec* server)
{
apr_status_t rv;
@@ -427,6 +455,9 @@ void Rivet_MPM_ChildInit (apr_pool_t* po
module_globals->mpm->workers = NULL;
module_globals->mpm->server_shutdown = 0;
+ /* We keep some atomic counters that could provide basic data for a workload balancer */
+
+ apr_atomic_init(pool);
module_globals->mpm->threads_count = (apr_uint32_t *) apr_pcalloc(pool,sizeof(apr_uint32_t));
module_globals->mpm->running_threads_count = (apr_uint32_t *) apr_pcalloc(pool,sizeof(apr_uint32_t));
apr_atomic_set32(module_globals->mpm->threads_count,0);
@@ -516,9 +547,8 @@ apr_status_t Worker_RequestPrivateCleanu
return APR_SUCCESS;
}
-
/*
- * -- Rivet_MPM_Request
+ * -- Worker_MPM_Request
*
* Content generation callback. Actually on the bridge this function is not
* generating directly the requested content but instead builds a handler_private
@@ -526,7 +556,7 @@ apr_status_t Worker_RequestPrivateCleanu
* queue (module_globals->mpm->queue). In this structure are also a
* condition variable and associated mutex. Through this condition variable a
* worker thread running a Tcl interpreter will tell the framework thread the request
- * has been served letting it go and return to the HTTP server framework
+ * has been served letting it return to the HTTP server framework
*
* Arguments:
*
@@ -537,7 +567,7 @@ apr_status_t Worker_RequestPrivateCleanu
* HTTP status code (see the Apache HTTP web server documentation)
*/
-int Rivet_MPM_Request (request_rec* r,rivet_req_ctype ctype)
+int Worker_MPM_Request (request_rec* r,rivet_req_ctype ctype)
{
handler_private* request_private;
apr_status_t rv;
@@ -603,13 +633,21 @@ int Rivet_MPM_Request (request_rec* r,ri
}
else
{
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ MODNAME ": rivet_worker_mpm: could not push request on threads queue");
request_private->code = HTTP_INTERNAL_SERVER_ERROR;
}
return request_private->code;
}
-apr_status_t Rivet_MPM_Finalize (void* data)
+/*
+ * -- Worker_MPM_Finalize
+ *
+ *
+ */
+
+apr_status_t Worker_MPM_Finalize (void* data)
{
apr_status_t rv;
apr_status_t thread_status;
@@ -617,6 +655,11 @@ apr_status_t Rivet_MPM_Finalize (void* d
apr_thread_mutex_lock(module_globals->mpm->job_mutex);
module_globals->mpm->server_shutdown = 1;
+
+ /* We wake up the supervisor who is now supposed to stop
+ * all the Tcl worker threads
+ */
+
apr_thread_cond_signal(module_globals->mpm->job_cond);
apr_thread_mutex_unlock(module_globals->mpm->job_mutex);
@@ -633,7 +676,7 @@ apr_status_t Rivet_MPM_Finalize (void* d
}
/*
- * Rivet_MPM_MasterInterp --
+ * -- Worker_MPM_MasterInterp
*
* Arguments:
*
@@ -643,14 +686,12 @@ apr_status_t Rivet_MPM_Finalize (void* d
*
*/
-rivet_thread_interp* Rivet_MPM_MasterInterp(void)
+rivet_thread_interp* Worker_MPM_MasterInterp(void)
{
rivet_thread_private* private;
- rivet_thread_interp* interp_obj;
+ rivet_thread_interp* interp_obj;
RIVET_PRIVATE_DATA_NOT_NULL(rivet_thread_key,private)
- //ap_assert (apr_threadkey_private_get ((void **)&private,rivet_thread_key) == APR_SUCCESS);
- //ap_assert (private != NULL);
interp_obj = Rivet_NewVHostInterp(private->pool);
//interp_obj->channel = Rivet_CreateRivetChannel(private->pool,rivet_thread_key);
@@ -658,29 +699,44 @@ rivet_thread_interp* Rivet_MPM_MasterInt
return interp_obj;
}
-int Rivet_MPM_ExitHandler(int code)
+/*
+ * -- Worker_MPM_ExitHandler
+ *
+ * Signals a thread to exit by setting the loop control flag to 0
+ * and by returning a Tcl error with error code THREAD_EXIT_CODE
+ *
+ * Arguments:
+ * int code
+ *
+ * Side Effects:
+ *
+ * the thread running the Tcl script will exit
+ */
+
+int Worker_MPM_ExitHandler(int code)
{
rivet_thread_private* private;
- Tcl_Interp* interp;
- static char* errorMessage = "Page generation terminated by thread_exit command";
- ap_assert (apr_threadkey_private_get ((void **)&private,rivet_thread_key) == APR_SUCCESS);
- ap_assert (private != NULL);
+ RIVET_PRIVATE_DATA_NOT_NULL(rivet_thread_key,private)
- interp = private->interps[private->running_conf->idx]->interp;
- private->keep_going = 0;
+ /* This is not strictly necessary, because this command will
+ * eventually terminate the whole processes */
- Tcl_AddErrorInfo (interp, errorMessage);
- Tcl_SetErrorCode (interp, "RIVET", THREAD_EXIT_CODE, errorMessage, (char *)NULL);
+ /* This will force the current thread to exit */
- return TCL_ERROR;
+ private->keep_going = 0;
+
+ /* We now tell the whole process to shutdown */
+
+ Worker_MPM_Finalize (private->r->server);
+ return TCL_OK;
}
rivet_bridge_table bridge_jump_table = {
NULL,
- Rivet_MPM_ChildInit,
- Rivet_MPM_Request,
- Rivet_MPM_Finalize,
- Rivet_MPM_MasterInterp,
- Rivet_MPM_ExitHandler
+ Worker_MPM_ChildInit,
+ Worker_MPM_Request,
+ Worker_MPM_Finalize,
+ Worker_MPM_MasterInterp,
+ Worker_MPM_ExitHandler
};
---------------------------------------------------------------------
To unsubscribe, e-mail: site-cvs-unsubscribe@tcl.apache.org
For additional commands, e-mail: site-cvs-help@tcl.apache.org