}
// Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
- if ( $pos->gtids ) {
+ if ( $pos->getGTIDs() ) {
+ // Ignore GTIDs from domains exclusive to the master DB (presumably inactive)
+ $rpos = $this->getReplicaPos();
+ $gtidsWait = $rpos ? MySQLMasterPos::getCommonDomainGTIDs( $pos, $rpos ) : [];
+ if ( !$gtidsWait ) {
+ return -1; // $pos is from the wrong cluster?
+ }
// Wait on the GTID set (MariaDB only)
- $gtidArg = $this->addQuotes( implode( ',', $pos->gtids ) );
+ $gtidArg = $this->addQuotes( implode( ',', $gtidsWait ) );
$res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" );
} else {
// Wait on the binlog coordinates
// Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
$status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
if ( $status === null ) {
- if ( !$pos->gtids ) {
+ if ( !$pos->getGTIDs() ) {
// T126436: jobs programmed to wait on master positions might be referencing
// binlogs with an old master hostname; this makes MASTER_POS_WAIT() return null.
// Try to detect this case and treat the replica DB as having reached the given
$this->binlog = $m[1]; // ideally something like host name
$this->pos = [ (int)$m[2], (int)$m[3] ];
} else {
- $this->gtids = array_map( 'trim', explode( ',', $position ) );
+ $gtids = array_filter( array_map( 'trim', explode( ',', $position ) ) );
+ foreach ( $gtids as $gtid ) {
+ if ( !$this->parseGTID( $gtid ) ) {
+ throw new InvalidArgumentException( "Invalid GTID '$gtid'." );
+ }
+ $this->gtids[] = $gtid;
+ }
if ( !$this->gtids ) {
- throw new InvalidArgumentException( "GTID set should not be empty." );
+ throw new InvalidArgumentException( "Got empty GTID set." );
}
}
return $this->gtids ? null : "{$this->binlog}.{$this->pos[0]}";
}
+ /**
+ * @return string[]
+ */
+ public function getGTIDs() {
+ return $this->gtids;
+ }
+
/**
* @return string GTID set or <binlog file>/<position> (e.g db1034-bin.000976/843431247)
*/
}
/**
- * @note: this returns false for multi-source replication GTID sets
+ * @param MySQLMasterPos $pos
+ * @param MySQLMasterPos $refPos
+ * @return string[] List of GTIDs from $pos that have domains in $refPos
+ */
+ public static function getCommonDomainGTIDs( MySQLMasterPos $pos, MySQLMasterPos $refPos ) {
+ $gtidsCommon = [];
+
+ $relevantDomains = $refPos->getGtidCoordinates(); // (domain => unused)
+ foreach ( $pos->gtids as $gtid ) {
+ list( $domain ) = self::parseGTID( $gtid );
+ if ( isset( $relevantDomains[$domain] ) ) {
+ $gtidsCommon[] = $gtid;
+ }
+ }
+
+ return $gtidsCommon;
+ }
+
+ /**
* @see https://mariadb.com/kb/en/mariadb/gtid
* @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
* @return array Map of (domain => integer position); possibly empty
protected function getGtidCoordinates() {
$gtidInfos = [];
foreach ( $this->gtids as $gtid ) {
- $m = [];
- // MariaDB style: <domain>-<server id>-<sequence number>
- if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
- $gtidInfos[(int)$m[1]] = (int)$m[2];
- // MySQL style: <UUID domain>:<sequence number>
- } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
- $gtidInfos[$m[1]] = (int)$m[2];
- } else {
- $gtidInfos = [];
- break; // unrecognized GTID
- }
-
+ list( $domain, $pos ) = self::parseGTID( $gtid );
+ $gtidInfos[$domain] = $pos;
}
return $gtidInfos;
}
+ /**
+ * @param string $gtid
+ * @return array|null [domain, integer position] or null
+ */
+ protected static function parseGTID( $gtid ) {
+ $m = [];
+ if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
+ // MariaDB style: <domain>-<server id>-<sequence number>
+ return [ (int)$m[1], (int)$m[2] ];
+ } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
+ // MySQL style: <UUID domain>:<sequence number>
+ return [ $m[1], (int)$m[2] ];
+ }
+
+ return null;
+ }
+
/**
* @see https://dev.mysql.com/doc/refman/5.7/en/show-master-status.html
* @see https://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html
];
}
+ /**
+ * @dataProvider provideCommonDomainGTIDs
+ * @covers Wikimedia\Rdbms\MySQLMasterPos
+ */
+ public function testCommonGtidDomains( MySQLMasterPos $pos, MySQLMasterPos $ref, $gtids ) {
+ $this->assertEquals( $gtids, MySQLMasterPos::getCommonDomainGTIDs( $pos, $ref ) );
+ }
+
+ public static function provideCommonDomainGTIDs() {
+ return [
+ [
+ new MySQLMasterPos( '255-13-99,256-12-50,257-14-50', 1 ),
+ new MySQLMasterPos( '255-11-1000', 1 ),
+ [ '255-13-99' ]
+ ],
+ [
+ new MySQLMasterPos(
+ '2E11FA47-71CA-11E1-9E33-C80AA9429562:5,' .
+ '3E11FA47-71CA-11E1-9E33-C80AA9429562:99,' .
+ '7E11FA47-71CA-11E1-9E33-C80AA9429562:30',
+ 1
+ ),
+ new MySQLMasterPos(
+ '1E11FA47-71CA-11E1-9E33-C80AA9429562:100,' .
+ '3E11FA47-71CA-11E1-9E33-C80AA9429562:66',
+ 1
+ ),
+ [ '3E11FA47-71CA-11E1-9E33-C80AA9429562:99' ]
+ ]
+ ];
+ }
+
/**
* @dataProvider provideLagAmounts
* @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLag