The problem found

Today, I wrote a script, Jing Ge gave me CR when I submitted the code, and resolutely helped me to point out the running time limit of the script. Don’t write like this

ini_set('max_execution_time','30');

To be written

set_limit_time(30);

Ini_set (‘max_execution_time’) needs to modify the original configuration for the time being… Well, the senior engineer knew better, so I began to test two functions.

test

The test code is as follows:

<? Ini_set ini_set('max_execution_time',1); sleep(10); echo 'begin'; while(true){ } <? PHP // test set_time_limit(1); sleep(10); echo 'begin'; while(true){ }

This accident does not matter, a test found the problem, the two tests are first sleep 10s, and then return

beginFatal error: Maximum execution time of 1 second exceeded in /Users/jdq/test.php on line 6

Is sleep not a time to execute the script? The answer is yes, but when I tested the backend interface for a timeout, I did use sleep, and it also timed out to return 504. The reason is that the php-fpm configuration overwrites the PHP ini configuration, so the time to sleep is also considered as the execution time of a CGI process. Back to business, immediately changed the test code

<? Ini_set ini_set('max_execution_time',1); echo 'begin'; while(true){ } <? PHP // test set_time_limit(1); echo 'begin'; while(true){ }

Both scripts are executed with 1s, directly FATAL. At what stage do these two functions come into play? Change the test code to

<? php echo time(),PHP_EOL; register_shutdown_function('func'); function func(){ echo time(),PHP_EOL; } sleep(5); ini_set('max_execution_time',5); echo 'begin',PHP_EOL; while(true){ } <? php echo time(),PHP_EOL; register_shutdown_function('func'); function func(){ echo time(),PHP_EOL; } sleep(5); set_time_limit(5); echo 'begin',PHP_EOL; while(true){ }

The returned results are respectively

Ini_set result: 1528297536 begin Fatal error: Maximum execution time of 5 seconds exceeded in /Users/ JDQ /test.php on line 11 1528297546 set_time_limit  1528297751 begin Fatal error: Maximum execution time of 5 seconds exceeded in /Users/jdq/test.php on line 11 1528297761

Both of these functions start to limit the execution time of the script at execution time, and it doesn’t seem to make much difference, so I looked for the source code for both functions.

Function source code

set_limit_time()

The following is the source code of PHP7.1.

PHP_FUNCTION(set_time_limit) { zend_long new_timeout; char *new_timeout_str; int new_timeout_strlen; zend_string *key; If (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &new_timeout) == FAILURE) {return; } new_timeout_strlen = (int)zend_spprintf(&new_timeout_str, 0, ZEND_LONG_FMT, new_timeout); Key = zend_string_init("max_execution_time", sizeof("max_execution_time")-1, 0); If (zend_alter_ini_entry_chars_ex(key, new_timeout_str, new_timeout_strlen, new_timeout_strlen, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0) == SUCCESS) { RETVAL_TRUE; } else { RETVAL_FALSE; } zend_string_release(key); efree(new_timeout_str); }

The set_limit_time function calls Zend_alter_ini_entry_chars_ex to configure max_execution_time. The set_limit_time function calls Zend_alter_ini_entry_chars_ex to configure max_execution_time

ZEND_API int zend_alter_ini_entry_chars_ex(zend_string *name, const char *value, size_t value_length, int modify_type, int stage, int force_change) /* {{{ */ { int ret; zend_string *new_value; new_value = zend_string_init(value, value_length, ! (stage & ZEND_INI_STAGE_IN_REQUEST)); Zend_alter_ini_entry_ex (ret = Zend_alter_ini_entry_ex (name, new_value, modify_type, stage, force_change); zend_string_release(new_value); return ret; }

As you can see, set_limit_time is ultimately implemented as Zend_alter_ini_entry_ex, which we’ll discuss next.

ini_set

The source code is as follows

PHP_FUNCTION(ini_set) { zend_string *varname; zend_string *new_value; zend_string *val; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STR(varname) Z_PARAM_STR(new_value) ZEND_PARSE_PARAMETERS_END(); Val = zend_ini_get_value(varname); val = zend_ini_get_value(varname); /* copy to return here, because alter might free it! */ if (val) { if (ZSTR_IS_INTERNED(val)) { RETVAL_INTERNED_STR(val); } else if (ZSTR_LEN(val) == 0) { RETVAL_EMPTY_STRING(); } else if (ZSTR_LEN(val) == 1) { RETVAL_INTERNED_STR(ZSTR_CHAR((zend_uchar)ZSTR_VAL(val)[0])); } else if (! (GC_FLAGS(val) & GC_PERSISTENT)) { ZVAL_NEW_STR(return_value, zend_string_copy(val)); } else { ZVAL_NEW_STR(return_value, zend_string_init(ZSTR_VAL(val), ZSTR_LEN(val), 0)); } } else { RETVAL_FALSE; Php_ini_check_path (var, var_len, ini) php_ini_check_path(var, var_len, ini) sizeof(ini)) /* open basedir check */ if (PG(open_basedir)) { if (_CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "error_log") || _CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "java.class.path") || _CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "java.home") || _CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "mail.log") || _CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "java.library.path") || _CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "vpopmail.directory")) { if (php_check_open_basedir(ZSTR_VAL(new_value))) { zval_dtor(return_value); RETURN_FALSE; If (zend_alter_ini_entry_ex(varname, new_value, varname, new_value, varname, new_value)) PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0) == FAILURE) { zval_dtor(return_value); RETURN_FALSE; }}

Zend_alter_ini_entry_ex is a Zend_alter_ini_entry_ex function, and set_limit_time is a Zend_alter_ini_entry_ex function. The set_limit_time function is a Zend_alter_ini_entry_ex function, and the set_limit_time function is a Zend_alter_ini_entry_ex function. At this time Beijing elder brother’s face has produced a slight change, ha ha ha……

So how is Zend_alter_ini_entry_ex implemented?

zend_alter_ini_entry_ex

The source code is as follows

ZEND_API int zend_alter_ini_entry_ex(zend_string *name, zend_string *new_value, int modify_type, int stage, int force_change) /* {{{ */ { zend_ini_entry *ini_entry; zend_string *duplicate; zend_bool modifiable; zend_bool modified; //EG(modified_ini_directives) to store modified ini_entry, Ini_entry if ((ini_entry = zend_hash_find_ptr(EG(ini_directives), name) == NULL) {return FAILURE; } modifiable = ini_entry->modifiable; modified = ini_entry->modified; if (stage == ZEND_INI_STAGE_ACTIVATE && modify_type == ZEND_INI_SYSTEM) { ini_entry->modifiable = ZEND_INI_SYSTEM; } if (! force_change) { if (! (ini_entry->modifiable & modify_type)) { return FAILURE; } } if (! EG(modified_ini_directives)) { ALLOC_HASHTABLE(EG(modified_ini_directives)); zend_hash_init(EG(modified_ini_directives), 8, NULL, NULL, 0); } // No matter how many times we call INI_SET in our PHP code, the first time we call INI_SET will only enter the logic and set ORIG_VALUE. From the second call to ini_set, this branch will not be executed again because modified has already been set to 1. Thus, ini_entry->orig_value always holds the configuration value before the first modification (that is, the original configuration) if (! modified) { ini_entry->orig_value = ini_entry->value; ini_entry->orig_modifiable = modifiable; ini_entry->modified = 1; zend_hash_add_ptr(EG(modified_ini_directives), ini_entry->name, ini_entry); } duplicate = zend_string_copy(new_value); // We call on_modify to update the module's global variables. Each INI_ENTRY stores the address of the module's global variable and its corresponding offset, making on_modify a quick way to modify memory. if (! ini_entry->on_modify || ini_entry->on_modify(ini_entry, duplicate, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage) == SUCCESS) { if (modified && ini_entry->orig_value ! = ini_entry->value) { /* we already changed the value, free the changed value */ zend_string_release(ini_entry->value); } ini_entry->value = duplicate; } else { zend_string_release(duplicate); return FAILURE; } return SUCCESS; }

As you can see, this function controls the execution time by modifying the in-memory global variables directly through the on_modify callback, which explains why ini_set is invalidated at the end of execution. With that said, in a few days I’ll be putting together a detailed summary of the entire PHP life cycle of INI loading.

Refer to the article: http://www.cnblogs.com/driftc…