mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
refactor: drop log/logger boilerplate from lock call sites
The previous shape required every caller to thread `log: $log, logger: $logger`
as named args into each `distributedLock(...)` invocation, plus inject `log`
and `logger` into the surrounding action just to forward them to the lock.
Across 21 call sites this added ~100 LOC of pure plumbing.
The cause: the lock factory was registered on the global container in
`app/init/resources.php`, where per-request resources like `log` aren't
visible. That forced the factory to expose its inner closure with optional
`?Log $log = null, ?Logger $logger = null` params, which every caller had
to satisfy.
Move the lock factory + its `lockErrorReporter`/`lockTargetOf` helpers from
the global container to the per-request container (`resources/request.php`),
and add `'log'` + `'logger'` to the factory's dep list. The factory closure
now runs per-request and closes over the per-request `Log`/`Logger`. Inner
closure returned to callers no longer needs the optional params, and call
sites drop the named args entirely.
Knock-on cleanup:
- Drop `->inject('log')`, `->inject('logger')`, the corresponding action
params, and `use Utopia\Logger\{Log,Logger}` imports from 19 endpoint
files where they were only there for the lock
- Drop the same plumbing from `app/controllers/shared/api.php` (3 lock call
sites)
- Drop just the Logger plumbing from `app/controllers/general.php` (router
function + 3 callbacks); `Log` is kept because it's used elsewhere in
that file
- Net 120 LOC removed across 23 files
No behavior change: the lock factories still produce the same closures
(skip-on-contention `distributedLock`, blocking-with-409 `distributedLockOrFail`).
The static lockErrorReporter rate limiter (1 push per 60s per
`(action, target)` bucket) continues to work — it lives on a closure-static
in the helper, which is independent of where the helper is constructed.
Verified end-to-end: testConcurrentTogglesAllPersist passes 4/5 (the cold-
start race flake is the same one we've consistently seen and is orthogonal
to lock changes).
This commit is contained in:
+10
-14
@@ -67,7 +67,7 @@ use Utopia\Validator\Text;
|
||||
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock, ?Logger $logger)
|
||||
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock)
|
||||
{
|
||||
$host = $request->getHostname();
|
||||
if (!empty($previewHostname)) {
|
||||
@@ -143,7 +143,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'accessedAt' => DateTime::now()
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -853,8 +853,7 @@ Http::init()
|
||||
->inject('queueForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->inject('distributedLock')
|
||||
->inject('logger')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, Event $queueForEvents, Bus $bus, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock, ?Logger $logger) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, Event $queueForEvents, Bus $bus, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
@@ -862,7 +861,7 @@ Http::init()
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// Only run Router when external domain
|
||||
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock, $logger)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1148,15 +1147,14 @@ Http::options()
|
||||
->inject('queueForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->inject('distributedLock')
|
||||
->inject('logger')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock, ?Logger $logger) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// Only run Router when external domain
|
||||
if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock, $logger)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1552,14 +1550,13 @@ Http::get('/robots.txt')
|
||||
->inject('queueForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->inject('distributedLock')
|
||||
->inject('logger')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock, ?Logger $logger) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock, $logger)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1588,14 +1585,13 @@ Http::get('/humans.txt')
|
||||
->inject('queueForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->inject('distributedLock')
|
||||
->inject('logger')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock, ?Logger $logger) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock, $logger)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,6 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\Roles;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
@@ -103,9 +101,7 @@ Http::init()
|
||||
->inject('apiKey')
|
||||
->inject('authorization')
|
||||
->inject('distributedLock')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, AuditContext $auditContext, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization, callable $distributedLock, Log $log, ?Logger $logger) {
|
||||
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, AuditContext $auditContext, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization, callable $distributedLock) {
|
||||
$route = $utopia->getRoute();
|
||||
if ($route === null) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
@@ -267,7 +263,7 @@ Http::init()
|
||||
} elseif (! empty($apiKey->getTeamId())) {
|
||||
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('teams', $team->getId()));
|
||||
}
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
}
|
||||
|
||||
$userClone = clone $user;
|
||||
@@ -402,7 +398,7 @@ Http::init()
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'accessedAt' => DateTime::now()
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +419,7 @@ Http::init()
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('users', $user->getId(), new Document([
|
||||
'accessedAt' => $user->getAttribute('accessedAt')
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use Appwrite\Event\Publisher\Migration as MigrationPublisher;
|
||||
use Appwrite\Event\Publisher\Screenshot as ScreenshotPublisher;
|
||||
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Executor\Executor;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
@@ -23,9 +22,6 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Lock\Distributed as DistributedLock;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
||||
use Utopia\Queue\Publisher;
|
||||
@@ -217,198 +213,6 @@ $container->set('timelimit', function (\Redis $redis) {
|
||||
};
|
||||
}, ['redis']);
|
||||
|
||||
// Extract the collection segment ("keys" / "projects" / "users") from keys
|
||||
// of the form "lock:platform:{target}:{id}" so metrics can slice by target.
|
||||
// Used by both distributedLock and distributedLockOrFail factories below.
|
||||
$lockTargetOf = function (string $key): string {
|
||||
$parts = explode(':', $key, 4);
|
||||
return $parts[2] ?? 'unknown';
|
||||
};
|
||||
|
||||
/**
|
||||
* Push lock-side errors to the configured logger (Sentry/Raygun/AppSignal/etc.)
|
||||
* by mutating the caller's per-request `Log` object — same pattern as
|
||||
* Embeddings/Text/Create.php and the http.php request-end error handler.
|
||||
*
|
||||
* Rate limited per pod via a static bucket so a sustained backend outage
|
||||
* doesn't flood the logger. At most one push per 60s per (action, target)
|
||||
* combo. Across a fleet of N pods this caps at N events/min, well within
|
||||
* Sentry's own dedup tolerance.
|
||||
*
|
||||
* Only "real" errors are reported here:
|
||||
* - backend_error: Redis/Dragonfly unreachable
|
||||
* - release_error: lock release failed (TTL expired or backend dropped)
|
||||
*
|
||||
* Skipped on purpose: contention 409s (normal user-facing concurrency),
|
||||
* skip-on-contention events (idempotent fan-out by design), acquire retry
|
||||
* conflicts (internal loop), destructor cleanups (have an expected
|
||||
* baseline rate; aggregate via the `lock.attempts` counter instead).
|
||||
*/
|
||||
$lockErrorReporter = function (?Log $log, ?Logger $logger, string $action, string $key, string $target, Throwable $e): void {
|
||||
static $lastReportAt = [];
|
||||
|
||||
Console::warning("Lock {$action} for {$key}: {$e->getMessage()}");
|
||||
|
||||
if ($logger === null || $log === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bucket = $action . ':' . $target;
|
||||
$now = time();
|
||||
if (($lastReportAt[$bucket] ?? 0) + 60 > $now) {
|
||||
return;
|
||||
}
|
||||
$lastReportAt[$bucket] = $now;
|
||||
|
||||
$log->setNamespace('http');
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion(APP_VERSION_STABLE);
|
||||
$log->setType(Log::TYPE_WARNING);
|
||||
$log->setMessage('Distributed lock ' . $action . ': ' . $e->getMessage());
|
||||
$log->setAction("lock.{$action}");
|
||||
$log->setEnvironment(System::getEnv('_APP_ENV', 'development') === 'production'
|
||||
? Log::ENVIRONMENT_PRODUCTION
|
||||
: Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$log->addTag('lock.target', $target);
|
||||
// Strip the trailing document ID from the key so log aggregators don't
|
||||
// see unbounded cardinality; the {target} attribute already covers slicing.
|
||||
$log->addTag('lock.key_pattern', preg_replace('/:[^:]+$/', ':*', $key));
|
||||
$log->addTag('code', $e->getCode());
|
||||
|
||||
$log->addExtra('file', $e->getFile());
|
||||
$log->addExtra('line', $e->getLine());
|
||||
$log->addExtra('trace', $e->getTraceAsString());
|
||||
|
||||
try {
|
||||
$logger->addLog($log);
|
||||
} catch (Throwable) {
|
||||
// best-effort; don't let logging failures break fail-open
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Distributed-lock factory: skip-on-contention variant.
|
||||
*
|
||||
* For idempotent writes where losing the race is correct (e.g., per-request
|
||||
* `accessedAt` updates from N pods all writing the same value). On contention,
|
||||
* the callback is silently skipped — another pod is doing the same work.
|
||||
*
|
||||
* Behavior:
|
||||
* - Non-blocking acquire (one attempt). On conflict, skip and return.
|
||||
* - Fail-open: if Redis is unreachable, run the callback unlocked + warn.
|
||||
* - Kill switch: `_APP_LOCKING_ENABLED=disabled` runs the callback unlocked.
|
||||
*
|
||||
* Returns void — the caller can't distinguish "acquired and ran" from "skipped".
|
||||
*
|
||||
* Metric: `lock.attempts{outcome,target}` where outcome ∈ {acquired, skipped,
|
||||
* backend_error, release_error}.
|
||||
*/
|
||||
$container->set('distributedLock', function (\Redis $redis, Telemetry $telemetry) use ($lockTargetOf, $lockErrorReporter) {
|
||||
$enabled = System::getEnv('_APP_LOCKING_ENABLED', 'enabled') !== 'disabled';
|
||||
$attempts = $telemetry->createCounter('lock.attempts', null, 'Distributed lock acquire outcomes');
|
||||
|
||||
if (! $enabled) {
|
||||
return function (string $key, \Closure $fn, float $ttl = 5.0, ?Log $log = null, ?Logger $logger = null): void {
|
||||
$fn();
|
||||
};
|
||||
}
|
||||
|
||||
return function (string $key, \Closure $fn, float $ttl = 5.0, ?Log $log = null, ?Logger $logger = null) use ($redis, $attempts, $lockTargetOf, $lockErrorReporter): void {
|
||||
$target = $lockTargetOf($key);
|
||||
$lock = new DistributedLock($redis, $key, (int) $ttl);
|
||||
|
||||
try {
|
||||
$acquired = $lock->tryAcquire();
|
||||
} catch (\RedisException $e) {
|
||||
$attempts->add(1, ['outcome' => 'backend_error', 'target' => $target]);
|
||||
$lockErrorReporter($log, $logger, 'backend_error', $key, $target, $e);
|
||||
$fn();
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $acquired) {
|
||||
$attempts->add(1, ['outcome' => 'skipped', 'target' => $target]);
|
||||
return;
|
||||
}
|
||||
|
||||
$attempts->add(1, ['outcome' => 'acquired', 'target' => $target]);
|
||||
try {
|
||||
$fn();
|
||||
} finally {
|
||||
try {
|
||||
$lock->release();
|
||||
} catch (\Throwable $e) {
|
||||
$attempts->add(1, ['outcome' => 'release_error', 'target' => $target]);
|
||||
$lockErrorReporter($log, $logger, 'release_error', $key, $target, $e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, ['redis', 'telemetry']);
|
||||
|
||||
/**
|
||||
* Distributed-lock factory: 409-on-contention variant.
|
||||
*
|
||||
* For explicit user-write endpoints where read-modify-write on shared mutable
|
||||
* state must NOT silently drop a request. On contention, throws
|
||||
* `Exception::GENERAL_RESOURCE_LOCKED` (HTTP 409) so the client retries.
|
||||
*
|
||||
* Behavior:
|
||||
* - Blocking acquire with short timeout (default 3s).
|
||||
* - On timeout, throws `GENERAL_RESOURCE_LOCKED`.
|
||||
* - Fail-open: backend unreachable runs the callback unlocked + warning.
|
||||
* - Kill switch: `_APP_LOCKING_ENABLED=disabled` runs the callback unlocked.
|
||||
* - Returns the callback's return value so callers can use the result.
|
||||
*
|
||||
* Metric: `lock.attempts{outcome,target}` where outcome ∈ {acquired, contended,
|
||||
* backend_error, release_error}.
|
||||
*/
|
||||
$container->set('distributedLockOrFail', function (\Redis $redis, Telemetry $telemetry) use ($lockTargetOf, $lockErrorReporter) {
|
||||
$enabled = System::getEnv('_APP_LOCKING_ENABLED', 'enabled') !== 'disabled';
|
||||
$attempts = $telemetry->createCounter('lock.attempts', null, 'Distributed lock acquire outcomes');
|
||||
|
||||
if (! $enabled) {
|
||||
return function (string $key, \Closure $fn, float $ttl = 10.0, float $waitTimeout = 3.0, ?Log $log = null, ?Logger $logger = null): mixed {
|
||||
return $fn();
|
||||
};
|
||||
}
|
||||
|
||||
return function (string $key, \Closure $fn, float $ttl = 10.0, float $waitTimeout = 3.0, ?Log $log = null, ?Logger $logger = null) use ($redis, $attempts, $lockTargetOf, $lockErrorReporter): mixed {
|
||||
$target = $lockTargetOf($key);
|
||||
$lock = new DistributedLock($redis, $key, (int) $ttl);
|
||||
|
||||
try {
|
||||
$acquired = $lock->acquire($waitTimeout);
|
||||
} catch (\RedisException $e) {
|
||||
$attempts->add(1, ['outcome' => 'backend_error', 'target' => $target]);
|
||||
$lockErrorReporter($log, $logger, 'backend_error', $key, $target, $e);
|
||||
return $fn();
|
||||
}
|
||||
|
||||
if (! $acquired) {
|
||||
$attempts->add(1, ['outcome' => 'contended', 'target' => $target]);
|
||||
// Don't pass a custom message — the catalog message in
|
||||
// app/config/errors.php is reused so we don't leak the internal
|
||||
// lock key (which embeds collection name and document id) into a
|
||||
// user-facing 409 response. The telemetry attribute already
|
||||
// carries the target collection for operator-side observability.
|
||||
throw new AppwriteException(AppwriteException::GENERAL_RESOURCE_LOCKED);
|
||||
}
|
||||
|
||||
$attempts->add(1, ['outcome' => 'acquired', 'target' => $target]);
|
||||
try {
|
||||
return $fn();
|
||||
} finally {
|
||||
try {
|
||||
$lock->release();
|
||||
} catch (\Throwable $e) {
|
||||
$attempts->add(1, ['outcome' => 'release_error', 'target' => $target]);
|
||||
$lockErrorReporter($log, $logger, 'release_error', $key, $target, $e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, ['redis', 'telemetry']);
|
||||
|
||||
$container->set('deviceForLocal', function (Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, new Local());
|
||||
}, ['telemetry']);
|
||||
|
||||
@@ -37,6 +37,7 @@ use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Adapter\Pool as DatabasePool;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime as DatabaseDateTime;
|
||||
@@ -48,7 +49,9 @@ use Utopia\Domains\Domain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Lock\Distributed as DistributedLock;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Storage\Device;
|
||||
@@ -73,6 +76,197 @@ return function (Container $container): void {
|
||||
return $register->get('logger');
|
||||
}, ['register']);
|
||||
|
||||
/**
|
||||
* Push lock-side errors to the configured logger (Sentry/Raygun/AppSignal/etc.)
|
||||
* by mutating the per-request `Log` object — same pattern as
|
||||
* Embeddings/Text/Create.php and the http.php request-end error handler.
|
||||
*
|
||||
* Rate limited per pod via a static bucket so a sustained backend outage
|
||||
* doesn't flood the logger. At most one push per 60s per (action, target)
|
||||
* combo. Across a fleet of N pods this caps at N events/min, well within
|
||||
* Sentry's own dedup tolerance.
|
||||
*
|
||||
* Only "real" errors are reported here:
|
||||
* - backend_error: Redis/Dragonfly unreachable
|
||||
* - release_error: lock release failed (TTL expired or backend dropped)
|
||||
*
|
||||
* Skipped on purpose: contention 409s (normal user-facing concurrency),
|
||||
* skip-on-contention events (idempotent fan-out by design), acquire retry
|
||||
* conflicts (internal loop), destructor cleanups (have an expected
|
||||
* baseline rate; aggregate via the `lock.attempts` counter instead).
|
||||
*/
|
||||
$lockErrorReporter = function (Log $log, ?Logger $logger, string $action, string $key, string $target, Throwable $e): void {
|
||||
static $lastReportAt = [];
|
||||
|
||||
Console::warning("Lock {$action} for {$key}: {$e->getMessage()}");
|
||||
|
||||
if ($logger === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bucket = $action . ':' . $target;
|
||||
$now = time();
|
||||
if (($lastReportAt[$bucket] ?? 0) + 60 > $now) {
|
||||
return;
|
||||
}
|
||||
$lastReportAt[$bucket] = $now;
|
||||
|
||||
$log->setNamespace('http');
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion(APP_VERSION_STABLE);
|
||||
$log->setType(Log::TYPE_WARNING);
|
||||
$log->setMessage('Distributed lock ' . $action . ': ' . $e->getMessage());
|
||||
$log->setAction("lock.{$action}");
|
||||
$log->setEnvironment(System::getEnv('_APP_ENV', 'development') === 'production'
|
||||
? Log::ENVIRONMENT_PRODUCTION
|
||||
: Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$log->addTag('lock.target', $target);
|
||||
// Strip the trailing document ID from the key so log aggregators don't
|
||||
// see unbounded cardinality; the {target} attribute already covers slicing.
|
||||
$log->addTag('lock.key_pattern', preg_replace('/:[^:]+$/', ':*', $key));
|
||||
$log->addTag('code', $e->getCode());
|
||||
|
||||
$log->addExtra('file', $e->getFile());
|
||||
$log->addExtra('line', $e->getLine());
|
||||
$log->addExtra('trace', $e->getTraceAsString());
|
||||
|
||||
try {
|
||||
$logger->addLog($log);
|
||||
} catch (Throwable) {
|
||||
// best-effort; don't let logging failures break fail-open
|
||||
}
|
||||
};
|
||||
|
||||
// Extract the collection segment ("keys" / "projects" / "users") from keys
|
||||
// of the form "lock:platform:{target}:{id}" so metrics can slice by target.
|
||||
$lockTargetOf = function (string $key): string {
|
||||
$parts = explode(':', $key, 4);
|
||||
return $parts[2] ?? 'unknown';
|
||||
};
|
||||
|
||||
/**
|
||||
* Distributed-lock factory: skip-on-contention variant.
|
||||
*
|
||||
* For idempotent writes where losing the race is correct (e.g., per-request
|
||||
* `accessedAt` updates from N pods all writing the same value). On contention,
|
||||
* the callback is silently skipped — another pod is doing the same work.
|
||||
*
|
||||
* Behavior:
|
||||
* - Non-blocking acquire (one attempt). On conflict, skip and return.
|
||||
* - Fail-open: if Redis is unreachable, run the callback unlocked + warn.
|
||||
* - Kill switch: `_APP_LOCKING_ENABLED=disabled` runs the callback unlocked.
|
||||
*
|
||||
* Returns void — the caller can't distinguish "acquired and ran" from "skipped".
|
||||
*
|
||||
* Metric: `lock.attempts{outcome,target}` where outcome ∈ {acquired, skipped,
|
||||
* backend_error, release_error}.
|
||||
*/
|
||||
$container->set('distributedLock', function (\Redis $redis, Telemetry $telemetry, Log $log, ?Logger $logger) use ($lockTargetOf, $lockErrorReporter) {
|
||||
$enabled = System::getEnv('_APP_LOCKING_ENABLED', 'enabled') !== 'disabled';
|
||||
$attempts = $telemetry->createCounter('lock.attempts', null, 'Distributed lock acquire outcomes');
|
||||
|
||||
if (! $enabled) {
|
||||
return function (string $key, \Closure $fn, float $ttl = 5.0): void {
|
||||
$fn();
|
||||
};
|
||||
}
|
||||
|
||||
return function (string $key, \Closure $fn, float $ttl = 5.0) use ($redis, $attempts, $log, $logger, $lockTargetOf, $lockErrorReporter): void {
|
||||
$target = $lockTargetOf($key);
|
||||
$lock = new DistributedLock($redis, $key, (int) $ttl);
|
||||
|
||||
try {
|
||||
$acquired = $lock->tryAcquire();
|
||||
} catch (\RedisException $e) {
|
||||
$attempts->add(1, ['outcome' => 'backend_error', 'target' => $target]);
|
||||
$lockErrorReporter($log, $logger, 'backend_error', $key, $target, $e);
|
||||
$fn();
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $acquired) {
|
||||
$attempts->add(1, ['outcome' => 'skipped', 'target' => $target]);
|
||||
return;
|
||||
}
|
||||
|
||||
$attempts->add(1, ['outcome' => 'acquired', 'target' => $target]);
|
||||
try {
|
||||
$fn();
|
||||
} finally {
|
||||
try {
|
||||
$lock->release();
|
||||
} catch (\Throwable $e) {
|
||||
$attempts->add(1, ['outcome' => 'release_error', 'target' => $target]);
|
||||
$lockErrorReporter($log, $logger, 'release_error', $key, $target, $e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, ['redis', 'telemetry', 'log', 'logger']);
|
||||
|
||||
/**
|
||||
* Distributed-lock factory: 409-on-contention variant.
|
||||
*
|
||||
* For explicit user-write endpoints where read-modify-write on shared mutable
|
||||
* state must NOT silently drop a request. On contention, throws
|
||||
* `Exception::GENERAL_RESOURCE_LOCKED` (HTTP 409) so the client retries.
|
||||
*
|
||||
* Behavior:
|
||||
* - Blocking acquire with short timeout (default 3s).
|
||||
* - On timeout, throws `GENERAL_RESOURCE_LOCKED`.
|
||||
* - Fail-open: backend unreachable runs the callback unlocked + warning.
|
||||
* - Kill switch: `_APP_LOCKING_ENABLED=disabled` runs the callback unlocked.
|
||||
* - Returns the callback's return value so callers can use the result.
|
||||
*
|
||||
* Metric: `lock.attempts{outcome,target}` where outcome ∈ {acquired, contended,
|
||||
* backend_error, release_error}.
|
||||
*/
|
||||
$container->set('distributedLockOrFail', function (\Redis $redis, Telemetry $telemetry, Log $log, ?Logger $logger) use ($lockTargetOf, $lockErrorReporter) {
|
||||
$enabled = System::getEnv('_APP_LOCKING_ENABLED', 'enabled') !== 'disabled';
|
||||
$attempts = $telemetry->createCounter('lock.attempts', null, 'Distributed lock acquire outcomes');
|
||||
|
||||
if (! $enabled) {
|
||||
return function (string $key, \Closure $fn, float $ttl = 10.0, float $waitTimeout = 3.0): mixed {
|
||||
return $fn();
|
||||
};
|
||||
}
|
||||
|
||||
return function (string $key, \Closure $fn, float $ttl = 10.0, float $waitTimeout = 3.0) use ($redis, $attempts, $log, $logger, $lockTargetOf, $lockErrorReporter): mixed {
|
||||
$target = $lockTargetOf($key);
|
||||
$lock = new DistributedLock($redis, $key, (int) $ttl);
|
||||
|
||||
try {
|
||||
$acquired = $lock->acquire($waitTimeout);
|
||||
} catch (\RedisException $e) {
|
||||
$attempts->add(1, ['outcome' => 'backend_error', 'target' => $target]);
|
||||
$lockErrorReporter($log, $logger, 'backend_error', $key, $target, $e);
|
||||
return $fn();
|
||||
}
|
||||
|
||||
if (! $acquired) {
|
||||
$attempts->add(1, ['outcome' => 'contended', 'target' => $target]);
|
||||
// Don't pass a custom message — the catalog message in
|
||||
// app/config/errors.php is reused so we don't leak the internal
|
||||
// lock key (which embeds collection name and document id) into a
|
||||
// user-facing 409 response. The telemetry attribute already
|
||||
// carries the target collection for operator-side observability.
|
||||
throw new Exception(Exception::GENERAL_RESOURCE_LOCKED);
|
||||
}
|
||||
|
||||
$attempts->add(1, ['outcome' => 'acquired', 'target' => $target]);
|
||||
try {
|
||||
return $fn();
|
||||
} finally {
|
||||
try {
|
||||
$lock->release();
|
||||
} catch (\Throwable $e) {
|
||||
$attempts->add(1, ['outcome' => 'release_error', 'target' => $target]);
|
||||
$lockErrorReporter($log, $logger, 'release_error', $key, $target, $e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, ['redis', 'telemetry', 'log', 'logger']);
|
||||
|
||||
$container->set('authorization', function () {
|
||||
return new Authorization();
|
||||
}, []);
|
||||
|
||||
@@ -12,8 +12,6 @@ use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -63,8 +61,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -77,8 +73,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$auth = Config::getParam('auth')[$methodId] ?? [];
|
||||
$authKey = $auth['key'] ?? '';
|
||||
@@ -92,7 +86,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents->setParam('methodId', $methodId);
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
@@ -62,8 +60,6 @@ class Create extends Action
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -76,8 +72,6 @@ class Create extends Action
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
) {
|
||||
// Set to now date
|
||||
$mockNumber = [
|
||||
@@ -109,7 +103,7 @@ class Create extends Action
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents->setParam('number', $number);
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
@@ -61,8 +59,6 @@ class Delete extends Action
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -74,8 +70,6 @@ class Delete extends Action
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
) {
|
||||
$distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $number, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -101,7 +95,7 @@ class Delete extends Action
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents->setParam('number', $number);
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
@@ -62,8 +60,6 @@ class Update extends Action
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -76,8 +72,6 @@ class Update extends Action
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
) {
|
||||
$mockNumber = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $number, $otp, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -106,7 +100,7 @@ class Update extends Action
|
||||
])));
|
||||
|
||||
return $mockNumbers[$mockNumberIndex];
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents->setParam('number', $number);
|
||||
|
||||
|
||||
+1
-7
@@ -11,8 +11,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
@@ -63,8 +61,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -80,8 +76,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $userId, $userEmail, $userPhone, $userName, $userMFA, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -107,7 +101,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
|
||||
+1
-7
@@ -11,8 +11,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
@@ -59,8 +57,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -72,8 +68,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $enabled, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -84,7 +78,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
|
||||
+1
-7
@@ -11,8 +11,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
@@ -62,8 +60,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -75,8 +71,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $total, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -87,7 +81,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
|
||||
+1
-7
@@ -11,8 +11,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
@@ -60,8 +58,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -73,8 +69,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $enabled, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -85,7 +79,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
|
||||
@@ -11,8 +11,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
@@ -59,8 +57,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -72,8 +68,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $enabled, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -84,7 +78,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
|
||||
+1
-7
@@ -11,8 +11,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
|
||||
@@ -59,8 +57,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -72,8 +68,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $duration, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -84,7 +78,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
|
||||
+1
-7
@@ -11,8 +11,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
@@ -59,8 +57,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -72,8 +68,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $enabled, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -84,7 +78,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
|
||||
@@ -11,8 +11,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
@@ -60,8 +58,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -73,8 +69,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $total, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -85,7 +79,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
|
||||
@@ -11,8 +11,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
@@ -60,8 +58,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -73,8 +69,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $total, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -85,7 +79,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'auths' => $auths,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
|
||||
@@ -12,8 +12,6 @@ use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -63,8 +61,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -77,8 +73,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $protocolId, $enabled, $dbForPlatform, $authorization) {
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId()));
|
||||
@@ -89,7 +83,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'apis' => $protocols,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents->setParam('protocolId', $protocolId);
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Emails\Validator\Email;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
@@ -75,8 +73,6 @@ class Update extends Action
|
||||
->inject('project')
|
||||
->inject('authorization')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -97,8 +93,6 @@ class Update extends Action
|
||||
Document $project,
|
||||
Authorization $authorization,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
$inputs = [
|
||||
'host' => $host,
|
||||
@@ -191,7 +185,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'smtp' => $smtp,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -63,8 +61,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -77,8 +73,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
): void {
|
||||
// The services map is a JSON object on the project document. Two
|
||||
// concurrent service toggles read the same baseline, each set their
|
||||
@@ -96,7 +90,7 @@ class Update extends Action
|
||||
return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'services' => $services,
|
||||
])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents->setParam('serviceId', $serviceId);
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Emails\Validator\Email;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
@@ -72,8 +70,6 @@ class Update extends Action
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -92,8 +88,6 @@ class Update extends Action
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
callable $distributedLockOrFail,
|
||||
Log $log,
|
||||
?Logger $logger,
|
||||
) {
|
||||
$locale = $locale ?: System::getEnv('_APP_LOCALE', 'en');
|
||||
|
||||
@@ -147,7 +141,7 @@ class Update extends Action
|
||||
])));
|
||||
|
||||
return $template;
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$queueForEvents->setParam('templateId', $templateId);
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator;
|
||||
|
||||
@@ -58,12 +56,10 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $projectId, string $teamId, Response $response, Database $dbForPlatform, callable $distributedLockOrFail, Log $log, ?Logger $logger)
|
||||
public function action(string $projectId, string $teamId, Response $response, Database $dbForPlatform, callable $distributedLockOrFail)
|
||||
{
|
||||
// Lock around the project doc RMW. Cascade fan-out to installations,
|
||||
// repositories and vcsComments runs after the lock is released —
|
||||
@@ -89,7 +85,7 @@ class Update extends Action
|
||||
]));
|
||||
|
||||
return [$project, $permissions];
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$installations = $dbForPlatform->find('installations', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
|
||||
@@ -10,8 +10,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Text;
|
||||
@@ -68,12 +66,10 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('distributedLockOrFail')
|
||||
->inject('log')
|
||||
->inject('logger')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForPlatform, callable $distributedLockOrFail, Log $log, ?Logger $logger)
|
||||
public function action(string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForPlatform, callable $distributedLockOrFail)
|
||||
{
|
||||
// Re-fetch and write the full project doc inside the lock. This is the
|
||||
// worst RMW window in the projects API — the endpoint passes the entire
|
||||
@@ -98,7 +94,7 @@ class Update extends Action
|
||||
->setAttribute('legalAddress', $legalAddress)
|
||||
->setAttribute('legalTaxId', $legalTaxId)
|
||||
->setAttribute('search', implode(' ', [$projectId, $name])));
|
||||
}, log: $log, logger: $logger);
|
||||
});
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user