diff --git a/.env b/.env index a132383554e..905b9b5e84d 100644 --- a/.env +++ b/.env @@ -6,7 +6,6 @@ _APP_NOTIFICATIONS_TRACKING_SECRET=your-secret-key _APP_RULES_FORMAT=md5 _APP_SYSTEM_RESPONSE_FORMAT= _APP_TRUSTED_HEADERS=x-forwarded-for -_APP_POOL_ADAPTER=stack _APP_WORKER_PER_CORE=6 _APP_WORKERS_NUM=1 _APP_WORKER_MAX_COROUTINES=1 diff --git a/Dockerfile b/Dockerfile index 58be688804e..9d4c0193e73 100755 --- a/Dockerfile +++ b/Dockerfile @@ -37,6 +37,7 @@ COPY ./public /usr/src/code/public COPY ./bin /usr/local/bin COPY ./src /usr/src/code/src COPY ./dev /usr/src/code/dev +COPY ./docker/opcache.ini /usr/local/etc/php/conf.d/zz-opcache.ini COPY ./mongo-init.js /usr/src/code/mongo-init.js COPY ./mongo-entrypoint.sh /usr/src/code/mongo-entrypoint.sh @@ -121,6 +122,9 @@ FROM appwrite/base:2.0.0-xdebug AS xdebug FROM base AS development +# Revalidate bind-mounted source files when development workers reload. +RUN printf 'opcache.validate_timestamps=1\nopcache.revalidate_freq=0\n' > /usr/local/etc/php/conf.d/zzz-opcache-dev.ini + COPY ./docs /usr/src/code/docs COPY ./dev /usr/src/code/dev diff --git a/app/http.php b/app/http.php index 9fef8e3f9b6..ceb595f1651 100644 --- a/app/http.php +++ b/app/http.php @@ -23,6 +23,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\DI\Container; +use Utopia\Http\Adapter\Swoole\Mode; use Utopia\Http\Adapter\Swoole\Server; use Utopia\Http\Files; use Utopia\Http\Http; @@ -54,8 +55,9 @@ host: "0.0.0.0", port: System::getEnv('PORT', 80), settings: [ + // Apply Cloud's coroutine preset, retaining Appwrite's worker and payload limits. + ...Mode::HYPERLOOP_B->settings(), Constant::OPTION_WORKER_NUM => $totalWorkers, - Constant::OPTION_HTTP_COMPRESSION => false, Constant::OPTION_PACKAGE_MAX_LENGTH => $payloadSize, Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize, ], diff --git a/app/init/registers.php b/app/init/registers.php index 7c5e5ea9d65..2e5e1b2420c 100644 --- a/app/init/registers.php +++ b/app/init/registers.php @@ -18,7 +18,6 @@ use Utopia\Http\Http; use Utopia\Messaging\Adapter\Email\SMTP; use Utopia\Mongo\Client as MongoClient; -use Utopia\Pools\Adapter\Stack as StackPool; use Utopia\Pools\Adapter\Swoole as SwoolePool; use Utopia\Pools\Group; use Utopia\Pools\Pool; @@ -223,8 +222,6 @@ }, }; - $poolAdapter = System::getEnv('_APP_POOL_ADAPTER', default: 'stack') === 'swoole' ? new SwoolePool() : new StackPool(); - // PubSub workers hold one long-lived subscribed connection and also need // spare capacity for publishes from the same process. $connectionPoolSize = match ($type) { @@ -232,7 +229,7 @@ default => $poolSize, }; - $pool = new Pool($poolAdapter, $name, $connectionPoolSize, function () use ($type, $resource, $dsn) { + $pool = new Pool(new SwoolePool(), $name, $connectionPoolSize, function () use ($type, $resource, $dsn) { // Get Adapter switch ($type) { case 'database': diff --git a/docker-compose.yml b/docker-compose.yml index 6d9be6bb0a4..f8cc7586434 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -91,7 +91,6 @@ services: - _APP_STATS_RESOURCES_INTERVAL - _APP_EDITION - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_LOCALE - _APP_COMPRESSION_ENABLED - _APP_COMPRESSION_MIN_SIZE_BYTES @@ -353,7 +352,6 @@ services: - _APP_DOCUMENTSDB - _APP_VECTORSDB - _APP_EMBEDDING - - _APP_POOL_ADAPTER=swoole appwrite-worker: entrypoint: worker @@ -398,7 +396,6 @@ services: # Pool sizing only (sum of per-queue caps). Each queue keeps its own maxCoroutines in PHP; databases stays at 1. - _APP_WORKER_MAX_COROUTINES=78 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_EMAIL_SECURITY - _APP_EMAIL_CERTIFICATES @@ -555,7 +552,6 @@ services: # against shared console/cache pools; floor must cover concurrent ticks. - _APP_WORKER_MAX_COROUTINES=8 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_OPTIONS_FORCE_HTTPS - _APP_DOMAIN @@ -622,7 +618,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=8 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_EMAIL_SECURITY - _APP_DB_ADAPTER @@ -674,7 +669,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=8 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_REDIS_HOST - _APP_REDIS_PORT @@ -751,7 +745,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=1 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_REDIS_HOST - _APP_REDIS_PORT @@ -810,7 +803,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=8 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_EXECUTOR_SECRET - _APP_EXECUTOR_HOST @@ -911,7 +903,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=8 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_STORAGE_DEVICE - _APP_STORAGE_S3_ACCESS_KEY @@ -980,7 +971,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=8 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_LOGGING_CONFIG - _APP_LOGGING_FORMAT - _APP_OPENSSL_KEY_V1 @@ -1043,7 +1033,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=8 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_DOMAIN - _APP_DOMAIN_TARGET_CNAME @@ -1095,7 +1084,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=8 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_REDIS_HOST - _APP_REDIS_PORT @@ -1132,7 +1120,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=8 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_DOMAIN - _APP_OPTIONS_FORCE_HTTPS @@ -1179,7 +1166,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=6 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_NOTIFICATIONS_TRACKING_SECRET - _APP_SYSTEM_EMAIL_NAME @@ -1225,7 +1211,6 @@ services: environment: - _APP_ENV - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_NOTIFICATIONS_TRACKING_SECRET - _APP_SYSTEM_EMAIL_NAME @@ -1273,7 +1258,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=1 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_REDIS_HOST - _APP_REDIS_PORT @@ -1337,7 +1321,6 @@ services: - _APP_WORKERS_NUM=1 - _APP_WORKER_MAX_COROUTINES=1 - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_DOMAIN - _APP_DOMAIN_TARGET_CNAME @@ -1396,7 +1379,6 @@ services: - _APP_ENV - _APP_LOGGING_FORMAT - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_DOMAIN - _APP_DOMAIN_TARGET_CNAME - _APP_DOMAIN_TARGET_AAAA @@ -1446,7 +1428,6 @@ services: - _APP_ENV - _APP_LOGGING_FORMAT - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_DOMAIN - _APP_DOMAIN_TARGET_CNAME - _APP_DOMAIN_TARGET_AAAA @@ -1491,7 +1472,6 @@ services: - _APP_ENV - _APP_LOGGING_FORMAT - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_OPTIONS_FORCE_HTTPS - _APP_DOMAIN @@ -1533,7 +1513,6 @@ services: - _APP_ENV - _APP_LOGGING_FORMAT - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_OPTIONS_FORCE_HTTPS - _APP_DOMAIN @@ -1574,7 +1553,6 @@ services: - _APP_ENV - _APP_LOGGING_FORMAT - _APP_WORKER_PER_CORE - - _APP_POOL_ADAPTER - _APP_OPENSSL_KEY_V1 - _APP_REDIS_HOST - _APP_REDIS_PORT diff --git a/docker/opcache.ini b/docker/opcache.ini new file mode 100644 index 00000000000..05f93cc4d05 --- /dev/null +++ b/docker/opcache.ini @@ -0,0 +1,9 @@ +opcache.enable=1 +opcache.enable_cli=1 +opcache.memory_consumption=384 +opcache.interned_strings_buffer=32 +opcache.max_accelerated_files=65536 +opcache.validate_timestamps=0 +opcache.save_comments=1 +opcache.jit=tracing +opcache.jit_buffer_size=128M diff --git a/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php b/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php index af1ca9b3fbd..f0b399b8fb6 100644 --- a/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php +++ b/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php @@ -355,7 +355,15 @@ public function action( } } - $chunksUploaded = max($uploaded, $chunksUploaded, (int) ($metadata['chunks'] ?? 0)); + // Another chunk may have finalized the upload and removed its parts + // before upload() counted them. Check the completed document above first. + if (empty($chunksUploaded)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed uploading file'); + } + + // A local chunk file is visible before its write finishes. Only parts + // recorded after upload() returns are safe to include in finalization. + $chunksUploaded = max($uploaded, isset($metadata['parts']) ? \count($metadata['parts']) : $chunksUploaded); if ($chunksUploaded === $chunks && $uploaded < $chunks) { $deviceForFiles->finalize($path, $chunks, $metadata); @@ -526,10 +534,6 @@ public function action( $metadata ); - if (empty($chunksUploaded)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed uploading file'); - } - $locks($lockKey, 600, fn () => $finalizeUpload($chunksUploaded), timeout: 120.0); } catch (LockContention) { $response->addHeader('Retry-After', '5');