Skip to content

opcache.dups_fix is honored for duplicate classes but not duplicate functions #22214

@sibidharan

Description

@sibidharan

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions