fix: handle PostgreSQL race condition in shared mode project creation

PostgreSQL adapter throws non-Duplicate exceptions when a table/index
already exists during concurrent createCollection. Catch Throwable and
attempt metadata-only creation as fallback. Also increase Realtime
coroutine attribute polling timeout from 30s to 60s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jake Barnby
2026-03-14 11:29:53 +13:00
co-authored by Claude Opus 4.6
parent 09c963abda
commit 341aabb942
2 changed files with 32 additions and 9 deletions
@@ -276,14 +276,37 @@ class Create extends Action
try {
$dbForProject->createCollection($key, $attributes, $indexes);
} catch (Duplicate) {
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom($key),
'$permissions' => [Permission::create(Role::any())],
'name' => $key,
'attributes' => $attributes,
'indexes' => $indexes,
'documentSecurity' => true
]));
try {
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom($key),
'$permissions' => [Permission::create(Role::any())],
'name' => $key,
'attributes' => $attributes,
'indexes' => $indexes,
'documentSecurity' => true
]));
} catch (Duplicate) {
// Metadata already exists from concurrent creation
}
} catch (\Throwable $e) {
// PostgreSQL adapter may throw a non-Duplicate exception when
// a table or index already exists during concurrent project
// creation in shared mode. Treat as duplicate if metadata
// can be created successfully.
try {
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom($key),
'$permissions' => [Permission::create(Role::any())],
'name' => $key,
'attributes' => $attributes,
'indexes' => $indexes,
'documentSecurity' => true
]));
} catch (Duplicate) {
// Metadata already exists from concurrent creation
} catch (\Throwable) {
throw $e; // Rethrow original if metadata creation also fails
}
}
}
}
@@ -3802,7 +3802,7 @@ class RealtimeCustomClientTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey'],
]));
$this->assertEquals('available', $response['body']['status']);
}, 30000, 250);
}, 60000, 500);
$creates = [
['name' => 'Doc A'],