From 3e1b29824bd7f2e79a59d4990fc7461d9941f68c Mon Sep 17 00:00:00 2001 From: Aaron Schulz Date: Mon, 22 Jul 2019 17:07:11 -0700 Subject: [PATCH] rdbms: assorted LBFactoryMulti/LBFactorySimple cleanups Ignore "groupLoads" in "serverTemplate" for consistency with "load". These server config map values should come from "groupLoadsBySection" and "sectionLoads" only. Simplify LBFactoryMulti::makeServerArray() to not bother setting values that LoadBalancer already sets ('flags', 'master', 'replica'). Move down private methods and clean up a various code comments. Change-Id: I0fe7f913a37236380127bef8d02768a9d1209596 --- includes/libs/rdbms/lbfactory/ILBFactory.php | 3 + .../libs/rdbms/lbfactory/LBFactoryMulti.php | 375 ++++++++---------- .../libs/rdbms/lbfactory/LBFactorySimple.php | 55 +-- 3 files changed, 207 insertions(+), 226 deletions(-) diff --git a/includes/libs/rdbms/lbfactory/ILBFactory.php b/includes/libs/rdbms/lbfactory/ILBFactory.php index 4b6afe7089..6e9591ba3e 100644 --- a/includes/libs/rdbms/lbfactory/ILBFactory.php +++ b/includes/libs/rdbms/lbfactory/ILBFactory.php @@ -38,6 +38,9 @@ interface ILBFactory { /** @var int Save DB positions, waiting on all DCs */ const SHUTDOWN_CHRONPROT_SYNC = 2; + /** @var string Default main LB cluster name (do not change this) */ + const CLUSTER_MAIN_DEFAULT = 'DEFAULT'; + /** * Construct a manager of ILoadBalancer objects * diff --git a/includes/libs/rdbms/lbfactory/LBFactoryMulti.php b/includes/libs/rdbms/lbfactory/LBFactoryMulti.php index f675b58778..ef1f0a68c4 100644 --- a/includes/libs/rdbms/lbfactory/LBFactoryMulti.php +++ b/includes/libs/rdbms/lbfactory/LBFactoryMulti.php @@ -24,6 +24,7 @@ namespace Wikimedia\Rdbms; use InvalidArgumentException; +use UnexpectedValueException; /** * A multi-database, multi-master factory for Wikimedia and similar installations. @@ -32,64 +33,45 @@ use InvalidArgumentException; * @ingroup Database */ class LBFactoryMulti extends LBFactory { - /** @var array A map of database names to section names */ - private $sectionsByDB; - /** - * @var array A 2-d map. For each section, gives a map of server names to - * load ratios - */ - private $sectionLoads; - /** - * @var array[] Server info associative array - * @note The host, hostName and load entries will be overridden - */ - private $serverTemplate; + /** @var LoadBalancer[] */ + private $mainLBs = []; + /** @var LoadBalancer[] */ + private $externalLBs = []; - /** @var array A 3-d map giving server load ratios for each section and group */ + /** @var string[] Map of (hostname => IP address) */ + private $hostsByName = []; + /** @var string[] Map of (database name => section name) */ + private $sectionsByDB = []; + /** @var int[][][] Map of (section => group => host => load ratio) */ private $groupLoadsBySection = []; - /** @var array A 3-d map giving server load ratios by DB name */ + /** @var int[][][] Map of (database => group => host => load ratio) */ private $groupLoadsByDB = []; - /** @var array A map of hostname to IP address */ - private $hostsByName = []; - /** @var array A map of external storage cluster name to server load map */ + /** @var int[][] Map of (cluster => host => load ratio) */ private $externalLoads = []; - /** - * @var array A set of server info keys overriding serverTemplate for - * external storage - */ - private $externalTemplateOverrides; - /** - * @var array A 2-d map overriding serverTemplate and - * externalTemplateOverrides on a server-by-server basis. Applies to both - * core and external storage - */ - private $templateOverridesByServer; - /** @var array A 2-d map overriding the server info by section */ - private $templateOverridesBySection; - /** @var array A 2-d map overriding the server info by external storage cluster */ - private $templateOverridesByCluster; - /** @var array An override array for all master servers */ - private $masterTemplateOverrides; - /** - * @var array|bool A map of section name to read-only message. Missing or - * false for read/write - */ + /** @var array Server config map ("host", "hostName", "load", and "groupLoads" are ignored) */ + private $serverTemplate = []; + /** @var array Server config map overriding "serverTemplate" for external storage */ + private $externalTemplateOverrides = []; + /** @var array[] Map of (section => server config map overrides) */ + private $templateOverridesBySection = []; + /** @var array[] Map of (cluster => server config map overrides) for external storage */ + private $templateOverridesByCluster = []; + /** @var array Server config override map for all main and external master servers */ + private $masterTemplateOverrides = []; + /** @var array[] Map of (host => server config map overrides) for main and external servers */ + private $templateOverridesByServer = []; + /** @var string[]|bool[] A map of section name to read-only message */ private $readOnlyBySection = []; - /** @var LoadBalancer[] */ - private $mainLBs = []; - /** @var LoadBalancer[] */ - private $extLBs = []; - /** @var string */ - private $loadMonitorClass = 'LoadMonitor'; + /** @var string An ILoadMonitor class */ + private $loadMonitorClass; + /** @var string */ private $lastDomain; /** @var string */ private $lastSection; /** - * @see LBFactory::__construct() - * * Template override precedence (highest => lowest): * - templateOverridesByServer * - masterTemplateOverrides @@ -98,122 +80,108 @@ class LBFactoryMulti extends LBFactory { * - serverTemplate * Overrides only work on top level keys (so nested values will not be merged). * - * Server configuration maps should be of the format Database::factory() requires. + * Server config maps should be of the format Database::factory() requires. * Additionally, a 'max lag' key should also be set on server maps, indicating how stale the * data can be before the load balancer tries to avoid using it. The map can have 'is static' * set to disable blocking replication sync checks (intended for archive servers with * unchanging data). - * - * @param array $conf Parameters of LBFactory::__construct() as well as: - * - sectionsByDB Map of database names to section names. - * - sectionLoads 2-d map. For each section, gives a map of server names to - * load ratios. For example: + + * @see LBFactory::__construct() + * @param array $conf Additional parameters include: + * - hostsByName Optional (hostname => IP address) map. + * - sectionsByDB Optional map of (database => section name). + * For example: * [ - * 'section1' => [ - * 'db1' => 100, - * 'db2' => 100 - * ] + * 'DEFAULT' => 'section1', + * 'database1' => 'section2' * ] - * - serverTemplate Server configuration map intended for Database::factory(). - * Note that "host", "hostName" and "load" entries will be - * overridden by "sectionLoads" and "hostsByName". - * - groupLoadsBySection 3-d map giving server load ratios for each section/group. + * - sectionLoads Optional map of (section => host => load ratio); the first + * host in each section is the master server for that section. + * For example: + * [ + * 'dbmaser' => 0, + * 'dbreplica1' => 100, + * 'dbreplica2' => 100 + * ] + * - groupLoadsBySection Optional map of (section => group => host => load ratio); + * any ILoadBalancer::GROUP_GENERIC group will be ignored. * For example: * [ * 'section1' => [ * 'group1' => [ - * 'db1' => 100, - * 'db2' => 100 + * 'dbreplica3 => 100, + * 'dbreplica4' => 100 * ] * ] * ] - * - groupLoadsByDB 3-d map giving server load ratios by DB name. - * - hostsByName Map of hostname to IP address. - * - externalLoads Map of external storage cluster name to server load map. - * - externalTemplateOverrides Set of server configuration maps overriding - * "serverTemplate" for external storage. - * - templateOverridesByServer 2-d map overriding "serverTemplate" and - * "externalTemplateOverrides" on a server-by-server basis. - * Applies to both core and external storage. - * - templateOverridesBySection 2-d map overriding the server configuration maps by section. - * - templateOverridesByCluster 2-d map overriding the server configuration maps by external - * storage cluster. - * - masterTemplateOverrides Server configuration map overrides for all master servers. - * - loadMonitorClass Name of the LoadMonitor class to always use. - * - readOnlyBySection A map of section name to read-only message. - * Missing or false for read/write. + * - groupLoadsByDB Optional (database => group => host => load ratio) map. + * - externalLoads Optional (cluster => host => load ratio) map. + * - serverTemplate server config map for Database::factory(). + * Note that "host", "hostName" and "load" entries will be + * overridden by "groupLoadsBySection" and "hostsByName". + * - externalTemplateOverrides Optional server config map overrides for external + * stores; respects the override precedence described above. + * - templateOverridesBySection Optional (section => server config map overrides) map; + * respects the override precedence described above. + * - templateOverridesByCluster Optional (external cluster => server config map overrides) + * map; respects the override precedence described above. + * - masterTemplateOverrides Optional server config map overrides for masters; + * respects the override precedence described above. + * - templateOverridesByServer Optional (host => server config map overrides) map; + * respects the override precedence described above + * and applies to both core and external storage. + * - loadMonitorClass Name of the LoadMonitor class to always use. [optional] + * - readOnlyBySection Optional map of (section name => message text or false). + * String values make sections read only, whereas anything + * else does not restrict read/write mode. */ public function __construct( array $conf ) { parent::__construct( $conf ); - $required = [ 'sectionsByDB', 'sectionLoads', 'serverTemplate' ]; - $optional = [ 'groupLoadsBySection', 'groupLoadsByDB', 'hostsByName', - 'externalLoads', 'externalTemplateOverrides', 'templateOverridesByServer', - 'templateOverridesByCluster', 'templateOverridesBySection', 'masterTemplateOverrides', - 'readOnlyBySection', 'loadMonitorClass' ]; - - foreach ( $required as $key ) { - if ( !isset( $conf[$key] ) ) { - throw new InvalidArgumentException( __CLASS__ . ": $key is required." ); - } - $this->$key = $conf[$key]; - } - - foreach ( $optional as $key ) { - if ( isset( $conf[$key] ) ) { - $this->$key = $conf[$key]; - } + $this->hostsByName = $conf['hostsByName'] ?? []; + $this->sectionsByDB = $conf['sectionsByDB']; + $this->groupLoadsBySection = $conf['groupLoadsBySection'] ?? []; + foreach ( ( $conf['sectionLoads'] ?? [] ) as $section => $loadByHost ) { + $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] = $loadByHost; } - } - - /** - * @param bool|string $domain - * @return string - */ - private function getSectionForDomain( $domain = false ) { - if ( $this->lastDomain === $domain ) { - return $this->lastSection; - } - - $database = $this->getDatabaseFromDomain( $domain ); - $section = $this->sectionsByDB[$database] ?? 'DEFAULT'; - $this->lastSection = $section; - $this->lastDomain = $domain; - - return $section; + $this->groupLoadsByDB = $conf['groupLoadsByDB'] ?? []; + $this->externalLoads = $conf['externalLoads'] ?? []; + $this->serverTemplate = $conf['serverTemplate'] ?? []; + $this->externalTemplateOverrides = $conf['externalTemplateOverrides'] ?? []; + $this->templateOverridesBySection = $conf['templateOverridesBySection'] ?? []; + $this->templateOverridesByCluster = $conf['templateOverridesByCluster'] ?? []; + $this->masterTemplateOverrides = $conf['masterTemplateOverrides'] ?? []; + $this->templateOverridesByServer = $conf['templateOverridesByServer'] ?? []; + $this->readOnlyBySection = $conf['readOnlyBySection'] ?? []; + + $this->loadMonitorClass = $conf['loadMonitorClass'] ?? LoadMonitor::class; } public function newMainLB( $domain = false ) { - $database = $this->getDatabaseFromDomain( $domain ); $section = $this->getSectionForDomain( $domain ); - $groupLoads = $this->groupLoadsByDB[$database] ?? []; - - if ( isset( $this->groupLoadsBySection[$section] ) ) { - $groupLoads = array_merge_recursive( - $groupLoads, $this->groupLoadsBySection[$section] ); + if ( !isset( $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] ) ) { + throw new UnexpectedValueException( "Section '$section' has no hosts defined." ); } - $readOnlyReason = $this->readOnlyReason; - // Use the LB-specific read-only reason if everything isn't already read-only - if ( $readOnlyReason === false && isset( $this->readOnlyBySection[$section] ) ) { - $readOnlyReason = $this->readOnlyBySection[$section]; - } - - $template = $this->serverTemplate; - if ( isset( $this->templateOverridesBySection[$section] ) ) { - $template = $this->templateOverridesBySection[$section] + $template; - } + $dbGroupLoads = $this->groupLoadsByDB[$this->getDomainDatabase( $domain )] ?? []; + unset( $dbGroupLoads[ILoadBalancer::GROUP_GENERIC] ); // cannot override return $this->newLoadBalancer( - $template, - $this->sectionLoads[$section], - $groupLoads, - $readOnlyReason + array_merge( + $this->serverTemplate, + $this->templateOverridesBySection[$section] ?? [] + ), + array_merge( $this->groupLoadsBySection[$section], $dbGroupLoads ), + // Use the LB-specific read-only reason if everything isn't already read-only + is_string( $this->readOnlyReason ) + ? $this->readOnlyReason + : ( $this->readOnlyBySection[$section] ?? false ) ); } public function getMainLB( $domain = false ) { $section = $this->getSectionForDomain( $domain ); + if ( !isset( $this->mainLBs[$section] ) ) { $this->mainLBs[$section] = $this->newMainLB( $domain ); } @@ -223,30 +191,26 @@ class LBFactoryMulti extends LBFactory { public function newExternalLB( $cluster ) { if ( !isset( $this->externalLoads[$cluster] ) ) { - throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"" ); - } - $template = $this->serverTemplate; - if ( $this->externalTemplateOverrides ) { - $template = $this->externalTemplateOverrides + $template; - } - if ( isset( $this->templateOverridesByCluster[$cluster] ) ) { - $template = $this->templateOverridesByCluster[$cluster] + $template; + throw new InvalidArgumentException( "Unknown cluster '$cluster'" ); } return $this->newLoadBalancer( - $template, - $this->externalLoads[$cluster], - [], + array_merge( + $this->serverTemplate, + $this->externalTemplateOverrides, + $this->templateOverridesByCluster[$cluster] ?? [] + ), + [ ILoadBalancer::GROUP_GENERIC => $this->externalLoads[$cluster] ], $this->readOnlyReason ); } public function getExternalLB( $cluster ) { - if ( !isset( $this->extLBs[$cluster] ) ) { - $this->extLBs[$cluster] = $this->newExternalLB( $cluster ); + if ( !isset( $this->externalLBs[$cluster] ) ) { + $this->externalLBs[$cluster] = $this->newExternalLB( $cluster ); } - return $this->extLBs[$cluster]; + return $this->externalLBs[$cluster]; } public function getAllMainLBs() { @@ -269,20 +233,45 @@ class LBFactoryMulti extends LBFactory { return $lbs; } + public function forEachLB( $callback, array $params = [] ) { + foreach ( $this->mainLBs as $lb ) { + $callback( $lb, ...$params ); + } + foreach ( $this->externalLBs as $lb ) { + $callback( $lb, ...$params ); + } + } + + /** + * @param bool|string $domain + * @return string + */ + private function getSectionForDomain( $domain = false ) { + if ( $this->lastDomain === $domain ) { + return $this->lastSection; + } + + $database = $this->getDomainDatabase( $domain ); + $section = $this->sectionsByDB[$database] ?? self::CLUSTER_MAIN_DEFAULT; + $this->lastSection = $section; + $this->lastDomain = $domain; + + return $section; + } + /** * Make a new load balancer object based on template and load array * - * @param array $template - * @param array $loads - * @param array $groupLoads + * @param array $serverTemplate Server config map + * @param int[][] $groupLoads Map of (group => host => load) * @param string|bool $readOnlyReason * @return LoadBalancer */ - private function newLoadBalancer( $template, $loads, $groupLoads, $readOnlyReason ) { + private function newLoadBalancer( $serverTemplate, $groupLoads, $readOnlyReason ) { $lb = new LoadBalancer( array_merge( $this->baseLoadBalancerParams(), [ - 'servers' => $this->makeServerArray( $template, $loads, $groupLoads ), + 'servers' => $this->makeServerArray( $serverTemplate, $groupLoads ), 'loadMonitor' => [ 'class' => $this->loadMonitorClass ], 'readOnlyReason' => $readOnlyReason ] @@ -293,45 +282,37 @@ class LBFactoryMulti extends LBFactory { } /** - * Make a server array as expected by LoadBalancer::__construct, using a template and load array + * Make a server array as expected by LoadBalancer::__construct() * - * @param array $template - * @param array $loads - * @param array $groupLoads - * @return array + * @param array $serverTemplate Server config map + * @param int[][] $groupLoads Map of (group => host => load) + * @return array[] List of server config maps */ - private function makeServerArray( $template, $loads, $groupLoads ) { - $servers = []; - $master = true; - $groupLoadsByServer = $this->reindexGroupLoads( $groupLoads ); - foreach ( $groupLoadsByServer as $server => $stuff ) { - if ( !isset( $loads[$server] ) ) { - $loads[$server] = 0; - } + private function makeServerArray( array $serverTemplate, array $groupLoads ) { + // The master server is the first host explicitly listed in the generic load group + if ( !$groupLoads[ILoadBalancer::GROUP_GENERIC] ) { + throw new UnexpectedValueException( "Empty generic load array; no master defined." ); } - foreach ( $loads as $serverName => $load ) { - $serverInfo = $template; - if ( $master ) { - $serverInfo['master'] = true; - if ( $this->masterTemplateOverrides ) { - $serverInfo = $this->masterTemplateOverrides + $serverInfo; - } - $master = false; - } else { - $serverInfo['replica'] = true; - } - if ( isset( $this->templateOverridesByServer[$serverName] ) ) { - $serverInfo = $this->templateOverridesByServer[$serverName] + $serverInfo; - } - if ( isset( $groupLoadsByServer[$serverName] ) ) { - $serverInfo['groupLoads'] = $groupLoadsByServer[$serverName]; - } - $serverInfo['host'] = $this->hostsByName[$serverName] ?? $serverName; - $serverInfo['hostName'] = $serverName; - $serverInfo['load'] = $load; - $serverInfo += [ 'flags' => IDatabase::DBO_DEFAULT ]; - $servers[] = $serverInfo; + $groupLoadsByHost = $this->reindexGroupLoads( $groupLoads ); + // Get the ordered map of (host => load); the master server is first + $genericLoads = $groupLoads[ILoadBalancer::GROUP_GENERIC]; + // Implictly append any hosts that only appear in custom load groups + $genericLoads += array_fill_keys( array_keys( $groupLoadsByHost ), 0 ); + + $servers = []; + foreach ( $genericLoads as $host => $load ) { + $servers[] = array_merge( + $serverTemplate, + $servers ? [] : $this->masterTemplateOverrides, + $this->templateOverridesByServer[$host] ?? [], + [ + 'host' => $this->hostsByName[$host] ?? $host, + 'hostName' => $host, + 'load' => $load, + 'groupLoads' => $groupLoadsByHost[$host] ?? [] + ] + ); } return $servers; @@ -339,14 +320,15 @@ class LBFactoryMulti extends LBFactory { /** * Take a group load array indexed by group then server, and reindex it by server then group - * @param array $groupLoads - * @return array + * @param int[][] $groupLoads Map of (group => host => load) + * @return int[][] Map of (host => group => load) */ - private function reindexGroupLoads( $groupLoads ) { + private function reindexGroupLoads( array $groupLoads ) { $reindexed = []; - foreach ( $groupLoads as $group => $loads ) { - foreach ( $loads as $server => $load ) { - $reindexed[$server][$group] = $load; + + foreach ( $groupLoads as $group => $loadByHost ) { + foreach ( $loadByHost as $host => $load ) { + $reindexed[$host][$group] = $load; } } @@ -357,18 +339,9 @@ class LBFactoryMulti extends LBFactory { * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain * @return string */ - private function getDatabaseFromDomain( $domain = false ) { + private function getDomainDatabase( $domain = false ) { return ( $domain === false ) ? $this->localDomain->getDatabase() : DatabaseDomain::newFromId( $domain )->getDatabase(); } - - public function forEachLB( $callback, array $params = [] ) { - foreach ( $this->mainLBs as $lb ) { - $callback( $lb, ...$params ); - } - foreach ( $this->extLBs as $lb ) { - $callback( $lb, ...$params ); - } - } } diff --git a/includes/libs/rdbms/lbfactory/LBFactorySimple.php b/includes/libs/rdbms/lbfactory/LBFactorySimple.php index fd76d88dd0..7e73e5bdcf 100644 --- a/includes/libs/rdbms/lbfactory/LBFactorySimple.php +++ b/includes/libs/rdbms/lbfactory/LBFactorySimple.php @@ -32,20 +32,20 @@ class LBFactorySimple extends LBFactory { /** @var LoadBalancer */ private $mainLB; /** @var LoadBalancer[] */ - private $extLBs = []; + private $externalLBs = []; - /** @var array[] Map of (server index => server config) */ - private $servers = []; - /** @var array[] Map of (cluster => (server index => server config)) */ - private $externalClusters = []; + /** @var array[] Map of (server index => server config map) */ + private $mainServers = []; + /** @var array[][] Map of (cluster => server index => server config map) */ + private $externalServersByCluster = []; /** @var string */ private $loadMonitorClass; /** * @see LBFactory::__construct() - * @param array $conf Parameters of LBFactory::__construct() as well as: - * - servers : list of server configuration maps to Database::factory(). + * @param array $conf Additional parameters include: + * - servers : list of server config maps to Database::factory(). * Additionally, the server maps should have a 'load' key, which is used to decide * how often clients connect to one server verses the others. A 'max lag' key should * also be set on server maps, indicating how stale the data can be before the load @@ -57,25 +57,30 @@ class LBFactorySimple extends LBFactory { public function __construct( array $conf ) { parent::__construct( $conf ); - $this->servers = $conf['servers'] ?? []; - foreach ( $this->servers as $i => $server ) { + $this->mainServers = $conf['servers'] ?? []; + foreach ( $this->mainServers as $i => $server ) { if ( $i == 0 ) { - $this->servers[$i]['master'] = true; + $this->mainServers[$i]['master'] = true; } else { - $this->servers[$i]['replica'] = true; + $this->mainServers[$i]['replica'] = true; } } - $this->externalClusters = $conf['externalClusters'] ?? []; - $this->loadMonitorClass = $conf['loadMonitorClass'] ?? 'LoadMonitor'; + foreach ( ( $conf['externalClusters'] ?? [] ) as $cluster => $servers ) { + foreach ( $servers as $index => $server ) { + $this->externalServersByCluster[$cluster][$index] = $server; + } + } + + $this->loadMonitorClass = $conf['loadMonitorClass'] ?? LoadMonitor::class; } public function newMainLB( $domain = false ) { - return $this->newLoadBalancer( $this->servers ); + return $this->newLoadBalancer( $this->mainServers ); } public function getMainLB( $domain = false ) { - if ( !$this->mainLB ) { + if ( $this->mainLB === null ) { $this->mainLB = $this->newMainLB( $domain ); } @@ -83,28 +88,28 @@ class LBFactorySimple extends LBFactory { } public function newExternalLB( $cluster ) { - if ( !isset( $this->externalClusters[$cluster] ) ) { - throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"." ); + if ( !isset( $this->externalServersByCluster[$cluster] ) ) { + throw new InvalidArgumentException( "Unknown cluster '$cluster'." ); } - return $this->newLoadBalancer( $this->externalClusters[$cluster] ); + return $this->newLoadBalancer( $this->externalServersByCluster[$cluster] ); } public function getExternalLB( $cluster ) { - if ( !isset( $this->extLBs[$cluster] ) ) { - $this->extLBs[$cluster] = $this->newExternalLB( $cluster ); + if ( !isset( $this->externalLBs[$cluster] ) ) { + $this->externalLBs[$cluster] = $this->newExternalLB( $cluster ); } - return $this->extLBs[$cluster]; + return $this->externalLBs[$cluster]; } public function getAllMainLBs() { - return [ 'DEFAULT' => $this->getMainLB() ]; + return [ self::CLUSTER_MAIN_DEFAULT => $this->getMainLB() ]; } public function getAllExternalLBs() { $lbs = []; - foreach ( $this->externalClusters as $cluster => $unused ) { + foreach ( array_keys( $this->externalServersByCluster ) as $cluster ) { $lbs[$cluster] = $this->getExternalLB( $cluster ); } @@ -125,10 +130,10 @@ class LBFactorySimple extends LBFactory { } public function forEachLB( $callback, array $params = [] ) { - if ( isset( $this->mainLB ) ) { + if ( $this->mainLB !== null ) { $callback( $this->mainLB, ...$params ); } - foreach ( $this->extLBs as $lb ) { + foreach ( $this->externalLBs as $lb ) { $callback( $lb, ...$params ); } } -- 2.20.1