aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristoph Wurst <christoph@winzerhof-wurst.at>2023-12-06 13:52:37 +0100
committerChristoph Wurst <christoph@winzerhof-wurst.at>2024-09-18 10:08:25 +0200
commit0e1d12b83986cf7ddc37226a70a6f7aa47ae060e (patch)
tree70f687729be8657320c68bd1d38f28e6d4083ecc
parent29a03ff8fea1e457fe6c3eb0dab29c2b329dc9b5 (diff)
downloadnextcloud-server-fix/carddav/create-sab-concurrently.tar.gz
nextcloud-server-fix/carddav/create-sab-concurrently.zip
fix(carddav): Handle race for SAB creation betterfix/carddav/create-sab-concurrently
* Accept non repeatable read and INSERT conflict by another read * Handle replication edge case Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
-rw-r--r--apps/dav/lib/CardDAV/CardDavBackend.php3
-rw-r--r--apps/dav/lib/CardDAV/SyncService.php33
2 files changed, 28 insertions, 8 deletions
diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php
index 9dee61638a5..c54999b14ff 100644
--- a/apps/dav/lib/CardDAV/CardDavBackend.php
+++ b/apps/dav/lib/CardDAV/CardDavBackend.php
@@ -351,6 +351,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @param array $properties
* @return int
* @throws BadRequest
+ * @throws Exception
*/
public function createAddressBook($principalUri, $url, array $properties) {
if (strlen($url) > 255) {
@@ -395,7 +396,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
'synctoken' => $query->createParameter('synctoken'),
])
->setParameters($values)
- ->execute();
+ ->executeStatement();
$addressBookId = $query->getLastInsertId();
return [
diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php
index 02b862f1930..82baea1a1ff 100644
--- a/apps/dav/lib/CardDAV/SyncService.php
+++ b/apps/dav/lib/CardDAV/SyncService.php
@@ -10,6 +10,7 @@ namespace OCA\DAV\CardDAV;
use OCP\AppFramework\Db\TTransactional;
use OCP\AppFramework\Http;
+use OCP\DB\Exception;
use OCP\Http\Client\IClientService;
use OCP\IDBConnection;
use OCP\IUser;
@@ -97,15 +98,33 @@ class SyncService {
* @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;
+ }
}
/**