/* rivet_lazy_mpm.c: dynamically loaded MPM aware functions for threaded MPM */

/*
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
 */

#include <httpd.h>
#include <http_request.h>
#include <ap_compat.h>
#include <math.h>
#include <tcl.h>
#include <ap_mpm.h>
#include <apr_strings.h>
#include <apr_atomic.h>

#include "mod_rivet.h"
#include "mod_rivet_common.h"
#include "mod_rivet_generator.h"
#include "rivetChannel.h"
#include "apache_config.h"

extern DLLIMPORT mod_rivet_globals*   module_globals;
extern DLLIMPORT apr_threadkey_t*     rivet_thread_key;
extern DLLIMPORT module rivet_module;

enum
{
    init,
    idle,
    processing,
    thread_exit,
    done
};

/* lazy bridge Tcl thread status and communication variables */

typedef struct lazy_tcl_worker {
    apr_thread_mutex_t* mutex;
    apr_thread_cond_t*  condition;
    int                 status;
    apr_thread_t*       thread_id;
    server_rec*         server;
    request_rec*        r;
    int                 ctype;
    int                 ap_sts;
    rivet_server_conf*  conf;               /* rivet_server_conf* record                */
} lazy_tcl_worker;

/* virtual host thread queue descriptor */

typedef struct vhost_iface {
    int                 threads_count;      /* total number of running and idle threads */
    apr_thread_mutex_t* mutex;              /* mutex protecting 'array'                 */
    apr_array_header_t* array;              /* LIFO array of lazy_tcl_worker pointers   */
} vhost;

/* Lazy bridge internal status data */

typedef struct mpm_bridge_status {
    apr_thread_mutex_t* mutex;
    int                 server_shutdown;    /* the child process is shutting down       */
    vhost*              vhosts;             /* array of vhost descriptors               */
} mpm_bridge_status;

/* lazy bridge thread private data extension */

typedef struct mpm_bridge_specific {
    rivet_thread_interp*  interp;           /* thread Tcl interpreter object        */
    int                   keep_going;       /* thread loop controlling variable     */
                                            /* the request_rec and TclWebRequest    *
                                             * are copied here to be passed to a    *
                                             * channel                              */
} mpm_bridge_specific;

enum {
    child_global,
    child_init,
    child_exit
};

#define MOD_RIVET_QUEUE_SIZE 100

static void Lazy_RunConfScript (rivet_thread_private* private,lazy_tcl_worker* w,int init)
{
    Tcl_Obj*    tcl_conf_script; 
    Tcl_Interp* interp = private->ext->interp->interp;
    void*       function = NULL;
    
    switch (init)
    {
        case child_global: function = w->conf->rivet_global_init_script;
                           break;
        case child_init: function = w->conf->rivet_child_init_script;
                           break;
        case child_exit: function = w->conf->rivet_child_exit_script;
    }

    if (function)
    {
        tcl_conf_script = Tcl_NewStringObj(function,-1);
        Tcl_IncrRefCount(tcl_conf_script);

        if (Tcl_EvalObjEx(interp,tcl_conf_script, 0) != TCL_OK) 
        {
            char*       errmsg = "rivet_lazy_mpm.so: Error in configuration script: %s";
            server_rec* root_server = module_globals->server;

            ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL,root_server,
                         errmsg, function);
            ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL,root_server, 
                         "errorCode: %s", Tcl_GetVar(interp, "errorCode", 0));
            ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL,root_server, 
                         "errorInfo: %s", Tcl_GetVar(interp, "errorInfo", 0));
        }

        Tcl_DecrRefCount(tcl_conf_script);
    }
}

/*
 * -- request_processor
 *
 * The lazy bridge worker thread. This thread prepares its control data and 
 * will serve requests addressed to a given virtual host. Virtual host server
 * data are stored in the lazy_tcl_worker structure stored in the generic 
 * pointer argument 'data'
 * 
 */

static void* APR_THREAD_FUNC request_processor (apr_thread_t *thd, void *data)
{
    lazy_tcl_worker*        w = (lazy_tcl_worker*) data; 
    rivet_thread_private*   private;
    int                     idx;
    rivet_server_conf*      rsc;

    /* The server configuration */

    rsc = RIVET_SERVER_CONF(w->server->module_config);

    /* Rivet_ExecutionThreadInit creates and returns the thread private data. */

    private = Rivet_ExecutionThreadInit();

    /* A bridge creates and stores in private->ext its own thread private
     * data. The lazy bridge is no exception. We just need a flag controlling 
     * the execution and an intepreter control structure */

    private->ext = apr_pcalloc(private->pool,sizeof(mpm_bridge_specific));
    private->ext->keep_going = 1;
    //private->ext->interp = Rivet_NewVHostInterp(private->pool,w->server);
    RIVET_POKE_INTERP(private,rsc,Rivet_NewVHostInterp(private->pool,rsc->default_cache_size));
    private->ext->interp->channel = private->channel;

    /* The worker thread can respond to a single request at a time therefore 
       must handle and register its own Rivet channel */

    Tcl_RegisterChannel(private->ext->interp->interp,*private->channel);

    /* From the rivet_server_conf structure we determine what scripts we
     * are using to serve requests */

    private->ext->interp->scripts = 
            Rivet_RunningScripts (private->pool,private->ext->interp->scripts,rsc);

    /* This is the standard Tcl interpreter initialization */

    Rivet_PerInterpInit(private->ext->interp,private,w->server,private->pool);
    
    /* The child initialization is fired. Beware of the terminologic 
     * trap: we inherited from fork capable systems the term 'child'
     * meaning 'child process'. In this case the child init actually
     * is a worker thread initialization, because in a threaded module
     * this is the agent playing the same role a child process plays
     * with the prefork bridge */

    Lazy_RunConfScript(private,w,child_init);

    idx = w->conf->idx;

    /* After the thread has run the configuration script we 
       increment the threads counter */

    apr_thread_mutex_lock(module_globals->mpm->vhosts[idx].mutex);
    (module_globals->mpm->vhosts[idx].threads_count)++;
    apr_thread_mutex_unlock(module_globals->mpm->vhosts[idx].mutex);

    /* The thread is now set up to serve request within the the 
     * do...while loop controlled by private->keep_going  */

    apr_thread_mutex_lock(w->mutex);
    do 
    {
        while ((w->status != init) && (w->status != thread_exit)) {
            apr_thread_cond_wait(w->condition,w->mutex);
        } 
        if (w->status == thread_exit) {
            private->ext->keep_going = 0;
            continue;
        }

        w->status = processing;

        /* Content generation */

        private->req_cnt++;
        private->ctype = w->ctype;
        private->r = w->r;

        w->ap_sts = Rivet_SendContent(private);

        // if (module_globals->mpm->server_shutdown) continue;

        w->status = done;
        apr_thread_cond_signal(w->condition);
        while (w->status == done) {
            apr_thread_cond_wait(w->condition,w->mutex);
        } 
 
        /* rescheduling itself in the array of idle threads */
       
        apr_thread_mutex_lock(module_globals->mpm->vhosts[idx].mutex);
        *(lazy_tcl_worker **) apr_array_push(module_globals->mpm->vhosts[idx].array) = w;
        apr_thread_mutex_unlock(module_globals->mpm->vhosts[idx].mutex);

    } while (private->ext->keep_going);
    apr_thread_mutex_unlock(w->mutex);
    
    Lazy_RunConfScript(private,w,child_exit);
    ap_log_error(APLOG_MARK,APLOG_DEBUG,APR_SUCCESS,w->server,"processor thread orderly exit");

    apr_thread_mutex_lock(module_globals->mpm->vhosts[idx].mutex);
    (module_globals->mpm->vhosts[idx].threads_count)--;
    apr_thread_mutex_unlock(module_globals->mpm->vhosts[idx].mutex);

    apr_thread_exit(thd,APR_SUCCESS);
    return NULL;
}

/*
 * -- create_worker
 *
 * Utility function to allocate a worker thread control structure
 * (lazy_tcl_worker) and start a thread. The worker thread code
 * is in the request_processor function
 *
 */


static lazy_tcl_worker* create_worker (apr_pool_t* pool,server_rec* server)
{
    lazy_tcl_worker* w;

    w = apr_pcalloc(pool,sizeof(lazy_tcl_worker));

    w->status = idle;
    w->server = server;
    ap_assert(apr_thread_mutex_create(&w->mutex,APR_THREAD_MUTEX_UNNESTED,pool) == APR_SUCCESS);
    ap_assert(apr_thread_cond_create(&w->condition, pool) == APR_SUCCESS); 
    apr_thread_create(&w->thread_id, NULL, request_processor, w, module_globals->pool);

    return w;
}

/*
 * -- LazyBridge_ChildInit
 * 
 * child process initialization. This function prepares the process
 * data structures for virtual hosts and threads management
 *
 */

void LazyBridge_ChildInit (apr_pool_t* pool, server_rec* server)
{
    apr_status_t    rv;
    server_rec*     s;
    server_rec*     root_server = module_globals->server;

    module_globals->mpm = apr_pcalloc(pool,sizeof(mpm_bridge_status));

    /* This mutex is only used to consistently carry out these 
     * two tasks
     *
     *  - set the exit status of a child process (hopefully will be 
     *    unnecessary when Tcl is able again of calling 
     *    Tcl_DeleteInterp safely) 
     *  - control the server_shutdown flag. Actually this is
     *    not entirely needed because once set this flag 
     *    is never reset to 0
     *
     */

    rv = apr_thread_mutex_create(&module_globals->mpm->mutex,
                                  APR_THREAD_MUTEX_UNNESTED,pool);
    ap_assert(rv == APR_SUCCESS);

    /* the mpm->vhosts array is created with as many entries as the number of
     * configured virtual hosts */

    module_globals->mpm->vhosts = 
        (vhost *) apr_pcalloc(pool,module_globals->vhosts_count*sizeof(vhost));
    ap_assert(module_globals->mpm->vhosts != NULL);

    /*
     * Each virtual host descriptor has its own mutex controlling
     * the queue of available threads
     */
     
    for (s = root_server; s != NULL; s = s->next)
    {
        int                 idx;
        apr_array_header_t* array;
        rivet_server_conf*  rsc = RIVET_SERVER_CONF(s->module_config);

        idx = rsc->idx;
        rv  = apr_thread_mutex_create(&module_globals->mpm->vhosts[idx].mutex,
                                      APR_THREAD_MUTEX_UNNESTED,pool);
        ap_assert(rv == APR_SUCCESS);
        array = apr_array_make(pool,0,sizeof(void*));
        ap_assert(array != NULL);
        module_globals->mpm->vhosts[idx].array = array;
        module_globals->mpm->vhosts[idx].threads_count = 0;
    }
    module_globals->mpm->server_shutdown = 0;
}

/* -- LazyBridge_Request
 *
 * The lazy bridge HTTP request function. This function 
 * stores the request_rec pointer into the lazy_tcl_worker
 * structure which is used to communicate with a worker thread.
 * Then the array of idle threads is checked and if empty
 * a new thread is created by calling create_worker
 */

int LazyBridge_Request (request_rec* r,rivet_req_ctype ctype)
{
    lazy_tcl_worker*    w;
    int                 ap_sts;
    rivet_server_conf*  conf = RIVET_SERVER_CONF(r->server->module_config);
    apr_array_header_t* array;
    apr_thread_mutex_t* mutex;

    mutex = module_globals->mpm->vhosts[conf->idx].mutex;
    array = module_globals->mpm->vhosts[conf->idx].array;
    apr_thread_mutex_lock(mutex);

    /* This request may have come while the child process was 
     * shutting down. We cannot run the risk that incoming requests 
     * may hang the child process by keeping its threads busy, 
     * so we simply return an HTTP_INTERNAL_SERVER_ERROR. 
     * This is hideous and explains why the 'exit' commands must 
     * be avoided at any costs when programming with mod_rivet
     */

    if (module_globals->mpm->server_shutdown == 1) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r,
                      MODNAME ": http request aborted during child process shutdown");
        apr_thread_mutex_unlock(mutex);
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* If the array is empty we create a new worker thread */

    if (apr_is_empty_array(array))
    {
        w = create_worker(module_globals->pool,r->server);
        //(module_globals->mpm->vhosts[conf->idx].threads_count)++; 
    }
    else
    {
        w = *(lazy_tcl_worker**) apr_array_pop(array);
    }

    apr_thread_mutex_unlock(mutex);

    /* Locking the thread descriptor structure mutex */    

    apr_thread_mutex_lock(w->mutex);
    w->r        = r;
    w->ctype    = ctype;
    w->status   = init;
    w->conf     = conf;
    apr_thread_cond_signal(w->condition);

    /* we wait for the Tcl worker thread to finish its job */

    while (w->status != done) {
        apr_thread_cond_wait(w->condition,w->mutex);
    } 
    ap_sts = w->ap_sts;

    w->status = idle;
    w->r      = NULL;
    apr_thread_cond_signal(w->condition);
    apr_thread_mutex_unlock(w->mutex);

    return ap_sts;
}

/* -- LazyBridge_Interp: lazy bridge accessor to the interpreter database
 *
 */

rivet_thread_interp* LazyBridge_Interp (rivet_thread_private* private,
                                      rivet_server_conf*    conf,
                                      rivet_thread_interp*  interp)
{
    if (interp != NULL) { private->ext->interp = interp; }

    return private->ext->interp;
}

/*
 * -- LazyBridge_Finalize
 *
 * Bridge thread and resources shutdown
 *
 */

apr_status_t LazyBridge_Finalize (void* data)
{
    int idx;
    server_rec* server = (server_rec*) data;
    rivet_server_conf* conf = RIVET_SERVER_CONF(server->module_config);
   
    module_globals->mpm->server_shutdown = 1;
    for (idx = 0; idx < module_globals->vhosts_count; idx++)
    {
        int try;
        int count;
        apr_array_header_t* array;
        apr_thread_mutex_t* mutex;

        mutex = module_globals->mpm->vhosts[idx].mutex;
        array = module_globals->mpm->vhosts[idx].array;

        /* we need to lock the vhost data mutex */

        apr_thread_mutex_lock(mutex);

        count = module_globals->mpm->vhosts[idx].threads_count;
        apr_thread_mutex_unlock(mutex);
        try = 0;

        while ((try++ < 3) && (count > 0)) 
        {
            ap_log_error(APLOG_MARK,APLOG_DEBUG,APR_SUCCESS,server,"waiting for %d thread to exit",count);
            if ((conf->idx == idx) && (count == 1)) { break; } 

            while (!apr_is_empty_array(array)) 
            {
                lazy_tcl_worker* w;

                w = *(lazy_tcl_worker**) apr_array_pop(array); 
                apr_thread_mutex_lock(w->mutex);
                w->r      = NULL;
                w->status = thread_exit;
                apr_thread_cond_signal(w->condition);
                apr_thread_mutex_unlock(w->mutex);
            }
            
            apr_thread_mutex_lock(mutex);
            count = module_globals->mpm->vhosts[idx].threads_count;
            apr_thread_mutex_unlock(mutex);

            apr_sleep(1000);
        }
    }

    return APR_SUCCESS;
}

/*
 * -- LazyBridge_ExitHandler
 *
 *
 *
 */


int LazyBridge_ExitHandler(rivet_thread_private* private)
{

    /* This is not strictly necessary, because this command will 
     * eventually terminate the whole processes */

    /* This will force the current thread to exit */

    private->ext->keep_going = 0;

    if (!module_globals->single_thread_exit)
    {
        /* We now tell the supervisor to terminate the Tcl worker 
         * thread pool to exit and is sequence the whole process
         * to shutdown by calling exit() */
     
        LazyBridge_Finalize(private->r->server);

    } 

    return TCL_OK;
}

/*
 *  -- LazyBridge_ServerInit
 *
 * Bridge server wide inizialization:
 *
 *  We set the default value of the flag single_thread_exit 
 *  stored in the module globals
 *
 */

int LazyBridge_ServerInit (apr_pool_t* pPool,apr_pool_t* pLog,apr_pool_t* pTemp,server_rec* s)
{
    if (module_globals->single_thread_exit == SINGLE_THREAD_EXIT_UNDEF)
    {
        module_globals->single_thread_exit = 1;
    }
    return OK;
}

/* Table of bridge control functions */

DLLEXPORT
RIVET_MPM_BRIDGE {
    LazyBridge_ServerInit,
    LazyBridge_ChildInit,
    LazyBridge_Request,
    LazyBridge_Finalize,
    LazyBridge_ExitHandler,
    LazyBridge_Interp
};
