From 341aabb9426757130bc7dc71dec17001df9a13da Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 14 Mar 2026 11:29:53 +1300 Subject: [PATCH] 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 --- .../Modules/Projects/Http/Projects/Create.php | 39 +++++++++++++++---- .../Realtime/RealtimeCustomClientTest.php | 2 +- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Projects/Http/Projects/Create.php b/src/Appwrite/Platform/Modules/Projects/Http/Projects/Create.php index b169391336..eb71e5a02f 100644 --- a/src/Appwrite/Platform/Modules/Projects/Http/Projects/Create.php +++ b/src/Appwrite/Platform/Modules/Projects/Http/Projects/Create.php @@ -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 + } } } } diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index c5fb71fcb6..6506ee2504 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -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'],