摘要:问题发现今天写了一个脚本,提交代码的时候京哥给我,果断帮我指出这个脚本的运行时间限制不要这么写要这么写然后给我讲了一堆原理,什么直接进内存啊,需要暂时修改原配置啊恩,还是高工懂得多,于是我开始对两个函数进行了测试。
问题发现
今天写了一个脚本,提交代码的时候京哥给我cr,果断帮我指出这个脚本的运行时间限制不要这么写
ini_set("max_execution_time","30");
要这么写
set_limit_time(30);
然后给我讲了一堆原理,什么 set_limit_time() 直接进内存啊,ini_set("max_execution_time",) 需要暂时修改原配置啊...恩,还是高工懂得多,于是我开始对两个函数进行了测试。
测试测试代码如下:
这不测不要紧,一测就发现了问题,两次测试都是先sleep了10s,然后返回
beginFatal error: Maximum execution time of 1 second exceeded in /Users/jdq/test.php on line 6难道sleep不算脚本执行的时间?答案应该是肯定的,可是我以前测试后端接口超时的时候确实用的sleep,而且也超时返回了504,思考了一下应该是php-fpm的配置覆盖了php的ini配置的原因吧,所以sleep的时间也视为一个cgi进程的执行时间。(此处推断有待确定)回归正题,马上修改了测试代码
两个脚本都是执行了1s,直接fatal。那这两个函数又是在什么阶段起作用的呢,修改测试代码为
返回结果分别为
ini_set结果: 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这两个函数都是在执行的时候才开始限定脚本执行时间,感觉并没有什么区别,所以我找了找两个函数的源码。
函数源码 set_limit_time()源码如下(以下均为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); //看到配置项max_execution_time这里我心里就开始哈哈哈了 key = zend_string_init("max_execution_time", sizeof("max_execution_time")-1, 0); //其实调用了zend_alter_ini_entry_chars_ex这个函数 if (zend_alter_ini_entry_chars_ex(key, new_timeout_str, 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); }通过源码我们可以看出,set_limit_time 函数就是调用了 zend_alter_ini_entry_chars_ex 对配置项 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; }所以可以看出,set_limit_time 最终实现要是 zend_alter_ini_entry_ex ,下面我们将讨论这个函数。
ini_set源码如下
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(); //去一张hash表根据配置项名字寻找当前value,下面会说到,这个value通常会被释放掉 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; } //一堆我也不知道要干什么的校验 #define _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; } } } #undef _CHECK_PATH //最终要执行zend_alter_ini_entry_ex这个函数 if (zend_alter_ini_entry_ex(varname, new_value, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0) == FAILURE) { zval_dtor(return_value); RETURN_FALSE; } }ini_set 的实现也是要靠函数 zend_alter_ini_entry_ex ,而 set_limit_time 只是其中一个配置项(参数)为 max_execution_time 的实现而已,原来这两个函数实现机制是一样的,这时京哥的脸色已经发生了微微的变化,哈哈哈......
那么zend_alter_ini_entry_ex又是如何实现的呢?
zend_alter_ini_entry_ex源码如下
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)用于存放被修改过的ini_entry,根据name(配置名称)寻找到对应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); } //不管我们先后在php代码中调用几次ini_set,只有第一次ini_set时才会进入这段逻辑,设置orig_value。从第二次调用ini_set开始,便不会再次执行这段分支,因为此时的modified已经被置为1了。因此,ini_entry->orig_value始终保存的是第一次修改之前的配置值(即最原始的配置) 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); //调用on_modify是为了能够更新模块的全局变量。每一个ini_entry中都存储了该模块全局变量的地址以及对应的偏移量,使得on_modify可以很迅速的进行内存修改。 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; }可以看出该函数是同过通过 on_modify 回调函数直接修改了内存中的全局变量而达到控制执行时间的目的,所以这也解释了为什么ini_set 在执行结束就会失效。先说这些,过几天我会整理一下把整个PHP生命周期的ini加载过程详细总结一下。
参考文章:http://www.cnblogs.com/driftc...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/28761.html
摘要:至,有同样的行为。表示关闭所有错误报告表示显示二函数说明设置应该报告何种错误说明函数能够在运行时设置指令。后果是导致脚本终止不再继续运行。初始化启动过程中发生的警告非致命错误。用户产少的警告信息。出外的所有错误和警告信息。 错误报告级别:指定了在什么情况下,脚本代码中的错误(这里的错误是广义的错误,包括E_NOTICE注意、E_WARNING警告、E_ERROR致命错误等)会以错误报告...
摘要:英文原始文档地址中文文档地址当被激活时,只要决定显示通知,警告,错误等,就会显示堆栈跟踪。堆栈跟踪中的变量默认情况下,将在它生成的堆栈跟踪中显示可变信息。 文档内容来自xdebug.org/docs,翻译时xdebug版本为2.6。我在官方文档基础上针对中文排版和教程内容的编排做了一些优化,希望中文文档看起来更容易理解。 英文原始文档地址:https://xdebug.org/docs...
摘要:为系统增加的第一行代码不会影响该脚本在下的运行,因此您也可以用该方法编写跨平台的脚本程序。指定会话页面在客户端中的有限期分钟缺省下为分钟。最原始的博主没有找到,只能在此声明,特为转载。 这几天需要用PHP写一个定时抓取网页的服务器应用. 在网上搜了一下解决办法, 发现OSchina的 一个问题的解答很精彩(值得一看,谢谢大牛们的精彩回答O(∩_∩)O~), 提出几种解决办法.现总结如下...
摘要:当程序开发完成,成为正式产品时,我们希望将没有预测到的报错信息记录到错误日志中,而不是将这些报错信息展示给用户,因为用户极有可能利用这些暴露出脚本路径数据库信息或其他的报错信息进行一些破坏性的黑客行动。 程序报错总是在所难免,尽管我们书写代码时已经格外小心。 在开发php程序时,我们希望遇到php报错,可以第一时间展示给我们,以便于调试。当程序开发完成,成为正式产品时,我们希望将没有预...
阅读 3131·2023-04-25 19:09
阅读 3849·2021-10-22 09:54
阅读 1712·2021-09-29 09:35
阅读 2871·2021-09-08 09:45
阅读 2194·2021-09-06 15:00
阅读 2740·2019-08-29 15:32
阅读 1013·2019-08-28 18:30
阅读 348·2019-08-26 13:43