Make flushSnapshot() logging more detailed and check for explicit transaction
rounds. Also removing periods and trailing newlines from log/exception messages.
Change-Id: I0f6520f563680ab3a65b6338ced59ba25a2ec7b5
return $this->__call( __FUNCTION__, func_get_args() );
}
return $this->__call( __FUNCTION__, func_get_args() );
}
- public function commit( $fname = __METHOD__, $flush = '' ) {
+ public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
return $this->__call( __FUNCTION__, func_get_args() );
}
return $this->__call( __FUNCTION__, func_get_args() );
}
- public function rollback( $fname = __METHOD__, $flush = '' ) {
+ public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
return $this->__call( __FUNCTION__, func_get_args() );
}
return $this->__call( __FUNCTION__, func_get_args() );
}
- public function flushSnapshot( $fname = __METHOD__ ) {
+ public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
return $this->__call( __FUNCTION__, func_get_args() );
}
return $this->__call( __FUNCTION__, func_get_args() );
}
*/
final public function initConnection() {
if ( $this->isOpen() ) {
*/
final public function initConnection() {
if ( $this->isOpen() ) {
- throw new LogicException( __METHOD__ . ': already connected.' );
+ throw new LogicException( __METHOD__ . ': already connected' );
}
// Establish the connection
$this->doInitConnection();
}
// Establish the connection
$this->doInitConnection();
$this->connectionParams['tablePrefix']
);
} else {
$this->connectionParams['tablePrefix']
);
} else {
- throw new InvalidArgumentException( "No database user provided." );
+ throw new InvalidArgumentException( "No database user provided" );
public function dbSchema( $schema = null ) {
if ( strlen( $schema ) && $this->getDBname() === null ) {
public function dbSchema( $schema = null ) {
if ( strlen( $schema ) && $this->getDBname() === null ) {
- throw new DBUnexpectedError( $this, "Cannot set schema to '$schema'; no database set." );
+ throw new DBUnexpectedError( $this, "Cannot set schema to '$schema'; no database set" );
}
$old = $this->currentDomain->getSchema();
}
$old = $this->currentDomain->getSchema();
public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
if ( ( $flag & self::DBO_IGNORE ) ) {
public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
if ( ( $flag & self::DBO_IGNORE ) ) {
- throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
+ throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed" );
}
if ( $remember === self::REMEMBER_PRIOR ) {
}
if ( $remember === self::REMEMBER_PRIOR ) {
public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
if ( ( $flag & self::DBO_IGNORE ) ) {
public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
if ( ( $flag & self::DBO_IGNORE ) ) {
- throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
+ throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed" );
}
if ( $remember === self::REMEMBER_PRIOR ) {
}
if ( $remember === self::REMEMBER_PRIOR ) {
$levels = $this->flatAtomicSectionList();
$exception = new DBUnexpectedError(
$this,
$levels = $this->flatAtomicSectionList();
$exception = new DBUnexpectedError(
$this,
- __METHOD__ . ": atomic sections $levels are still open."
+ __METHOD__ . ": atomic sections $levels are still open"
);
} elseif ( $this->trxAutomatic ) {
// Only the connection manager can commit non-empty DBO_TRX transactions
);
} elseif ( $this->trxAutomatic ) {
// Only the connection manager can commit non-empty DBO_TRX transactions
$exception = new DBUnexpectedError(
$this,
__METHOD__ .
$exception = new DBUnexpectedError(
$this,
__METHOD__ .
- ": mass commit/rollback of peer transaction required (DBO_TRX set)."
+ ": mass commit/rollback of peer transaction required (DBO_TRX set)"
// back, even if empty.
$exception = new DBUnexpectedError(
$this,
// back, even if empty.
$exception = new DBUnexpectedError(
$this,
- __METHOD__ . ": transaction is still open (from {$this->trxFname})."
+ __METHOD__ . ": transaction is still open (from {$this->trxFname})"
);
}
if ( $this->trxEndCallbacksSuppressed ) {
$exception = $exception ?: new DBUnexpectedError(
$this,
);
}
if ( $this->trxEndCallbacksSuppressed ) {
$exception = $exception ?: new DBUnexpectedError(
$this,
- __METHOD__ . ': callbacks are suppressed; cannot properly commit.'
+ __METHOD__ . ': callbacks are suppressed; cannot properly commit'
$fnames = $this->pendingWriteAndCallbackCallers();
if ( $fnames ) {
throw new RuntimeException(
$fnames = $this->pendingWriteAndCallbackCallers();
if ( $fnames ) {
throw new RuntimeException(
- "Transaction callbacks are still pending:\n" . implode( ', ', $fnames )
+ "Transaction callbacks are still pending: " . implode( ', ', $fnames )
*/
final protected function assertHasConnectionHandle() {
if ( !$this->isOpen() ) {
*/
final protected function assertHasConnectionHandle() {
if ( !$this->isOpen() ) {
- throw new DBUnexpectedError( $this, "DB connection was already closed." );
+ throw new DBUnexpectedError( $this, "DB connection was already closed" );
if ( $this->getLBInfo( 'replica' ) === true ) {
throw new DBReadOnlyRoleError(
$this,
if ( $this->getLBInfo( 'replica' ) === true ) {
throw new DBReadOnlyRoleError(
$this,
- 'Write operations are not allowed on replica database connections.'
+ 'Write operations are not allowed on replica database connections'
);
}
$reason = $this->getReadOnlyReason();
);
}
$reason = $this->getReadOnlyReason();
private function assertQueryIsCurrentlyAllowed( $sql, $fname ) {
$verb = $this->getQueryVerb( $sql );
if ( $verb === 'USE' ) {
private function assertQueryIsCurrentlyAllowed( $sql, $fname ) {
$verb = $this->getQueryVerb( $sql );
if ( $verb === 'USE' ) {
- throw new DBUnexpectedError( $this, "Got USE query; use selectDomain() instead." );
+ throw new DBUnexpectedError( $this, "Got USE query; use selectDomain() instead" );
}
if ( $verb === 'ROLLBACK' ) { // transaction/savepoint
}
if ( $verb === 'ROLLBACK' ) { // transaction/savepoint
if ( $this->trxStatus < self::STATUS_TRX_OK ) {
throw new DBTransactionStateError(
$this,
if ( $this->trxStatus < self::STATUS_TRX_OK ) {
throw new DBTransactionStateError(
$this,
- "Cannot execute query from $fname while transaction status is ERROR.",
+ "Cannot execute query from $fname while transaction status is ERROR",
[],
$this->trxStatusCause
);
[],
$this->trxStatusCause
);
*/
public function reportQueryError( $error, $errno, $sql, $fname, $ignore = false ) {
if ( $ignore ) {
*/
public function reportQueryError( $error, $errno, $sql, $fname, $ignore = false ) {
if ( $ignore ) {
- $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" );
+ $this->queryLogger->debug( "SQL ERROR (ignored): $error" );
} else {
$exception = $this->getQueryExceptionAndLog( $error, $errno, $sql, $fname );
} else {
$exception = $this->getQueryExceptionAndLog( $error, $errno, $sql, $fname );
'trace' => ( new RuntimeException() )->getTraceAsString()
] )
);
'trace' => ( new RuntimeException() )->getTraceAsString()
] )
);
- $this->queryLogger->debug( "SQL ERROR: " . $error . "\n" );
+ $this->queryLogger->debug( "SQL ERROR: " . $error . "" );
if ( $this->wasQueryTimeout( $error, $errno ) ) {
$e = new DBQueryTimeoutError( $this, $error, $errno, $sql, $fname );
} elseif ( $this->wasConnectionError( $errno ) ) {
if ( $this->wasQueryTimeout( $error, $errno ) ) {
$e = new DBQueryTimeoutError( $this, $error, $errno, $sql, $fname );
} elseif ( $this->wasConnectionError( $errno ) ) {
// functions. Discourage use of such queries to encourage compatibility.
call_user_func(
$this->deprecationLogger,
// functions. Discourage use of such queries to encourage compatibility.
call_user_func(
$this->deprecationLogger,
- __METHOD__ . ": aggregation used with a locking SELECT ($fname)."
+ __METHOD__ . ": aggregation used with a locking SELECT ($fname)"
} elseif ( count( $var ) == 1 ) {
$column = $var[0] ?? reset( $var );
} else {
} elseif ( count( $var ) == 1 ) {
$column = $var[0] ?? reset( $var );
} else {
- throw new DBUnexpectedError( $this, __METHOD__ . ': got multiple columns.' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ': got multiple columns' );
}
} else {
$column = $var;
}
} else {
$column = $var;
if ( $name instanceof Subquery ) {
throw new DBUnexpectedError(
$this,
if ( $name instanceof Subquery ) {
throw new DBUnexpectedError(
$this,
- __METHOD__ . ': got Subquery instance when expecting a string.'
+ __METHOD__ . ': got Subquery instance when expecting a string'
# surrounded by symbols which may be considered word breaks.
if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
$this->queryLogger->warning(
# surrounded by symbols which may be considered word breaks.
if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
$this->queryLogger->warning(
- __METHOD__ . ": use of subqueries is not supported this way.",
+ __METHOD__ . ": use of subqueries is not supported this way",
[ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
);
[ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
);
} elseif ( $table instanceof Subquery ) {
$quotedTable = (string)$table;
} else {
} elseif ( $table instanceof Subquery ) {
$quotedTable = (string)$table;
} else {
- throw new InvalidArgumentException( "Table must be a string or Subquery." );
+ throw new InvalidArgumentException( "Table must be a string or Subquery" );
}
if ( $alias === false || $alias === $table ) {
if ( $table instanceof Subquery ) {
}
if ( $alias === false || $alias === $table ) {
if ( $table instanceof Subquery ) {
- throw new InvalidArgumentException( "Subquery table missing alias." );
+ throw new InvalidArgumentException( "Subquery table missing alias" );
public function limitResult( $sql, $limit, $offset = false ) {
if ( !is_numeric( $limit ) ) {
public function limitResult( $sql, $limit, $offset = false ) {
if ( !is_numeric( $limit ) ) {
- throw new DBUnexpectedError( $this,
- "Invalid non-numeric limit passed to limitResult()\n" );
+ throw new DBUnexpectedError(
+ $this,
+ "Invalid non-numeric limit passed to " . __METHOD__
+ );
}
// This version works in MySQL and SQLite. It will very likely need to be
// overridden for most other RDBMS subclasses.
}
// This version works in MySQL and SQLite. It will very likely need to be
// overridden for most other RDBMS subclasses.
final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
if ( !$this->trxLevel() ) {
final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
if ( !$this->trxLevel() ) {
- throw new DBUnexpectedError( $this, "No transaction is active." );
+ throw new DBUnexpectedError( $this, "No transaction is active" );
}
$this->trxEndCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
}
}
$this->trxEndCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
}
final public function onAtomicSectionCancel( callable $callback, $fname = __METHOD__ ) {
if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
final public function onAtomicSectionCancel( callable $callback, $fname = __METHOD__ ) {
if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
- throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
+ throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)" );
}
$this->trxSectionCancelCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
}
}
$this->trxSectionCancelCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
}
*/
public function runOnTransactionIdleCallbacks( $trigger ) {
if ( $this->trxLevel() ) { // sanity
*/
public function runOnTransactionIdleCallbacks( $trigger ) {
if ( $this->trxLevel() ) { // sanity
- throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open.' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open' );
}
if ( $this->trxEndCallbacksSuppressed ) {
}
if ( $this->trxEndCallbacksSuppressed ) {
final public function endAtomic( $fname = __METHOD__ ) {
if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
final public function endAtomic( $fname = __METHOD__ ) {
if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
- throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
+ throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)" );
}
// Check if the current section matches $fname
}
// Check if the current section matches $fname
if ( $savedFname !== $fname ) {
throw new DBUnexpectedError(
$this,
if ( $savedFname !== $fname ) {
throw new DBUnexpectedError(
$this,
- "Invalid atomic section ended (got $fname but expected $savedFname)."
+ "Invalid atomic section ended (got $fname but expected $savedFname)"
$fname = __METHOD__, AtomicSectionIdentifier $sectionId = null
) {
if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
$fname = __METHOD__, AtomicSectionIdentifier $sectionId = null
) {
if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
- throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
+ throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)" );
if ( $savedFname !== $fname ) {
throw new DBUnexpectedError(
$this,
if ( $savedFname !== $fname ) {
throw new DBUnexpectedError(
$this,
- "Invalid atomic section ended (got $fname but expected $savedFname)."
+ "Invalid atomic section ended (got $fname but expected $savedFname)"
$this->trxStatus = self::STATUS_TRX_ERROR;
$this->trxStatusCause = new DBUnexpectedError(
$this,
$this->trxStatus = self::STATUS_TRX_ERROR;
$this->trxStatusCause = new DBUnexpectedError(
$this,
- "Uncancelable atomic section canceled (got $fname)."
+ "Uncancelable atomic section canceled (got $fname)"
final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
static $modes = [ self::TRANSACTION_EXPLICIT, self::TRANSACTION_INTERNAL ];
if ( !in_array( $mode, $modes, true ) ) {
final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
static $modes = [ self::TRANSACTION_EXPLICIT, self::TRANSACTION_INTERNAL ];
if ( !in_array( $mode, $modes, true ) ) {
- throw new DBUnexpectedError( $this, "$fname: invalid mode parameter '$mode'." );
+ throw new DBUnexpectedError( $this, "$fname: invalid mode parameter '$mode'" );
}
// Protect against mismatched atomic section, transaction nesting, and snapshot loss
if ( $this->trxLevel() ) {
if ( $this->trxAtomicLevels ) {
$levels = $this->flatAtomicSectionList();
}
// Protect against mismatched atomic section, transaction nesting, and snapshot loss
if ( $this->trxLevel() ) {
if ( $this->trxAtomicLevels ) {
$levels = $this->flatAtomicSectionList();
- $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
+ $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open";
throw new DBUnexpectedError( $this, $msg );
} elseif ( !$this->trxAutomatic ) {
throw new DBUnexpectedError( $this, $msg );
} elseif ( !$this->trxAutomatic ) {
- $msg = "$fname: Explicit transaction already active (from {$this->trxFname}).";
+ $msg = "$fname: Explicit transaction already active (from {$this->trxFname})";
throw new DBUnexpectedError( $this, $msg );
} else {
throw new DBUnexpectedError( $this, $msg );
} else {
- $msg = "$fname: Implicit transaction already active (from {$this->trxFname}).";
+ $msg = "$fname: Implicit transaction already active (from {$this->trxFname})";
throw new DBUnexpectedError( $this, $msg );
}
} elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
throw new DBUnexpectedError( $this, $msg );
}
} elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
- $msg = "$fname: Implicit transaction expected (DBO_TRX set).";
+ $msg = "$fname: Implicit transaction expected (DBO_TRX set)";
throw new DBUnexpectedError( $this, $msg );
}
throw new DBUnexpectedError( $this, $msg );
}
final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
static $modes = [ self::FLUSHING_ONE, self::FLUSHING_ALL_PEERS, self::FLUSHING_INTERNAL ];
if ( !in_array( $flush, $modes, true ) ) {
final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
static $modes = [ self::FLUSHING_ONE, self::FLUSHING_ALL_PEERS, self::FLUSHING_INTERNAL ];
if ( !in_array( $flush, $modes, true ) ) {
- throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'." );
+ throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'" );
}
if ( $this->trxLevel() && $this->trxAtomicLevels ) {
}
if ( $this->trxLevel() && $this->trxAtomicLevels ) {
$levels = $this->flatAtomicSectionList();
throw new DBUnexpectedError(
$this,
$levels = $this->flatAtomicSectionList();
throw new DBUnexpectedError(
$this,
- "$fname: Got COMMIT while atomic sections $levels are still open."
+ "$fname: Got COMMIT while atomic sections $levels are still open"
} elseif ( !$this->trxAutomatic ) {
throw new DBUnexpectedError(
$this,
} elseif ( !$this->trxAutomatic ) {
throw new DBUnexpectedError(
$this,
- "$fname: Flushing an explicit transaction, getting out of sync."
+ "$fname: Flushing an explicit transaction, getting out of sync"
);
}
} elseif ( !$this->trxLevel() ) {
$this->queryLogger->error(
);
}
} elseif ( !$this->trxLevel() ) {
$this->queryLogger->error(
- "$fname: No transaction to commit, something got out of sync." );
+ "$fname: No transaction to commit, something got out of sync" );
return; // nothing to do
} elseif ( $this->trxAutomatic ) {
throw new DBUnexpectedError(
$this,
return; // nothing to do
} elseif ( $this->trxAutomatic ) {
throw new DBUnexpectedError(
$this,
- "$fname: Expected mass commit of all peer transactions (DBO_TRX set)."
+ "$fname: Expected mass commit of all peer transactions (DBO_TRX set)"
) {
throw new DBUnexpectedError(
$this,
) {
throw new DBUnexpectedError(
$this,
- "$fname: Expected mass rollback of all peer transactions (DBO_TRX set)."
+ "$fname: Expected mass rollback of all peer transactions (DBO_TRX set)"
- public function flushSnapshot( $fname = __METHOD__ ) {
- if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
+ public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
+ if ( $this->explicitTrxActive() ) {
+ // Committing this transaction would break callers that assume it is still open
+ throw new DBUnexpectedError(
+ $this,
+ "$fname: Cannot flush snapshot; " .
+ "explicit transaction '{$this->trxFname}' is still open"
+ );
+ } elseif ( $this->writesOrCallbacksPending() ) {
// This only flushes transactions to clear snapshots, not to write data
$fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
throw new DBUnexpectedError(
$this,
// This only flushes transactions to clear snapshots, not to write data
$fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
throw new DBUnexpectedError(
$this,
- "$fname: Cannot flush snapshot because writes are pending ($fnames)."
+ "$fname: Cannot flush snapshot; " .
+ "writes from transaction {$this->trxFname} are still pending ($fnames)"
+ );
+ } elseif (
+ $this->trxLevel() &&
+ $this->getTransactionRoundId() &&
+ $flush !== self::FLUSHING_INTERNAL &&
+ $flush !== self::FLUSHING_ALL_PEERS
+ ) {
+ $this->queryLogger->warning(
+ "$fname: Expected mass snapshot flush of all peer transactions " .
+ "in the explicit transactions round '{$this->getTransactionRoundId()}'",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
Wikimedia\restoreWarnings();
if ( $fp === false ) {
Wikimedia\restoreWarnings();
if ( $fp === false ) {
- throw new RuntimeException( "Could not open \"{$filename}\".\n" );
+ throw new RuntimeException( "Could not open \"{$filename}\"" );
$fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
throw new DBUnexpectedError(
$this,
$fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
throw new DBUnexpectedError(
$this,
- "$fname: Cannot flush pre-lock snapshot because writes are pending ($fnames)."
+ "$fname: Cannot flush pre-lock snapshot; " .
+ "writes from transaction {$this->trxFname} are still pending ($fnames)"
final public function lockTables( array $read, array $write, $method ) {
if ( $this->writesOrCallbacksPending() ) {
final public function lockTables( array $read, array $write, $method ) {
if ( $this->writesOrCallbacksPending() ) {
- throw new DBUnexpectedError( $this, "Transaction writes or callbacks still pending." );
+ throw new DBUnexpectedError( $this, "Transaction writes or callbacks still pending" );
}
if ( $this->tableLocksHaveTransactionScope() ) {
}
if ( $this->tableLocksHaveTransactionScope() ) {
if ( !$this->conn ) {
throw new DBUnexpectedError(
$this,
if ( !$this->conn ) {
throw new DBUnexpectedError(
$this,
- 'DB connection was already closed or the connection dropped.'
+ 'DB connection was already closed or the connection dropped'
*/
public function __clone() {
$this->connLogger->warning(
*/
public function __clone() {
$this->connLogger->warning(
- "Cloning " . static::class . " is not recommended; forking connection:\n" .
- ( new RuntimeException() )->getTraceAsString()
+ "Cloning " . static::class . " is not recommended; forking connection",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
);
if ( $this->isOpen() ) {
);
if ( $this->isOpen() ) {
*/
public function __sleep() {
throw new RuntimeException( 'Database serialization may cause problems, since ' .
*/
public function __sleep() {
throw new RuntimeException( 'Database serialization may cause problems, since ' .
- 'the connection is not restored on wakeup.' );
+ 'the connection is not restored on wakeup' );
*/
public function __destruct() {
if ( $this->trxLevel() && $this->trxDoneWrites ) {
*/
public function __destruct() {
if ( $this->trxLevel() && $this->trxDoneWrites ) {
- trigger_error( "Uncommitted DB writes (transaction from {$this->trxFname})." );
+ trigger_error( "Uncommitted DB writes (transaction from {$this->trxFname})" );
}
$danglingWriters = $this->pendingWriteAndCallbackCallers();
if ( $danglingWriters ) {
$fnames = implode( ', ', $danglingWriters );
}
$danglingWriters = $this->pendingWriteAndCallbackCallers();
if ( $danglingWriters ) {
$fnames = implode( ', ', $danglingWriters );
- trigger_error( "DB transaction writes or callbacks still pending ($fnames)." );
+ trigger_error( "DB transaction writes or callbacks still pending ($fnames)" );
- public function commit( $fname = __METHOD__, $flush = '' );
+ public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE );
/**
* Rollback a transaction previously started using begin().
/**
* Rollback a transaction previously started using begin().
* @throws DBError
* @since 1.23 Added $flush parameter
*/
* @throws DBError
* @since 1.23 Added $flush parameter
*/
- public function rollback( $fname = __METHOD__, $flush = '' );
+ public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE );
/**
* Commit any transaction but error out if writes or callbacks are pending
/**
* Commit any transaction but error out if writes or callbacks are pending
* useful to call on a replica DB after waiting on replication to catch up to the master.
*
* @param string $fname Calling function name
* useful to call on a replica DB after waiting on replication to catch up to the master.
*
* @param string $fname Calling function name
+ * @param string $flush Flush flag, set to situationally valid IDatabase::FLUSHING_*
+ * constant to disable warnings about explicitly committing implicit transactions,
+ * or calling commit when no transaction is in progress.
+ *
+ * This will trigger an exception if there is an ongoing explicit transaction.
+ *
+ * Only set the flush flag if you are sure that these warnings are not applicable,
+ * and no explicit transactions are open.
+ *
* @throws DBError
* @since 1.28
* @throws DBError
* @since 1.28
+ * @since 1.34 Added $flush parameter
- public function flushSnapshot( $fname = __METHOD__ );
+ public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE );
/**
* Convert a timestamp in one of the formats accepted by wfTimestamp()
/**
* Convert a timestamp in one of the formats accepted by wfTimestamp()
/**
* Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
*
/**
* Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
*
+ * This is useful for getting rid of stale data from an implicit transaction round
+ *
* @param string $fname Caller name
*/
public function flushReplicaSnapshots( $fname = __METHOD__ );
* @param string $fname Caller name
*/
public function flushReplicaSnapshots( $fname = __METHOD__ );
}
public function flushReplicaSnapshots( $fname = __METHOD__ ) {
}
public function flushReplicaSnapshots( $fname = __METHOD__ ) {
+ if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
+ $this->queryLogger->warning(
+ "$fname: transaction round '{$this->trxRoundId}' still running",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
+ );
+ }
$this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
}
$this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
}
if ( $this->trxRoundId !== false ) {
throw new DBTransactionError(
null,
if ( $this->trxRoundId !== false ) {
throw new DBTransactionError(
null,
- "$fname: transaction round '{$this->trxRoundId}' already started."
+ "$fname: transaction round '{$this->trxRoundId}' already started"
);
}
$this->trxRoundId = $fname;
);
}
$this->trxRoundId = $fname;
if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
throw new DBTransactionError(
null,
if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
throw new DBTransactionError(
null,
- "$fname: transaction round '{$this->trxRoundId}' still running."
+ "$fname: transaction round '{$this->trxRoundId}' still running"
);
}
/** @noinspection PhpUnusedLocalVariableInspection */
);
}
/** @noinspection PhpUnusedLocalVariableInspection */
public function getEmptyTransactionTicket( $fname ) {
if ( $this->hasMasterChanges() ) {
public function getEmptyTransactionTicket( $fname ) {
if ( $this->hasMasterChanges() ) {
- $this->queryLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" .
- ( new RuntimeException() )->getTraceAsString() );
+ $this->queryLogger->error(
+ __METHOD__ . ": $fname does not have outer scope",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
+ );
final public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
if ( $ticket !== $this->ticket ) {
final public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
if ( $ticket !== $this->ticket ) {
- $this->perfLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" .
- ( new RuntimeException() )->getTraceAsString() );
+ $this->perfLogger->error(
+ __METHOD__ . ": $fname does not have outer scope",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
+ );
// The transaction owner and any caller with the empty transaction ticket can commit
// so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError.
if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) {
// The transaction owner and any caller with the empty transaction ticket can commit
// so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError.
if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) {
- $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}." );
+ $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}" );
$fnameEffective = $this->trxRoundId;
} else {
$fnameEffective = $fname;
$fnameEffective = $this->trxRoundId;
} else {
$fnameEffective = $fname;
} elseif ( $this->memStash instanceof EmptyBagOStuff ) {
// No where to store any DB positions and wait for them to appear
$this->chronProt->setEnabled( false );
} elseif ( $this->memStash instanceof EmptyBagOStuff ) {
// No where to store any DB positions and wait for them to appear
$this->chronProt->setEnabled( false );
- $this->replLogger->info( 'Cannot use ChronologyProtector with EmptyBagOStuff.' );
+ $this->replLogger->info( 'Cannot use ChronologyProtector with EmptyBagOStuff' );
- $this->replLogger->debug( __METHOD__ . ': using request info ' .
- json_encode( $this->requestInfo, JSON_PRETTY_PRINT ) );
+ $this->replLogger->debug(
+ __METHOD__ . ': request info ' .
+ json_encode( $this->requestInfo, JSON_PRETTY_PRINT )
+ );
return $this->chronProt;
}
return $this->chronProt;
}
public function setRequestInfo( array $info ) {
if ( $this->chronProt ) {
public function setRequestInfo( array $info ) {
if ( $this->chronProt ) {
- throw new LogicException( 'ChronologyProtector already initialized.' );
+ throw new LogicException( 'ChronologyProtector already initialized' );
}
$this->requestInfo = $info + $this->requestInfo;
}
$this->requestInfo = $info + $this->requestInfo;
/**
* Wait for a replica DB to reach a specified master position
*
/**
* Wait for a replica DB to reach a specified master position
*
- * This will connect to the master to get an accurate position if $pos is not given
+ * If $conn is not a replica server connection, then this will return true.
+ * Otherwise, if $pos is not provided, this will connect to the master server
+ * to get an accurate position.
*
* @param IDatabase $conn Replica DB
* @param DBMasterPos|bool $pos Master position; default: current position
*
* @param IDatabase $conn Replica DB
* @param DBMasterPos|bool $pos Master position; default: current position
/** @var bool Whether any connection has been attempted yet */
private $connectionAttempted = false;
/** @var bool Whether any connection has been attempted yet */
private $connectionAttempted = false;
- /** @var int|null An integer ID of the managing LBFactory instance or null */
+ /** @var int|null Integer ID of the managing LBFactory instance or null if none */
- /** @var string|bool String if a requested DBO_TRX transaction round is active */
+ /** @var string|bool Explicit DBO_TRX transaction round active or false if none */
private $trxRoundId = false;
/** @var string Stage of the current transaction round in the transaction round life-cycle */
private $trxRoundStage = self::ROUND_CURSORY;
private $trxRoundId = false;
/** @var string Stage of the current transaction round in the transaction round life-cycle */
private $trxRoundStage = self::ROUND_CURSORY;
$this->fail( 'Test exception not thrown' );
} catch ( DBTransactionError $ex ) {
$this->assertSame(
$this->fail( 'Test exception not thrown' );
} catch ( DBTransactionError $ex ) {
$this->assertSame(
- 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
+ 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR',
} catch ( DBUnexpectedError $e ) {
$m = __METHOD__;
$this->assertSame(
} catch ( DBUnexpectedError $e ) {
$m = __METHOD__;
$this->assertSame(
- "Invalid atomic section ended (got {$m}_X but expected {$m}).",
+ "Invalid atomic section ended (got {$m}_X but expected {$m})",
$this->fail( 'Expected exception not thrown' );
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
$this->fail( 'Expected exception not thrown' );
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
- 'No atomic section is open (got ' . __METHOD__ . ').',
+ 'No atomic section is open (got ' . __METHOD__ . ')',
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
'Invalid atomic section ended (got ' . __METHOD__ . ' but expected ' .
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
'Invalid atomic section ended (got ' . __METHOD__ . ' but expected ' .
$this->fail( 'Expected exception not thrown' );
} catch ( DBTransactionError $ex ) {
$this->assertSame(
$this->fail( 'Expected exception not thrown' );
} catch ( DBTransactionError $ex ) {
$this->assertSame(
- 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
+ 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR',
$this->fail( 'Expected exception not thrown' );
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
$this->fail( 'Expected exception not thrown' );
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
- 'No atomic section is open (got ' . __METHOD__ . ').',
+ 'No atomic section is open (got ' . __METHOD__ . ')',
$this->fail( 'Expected exception not thrown' );
} catch ( DBTransactionError $e ) {
$this->assertEquals(
$this->fail( 'Expected exception not thrown' );
} catch ( DBTransactionError $e ) {
$this->assertEquals(
- 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
+ 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR',
$this->fail( 'Expected exception not thrown' );
} catch ( DBTransactionError $e ) {
$this->assertEquals(
$this->fail( 'Expected exception not thrown' );
} catch ( DBTransactionError $e ) {
$this->assertEquals(
- 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR.',
+ 'Cannot execute query from ' . __METHOD__ . ' while transaction status is ERROR',
$this->fail( 'Expected exception not thrown' );
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
$this->fail( 'Expected exception not thrown' );
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
- "Wikimedia\Rdbms\Database::close: transaction is still open (from $fname).",
+ "Wikimedia\Rdbms\Database::close: transaction is still open (from $fname)",
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
'Wikimedia\Rdbms\Database::close: atomic sections ' .
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
'Wikimedia\Rdbms\Database::close: atomic sections ' .
- 'DatabaseSQLTest::testPrematureClose2 are still open.',
+ 'DatabaseSQLTest::testPrematureClose2 are still open',
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
'Wikimedia\Rdbms\Database::close: ' .
} catch ( DBUnexpectedError $ex ) {
$this->assertSame(
'Wikimedia\Rdbms\Database::close: ' .
- 'mass commit/rollback of peer transaction required (DBO_TRX set).',
+ 'mass commit/rollback of peer transaction required (DBO_TRX set)',