Fix batch promises with coroutine-based synchronization

- Use Swoole Coroutine::sleep for proper coroutine yielding instead of
  callback-based completion tracking
- Spawn a coroutine for each promise to wait for its completion
- Use Channel for synchronization between coroutines
- Add public accessors to Promise class for state checking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jake Barnby
2026-01-21 20:10:07 +13:00
co-authored by Claude Opus 4.5
parent 4d6a3d36f7
commit 2544baa669
2 changed files with 49 additions and 28 deletions
@@ -5,6 +5,7 @@ namespace Appwrite\GraphQL\Promises\Adapter;
use Appwrite\GraphQL\Promises\Adapter;
use Appwrite\Promises\Swoole as SwoolePromise;
use GraphQL\Executor\Promise\Promise as GQLPromise;
use Swoole\Coroutine\Channel;
class Swoole extends Adapter
{
@@ -46,40 +47,50 @@ class Swoole extends Adapter
return;
}
$result = \array_fill(0, $count, null);
$pending = $count;
$rejected = false;
$result = [];
$error = null;
$channel = new Channel($count);
foreach ($promisesOrValues as $index => $promiseOrValue) {
if ($promiseOrValue instanceof GQLPromise) {
$promiseOrValue->then(
function ($value) use ($index, &$result, &$pending, &$rejected, $resolve) {
if ($rejected) {
return;
}
$result[$index] = $value;
$pending--;
if ($pending === 0) {
\ksort($result);
$resolve($result);
}
},
function ($error) use (&$rejected, $reject) {
if (!$rejected) {
$rejected = true;
$reject($error);
// Spawn a coroutine to wait for each promise
\go(function () use ($promiseOrValue, $index, &$result, &$error, $channel) {
/** @var SwoolePromise $adoptedPromise */
$adoptedPromise = $promiseOrValue->adoptedPromise;
// Wait for the promise to resolve using a polling approach
while ($adoptedPromise->isPending()) {
\Swoole\Coroutine::sleep(0.001);
}
if ($adoptedPromise->isFulfilled()) {
$result[$index] = $adoptedPromise->getResult();
} else {
if ($error === null) {
$error = $adoptedPromise->getResult();
}
}
);
$channel->push(true);
});
} else {
$result[$index] = $promiseOrValue;
$pending--;
if ($pending === 0 && !$rejected) {
\ksort($result);
$resolve($result);
}
$channel->push(true);
}
}
// Wait for all promises to complete
for ($i = 0; $i < $count; $i++) {
$channel->pop();
}
$channel->close();
if ($error !== null) {
$reject($error);
return;
}
\ksort($result);
$resolve($result);
});
}
}
+13 -3
View File
@@ -167,7 +167,7 @@ abstract class Promise
*
* @return boolean
*/
protected function isPending(): bool
public function isPending(): bool
{
return $this->state == self::STATE_PENDING;
}
@@ -177,7 +177,7 @@ abstract class Promise
*
* @return boolean
*/
protected function isFulfilled(): bool
public function isFulfilled(): bool
{
return $this->state == self::STATE_FULFILLED;
}
@@ -187,8 +187,18 @@ abstract class Promise
*
* @return boolean
*/
protected function isRejected(): bool
public function isRejected(): bool
{
return $this->state == self::STATE_REJECTED;
}
/**
* Get the result value (only valid after promise is settled)
*
* @return mixed
*/
public function getResult(): mixed
{
return $this->result;
}
}