-
Notifications
You must be signed in to change notification settings - Fork 7.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Don't involve PHP in Apache mpm_winnt control process #7865
base: master
Are you sure you want to change the base?
Conversation
Hi Christoph, |
@wrowe Are you aware of Apache setups on Windows with an alternative MPM ? |
Hmm, just seen php-src/ext/opcache/shared_alloc_win32.c Lines 221 to 223 in 1d2c544
So running PHP for the control job might be deliberate: if a worker thread crashes, the whole worker process crashes, but OPcache is still up, and can be re-attached to (at least on good days). |
If this is the only reason, running PHP in the control job may not be worth it. Crashing may leave SHM in a corrupted state (because an SHM update was interrupted), so it's risky to re-use it after. |
Oh, very good point; I haven't thought about that! However, we have basically the same problem with other SAPIs, too. If e.g. an FPM worker crashes, SHM might be corrupt, but would still be reused, if I'm not wrong. Invalidating SHM for any crash which would not affect OPcache, could be a serious issue for large applications under high traffic. |
I think you are right about FPM. However in a threaded SAPI a crash kills all requests, which is far more disastrous. The cost of re-compiling due to SHM invalidation would probably be minor compared to that. Also, under Windows, the existence of |
Apache mpm_winnt uses a single control process which launches a single child process which in turn creates threads to handle requests. Since the child process is not forked, but rather spawned, and the control process is never involved in request handling, there is no need to power up PHP for the control process. Besides not doing this saves some resources, we no longer have to deal with potential re-attaching to OPcache shared memory which avoids issues due to ASLR, and paves the way to further improvements (such as preloading support for Apache mpm_winnt).
Since we no longer involve PHP in the parent process, the comment is no longer true for Apache2, but we might still need this wait loop for other servers/SAPIs.
Your reasoning makes perfect sense, @arnaud-lb. Thanks!
Indeed. We try to map to a certain base address, and if that fails (can only be caused by ASLR), we fall back to file cache (or bail out if not configured). Anyhow, I've rebased onto master, to resolve the merge conflict, and also adjusted the comment in shared_alloc_win32.c (I'm not sure if that waiting is still required for other servers/SAPIs). I've also did some testing against Symfony demo, and couldn't find any issues. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't aware of how re-attachment happened precisely and I just took a look at create_segment()
now. My understanding is that calling OpenFileMapping(flags, name)
in different processes will end up opening a mapping to the same file if name
is the same? Or is there something that's inherited by spawns that lets them see the same file? In the former case we may need more changes to prevent re-attachment.
We attempt to map the opened file to fixed addresses (vista_mapping_base_set
) with MapViewOfFileEx()
, when creating the initial mapping. Can this fail due to ASLR?
In zend_shared_alloc_reattach()
, we check if the address of execute_ex
changed. If it did, a fatal error is triggered:
php-src/ext/opcache/shared_alloc_win32.c
Line 168 in 3b23de3
zend_win_error_message(ACCEL_LOG_FATAL, "Opcode handlers are unusable due to ASLR. Please setup opcache.file_cache and opcache.file_cache_fallback directives for more convenient Opcache usage", err); |
It is my understanding that unless file_cache_fallback
is enabled, this will always fail when ASLR is enabled, so re-attachment always fallbacks to the file cache.
As we are storing addresses in cached op arrays now, re-attachment is never safe with ASLR.
I think we may as well rewrite create_segments()
to remove re-attachment support, and to use VirtualAlloc()
:)
sapi_startup(&apache2_sapi_module); | ||
if (apache2_sapi_module.startup(&apache2_sapi_module) != SUCCESS) { | ||
#ifndef PHP_WIN32 | ||
if (php_apache_startup_php() != OK) { | ||
return DONE; | ||
} | ||
apr_pool_cleanup_register(pconf, NULL, php_apache_server_shutdown, apr_pool_cleanup_null); | ||
php_apache_add_version(pconf); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately this accesses PG(expose_php)
, which is controlled by ini settings. It's too early to call this as ini settings haven't been processed yet. It may be safe to move into php_apache_startup_php()
since it's called once per process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As is, that is already moved into php_apache_child_init()
for PHP_WIN32
, and apparently works (tested with default, and both alternatives in php.ini).
Right, only if the name (username + zend_system_id + sapi_name) matches, re-attachment is attempted.
If none of the attempted addresses (hardcoded
Do we really store direct addresses, except for preloading and some other places which are disabled for Windows already?
Well, I'm not sure whether there is a performance difference when using Thinking about this, we could actually build with That said, I still like to get rid of the re-attaching, but in my opinion, we need to provide solid alternatives to FCGI first. |
We normally don't in win32 builds, but I realized earlier today after reading the error message, that we store the absolute op code handler address in
Ok I was suggesting I think that we should not build with
What do we have currently? FCGI is php-cgi in FastCGI mode, or FPM ? (I don't know if fpm is supported on Windows). Both would indeed benefit from re-attachment (although it probably always fallback to file currently). Besides FCGI, we do have the Apache2 module. And #17838 in the future. Keeping re-attachment support around until #17838 is released sounds good to me. Maybe we can disable it explicitly in the Apache2 module? |
FPM is not supported on Windows (there is no To be able to do some testing, I've applied the following patch ext/opcache/ZendAccelerator.h | 1 +
ext/opcache/shared_alloc_win32.c | 16 +++++++++++++---
ext/opcache/zend_accelerator_module.c | 4 +++-
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h
index 440d09e235d..6b1d5a01b8d 100644
--- a/ext/opcache/ZendAccelerator.h
+++ b/ext/opcache/ZendAccelerator.h
@@ -192,6 +192,7 @@ typedef struct _zend_accel_directives {
#endif
#ifdef ZEND_WIN32
char *cache_id;
+ bool per_worker;
#endif
} zend_accel_directives;
diff --git a/ext/opcache/shared_alloc_win32.c b/ext/opcache/shared_alloc_win32.c
index 3f0c71a7c66..ac311b8bf6c 100644
--- a/ext/opcache/shared_alloc_win32.c
+++ b/ext/opcache/shared_alloc_win32.c
@@ -266,8 +266,13 @@ static int create_segments(size_t requested_size, zend_shared_segment ***shared_
shared_segment = (zend_shared_segment *)((char *)(*shared_segments_p) + sizeof(void *));
(*shared_segments_p)[0] = shared_segment;
- memfile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE | SEC_COMMIT, size_high, size_low,
- create_name_with_username(ACCEL_FILEMAP_NAME));
+ if (ZCG(accel_directives).per_worker) {
+ memfile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE | SEC_COMMIT, size_high, size_low, NULL);
+ } else {
+ memfile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE | SEC_COMMIT, size_high, size_low,
+ create_name_with_username(ACCEL_FILEMAP_NAME));
+ }
+ zend_accel_error(ACCEL_LOG_WARNING, "CreateFileMapping %p", memfile);
if (memfile == NULL) {
err = GetLastError();
zend_shared_alloc_unlock_win32();
@@ -296,7 +301,12 @@ static int create_segments(size_t requested_size, zend_shared_segment ***shared_
}
do {
- shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE, 0, 0, 0, *wanted_mapping_base);
+ if (ZCG(accel_directives).per_worker) {
+ shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE, 0, 0, 0, NULL);
+ } else {
+ shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE, 0, 0, 0, *wanted_mapping_base);
+ }
+ zend_accel_error(ACCEL_LOG_WARNING, "MapViewOfFileEx at %p", mapping_base);
if (*wanted_mapping_base == NULL) { /* Auto address (NULL) is the last option on the array */
break;
}
diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c
index e6642c76e1a..8cab0f125fd 100644
--- a/ext/opcache/zend_accelerator_module.c
+++ b/ext/opcache/zend_accelerator_module.c
@@ -313,7 +313,8 @@ ZEND_INI_BEGIN()
#endif
#ifdef ZEND_WIN32
STD_PHP_INI_ENTRY("opcache.cache_id" , "" , PHP_INI_SYSTEM, OnUpdateString, accel_directives.cache_id, zend_accel_globals, accel_globals)
-#endif
+ STD_PHP_INI_BOOLEAN("opcache.per_worker" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.per_worker, zend_accel_globals, accel_globals)
+ #endif
#ifdef HAVE_JIT
STD_PHP_INI_ENTRY("opcache.jit" , "disable", PHP_INI_ALL, OnUpdateJit, options, zend_jit_globals, jit_globals)
STD_PHP_INI_ENTRY("opcache.jit_buffer_size" , ZEND_JIT_DEFAULT_BUFFER_SIZE, PHP_INI_SYSTEM, OnUpdateLong, buffer_size, zend_jit_globals, jit_globals)
@@ -829,6 +830,7 @@ ZEND_FUNCTION(opcache_get_configuration)
#endif
#ifdef ZEND_WIN32
add_assoc_string(&directives, "opcache.cache_id", STRING_NOT_NULL(ZCG(accel_directives).cache_id));
+ add_assoc_bool(&directives, "opcache.per_worker", ZCG(accel_directives).per_worker);
#endif
#ifdef HAVE_JIT
add_assoc_string(&directives, "opcache.jit", JIT_G(options)); Then I did a simple comparison against a single Symfony demo URL (de/blog/). First I triggered a single request to that URL, what always spins up a single FCGI worker with usual IIS configuration. Then I've triggered concurrent request against that same URL (spawned 2 additional workers). With Further issues with a SHM per worker:
I'm afraid that having a separate OPcache SHM per process is only viable if there is only a single worker process (with multiple worker threads), and even then it might be necessary to keep the worker process up, even if a thread crashes, like it's currently done for ISAPI; need to do some experiments with that). |
I agree with your conclusion that having a separate SHM per process is only viable if there is only one process. My understanding is it will be the case for Apache2 and ISAPI. One thing to take into consideration when measuring the effect of losing SHM after a crash is that if reattachment was removed entirely, win32 would benefit from the same level of optimizations as other platforms. Normal execution would be faster, and would offset the increased cost of occasional crashes. |
Apache mpm_winnt uses a single control process which launches a single
child process which in turn creates threads to handle requests. Since
the child process is not forked, but rather spawned, and the control
process is never involved in request handling, there is no need to
power up PHP for the control process. Besides not doing this saves
some resources, we no longer have to deal with potential re-attaching
to OPcache shared memory which avoids issues due to ASLR, and paves the
way to further improvements (such as preloading support for Apache
mpm_winnt).
Possibly, a problem with that exact implementation is that it assumes that mpm_winnt is always used on Windows, what might be wrong. Maybe someone knowlegable with Apache on Windows can clarify whether alternative MPMs might be used there, and if so, suggest a better way to detect that (i.e. is there a respective macro we could use in favor of
PHP_WIN32
)?cc @Jan-E