Skip to content

Commit

Permalink
fix(carddav): Handle race for SAB creation better
Browse files Browse the repository at this point in the history
* Accept non repeatable read and INSERT conflict by another read
* Handle replication edge case

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
  • Loading branch information
ChristophWurst committed Dec 6, 2023
1 parent 5cf42ff commit a076356
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 8 deletions.
3 changes: 2 additions & 1 deletion apps/dav/lib/CardDAV/CardDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatc
* @param array $properties
* @return int
* @throws BadRequest
* @throws Exception
*/
public function createAddressBook($principalUri, $url, array $properties) {
if (strlen($url) > 255) {
Expand Down Expand Up @@ -433,7 +434,7 @@ public function createAddressBook($principalUri, $url, array $properties) {
'synctoken' => $query->createParameter('synctoken'),
])
->setParameters($values)
->execute();
->executeStatement();

$addressBookId = $query->getLastInsertId();
return [
Expand Down
34 changes: 27 additions & 7 deletions apps/dav/lib/CardDAV/SyncService.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

use OCP\AppFramework\Db\TTransactional;
use OCP\AppFramework\Http;
use OCP\DB\Exception;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
Expand All @@ -41,6 +42,7 @@
use Sabre\DAV\Xml\Service;
use Sabre\HTTP\ClientHttpException;
use Sabre\VObject\Reader;
use Throwable;
use function is_null;

class SyncService {
Expand Down Expand Up @@ -116,15 +118,33 @@ public function syncRemoteAddressBook(string $url, string $userName, string $add
* @throws \Sabre\DAV\Exception\BadRequest
*/
public function ensureSystemAddressBookExists(string $principal, string $uri, array $properties): ?array {
return $this->atomic(function () use ($principal, $uri, $properties) {
$book = $this->backend->getAddressBooksByUri($principal, $uri);
if (!is_null($book)) {
return $book;
try {
return $this->atomic(function () use ($principal, $uri, $properties) {
$book = $this->backend->getAddressBooksByUri($principal, $uri);
if (!is_null($book)) {
return $book;
}
$this->backend->createAddressBook($principal, $uri, $properties);

return $this->backend->getAddressBooksByUri($principal, $uri);
}, $this->dbConnection);
} catch (Exception $e) {
// READ COMMITTED doesn't prevent a nonrepeatable read above, so
// two processes might create an address book here. Ignore our
// failure and continue loading the entry written by the other process
if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
throw $e;
}
$this->backend->createAddressBook($principal, $uri, $properties);

return $this->backend->getAddressBooksByUri($principal, $uri);
}, $this->dbConnection);
// If this fails we might have hit a replication node that does not
// have the row written in the other process.
// TODO: find an elegant way to handle this
$ab = $this->backend->getAddressBooksByUri($principal, $uri);
if ($ab === null) {
throw new Exception('Could not create system address book', $e->getCode(), $e);
}
return $ab;
}
}

/**
Expand Down

0 comments on commit a076356

Please sign in to comment.