摘要:問題發現今天寫了一個腳本,提交代碼的時候京哥給我,果斷幫我指出這個腳本的運行時間限制不要這么寫要這么寫然后給我講了一堆原理,什么直接進內存啊,需要暫時修改原配置啊恩,還是高工懂得多,于是我開始對兩個函數進行了測試。
問題發現
今天寫了一個腳本,提交代碼的時候京哥給我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...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/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報錯,可以第一時間展示給我們,以便于調試。當程序開發完成,成為正式產品時,我們希望將沒有預...
閱讀 3161·2023-04-25 19:09
閱讀 3875·2021-10-22 09:54
閱讀 1743·2021-09-29 09:35
閱讀 2904·2021-09-08 09:45
閱讀 2232·2021-09-06 15:00
閱讀 2766·2019-08-29 15:32
閱讀 1029·2019-08-28 18:30
閱讀 370·2019-08-26 13:43