- URL: https://www.laruence.com/en/2012/09/12/2765.html
- Please include attribution when republishing.
Lately, we've had a few discussions about whether to keep or drop apc.include_once_override. This APC setting has never really been implemented well.
Here I'd like to share, with everyone, the reason behind this problem, and a few takeaways for us.
The debate over using include vs include_once (hereafter, this also includes require_once) has been going on for a long time, and there's a settled conclusion: try to use include rather than include_once. The most common reason given in the past is that include_once needs to scan the list of already-loaded files to confirm whether the file is present, and only then load it.
Sure, that reason is correct. But what I want to talk about today is a different reason.
We know that for PHP to determine whether a file has been loaded, it needs the file's opened_path. That is to say, for example:
<?php
set_include_path("/tmp/:/tmp2/");
include_once("2.php");
?>
When PHP sees include_once "2.php", it doesn't know the file's real path, and so it can't check the loaded-files list to determine whether it's already been loaded. So in the include_once implementation, PHP first tries to resolve the file's real path (for a regular file this resolution is merely something like checking getcwd and the file path, so if it's a relative path it usually won't succeed). If the resolution succeeds, it looks up EG(include_files); if it's there, the file was already included, so it returns. Otherwise it opens the file, thereby obtaining the file's opened_path. For example, in the case above, the file exists at "/tmp2/2.php".
Then, once this opened_path is obtained, PHP looks up the loaded-files list to see whether it has already been included. If it hasn't, it just compiles the file directly — no need to open the file again.
1. Try to resolve the file's absolute path. If it succeeds, check EG(included_files) — if present, return; otherwise continue. 2. Open the file to get its opened path. 3. Use the opened path to look up EG(included_files) — if present, return; otherwise continue. 4. Compile the file (compile_file).
This isn't a problem in most cases. However, the problem arises when you're using APC...
When you use APC, APC hijacks the compile_file function pointer that compiles the file, so it retrieves the compiled result directly from the cache, avoiding opening the actual file and avoiding the open system call.
However, when your code uses include_once, before compile_file is reached, PHP has already tried to open the file, and only then enters APC's hijacked compile_file. This produces an extra open operation. It's precisely to solve this problem that APC introduced include_once_override. When include_once_override is enabled, APC hijacks PHP's ZEND_INCLUDE_OR_EVAL opcode handler, uses stat to determine the file's absolute path, and if it finds the file hasn't been loaded, rewrites the opcode to include — a tricky workaround.
But unfortunately, as I said, APC's include_once_override has never been implemented well, and it has some undefined-behavior issues. For example:
<?php
set_include_path("/tmp");
function a($arg = array()) {
include_once("b.php");
}
a();
a();
?>
Then, our b.php is placed at "/tmp/b.php", with the following content:
<?php
class B {}
?>
Then, with apc.include_once_override turned on, hitting it repeatedly gives the following error:
Fatal error - include() : Cannot redeclare class b
(Postscript, 2012-09-15 02:07:20: I have fixed this APC bug: #63070)
Setting these technical factors aside, I've always believed that we should use include rather than include_once, because we can fully plan for it ourselves that a file is only loaded once. And we can use autoloading to achieve this too.
If you use include_once, it can only prove that you have no confidence in your own code.
So, I'd recommend everyone: stop using include_once.
Be First to Comment