1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-04-01 23:18:15 +02:00

Read lock all transaction edits

Summary: Ref T13054. Fixes T12714. Applies read locks to all transactions instead of only a very select subset (chat messages in Conpherence).

Test Plan: See <T13054#235650> for discussion and testing.

Maniphest Tasks: T13054, T12714

Differential Revision: https://secure.phabricator.com/D19059
This commit is contained in:
epriestley 2018-02-10 17:42:02 -08:00
parent f43d08c2bb
commit 653bc0fa01
3 changed files with 64 additions and 124 deletions

View file

@ -300,6 +300,10 @@ final class PhabricatorAuthPasswordEngine
$password->upgradePasswordHasher($envelope, $this->getObject());
$new_hasher = $password->getHasher();
// NOTE: We must save the change before applying transactions because
// the editor will reload the object to obtain a read lock.
$password->save();
$xactions = array();
$xactions[] = $password->getApplicationTransactionTemplate()

View file

@ -99,24 +99,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
return pht('%s created this room.', $author);
}
/**
* We really only need a read lock if we have a comment. In that case, we
* must update the messagesCount field on the conpherence and
* seenMessagesCount(s) for the participant(s).
*/
protected function shouldReadLock(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$lock = false;
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$lock = true;
break;
}
return $lock;
}
protected function applyBuiltinInternalTransaction(
PhabricatorLiskDAO $object,

View file

@ -924,23 +924,13 @@ abstract class PhabricatorApplicationTransactionEditor
if ($object->getID()) {
$this->buildOldRecipientLists($object, $xactions);
foreach ($xactions as $xaction) {
// If any of the transactions require a read lock, hold one and
// reload the object. We need to do this fairly early so that the
// call to `adjustTransactionValues()` (which populates old values)
// is based on the synchronized state of the object, which may differ
// from the state when it was originally loaded.
if ($this->shouldReadLock($object, $xaction)) {
$object->openTransaction();
$object->beginReadLocking();
$transaction_open = true;
$object->beginReadLocking();
$read_locking = true;
$object->reload();
break;
}
}
}
if ($this->shouldApplyInitialEffects($object, $xactions)) {
@ -951,6 +941,7 @@ abstract class PhabricatorApplicationTransactionEditor
}
}
try {
if ($this->shouldApplyInitialEffects($object, $xactions)) {
$this->applyInitialEffects($object, $xactions);
}
@ -959,17 +950,7 @@ abstract class PhabricatorApplicationTransactionEditor
$this->adjustTransactionValues($object, $xaction);
}
try {
$xactions = $this->filterTransactions($object, $xactions);
} catch (Exception $ex) {
if ($read_locking) {
$object->endReadLocking();
}
if ($transaction_open) {
$object->killTransaction();
}
throw $ex;
}
// TODO: Once everything is on EditEngine, just use getIsNewObject() to
// figure this out instead.
@ -1008,9 +989,9 @@ abstract class PhabricatorApplicationTransactionEditor
if (!$transaction_open) {
$object->openTransaction();
$transaction_open = true;
}
try {
foreach ($xactions as $xaction) {
$this->applyInternalEffects($object, $xaction);
}
@ -1070,14 +1051,27 @@ abstract class PhabricatorApplicationTransactionEditor
}
$xactions = $this->applyFinalEffects($object, $xactions);
if ($read_locking) {
$object->endReadLocking();
$read_locking = false;
}
if ($transaction_open) {
$object->saveTransaction();
$transaction_open = false;
}
} catch (Exception $ex) {
if ($read_locking) {
$object->endReadLocking();
$read_locking = false;
}
if ($transaction_open) {
$object->killTransaction();
$transaction_open = false;
}
throw $ex;
}
@ -1319,46 +1313,6 @@ abstract class PhabricatorApplicationTransactionEditor
return $xactions;
}
/**
* Determine if the editor should hold a read lock on the object while
* applying a transaction.
*
* If the editor does not hold a lock, two editors may read an object at the
* same time, then apply their changes without any synchronization. For most
* transactions, this does not matter much. However, it is important for some
* transactions. For example, if an object has a transaction count on it, both
* editors may read the object with `count = 23`, then independently update it
* and save the object with `count = 24` twice. This will produce the wrong
* state: the object really has 25 transactions, but the count is only 24.
*
* Generally, transactions fall into one of four buckets:
*
* - Append operations: Actions like adding a comment to an object purely
* add information to its state, and do not depend on the current object
* state in any way. These transactions never need to hold locks.
* - Overwrite operations: Actions like changing the title or description
* of an object replace the current value with a new value, so the end
* state is consistent without a lock. We currently do not lock these
* transactions, although we may in the future.
* - Edge operations: Edge and subscription operations have internal
* synchronization which limits the damage race conditions can cause.
* We do not currently lock these transactions, although we may in the
* future.
* - Update operations: Actions like incrementing a count on an object.
* These operations generally should use locks, unless it is not
* important that the state remain consistent in the presence of races.
*
* @param PhabricatorLiskDAO Object being updated.
* @param PhabricatorApplicationTransaction Transaction being applied.
* @return bool True to synchronize the edit with a lock.
*/
protected function shouldReadLock(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return false;
}
private function loadHandles(array $xactions) {
$phids = array();
foreach ($xactions as $key => $xaction) {