Jetpack: Autoloader: Fatal error when switching from 9.6 to 9.5 when both versions are installed

Created on 8 Apr 2021  路  4Comments  路  Source: Automattic/jetpack

The autoloader causes this fatal error when switching from 9.6 to 9.5 on a site with both 9.5 and 9.6 installed:

Fatal error: Cannot declare class Automattic\Jetpack\Autoloader\jpf11009ded9fc4592b6a05b61ce272b3cjetpack\Autoloader,
because the name is already in use ...

Steps to reproduce the issue

  1. Set up a test site:

    • Create an wp-content/mu-plugins folder.
    • Add both Jetpack 9.5 and 9.6 to the mu-plugins folder. Name them jetpack-9.5 and jetpack-9.6.
    • In the mu-plugins folder, add a file with code that allows you to set a constant to switch between Jetpack versions.
    <?php
    
    if ( defined( 'JETPACK_LOAD_VERSION' ) ) {
        $version = JETPACK_LOAD_VERSION;
        $path = WPMU_PLUGIN_DIR . "/jetpack-$version/jetpack.php";
    
        if ( file_exists( $path ) ) {
                require_once( $path );
        }
    }
    
  2. Set the JETPACK_LOAD_VERSION constant to 9.6 to load Jetpack 9.6.
  3. Set the JETPACK_LOAD_VERSION constant to 9.5 to load Jetpack 9.5. The error should be generated.

Cause

Here's what causes the fatal error:

  1. When Jetpack 9.6 is loaded, its plugin path is added to the jetpack_autoloader_plugin_paths transient value. The autoloader uses this value to cache the plugin paths so it's aware of the expected plugins on each page load.
  2. When the constant is changed to 9.5, the autoloader sees both 9.5 and 9.6 as active plugins, 9.5 because it's actually active, and 9.6 because it's in the cached plugins list in the transient.
  3. 9.5's autoloader is loaded first. It checks for an active plugin with a newer autoloader, and finds that 9.6 has a newer autoloader. 9.5's autoloader tries to load 9.6's autoloader files.
  4. The namespace for the autoloader files in 9.5 and 9.6 are the same, so the fatal error is generated.
Does this affect other Jetpack versions?

Other versions of Jetpack had different namespaces in the autoloader files, so even when two autoloaders from different Jetpack versions were loaded, they didn't conflict. For example, you can add Jetpack 9.4 to your site, and switch between 9.5 and 9.4 without errors.

How should we fix this?

Ideally, we wouldn't unnecessarily load two autoloaders like this. When we switch to 9.5, 9.6 is no longer active so we shouldn't load its autoloader. However, I'm not sure how we can preemptively detect an inactive plugin in the plugins cache in this type of environment.

We could also change the namespace, as suggested in the comments below. We would still unnecessarily load two autoloaders with this fix, but they wouldn't conflict.

More Info

p1617814639001300-slack-CDD9LQRSN

[Package] Autoloader [Pri] High [Type] Bug

All 4 comments

When fixing this let's please avoid reintroducing the problem that #18570 solved, i.e. we don't want https://github.com/Automattic/jetpack-production/commits/master/vendor/autoload.php to start filling up again with a new hash being generated on every commit.

So if it comes to changing the .config.autoloader-suffix in composer.json as the chosen fix, let's incorporate the version number in there by adding some logic to tools/project-version.sh rather than going back to a random suffix.

(for the record, if that's the chosen solution I'll volunteer to write the PR)

Marrying the two -- consistent hashes that bump/prefix/etc with the version seems like a fair compromise of 18570 and resolving this issue.

This issue, while impacting a major host, is relatively specific so definitely concur with @anomiex that we shouldn't regress against 18570.

So if it comes to changing the .config.autoloader-suffix in composer.json as the chosen fix, let's incorporate the version number in there by adding some logic to tools/project-version.sh rather than going back to a random suffix.

That seems like a reasonable solution for the matching namespace issue.

I've also been thinking about how to prevent loading two autoloaders in the first place. In the environment that caused the error, multiple versions of Jetpack are installed and they're selected using a constant. I haven't thought of a reasonable way to detect that the transient has an outdated plugins list and preemptively clear the transient in this environment.

To prevent a site from getting stuck with this error, I think we could register a shutdown function using register_shutdown_function, and just delete the jetpack_autoloader_plugins_path transient if the last error was an E_COMPILE_ERROR.

I added this to the 9.7 milestone because this is a high priority bug, and we should try to have it fixed in the next release.

Was this page helpful?
0 / 5 - 0 ratings