@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow transactions to require read locks during edits

Summary:
Let applicationtransaction editors request locks when applying certain transactions. See D5567.

We should probably do this for all not-guaranteed-safe operations, but let's put our toe in the water with Conpherence first. I believe the cost of these locks is very small and my cautious stance toward acquiring them isn't really warranted, but I've also never seen, e.g., a race on a title edit.

Test Plan: Added comments with and without locks, verified both pathways worked correctly.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Differential Revision: https://secure.phabricator.com/D5582

+78 -2
+78 -2
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 314 314 $xaction->setContentSource($this->getContentSource()); 315 315 } 316 316 317 + $is_preview = $this->getIsPreview(); 318 + $read_locking = false; 319 + 320 + if (!$is_preview && $object->getID()) { 321 + foreach ($xactions as $xaction) { 322 + 323 + // If any of the transactions require a read lock, hold one and reload 324 + // the object. We need to do this fairly early so that the call to 325 + // `adjustTransactionValues()` (which populates old values) is based 326 + // on the synchronized state of the object, which may differ from the 327 + // state when it was originally loaded. 328 + 329 + if ($this->shouldReadLock($object, $xaction)) { 330 + $object->openTransaction(); 331 + $object->beginReadLocking(); 332 + $read_locking = true; 333 + $object->reload(); 334 + break; 335 + } 336 + } 337 + } 338 + 317 339 foreach ($xactions as $xaction) { 318 340 $this->adjustTransactionValues($object, $xaction); 319 341 } ··· 321 343 $xactions = $this->filterTransactions($object, $xactions); 322 344 323 345 if (!$xactions) { 346 + if ($read_locking) { 347 + $object->endReadLocking(); 348 + $read_locking = false; 349 + $object->killTransaction(); 350 + } 324 351 return array(); 325 352 } 326 353 327 354 $xactions = $this->sortTransactions($xactions); 328 355 329 - if ($this->getIsPreview()) { 356 + if ($is_preview) { 330 357 $this->loadHandles($xactions); 331 358 return $xactions; 332 359 } ··· 335 362 ->setActor($actor) 336 363 ->setContentSource($this->getContentSource()); 337 364 338 - $object->openTransaction(); 365 + if (!$read_locking) { 366 + $object->openTransaction(); 367 + } 368 + 339 369 foreach ($xactions as $xaction) { 340 370 $this->applyInternalEffects($object, $xaction); 341 371 } ··· 355 385 foreach ($xactions as $xaction) { 356 386 $this->applyExternalEffects($object, $xaction); 357 387 } 388 + 389 + if ($read_locking) { 390 + $object->endReadLocking(); 391 + $read_locking = false; 392 + } 393 + 358 394 $object->saveTransaction(); 359 395 360 396 $this->loadHandles($xactions); ··· 388 424 protected function didApplyTransactions(array $xactions) { 389 425 // Hook for subclasses. 390 426 return; 427 + } 428 + 429 + 430 + /** 431 + * Determine if the editor should hold a read lock on the object while 432 + * applying a transaction. 433 + * 434 + * If the editor does not hold a lock, two editors may read an object at the 435 + * same time, then apply their changes without any synchronization. For most 436 + * transactions, this does not matter much. However, it is important for some 437 + * transactions. For example, if an object has a transaction count on it, both 438 + * editors may read the object with `count = 23`, then independently update it 439 + * and save the object with `count = 24` twice. This will produce the wrong 440 + * state: the object really has 25 transactions, but the count is only 24. 441 + * 442 + * Generally, transactions fall into one of four buckets: 443 + * 444 + * - Append operations: Actions like adding a comment to an object purely 445 + * add information to its state, and do not depend on the current object 446 + * state in any way. These transactions never need to hold locks. 447 + * - Overwrite operations: Actions like changing the title or description 448 + * of an object replace the current value with a new value, so the end 449 + * state is consistent without a lock. We currently do not lock these 450 + * transactions, although we may in the future. 451 + * - Edge operations: Edge and subscription operations have internal 452 + * synchronization which limits the damage race conditions can cause. 453 + * We do not currently lock these transactions, although we may in the 454 + * future. 455 + * - Update operations: Actions like incrementing a count on an object. 456 + * These operations generally should use locks, unless it is not 457 + * important that the state remain consistent in the presence of races. 458 + * 459 + * @param PhabricatorLiskDAO Object being updated. 460 + * @param PhabricatorApplicationTransaction Transaction being applied. 461 + * @return bool True to synchronize the edit with a lock. 462 + */ 463 + protected function shouldReadLock( 464 + PhabricatorLiskDAO $object, 465 + PhabricatorApplicationTransaction $xaction) { 466 + return false; 391 467 } 392 468 393 469 private function loadHandles(array $xactions) {