Description
opcache.dups_fix is documented as a fix for "Cannot redeclare" errors, but it only covers classes, not functions.
Inside opcache the directive is read in the table-copy that installs a cached script's symbols (ext/opcache/zend_accelerator_util_funcs.c). The class-table copy honors it — when ignore_dups is set it keeps the existing class and skips the duplicate:
/* _zend_accel_class_hash_copy */
} else if (UNEXPECTED(!ZCG(accel_directives).ignore_dups)) {
...
zend_class_redeclaration_error(E_ERROR, Z_PTR_P(t));
return;
}
continue; /* ignore_dups: keep the first definition */
The function-table copy right next to it doesn't check the directive at all — it goes straight to the fatal:
/* _zend_accel_function_hash_copy */
t = zend_hash_find_known_hash(target, p->key);
if (UNEXPECTED(t != NULL)) {
goto failure; /* -> "Cannot redeclare function ..." regardless of opcache.dups_fix */
}
So with opcache.dups_fix=1 a duplicate class is tolerated (first wins) but a duplicate function still fatals. The directive name and docs don't distinguish between the two, so this reads like an oversight rather than something intentional.
Where it bites
Long-running application servers that re-execute require_once'd files per request (we ran into this building ZealPHP, an OpenSwoole-based runtime). opcache re-installs a cached script's symbols into a table that already has them; dups_fix covers the class collision, but the function collision still kills the request with "Cannot redeclare function". So dups_fix only half-solves it for these setups — WordPress for example gets past its class redeclares with dups_fix=1 but then dies on the first function (_wp_can_use_pcre_u in wp-includes/compat.php).
Suggested fix
Make the function copy consistent with the class copy:
t = zend_hash_find_known_hash(target, p->key);
if (UNEXPECTED(t != NULL)) {
- goto failure;
+ /* Honor opcache.dups_fix for functions too — the class-table
+ * copy above already does. Keep the first-declared function. */
+ if (!ZCG(accel_directives).ignore_dups) {
+ goto failure;
+ }
+ continue;
}
I've tested this against 8.4 and it does the job (WordPress runs clean under opcache + a per-request re-execution model with it). Happy to open a PR with a .phpt if the asymmetry is agreed to be unintended — mostly wanted to check whether it's deliberate before sending one.
PHP Version
PHP 8.4 (the code is the same on master)
Operating System
Linux
Description
opcache.dups_fixis documented as a fix for "Cannot redeclare" errors, but it only covers classes, not functions.Inside opcache the directive is read in the table-copy that installs a cached script's symbols (
ext/opcache/zend_accelerator_util_funcs.c). The class-table copy honors it — whenignore_dupsis set it keeps the existing class and skips the duplicate:The function-table copy right next to it doesn't check the directive at all — it goes straight to the fatal:
So with
opcache.dups_fix=1a duplicate class is tolerated (first wins) but a duplicate function still fatals. The directive name and docs don't distinguish between the two, so this reads like an oversight rather than something intentional.Where it bites
Long-running application servers that re-execute
require_once'd files per request (we ran into this building ZealPHP, an OpenSwoole-based runtime). opcache re-installs a cached script's symbols into a table that already has them;dups_fixcovers the class collision, but the function collision still kills the request with "Cannot redeclare function". Sodups_fixonly half-solves it for these setups — WordPress for example gets past its class redeclares withdups_fix=1but then dies on the first function (_wp_can_use_pcre_uinwp-includes/compat.php).Suggested fix
Make the function copy consistent with the class copy:
t = zend_hash_find_known_hash(target, p->key); if (UNEXPECTED(t != NULL)) { - goto failure; + /* Honor opcache.dups_fix for functions too — the class-table + * copy above already does. Keep the first-declared function. */ + if (!ZCG(accel_directives).ignore_dups) { + goto failure; + } + continue; }I've tested this against 8.4 and it does the job (WordPress runs clean under opcache + a per-request re-execution model with it). Happy to open a PR with a
.phptif the asymmetry is agreed to be unintended — mostly wanted to check whether it's deliberate before sending one.PHP Version
PHP 8.4 (the code is the same on master)
Operating System
Linux