X-Git-Url: http://ftp.carnet.hr/carnet-debian/scm?p=php5-apc.git;a=blobdiff_plain;f=apc_main.c;fp=apc_main.c;h=50b673d7504ad7705114beac95ce6992413efdfb;hp=0000000000000000000000000000000000000000;hb=27531cfcd6e8fb46402c6771ef4c2d97233917af;hpb=5ee5738a895adae39c3e6ad85f158331cc89d522 diff --git a/apc_main.c b/apc_main.c new file mode 100644 index 0000000..50b673d --- /dev/null +++ b/apc_main.c @@ -0,0 +1,701 @@ +/* + +----------------------------------------------------------------------+ + | APC | + +----------------------------------------------------------------------+ + | Copyright (c) 2006 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Daniel Cowgill | + | Rasmus Lerdorf | + | Arun C. Murthy | + | Gopal Vijayaraghavan | + +----------------------------------------------------------------------+ + + This software was contributed to PHP by Community Connect Inc. in 2002 + and revised in 2005 by Yahoo! Inc. to add support for PHP 5.1. + Future revisions and derivatives of this source code must acknowledge + Community Connect Inc. as the original contributor of this module by + leaving this note intact in the source code. + + All other licensing and usage conditions are those of the PHP Group. + + */ + +/* $Id: apc_main.c,v 3.103 2007/11/14 19:46:46 shire Exp $ */ + +#include "apc_php.h" +#include "apc_main.h" +#include "apc.h" +#include "apc_lock.h" +#include "apc_cache.h" +#include "apc_compile.h" +#include "apc_globals.h" +#include "apc_sma.h" +#include "apc_stack.h" +#include "apc_zend.h" +#include "SAPI.h" +#if PHP_API_VERSION <= 20020918 +#if HAVE_APACHE +#ifdef APC_PHP4_STAT +#undef XtOffsetOf +#include "httpd.h" +#endif +#endif +#endif + +/* {{{ module variables */ + +/* pointer to the original Zend engine compile_file function */ +typedef zend_op_array* (zend_compile_t)(zend_file_handle*, int TSRMLS_DC); +static zend_compile_t *old_compile_file; + +/* }}} */ + +/* {{{ get/set old_compile_file (to interact with other extensions that need the compile hook) */ +static zend_compile_t* set_compile_hook(zend_compile_t *ptr) +{ + zend_compile_t *retval = old_compile_file; + + if (ptr != NULL) old_compile_file = ptr; + return retval; +} +/* }}} */ + +/* {{{ install_function */ +static int install_function(apc_function_t fn TSRMLS_DC) +{ + int status = + zend_hash_add(EG(function_table), + fn.name, + fn.name_len+1, + apc_copy_function_for_execution(fn.function), + sizeof(fn.function[0]), + NULL); + + if (status == FAILURE) { + /* apc_eprint("Cannot redeclare %s()", fn.name); */ + } + + return status; +} +/* }}} */ + +/* {{{ install_class */ +static int install_class(apc_class_t cl TSRMLS_DC) +{ + zend_class_entry* class_entry = cl.class_entry; + zend_class_entry* parent = NULL; + int status; +#ifdef ZEND_ENGINE_2 + zend_class_entry** allocated_ce = NULL; +#endif + + + /* Special case for mangled names. Mangled names are unique to a file. + * There is no way two classes with the same mangled name will occur, + * unless a file is included twice. And if in case, a file is included + * twice, all mangled name conflicts can be ignored and the class redeclaration + * error may be deferred till runtime of the corresponding DECLARE_CLASS + * calls. + */ + + if(cl.name_len != 0 && cl.name[0] == '\0') { + if(zend_hash_exists(CG(class_table), cl.name, cl.name_len+1)) { + return SUCCESS; + } + } + +#ifdef ZEND_ENGINE_2 + /* + * XXX: We need to free this somewhere... + */ + allocated_ce = apc_php_malloc(sizeof(zend_class_entry*)); + + if(!allocated_ce) { + return FAILURE; + } + + *allocated_ce = +#endif + class_entry = + apc_copy_class_entry_for_execution(cl.class_entry, + cl.is_derived); + + + /* restore parent class pointer for compile-time inheritance */ + if (cl.parent_name != NULL) { +#ifdef ZEND_ENGINE_2 + zend_class_entry** parent_ptr = NULL; + /* + * zend_lookup_class has to be due to presence of __autoload, + * just looking up the EG(class_table) is not enough in php5! + * Even more dangerously, thanks to __autoload and people using + * class names as filepaths for inclusion, this has to be case + * sensitive. zend_lookup_class automatically does a case_fold + * internally, but passes the case preserved version to __autoload. + * Aside: Do NOT pass *strlen(cl.parent_name)+1* because + * zend_lookup_class does it internally anyway! + */ + status = zend_lookup_class(cl.parent_name, + strlen(cl.parent_name), + &parent_ptr TSRMLS_CC); +#else + status = zend_hash_find(EG(class_table), + cl.parent_name, + strlen(cl.parent_name)+1, + (void**) &parent); +#endif + if (status == FAILURE) { + if(APCG(report_autofilter)) { + apc_wprint("Dynamic inheritance detected for class %s", cl.name); + } + class_entry->parent = NULL; + return status; + } + else { +#ifdef ZEND_ENGINE_2 + parent = *parent_ptr; +#endif + class_entry->parent = parent; +#ifdef ZEND_ENGINE_2 + zend_do_inheritance(class_entry, parent TSRMLS_CC); +#else + zend_do_inheritance(class_entry, parent); +#endif + } + + + } + +#ifdef ZEND_ENGINE_2 + status = zend_hash_add(EG(class_table), + cl.name, + cl.name_len+1, + allocated_ce, + sizeof(zend_class_entry*), + NULL); +#else + status = zend_hash_add(EG(class_table), + cl.name, + cl.name_len+1, + class_entry, + sizeof(zend_class_entry), + NULL); +#endif + + if (status == FAILURE) { + apc_eprint("Cannot redeclare class %s", cl.name); + } + return status; +} +/* }}} */ + +/* {{{ uninstall_class */ +static int uninstall_class(apc_class_t cl TSRMLS_DC) +{ + int status; + +#ifdef ZEND_ENGINE_2 + status = zend_hash_del(EG(class_table), + cl.name, + cl.name_len+1); +#else + status = zend_hash_del(EG(class_table), + cl.name, + cl.name_len+1); +#endif + if (status == FAILURE) { + apc_eprint("Cannot delete class %s", cl.name); + } + return status; +} +/* }}} */ + +/* {{{ compare_file_handles */ +static int compare_file_handles(void* a, void* b) +{ + zend_file_handle* fh1 = (zend_file_handle*)a; + zend_file_handle* fh2 = (zend_file_handle*)b; + return (fh1->type == fh2->type && + fh1->filename == fh2->filename && + fh1->opened_path == fh2->opened_path); +} +/* }}} */ + +/* {{{ cached_compile */ +static zend_op_array* cached_compile(zend_file_handle* h, + int type TSRMLS_DC) +{ + apc_cache_entry_t* cache_entry; + int i, ii; + + cache_entry = (apc_cache_entry_t*) apc_stack_top(APCG(cache_stack)); + assert(cache_entry != NULL); + + if (cache_entry->data.file.classes) { + for (i = 0; cache_entry->data.file.classes[i].class_entry != NULL; i++) { + if(install_class(cache_entry->data.file.classes[i] TSRMLS_CC) == FAILURE) { + goto default_compile; + } + } + } + + if (cache_entry->data.file.functions) { + for (i = 0; cache_entry->data.file.functions[i].function != NULL; i++) { + install_function(cache_entry->data.file.functions[i] TSRMLS_CC); + } + } + + + return apc_copy_op_array_for_execution(NULL, cache_entry->data.file.op_array TSRMLS_CC); + +default_compile: + + if(APCG(report_autofilter)) { + apc_wprint("Autofiltering %s", h->opened_path); + } + + if(cache_entry->data.file.classes) { + for(ii = 0; ii < i ; ii++) { + uninstall_class(cache_entry->data.file.classes[ii] TSRMLS_CC); + } + } + + apc_stack_pop(APCG(cache_stack)); /* pop out cache_entry */ + + apc_cache_release(apc_cache, cache_entry); + + /* cannot free up cache data yet, it maybe in use */ + + zend_llist_del_element(&CG(open_files), h, compare_file_handles); /* XXX: kludge */ + + h->type = ZEND_HANDLE_FILENAME; + + return NULL; +} +/* }}} */ + +/* {{{ my_compile_file + Overrides zend_compile_file */ +static zend_op_array* my_compile_file(zend_file_handle* h, + int type TSRMLS_DC) +{ + apc_cache_key_t key; + apc_cache_entry_t* cache_entry; + zend_op_array* op_array; + int num_functions, num_classes, ret; + zend_op_array* alloc_op_array; + apc_function_t* alloc_functions; + apc_class_t* alloc_classes; + time_t t; + char *path; + size_t mem_size; + + if (!APCG(enabled) || (apc_cache_busy(apc_cache) && !APCG(localcache))) { + return old_compile_file(h, type TSRMLS_CC); + } + + /* check our regular expression filters */ + if (APCG(filters) && apc_compiled_filters) { + int ret = apc_regex_match_array(apc_compiled_filters, h->filename); + if(ret == APC_NEGATIVE_MATCH || (ret != APC_POSITIVE_MATCH && !APCG(cache_by_default))) { + return old_compile_file(h, type TSRMLS_CC); + } + } else if(!APCG(cache_by_default)) { + return old_compile_file(h, type TSRMLS_CC); + } + +#if PHP_API_VERSION < 20041225 +#if HAVE_APACHE && defined(APC_PHP4_STAT) + t = ((request_rec *)SG(server_context))->request_time; +#else + t = time(0); +#endif +#else + t = sapi_get_request_time(TSRMLS_C); +#endif + +#ifdef __DEBUG_APC__ + fprintf(stderr,"1. h->opened_path=[%s] h->filename=[%s]\n", h->opened_path?h->opened_path:"null",h->filename); +#endif + + /* try to create a cache key; if we fail, give up on caching */ + if (!apc_cache_make_file_key(&key, h->filename, PG(include_path), t TSRMLS_CC)) { + return old_compile_file(h, type TSRMLS_CC); + } + + + if(!APCG(force_file_update)) { + if(APCG(localcache)) { + /* search for the file in the local cache */ + cache_entry = apc_local_cache_find(APCG(lcache), key, t); + } else { + /* search for the file in the cache */ + cache_entry = apc_cache_find(apc_cache, key, t); + } + } else { + cache_entry = NULL; + } + + if (cache_entry != NULL) { + int dummy = 1; + if (h->opened_path == NULL) { + h->opened_path = estrdup(cache_entry->data.file.filename); + } + zend_hash_add(&EG(included_files), h->opened_path, strlen(h->opened_path)+1, (void *)&dummy, sizeof(int), NULL); + zend_llist_add_element(&CG(open_files), h); /* XXX kludge */ + apc_stack_push(APCG(cache_stack), cache_entry); + op_array = cached_compile(h, type TSRMLS_CC); + if(op_array) { +#ifdef APC_FILEHITS + /* If the file comes from the cache, add it to the global request file list */ + add_next_index_string(APCG(filehits), h->filename, 1); +#endif + return op_array; + } + if(APCG(report_autofilter)) { + apc_wprint("Recompiling %s", h->opened_path); + } + /* TODO: check what happens with EG(included_files) */ + } + + if(apc_cache_busy(apc_cache) && APCG(localcache)) { + /* possibly local cache returned NULL because cache is busy */ + return old_compile_file(h, type TSRMLS_CC); + } + + /* remember how many functions and classes existed before compilation */ + num_functions = zend_hash_num_elements(CG(function_table)); + num_classes = zend_hash_num_elements(CG(class_table)); + + /* compile the file using the default compile function */ + op_array = old_compile_file(h, type TSRMLS_CC); + if (op_array == NULL) { + return NULL; + } + /* + * Basically this will cause a file only to be cached on a percentage + * of the attempts. This is to avoid cache slams when starting up a + * very busy server or when modifying files on a very busy live server. + * There is no point having many processes all trying to cache the same + * file at the same time. By introducing a chance of being cached + * we theoretically cut the cache slam problem by the given percentage. + * For example if apc.slam_defense is set to 66 then 2/3 of the attempts + * to cache an uncached file will be ignored. + */ + if(APCG(slam_defense)) { + if(APCG(slam_rand)==-1) { + APCG(slam_rand) = (int)(100.0*rand()/(RAND_MAX+1.0)); + } + if(APCG(slam_rand) < APCG(slam_defense)) { + return op_array; + } + } + + /* Make sure the mtime reflects the files last known mtime in the case of fpstat==0 */ + if(key.type == APC_CACHE_KEY_FPFILE) { + apc_fileinfo_t fileinfo; + struct stat *tmp_buf = NULL; + if(!strcmp(SG(request_info).path_translated, h->filename)) { + tmp_buf = sapi_get_stat(TSRMLS_C); /* Apache has already done this stat() for us */ + } + if(tmp_buf) { + fileinfo.st_buf = *tmp_buf; + } else { + if (apc_search_paths(h->filename, PG(include_path), &fileinfo) != 0) { +#ifdef __DEBUG_APC__ + fprintf(stderr,"Stat failed %s - bailing (%s) (%d)\n",filename,SG(request_info).path_translated); +#endif + return op_array; + } + } + key.mtime = fileinfo.st_buf.st_mtime; + } + + HANDLE_BLOCK_INTERRUPTIONS(); + +#if NONBLOCKING_LOCK_AVAILABLE + if(APCG(write_lock)) { + if(!apc_cache_write_lock(apc_cache)) { + HANDLE_UNBLOCK_INTERRUPTIONS(); + return op_array; + } + } +#endif + + mem_size = 0; + APCG(mem_size_ptr) = &mem_size; + if(!(alloc_op_array = apc_copy_op_array(NULL, op_array, apc_sma_malloc, apc_sma_free TSRMLS_CC))) { + apc_cache_expunge(apc_cache,t); + apc_cache_expunge(apc_user_cache,t); + APCG(mem_size_ptr) = NULL; +#if NONBLOCKING_LOCK_AVAILABLE + if(APCG(write_lock)) { + apc_cache_write_unlock(apc_cache); + } +#endif + HANDLE_UNBLOCK_INTERRUPTIONS(); + return op_array; + } + + if(!(alloc_functions = apc_copy_new_functions(num_functions, apc_sma_malloc, apc_sma_free TSRMLS_CC))) { + apc_free_op_array(alloc_op_array, apc_sma_free); + apc_cache_expunge(apc_cache,t); + apc_cache_expunge(apc_user_cache,t); + APCG(mem_size_ptr) = NULL; +#if NONBLOCKING_LOCK_AVAILABLE + if(APCG(write_lock)) { + apc_cache_write_unlock(apc_cache); + } +#endif + HANDLE_UNBLOCK_INTERRUPTIONS(); + return op_array; + } + if(!(alloc_classes = apc_copy_new_classes(op_array, num_classes, apc_sma_malloc, apc_sma_free TSRMLS_CC))) { + apc_free_op_array(alloc_op_array, apc_sma_free); + apc_free_functions(alloc_functions, apc_sma_free); + apc_cache_expunge(apc_cache,t); + apc_cache_expunge(apc_user_cache,t); + APCG(mem_size_ptr) = NULL; +#if NONBLOCKING_LOCK_AVAILABLE + if(APCG(write_lock)) { + apc_cache_write_unlock(apc_cache); + } +#endif + HANDLE_UNBLOCK_INTERRUPTIONS(); + return op_array; + } + + path = h->opened_path; + if(!path) path=h->filename; + +#ifdef __DEBUG_APC__ + fprintf(stderr,"2. h->opened_path=[%s] h->filename=[%s]\n", h->opened_path?h->opened_path:"null",h->filename); +#endif + + if(!(cache_entry = apc_cache_make_file_entry(path, alloc_op_array, alloc_functions, alloc_classes))) { + apc_free_op_array(alloc_op_array, apc_sma_free); + apc_free_functions(alloc_functions, apc_sma_free); + apc_free_classes(alloc_classes, apc_sma_free); + apc_cache_expunge(apc_cache,t); + apc_cache_expunge(apc_user_cache,t); + APCG(mem_size_ptr) = NULL; +#if NONBLOCKING_LOCK_AVAILABLE + if(APCG(write_lock)) { + apc_cache_write_unlock(apc_cache); + } +#endif + HANDLE_UNBLOCK_INTERRUPTIONS(); + return op_array; + } + APCG(mem_size_ptr) = NULL; + cache_entry->mem_size = mem_size; + + if ((ret = apc_cache_insert(apc_cache, key, cache_entry, t)) != 1) { + apc_cache_free_entry(cache_entry); + if(ret==-1) { + apc_cache_expunge(apc_cache,t); + apc_cache_expunge(apc_user_cache,t); + } + } + +#if NONBLOCKING_LOCK_AVAILABLE + if(APCG(write_lock)) { + apc_cache_write_unlock(apc_cache); + } +#endif + HANDLE_UNBLOCK_INTERRUPTIONS(); + + return op_array; +} +/* }}} */ + +/* {{{ module init and shutdown */ + +int apc_module_init(int module_number TSRMLS_DC) +{ + /* apc initialization */ +#if APC_MMAP + apc_sma_init(APCG(shm_segments), APCG(shm_size)*1024*1024, APCG(mmap_file_mask)); +#else + apc_sma_init(APCG(shm_segments), APCG(shm_size)*1024*1024, NULL); +#endif + apc_cache = apc_cache_create(APCG(num_files_hint), APCG(gc_ttl), APCG(ttl)); + apc_user_cache = apc_cache_create(APCG(user_entries_hint), APCG(gc_ttl), APCG(user_ttl)); + + apc_compiled_filters = apc_regex_compile_array(APCG(filters)); + + /* override compilation */ + old_compile_file = zend_compile_file; + zend_compile_file = my_compile_file; + REGISTER_LONG_CONSTANT("\000apc_magic", (long)&set_compile_hook, CONST_PERSISTENT | CONST_CS); + + APCG(initialized) = 1; + return 0; +} + +int apc_module_shutdown(TSRMLS_D) +{ + if (!APCG(initialized)) + return 0; + + /* restore compilation */ + zend_compile_file = old_compile_file; + + /* + * In case we got interrupted by a SIGTERM or something else during execution + * we may have cache entries left on the stack that we need to check to make + * sure that any functions or classes these may have added to the global function + * and class tables are removed before we blow away the memory that hold them. + * + * This is merely to remove memory leak warnings - as the process is terminated + * immediately after shutdown. The following while loop can be removed without + * affecting anything else. + */ + while (apc_stack_size(APCG(cache_stack)) > 0) { + int i; + apc_cache_entry_t* cache_entry = (apc_cache_entry_t*) apc_stack_pop(APCG(cache_stack)); + if (cache_entry->data.file.functions) { + for (i = 0; cache_entry->data.file.functions[i].function != NULL; i++) { + zend_hash_del(EG(function_table), + cache_entry->data.file.functions[i].name, + cache_entry->data.file.functions[i].name_len+1); + } + } + if (cache_entry->data.file.classes) { + for (i = 0; cache_entry->data.file.classes[i].class_entry != NULL; i++) { + zend_hash_del(EG(class_table), + cache_entry->data.file.classes[i].name, + cache_entry->data.file.classes[i].name_len+1); + } + } + apc_cache_release(apc_cache, cache_entry); + } + + apc_cache_destroy(apc_cache); + apc_cache_destroy(apc_user_cache); + apc_sma_cleanup(); + + APCG(initialized) = 0; + return 0; +} + +/* }}} */ + +/* {{{ process init and shutdown */ +int apc_process_init(int module_number TSRMLS_DC) +{ + int minttl = (APCG(gc_ttl) > APCG(ttl) ? APCG(ttl) : APCG(gc_ttl))/2; + int size = APCG(localcache_size); + if(APCG(initialized) && APCG(localcache)) { + /* TTL is 2 mins by default */ + APCG(lcache) = apc_local_cache_create(apc_cache, size, minttl ? minttl : 120); + } + return 0; +} + +int apc_process_shutdown(TSRMLS_D) +{ + if(APCG(initialized) && APCG(localcache) && APCG(lcache)) { + apc_local_cache_destroy(APCG(lcache)); + APCG(lcache) = NULL; + } + return 0; +} +/* }}} */ + +/* {{{ request init and shutdown */ + +int apc_request_init(TSRMLS_D) +{ + apc_stack_clear(APCG(cache_stack)); + APCG(slam_rand) = -1; + APCG(copied_zvals) = NULL; + +#ifdef APC_FILEHITS + ALLOC_INIT_ZVAL(APCG(filehits)); + array_init(APCG(filehits)); +#endif + + return 0; +} + +int apc_request_shutdown(TSRMLS_D) +{ + apc_deactivate(TSRMLS_C); + +#ifdef APC_FILEHITS + zval_ptr_dtor(&APCG(filehits)); +#endif + + return 0; +} + +/* }}} */ + +/* {{{ apc_deactivate */ +void apc_deactivate(TSRMLS_D) +{ + /* The execution stack was unwound, which prevented us from decrementing + * the reference counts on active cache entries in `my_execute`. + */ + while (apc_stack_size(APCG(cache_stack)) > 0) { + int i; + zend_class_entry* zce = NULL; + void ** centry = (void*)(&zce); +#ifdef ZEND_ENGINE_2 + zend_class_entry** pzce = NULL; +#endif + + apc_cache_entry_t* cache_entry = + (apc_cache_entry_t*) apc_stack_pop(APCG(cache_stack)); + + if (cache_entry->data.file.classes) { + for (i = 0; cache_entry->data.file.classes[i].class_entry != NULL; i++) { +#ifdef ZEND_ENGINE_2 + centry = (void**)&pzce; /* a triple indirection to get zend_class_entry*** */ +#endif + if(zend_hash_find(EG(class_table), + cache_entry->data.file.classes[i].name, + cache_entry->data.file.classes[i].name_len+1, + (void**)centry) == FAILURE) + { + /* double inclusion of conditional classes ends up failing + * this lookup the second time around. + */ + continue; + } + +#ifdef ZEND_ENGINE_2 + zce = *pzce; +#endif + zend_hash_del(EG(class_table), + cache_entry->data.file.classes[i].name, + cache_entry->data.file.classes[i].name_len+1); + + apc_free_class_entry_after_execution(zce); + } + } + apc_cache_release(apc_cache, cache_entry); + } + if(APCG(localcache)) { + apc_local_cache_cleanup(APCG(lcache)); + } +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: expandtab sw=4 ts=4 sts=4 fdm=marker + * vim<600: expandtab sw=4 ts=4 sts=4 + */