From: Timo Tijhof Date: Wed, 22 May 2019 18:29:32 +0000 (+0100) Subject: resourceloader: Skip modules with circular deps in tree optimiser X-Git-Tag: 1.34.0-rc.0~1356^2 X-Git-Url: https://git.cyclocoop.org/%242?a=commitdiff_plain;h=14547fd9dcde5d1be5252bee62f25ea7d085dff1;p=lhc%2Fweb%2Fwiklou.git resourceloader: Skip modules with circular deps in tree optimiser Either the server needs to omit these from the registry with state=error output to the client (and server-side error logging), or it needs to detect them, and transport them unchanged, so that the existing client-side logic can handle it. This patch does the latter. Without the source code change in this patch, the added test case fails due to "top" and "middle1" then being registered with an empty array as dependencies. Bug: T223402 Change-Id: I57502d7c4e434de4737759aed325dd4200ca89bf --- diff --git a/autoload.php b/autoload.php index ae044f45c9..2d1e175fdb 100644 --- a/autoload.php +++ b/autoload.php @@ -1235,6 +1235,7 @@ $wgAutoloadLocalClasses = [ 'ResetUserTokens' => __DIR__ . '/maintenance/resetUserTokens.php', 'ResourceFileCache' => __DIR__ . '/includes/cache/ResourceFileCache.php', 'ResourceLoader' => __DIR__ . '/includes/resourceloader/ResourceLoader.php', + 'ResourceLoaderCircularDependencyError' => __DIR__ . '/includes/resourceloader/ResourceLoaderCircularDependencyError.php', 'ResourceLoaderClientHtml' => __DIR__ . '/includes/resourceloader/ResourceLoaderClientHtml.php', 'ResourceLoaderContext' => __DIR__ . '/includes/resourceloader/ResourceLoaderContext.php', 'ResourceLoaderFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderFileModule.php', diff --git a/includes/resourceloader/ResourceLoaderCircularDependencyError.php b/includes/resourceloader/ResourceLoaderCircularDependencyError.php new file mode 100644 index 0000000000..7cd53fe126 --- /dev/null +++ b/includes/resourceloader/ResourceLoaderCircularDependencyError.php @@ -0,0 +1,26 @@ + &$data ) { $dependencies = $data['dependencies']; - foreach ( $data['dependencies'] as $dependency ) { - $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency ); - $dependencies = array_diff( $dependencies, $implicitDependencies ); + try { + foreach ( $data['dependencies'] as $dependency ) { + $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency ); + $dependencies = array_diff( $dependencies, $implicitDependencies ); + } + } catch ( ResourceLoaderCircularDependencyError $err ) { + // Leave unchanged + $dependencies = $data['dependencies']; } + // Rebuild keys $data['dependencies'] = array_values( $dependencies ); } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php index b5dd008b3f..99f5e1b10a 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php @@ -105,6 +105,83 @@ mw.loader.register( [ "c", "{blankVer}" ] +] );', + ] ], + [ [ + // Regression test for T223402. + 'msg' => 'Optimise the dependency tree (indirect circular dependency)', + 'modules' => [ + 'top' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'middle1', 'util' ] ] ), + 'middle1' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'middle2', 'util' ] ] ), + 'middle2' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'bottom' ] ] ), + 'bottom' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'top' ] ] ), + 'util' => new ResourceLoaderTestModule( [ 'dependencies' => [] ] ), + ], + 'out' => ' +mw.loader.addSource( { + "local": "/w/load.php" +} ); +mw.loader.register( [ + [ + "top", + "{blankVer}", + [ + 1, + 4 + ] + ], + [ + "middle1", + "{blankVer}", + [ + 2, + 4 + ] + ], + [ + "middle2", + "{blankVer}", + [ + 3 + ] + ], + [ + "bottom", + "{blankVer}", + [ + 0 + ] + ], + [ + "util", + "{blankVer}" + ] +] );', + ] ], + [ [ + // Regression test for T223402. + 'msg' => 'Optimise the dependency tree (direct circular dependency)', + 'modules' => [ + 'top' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'util', 'top' ] ] ), + 'util' => new ResourceLoaderTestModule( [ 'dependencies' => [] ] ), + ], + 'out' => ' +mw.loader.addSource( { + "local": "/w/load.php" +} ); +mw.loader.register( [ + [ + "top", + "{blankVer}", + [ + 1, + 0 + ] + ], + [ + "util", + "{blankVer}" + ] ] );', ] ], [ [