blob: 35f5cea8f60039cd020704201fbc9dee6558817f [file] [log] [blame]
/* 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 "mod_perl.h"
/*
* XXX: this is not the most efficent interpreter pool implementation
* but it will do for proof-of-concept
*/
#ifdef USE_ITHREADS
void modperl_interp_clone_init(modperl_interp_t *interp)
{
dTHXa(interp->perl);
MpInterpCLONED_On(interp);
MP_ASSERT_CONTEXT(aTHX);
/* clear @DynaLoader::dl_librefs so we only dlclose() those
* which are opened by the clone
*/
modperl_xs_dl_handles_clear(aTHX);
}
modperl_interp_t *modperl_interp_new(modperl_interp_pool_t *mip,
PerlInterpreter *perl)
{
UV clone_flags = CLONEf_KEEP_PTR_TABLE;
modperl_interp_t *interp =
(modperl_interp_t *)malloc(sizeof(*interp));
memset(interp, '\0', sizeof(*interp));
interp->mip = mip;
interp->refcnt = 0;
if (perl) {
#ifdef MP_USE_GTOP
MP_dSCFG(mip->server);
MP_TRACE_m_do(
modperl_gtop_do_proc_mem_before(MP_FUNC, "perl_clone");
);
#endif
#if defined(WIN32) && defined(CLONEf_CLONE_HOST)
clone_flags |= CLONEf_CLONE_HOST;
#endif
PERL_SET_CONTEXT(perl);
interp->perl = perl_clone(perl, clone_flags);
MP_ASSERT_CONTEXT(interp->perl);
{
PTR_TBL_t *source = modperl_module_config_table_get(perl, FALSE);
if (source) {
PTR_TBL_t *table = modperl_svptr_table_clone(interp->perl,
perl,
source);
modperl_module_config_table_set(interp->perl, table);
}
}
/*
* we keep the PL_ptr_table past perl_clone so it can be used
* within modperl_svptr_table_clone. Perl_sv_dup() uses it.
* Don't confuse our svptr_table with Perl's ptr_table. They
* are different things, although they use the same type.
*/
if ((clone_flags & CLONEf_KEEP_PTR_TABLE)) {
dTHXa(interp->perl);
ptr_table_free(PL_ptr_table);
PL_ptr_table = NULL;
}
modperl_interp_clone_init(interp);
PERL_SET_CONTEXT(perl);
#ifdef MP_USE_GTOP
MP_TRACE_m_do(
modperl_gtop_do_proc_mem_after(MP_FUNC, "perl_clone");
);
#endif
}
MP_TRACE_i(MP_FUNC, "0x%lx / perl: 0x%lx / parent perl: 0x%lx",
(unsigned long)interp, (unsigned long)interp->perl,
(unsigned long)perl);
return interp;
}
void modperl_interp_destroy(modperl_interp_t *interp)
{
void **handles;
dTHXa(interp->perl);
PERL_SET_CONTEXT(interp->perl);
MP_TRACE_i(MP_FUNC, "interp == 0x%lx / perl: 0x%lx",
(unsigned long)interp, (unsigned long)interp->perl);
if (MpInterpIN_USE(interp)) {
MP_TRACE_i(MP_FUNC, "*error - still in use!*");
}
handles = modperl_xs_dl_handles_get(aTHX);
modperl_perl_destruct(interp->perl);
modperl_xs_dl_handles_close(handles);
free(interp);
}
apr_status_t modperl_interp_cleanup(void *data)
{
modperl_interp_destroy((modperl_interp_t *)data);
return APR_SUCCESS;
}
modperl_interp_t *modperl_interp_get(server_rec *s)
{
MP_dSCFG(s);
modperl_interp_t *interp = NULL;
modperl_interp_pool_t *mip = scfg->mip;
modperl_list_t *head;
head = modperl_tipool_pop(mip->tipool);
interp = (modperl_interp_t *)head->data;
MP_TRACE_i(MP_FUNC, "head == 0x%lx, parent == 0x%lx",
(unsigned long)head, (unsigned long)mip->parent);
MP_TRACE_i(MP_FUNC, "selected 0x%lx (perl==0x%lx)",
(unsigned long)interp,
(unsigned long)interp->perl);
#ifdef MP_TRACE
interp->tid = MP_TIDF;
MP_TRACE_i(MP_FUNC, "thread == 0x%lx", interp->tid);
#endif
MpInterpIN_USE_On(interp);
return interp;
}
apr_status_t modperl_interp_pool_destroy(void *data)
{
modperl_interp_pool_t *mip = (modperl_interp_pool_t *)data;
if (mip->tipool) {
modperl_tipool_destroy(mip->tipool);
mip->tipool = NULL;
}
if (MpInterpBASE(mip->parent)) {
/* multiple mips might share the same parent
* make sure its only destroyed once
*/
MP_TRACE_i(MP_FUNC, "parent == 0x%lx",
(unsigned long)mip->parent);
modperl_interp_destroy(mip->parent);
}
return APR_SUCCESS;
}
static void *interp_pool_grow(modperl_tipool_t *tipool, void *data)
{
modperl_interp_pool_t *mip = (modperl_interp_pool_t *)data;
MP_TRACE_i(MP_FUNC, "adding new interpreter to the pool");
return (void *)modperl_interp_new(mip, mip->parent->perl);
}
static void interp_pool_shrink(modperl_tipool_t *tipool, void *data,
void *item)
{
modperl_interp_destroy((modperl_interp_t *)item);
}
static void interp_pool_dump(modperl_tipool_t *tipool, void *data,
modperl_list_t *listp)
{
while (listp) {
modperl_interp_t *interp = (modperl_interp_t *)listp->data;
MP_TRACE_i(MP_FUNC, "listp==0x%lx, interp==0x%lx, requests=%d",
(unsigned long)listp, (unsigned long)interp,
interp->num_requests);
listp = listp->next;
}
}
static modperl_tipool_vtbl_t interp_pool_func = {
interp_pool_grow,
interp_pool_grow,
interp_pool_shrink,
interp_pool_shrink,
interp_pool_dump,
};
void modperl_interp_init(server_rec *s, apr_pool_t *p,
PerlInterpreter *perl)
{
apr_pool_t *server_pool = modperl_server_pool();
pTHX;
MP_dSCFG(s);
modperl_interp_pool_t *mip =
(modperl_interp_pool_t *)apr_pcalloc(p, sizeof(*mip));
MP_TRACE_i(MP_FUNC, "server=%s", modperl_server_desc(s, p));
if (modperl_threaded_mpm()) {
mip->tipool = modperl_tipool_new(p, scfg->interp_pool_cfg,
&interp_pool_func, mip);
}
mip->server = s;
mip->parent = modperl_interp_new(mip, NULL);
aTHX = mip->parent->perl = perl;
/* this happens post-config in mod_perl.c:modperl_init_clones() */
/* modperl_tipool_init(tipool); */
apr_pool_cleanup_register(server_pool, (void*)mip,
modperl_interp_pool_destroy,
apr_pool_cleanup_null);
scfg->mip = mip;
}
apr_status_t modperl_interp_unselect(void *data)
{
modperl_interp_t *interp = (modperl_interp_t *)data;
modperl_interp_pool_t *mip = interp->mip;
MP_ASSERT(interp && MpInterpIN_USE(interp) && interp->refcnt > 0);
MP_TRACE_i(MP_FUNC, "unselect(interp=%pp): refcnt=%d",
interp, interp->refcnt);
--interp->refcnt;
if (interp->refcnt > 0) {
MP_TRACE_i(MP_FUNC, "interp=0x%lx, refcnt=%d -- interp still in use",
(unsigned long)interp, interp->refcnt);
return APR_SUCCESS;
}
if (!MpInterpIN_USE(interp)){
MP_TRACE_i(MP_FUNC, "interp=0x%pp, refcnt=%d -- interp already not in use",
interp, interp->refcnt);
return APR_SUCCESS;
}
MpInterpIN_USE_Off(interp);
modperl_thx_interp_set(interp->perl, NULL);
#ifdef MP_DEBUG
PERL_SET_CONTEXT(NULL);
#endif
if (interp == mip->parent) {
MP_TRACE_i(MP_FUNC, "parent interp=%pp freed", interp);
}
else {
interp->ccfg->interp = NULL;
modperl_tipool_putback_data(mip->tipool, data, interp->num_requests);
MP_TRACE_i(MP_FUNC, "interp=%pp freed, tipool(size=%ld, in_use=%ld)",
interp, mip->tipool->size, mip->tipool->in_use);
}
return APR_SUCCESS;
}
/* XXX:
* interp is marked as in_use for the scope of the pool it is
* stashed in. this is done to avoid the tipool->tlock whenever
* possible. neither approach is ideal.
*/
#define MP_INTERP_KEY "MODPERL_INTERP"
#define get_interp(p) \
(void)apr_pool_userdata_get((void **)&interp, MP_INTERP_KEY, p)
#define set_interp(p) \
(void)apr_pool_userdata_set((void *)interp, MP_INTERP_KEY, \
modperl_interp_unselect, \
p)
modperl_interp_t *modperl_interp_pool_get(apr_pool_t *p)
{
modperl_interp_t *interp = NULL;
get_interp(p);
return interp;
}
void modperl_interp_pool_set(apr_pool_t *p,
modperl_interp_t *interp)
{
(void)apr_pool_userdata_set((void *)interp, MP_INTERP_KEY, NULL, p);
}
/*
* used in the case where we don't have a request_rec or conn_rec,
* such as for directive handlers per-{dir,srv} create and merge.
* "request time pool" is most likely a request_rec->pool.
*/
modperl_interp_t *modperl_interp_pool_select(apr_pool_t *p,
server_rec *s)
{
int is_startup = (p == s->process->pconf);
modperl_interp_t *interp = NULL;
if (is_startup) {
MP_dSCFG(s);
if (scfg) {
MP_TRACE_i(MP_FUNC, "using parent interpreter at startup");
if (!scfg->mip) {
/* we get here if directive handlers are invoked
* before server merge.
*/
modperl_init_vhost(s, p, NULL);
if (!scfg->mip) {
/* FIXME: We get here if global "server_rec" == s, scfg->mip
* is not created then. I'm not sure if that's bug or
* bad/good design decicision. For now just return NULL.
*/
return NULL;
}
}
interp = scfg->mip->parent;
}
else {
if (!(interp = modperl_interp_pool_get(p))) {
interp = modperl_interp_get(s);
modperl_interp_pool_set(p, interp);
MP_TRACE_i(MP_FUNC, "set interp %pp in pconf pool %pp",
interp, p);
}
else {
MP_TRACE_i(MP_FUNC, "found interp %pp in pconf pool %pp",
interp, p);
}
}
MpInterpIN_USE_On(interp);
interp->refcnt++;
/* set context (THX) for this thread */
PERL_SET_CONTEXT(interp->perl);
/* let the perl interpreter point back to its interp */
modperl_thx_interp_set(interp->perl, interp);
return interp;
}
else {
request_rec *r;
apr_pool_userdata_get((void **)&r, "MODPERL_R", p);
MP_ASSERT(r);
MP_TRACE_i(MP_FUNC, "found userdata MODPERL_R in pool %#lx as %lx",
(unsigned long)r->pool, (unsigned long)r);
return modperl_interp_select(r, NULL, NULL);
}
}
modperl_interp_t *modperl_interp_select(request_rec *r, conn_rec *c,
server_rec *s)
{
MP_dSCFG((r ? s=r->server : c ? s=c->base_server : s));
MP_dDCFG;
modperl_config_con_t *ccfg;
const char *desc = NULL;
modperl_interp_t *interp = NULL;
apr_pool_t *p = NULL;
/* What does the following condition mean?
* (r || c): if true we are at runtime. There is some kind of request
* being processed.
* threaded_mpm: self-explanatory
*
* Thus, it is true if we are either at initialization time or at runtime
* but with prefork-MPM. */
if (!((r || c) && modperl_threaded_mpm())) {
interp = scfg->mip->parent;
MpInterpIN_USE_On(interp);
interp->refcnt++;
/* XXX: if no VirtualHosts w/ PerlOptions +Parent we can skip this */
PERL_SET_CONTEXT(interp->perl);
/* let the perl interpreter point back to its interp */
modperl_thx_interp_set(interp->perl, interp);
MP_TRACE_i(MP_FUNC,
"using parent 0x%pp (perl=0x%pp) for %s:%d refcnt set to %d",
interp, interp->perl, s->server_hostname, s->port,
interp->refcnt);
return interp;
}
if(!c) c = r->connection;
ccfg = modperl_config_con_get(c);
if (ccfg && ccfg->interp) {
ccfg->interp->refcnt++;
MP_TRACE_i(MP_FUNC,
"found interp 0x%lx in con config, refcnt incremented to %d",
(unsigned long)ccfg->interp, ccfg->interp->refcnt);
/* set context (THX) for this thread */
PERL_SET_CONTEXT(ccfg->interp->perl);
/* modperl_thx_interp_set() is not called here because the interp
* already belongs to the perl interpreter
*/
return ccfg->interp;
}
MP_TRACE_i(MP_FUNC,
"fetching interp for %s:%d", s->server_hostname, s->port);
interp = modperl_interp_get(s);
MP_TRACE_i(MP_FUNC, " --> got %pp (perl=%pp)", interp, interp->perl);
++interp->num_requests; /* should only get here once per request */
interp->refcnt = 1;
/* set context (THX) for this thread */
PERL_SET_CONTEXT(interp->perl);
/* let the perl interpreter point back to its interp */
modperl_thx_interp_set(interp->perl, interp);
/* make sure ccfg is initialized */
modperl_config_con_init(c, ccfg);
ccfg->interp = interp;
interp->ccfg = ccfg;
MP_TRACE_i(MP_FUNC,
"pulled interp %pp (perl=%pp) from mip, num_requests is %d",
interp, interp->perl, interp->num_requests);
return interp;
}
/* currently up to the caller if mip needs locking */
void modperl_interp_mip_walk(PerlInterpreter *current_perl,
PerlInterpreter *parent_perl,
modperl_interp_pool_t *mip,
modperl_interp_mip_walker_t walker,
void *data)
{
modperl_list_t *head = mip->tipool ? mip->tipool->idle : NULL;
if (!current_perl) {
current_perl = PERL_GET_CONTEXT;
}
if (parent_perl) {
PERL_SET_CONTEXT(parent_perl);
walker(parent_perl, mip, data);
}
while (head) {
PerlInterpreter *perl = ((modperl_interp_t *)head->data)->perl;
PERL_SET_CONTEXT(perl);
walker(perl, mip, data);
head = head->next;
}
PERL_SET_CONTEXT(current_perl);
}
void modperl_interp_mip_walk_servers(PerlInterpreter *current_perl,
server_rec *base_server,
modperl_interp_mip_walker_t walker,
void *data)
{
server_rec *s = base_server->next;
modperl_config_srv_t *base_scfg = modperl_config_srv_get(base_server);
PerlInterpreter *base_perl = base_scfg->mip->parent->perl;
modperl_interp_mip_walk(current_perl, base_perl,
base_scfg->mip, walker, data);
while (s) {
MP_dSCFG(s);
PerlInterpreter *perl = scfg->mip->parent->perl;
modperl_interp_pool_t *mip = scfg->mip;
/* skip vhosts who share parent perl */
if (perl == base_perl) {
perl = NULL;
}
/* skip vhosts who share parent mip */
if (scfg->mip == base_scfg->mip) {
mip = NULL;
}
if (perl || mip) {
modperl_interp_mip_walk(current_perl, perl,
mip, walker, data);
}
s = s->next;
}
}
#define MP_THX_INTERP_KEY "modperl2::thx_interp_key"
modperl_interp_t *modperl_thx_interp_get(pTHX)
{
modperl_interp_t *interp;
SV **svp = hv_fetch(PL_modglobal, MP_THX_INTERP_KEY,
strlen(MP_THX_INTERP_KEY), 0);
if (!svp) return NULL;
interp = INT2PTR(modperl_interp_t *, SvIV(*svp));
return interp;
}
void modperl_thx_interp_set(pTHX_ modperl_interp_t *interp)
{
(void)hv_store(PL_modglobal, MP_THX_INTERP_KEY, strlen(MP_THX_INTERP_KEY),
newSViv(PTR2IV(interp)), 0);
return;
}
#else
void modperl_interp_init(server_rec *s, apr_pool_t *p,
PerlInterpreter *perl)
{
MP_dSCFG(s);
scfg->perl = perl;
}
apr_status_t modperl_interp_cleanup(void *data)
{
return APR_SUCCESS;
}
#endif /* USE_ITHREADS */
/*
* Local Variables:
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/