/* 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 static const char *MP_interp_scope_desc[] = { "undef", "handler", "subrequest", "request", "connection", }; const char *modperl_interp_scope_desc(modperl_interp_scope_e scope) { return MP_interp_scope_desc[scope]; } void modperl_interp_clone_init(modperl_interp_t *interp) { dTHXa(interp->perl); MpInterpCLONED_On(interp); PERL_SET_CONTEXT(aTHX); /* XXX: hack for bug fixed in 5.6.1 */ if (PL_scopestack_ix == 0) { ENTER; } /* 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; /* for use by APR::Pool->cleanup_register */ 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); #if MP_PERL_VERSION(5, 8, 0) && \ defined(USE_REENTRANT_API) && defined(HAS_CRYPT_R) && defined(__GLIBC__) { dTHXa(interp->perl); /* workaround 5.8.0 bug */ PL_reentrant_buffer->_crypt_struct.current_saltbits = 0; } #endif { 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. */ 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; if (interp->refcnt != 0) { --interp->refcnt; MP_TRACE_i(MP_FUNC, "interp=0x%lx, refcnt=%d", (unsigned long)interp, interp->refcnt); return APR_SUCCESS; } if (interp->request) { /* ithreads + a threaded mpm + PerlInterpScope handler */ request_rec *r = interp->request; MP_dRCFG; modperl_config_request_cleanup(interp->perl, r); MpReqCLEANUP_REGISTERED_Off(rcfg); } MpInterpIN_USE_Off(interp); MpInterpPUTBACK_Off(interp); modperl_thx_interp_set(interp->perl, NULL); modperl_tipool_putback_data(mip->tipool, data, interp->num_requests); 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, int cleanup) { /* same as get_interp but optional cleanup */ (void)apr_pool_userdata_set((void *)interp, MP_INTERP_KEY, cleanup ? modperl_interp_unselect : 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); MP_dSCFG(s); modperl_interp_t *interp = NULL; if (scfg && (is_startup || !modperl_threaded_mpm())) { MP_TRACE_i(MP_FUNC, "using parent interpreter at %s", is_startup ? "startup" : "request time (non-threaded MPM)"); if (!scfg->mip) { /* we get here if directive handlers are invoked * before server merge. */ modperl_init_vhost(s, p, NULL); } interp = scfg->mip->parent; } else { if (!(interp = modperl_interp_pool_get(p))) { interp = modperl_interp_get(s); modperl_interp_pool_set(p, interp, TRUE); MP_TRACE_i(MP_FUNC, "set interp in request time pool 0x%lx", (unsigned long)p); } else { MP_TRACE_i(MP_FUNC, "found interp in request time pool 0x%lx", (unsigned long)p); } } return interp; } modperl_interp_t *modperl_interp_select(request_rec *r, conn_rec *c, server_rec *s) { MP_dSCFG(s); MP_dRCFG; modperl_config_dir_t *dcfg = modperl_config_dir_get(r); const char *desc = NULL; modperl_interp_t *interp = NULL; apr_pool_t *p = NULL; int is_subrequest = (r && r->main) ? 1 : 0; modperl_interp_scope_e scope; if (!modperl_threaded_mpm()) { MP_TRACE_i(MP_FUNC, "using parent 0x%lx for non-threaded mpm (%s:%d)", (unsigned long)scfg->mip->parent, s->server_hostname, s->port); /* XXX: if no VirtualHosts w/ PerlOptions +Parent we can skip this */ PERL_SET_CONTEXT(scfg->mip->parent->perl); return scfg->mip->parent; } if (rcfg && rcfg->interp) { /* if scope is per-handler and something selected an interpreter * before modperl_callback_run_handlers() and is still holding it, * e.g. modperl_response_handler_cgi(), that interpreter will * be here */ MP_TRACE_i(MP_FUNC, "found interp 0x%lx in request config\n", (unsigned long)rcfg->interp); return rcfg->interp; } /* * if a per-dir PerlInterpScope is specified, use it. * else if r != NULL use per-server PerlInterpScope * else scope must be per-connection */ scope = (dcfg && !modperl_interp_scope_undef(dcfg)) ? dcfg->interp_scope : (r ? scfg->interp_scope : MP_INTERP_SCOPE_CONNECTION); MP_TRACE_i(MP_FUNC, "scope is per-%s", modperl_interp_scope_desc(scope)); /* * XXX: goto modperl_interp_get() if scope == handler ? */ if (c && (scope == MP_INTERP_SCOPE_CONNECTION)) { desc = "conn_rec pool"; get_interp(c->pool); if (interp) { MP_TRACE_i(MP_FUNC, "found interp 0x%lx in %s 0x%lx\n", (unsigned long)interp, desc, (unsigned long)c->pool); return interp; } p = c->pool; } else if (r) { if (is_subrequest && (scope == MP_INTERP_SCOPE_REQUEST)) { /* share 1 interpreter across sub-requests */ request_rec *main_r = r->main; while (main_r && !interp) { p = main_r->pool; get_interp(p); MP_TRACE_i(MP_FUNC, "looking for interp in main request for %s...%s\n", main_r->uri, interp ? "found" : "not found"); main_r = main_r->main; } } else { p = r->pool; get_interp(p); } desc = "request_rec pool"; if (interp) { MP_TRACE_i(MP_FUNC, "found interp 0x%lx in %s 0x%lx (%s request for %s)\n", (unsigned long)interp, desc, (unsigned long)p, (is_subrequest ? "sub" : "main"), r->uri); return interp; } /* might have already been set by a ConnectionHandler */ get_interp(r->connection->pool); if (interp) { desc = "r->connection pool"; MP_TRACE_i(MP_FUNC, "found interp 0x%lx in %s 0x%lx\n", (unsigned long)interp, desc, (unsigned long)r->connection->pool); return interp; } } interp = modperl_interp_get(s ? s : r->server); ++interp->num_requests; /* should only get here once per request */ if (scope == MP_INTERP_SCOPE_HANDLER) { /* caller is responsible for calling modperl_interp_unselect() */ interp->request = r; MpReqCLEANUP_REGISTERED_On(rcfg); MpInterpPUTBACK_On(interp); } else { if (!p) { /* should never happen */ MP_TRACE_i(MP_FUNC, "no pool"); return NULL; } set_interp(p); MP_TRACE_i(MP_FUNC, "set interp 0x%lx in %s 0x%lx (%s request for %s)\n", (unsigned long)interp, desc, (unsigned long)p, (r ? (is_subrequest ? "sub" : "main") : "conn"), (r ? r->uri : c->remote_ip)); } /* set context (THX) for this thread */ PERL_SET_CONTEXT(interp->perl); modperl_thx_interp_set(interp->perl, interp); 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(PerlInterpreter *thx) { modperl_interp_t *interp; dTHXa(thx); 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(PerlInterpreter *thx, modperl_interp_t *interp) { dTHXa(thx); 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 */