mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Execute promise callbacks synchronously for WaitGroup compat
The GraphQL controller uses Swoole WaitGroup to wait for promise callbacks. Queue-based deferred execution causes deadlock because runQueue() is never called. This change executes callbacks immediately when then() is called on settled promises, allowing the WaitGroup pattern to work correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
6ae070c3f1
commit
ca30269486
@@ -9,7 +9,8 @@ use GraphQL\Executor\Promise\Promise as GQLPromise;
|
||||
class Swoole extends Adapter
|
||||
{
|
||||
/**
|
||||
* Synchronously wait for promise completion by running the task queue.
|
||||
* Wait for promise completion and return the result.
|
||||
* Since callbacks are executed synchronously, the promise should already be settled.
|
||||
*
|
||||
* @param GQLPromise $promise
|
||||
* @return mixed
|
||||
@@ -19,14 +20,6 @@ class Swoole extends Adapter
|
||||
{
|
||||
/** @var SwoolePromise $swoolePromise */
|
||||
$swoolePromise = $promise->adoptedPromise;
|
||||
$taskQueue = SwoolePromise::getQueue();
|
||||
|
||||
while (
|
||||
$swoolePromise->state === SwoolePromise::PENDING
|
||||
&& !$taskQueue->isEmpty()
|
||||
) {
|
||||
SwoolePromise::runQueue();
|
||||
}
|
||||
|
||||
if ($swoolePromise->state === SwoolePromise::FULFILLED) {
|
||||
return $swoolePromise->result;
|
||||
@@ -36,7 +29,8 @@ class Swoole extends Adapter
|
||||
throw $swoolePromise->result;
|
||||
}
|
||||
|
||||
throw new \Exception('Could not resolve promise');
|
||||
// Promise should already be settled in synchronous mode
|
||||
throw new \Exception('Could not resolve promise - still pending');
|
||||
}
|
||||
|
||||
public function create(callable $resolver): GQLPromise
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace Appwrite\Promises;
|
||||
|
||||
/**
|
||||
* Swoole-compatible promise implementation that uses a task queue
|
||||
* for deferred callback execution, similar to graphql-php's SyncPromise.
|
||||
* Swoole-compatible promise implementation that executes callbacks synchronously.
|
||||
* This works with Swoole's WaitGroup pattern used in the GraphQL controller.
|
||||
*/
|
||||
class Swoole extends Promise
|
||||
{
|
||||
@@ -22,46 +22,21 @@ class Swoole extends Promise
|
||||
*/
|
||||
protected array $waiting = [];
|
||||
|
||||
/**
|
||||
* Run all tasks in the queue
|
||||
*/
|
||||
public static function runQueue(): void
|
||||
{
|
||||
$q = self::getQueue();
|
||||
while (!$q->isEmpty()) {
|
||||
$task = $q->dequeue();
|
||||
$task();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shared task queue
|
||||
*
|
||||
* @return \SplQueue<callable(): void>
|
||||
*/
|
||||
public static function getQueue(): \SplQueue
|
||||
{
|
||||
static $queue;
|
||||
|
||||
return $queue ??= new \SplQueue();
|
||||
}
|
||||
|
||||
public function __construct(?callable $executor = null)
|
||||
{
|
||||
if ($executor === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::getQueue()->enqueue(function () use ($executor): void {
|
||||
try {
|
||||
$executor(
|
||||
fn ($value) => $this->resolve($value),
|
||||
fn ($reason) => $this->reject($reason)
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
$this->reject($e);
|
||||
}
|
||||
});
|
||||
// Execute the executor synchronously
|
||||
try {
|
||||
$executor(
|
||||
fn ($value) => $this->resolve($value),
|
||||
fn ($reason) => $this->reject($reason)
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
$this->reject($e);
|
||||
}
|
||||
}
|
||||
|
||||
protected function execute(
|
||||
@@ -69,7 +44,7 @@ class Swoole extends Promise
|
||||
callable $resolve,
|
||||
callable $reject
|
||||
): void {
|
||||
// Not used - we use the task queue mechanism instead
|
||||
// Not used - we execute synchronously in constructor
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,7 +67,7 @@ class Swoole extends Promise
|
||||
|
||||
$this->state = self::FULFILLED;
|
||||
$this->result = $value;
|
||||
$this->enqueueWaitingPromises();
|
||||
$this->processWaiting();
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -108,36 +83,34 @@ class Swoole extends Promise
|
||||
|
||||
$this->state = self::REJECTED;
|
||||
$this->result = $reason;
|
||||
$this->enqueueWaitingPromises();
|
||||
$this->processWaiting();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue callbacks for waiting promises
|
||||
* Process waiting callbacks immediately (synchronously)
|
||||
*/
|
||||
protected function enqueueWaitingPromises(): void
|
||||
protected function processWaiting(): void
|
||||
{
|
||||
foreach ($this->waiting as [$promise, $onFulfilled, $onRejected]) {
|
||||
self::getQueue()->enqueue(function () use ($promise, $onFulfilled, $onRejected): void {
|
||||
if ($this->state === self::FULFILLED) {
|
||||
try {
|
||||
$promise->resolve($onFulfilled === null ? $this->result : $onFulfilled($this->result));
|
||||
} catch (\Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
} elseif ($this->state === self::REJECTED) {
|
||||
try {
|
||||
if ($onRejected === null) {
|
||||
$promise->reject($this->result);
|
||||
} else {
|
||||
$promise->resolve($onRejected($this->result));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
if ($this->state === self::FULFILLED) {
|
||||
try {
|
||||
$promise->resolve($onFulfilled === null ? $this->result : $onFulfilled($this->result));
|
||||
} catch (\Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
});
|
||||
} elseif ($this->state === self::REJECTED) {
|
||||
try {
|
||||
if ($onRejected === null) {
|
||||
$promise->reject($this->result);
|
||||
} else {
|
||||
$promise->resolve($onRejected($this->result));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->waiting = [];
|
||||
@@ -156,10 +129,29 @@ class Swoole extends Promise
|
||||
}
|
||||
|
||||
$promise = new self();
|
||||
$this->waiting[] = [$promise, $onFulfilled, $onRejected];
|
||||
|
||||
if ($this->state !== self::PENDING) {
|
||||
$this->enqueueWaitingPromises();
|
||||
if ($this->state === self::PENDING) {
|
||||
// Promise not settled yet - queue the callbacks
|
||||
$this->waiting[] = [$promise, $onFulfilled, $onRejected];
|
||||
} else {
|
||||
// Promise already settled - execute callback immediately
|
||||
if ($this->state === self::FULFILLED) {
|
||||
try {
|
||||
$promise->resolve($onFulfilled === null ? $this->result : $onFulfilled($this->result));
|
||||
} catch (\Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if ($onRejected === null) {
|
||||
$promise->reject($this->result);
|
||||
} else {
|
||||
$promise->resolve($onRejected($this->result));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $promise;
|
||||
@@ -201,9 +193,10 @@ class Swoole extends Promise
|
||||
|
||||
$count = 0;
|
||||
$result = [];
|
||||
$rejected = false;
|
||||
|
||||
$resolveAllWhenFinished = static function () use (&$count, $total, $all, &$result): void {
|
||||
if ($count === $total) {
|
||||
$resolveAllWhenFinished = static function () use (&$count, $total, $all, &$result, &$rejected): void {
|
||||
if (!$rejected && $count === $total) {
|
||||
$all->resolve($result);
|
||||
}
|
||||
};
|
||||
@@ -217,7 +210,12 @@ class Swoole extends Promise
|
||||
++$count;
|
||||
$resolveAllWhenFinished();
|
||||
},
|
||||
[$all, 'reject']
|
||||
static function ($error) use (&$rejected, $all) {
|
||||
if (!$rejected) {
|
||||
$rejected = true;
|
||||
$all->reject($error);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$result[$index] = $promiseOrValue;
|
||||
@@ -229,4 +227,18 @@ class Swoole extends Promise
|
||||
|
||||
return $all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static queue methods for graphql-php compatibility (not used in sync mode)
|
||||
*/
|
||||
public static function runQueue(): void
|
||||
{
|
||||
// No-op in synchronous mode
|
||||
}
|
||||
|
||||
public static function getQueue(): \SplQueue
|
||||
{
|
||||
static $queue;
|
||||
return $queue ??= new \SplQueue();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user