Skip to content

Commit 19b66f4

Browse files
committed
fix(preview): Fix possible data race in oc_preview_location insert
If two process try to create at the same time the same row in oc_preview_location, catch the error in the second process and fetch the location id again. Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
1 parent 1e4b8c8 commit 19b66f4

File tree

2 files changed

+33
-9
lines changed

2 files changed

+33
-9
lines changed

core/Migrations/Version33000Date20250819110529.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
3434
$table->addColumn('id', Types::BIGINT, ['notnull' => true, 'length' => 20, 'unsigned' => true]);
3535
$table->addColumn('bucket_name', Types::STRING, ['notnull' => true, 'length' => 40]);
3636
$table->addColumn('object_store_name', Types::STRING, ['notnull' => true, 'length' => 40]);
37+
$table->addUniqueIndex(['bucket_name', 'object_store_name']);
3738
$table->setPrimaryKey(['id']);
3839
}
3940

lib/private/Preview/Db/PreviewMapper.php

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ protected function joinLocation(IQueryBuilder $qb): IQueryBuilder {
152152
));
153153
}
154154

155+
/**
156+
* Get the location id corresponding to the $bucket and $objectStore. Create one
157+
* if not existing yet.
158+
*
159+
* @throws Exception
160+
*/
155161
public function getLocationId(string $bucket, string $objectStore): string {
156162
$qb = $this->db->getQueryBuilder();
157163
$result = $qb->select('id')
@@ -161,16 +167,33 @@ public function getLocationId(string $bucket, string $objectStore): string {
161167
->executeQuery();
162168
$data = $result->fetchOne();
163169
if ($data) {
164-
return $data;
170+
return (string)$data;
165171
} else {
166-
$id = $this->snowflake->id();
167-
$qb->insert(self::LOCATION_TABLE_NAME)
168-
->values([
169-
'id' => $qb->createNamedParameter($id),
170-
'bucket_name' => $qb->createNamedParameter($bucket),
171-
'object_store_name' => $qb->createNamedParameter($objectStore),
172-
])->executeStatement();
173-
return $id;
172+
try {
173+
$id = $this->snowflake->id();
174+
$qb->insert(self::LOCATION_TABLE_NAME)
175+
->values([
176+
'id' => $qb->createNamedParameter($id),
177+
'bucket_name' => $qb->createNamedParameter($bucket),
178+
'object_store_name' => $qb->createNamedParameter($objectStore),
179+
])->executeStatement();
180+
return $id;
181+
} catch (Exception $e) {
182+
if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
183+
// Fetch again as there seems to be another entry added meanwhile
184+
$result = $qb->select('id')
185+
->from(self::LOCATION_TABLE_NAME)
186+
->where($qb->expr()->eq('bucket_name', $qb->createNamedParameter($bucket)))
187+
->andWhere($qb->expr()->eq('object_store_name', $qb->createNamedParameter($objectStore)))
188+
->executeQuery();
189+
$data = $result->fetchOne();
190+
if ($data) {
191+
return $data;
192+
}
193+
}
194+
195+
throw $e;
196+
}
174197
}
175198
}
176199

0 commit comments

Comments
 (0)