1
0
Fork 0
mirror of synced 2024-07-01 04:30:59 +12:00

Async resolution fixes

This commit is contained in:
Jake Barnby 2022-04-26 19:49:36 +12:00
parent 5b1f973b45
commit dbb49ac7bf
No known key found for this signature in database
GPG key ID: A4674EBC0E404657
8 changed files with 172 additions and 158 deletions

View file

@ -491,5 +491,10 @@ return [
'name' => Exception::DOMAIN_VERIFICATION_FAILED,
'description' => 'Domain verification for the requested domain has failed.',
'code' => 401,
]
],
Exception::GRAPHQL_NO_QUERY => [
'name' => Exception::GRAPHQL_NO_QUERY,
'description' => 'Query is required and can be provided via parameter or as the raw body if the content-type header is application/graphql.',
'code' => 400,
],
];

View file

@ -1,6 +1,6 @@
<?php
use Appwrite\GraphQL\Builder;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use GraphQL\Error\DebugFlag;
use GraphQL\GraphQL;
@ -56,8 +56,6 @@ App::post('/v1/graphql')
->param('variables', [], new JSON(), 'Variables to use in the operation', true)
->inject('request')
->inject('response')
->inject('utopia')
->inject('dbForProject')
->inject('promiseAdapter')
->inject('gqlSchema')
->action(Closure::fromCallable('graphqlRequest'));
@ -71,8 +69,6 @@ function graphqlRequest(
$variables,
$request,
$response,
$utopia,
$dbForProject,
$promiseAdapter,
$gqlSchema
)
@ -89,24 +85,23 @@ function graphqlRequest(
$query = $request->getSwoole()->rawContent();
}
if (empty($query)) {
throw new Exception('Query is empty', Response::STATUS_CODE_BAD_REQUEST);
throw new Exception('No query supplied.', 400, Exception::GRAPHQL_NO_QUERY);
}
$debugFlags = App::isDevelopment()
? DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE | DebugFlag::RETHROW_INTERNAL_EXCEPTIONS
: DebugFlag::NONE;
$validations = array_merge(
GraphQL::getStandardValidationRules(),
[
new QueryComplexity(App::getEnv('_APP_GRAPHQL_MAX_QUERY_COMPLEXITY', 200)),
new QueryDepth(App::getEnv('_APP_GRAPHQL_MAX_QUERY_DEPTH', 3)),
]
);
// Roughly equivalent to 200 REST requests of work per GraphQL request
$maxComplexity = App::getEnv('_APP_GRAPHQL_MAX_QUERY_COMPLEXITY', 200);
if (App::isProduction()) {
// Maximum nested query depth. Limited to 3 as we don't have more than 3 levels of data relationships
$maxDepth = App::getEnv('_APP_GRAPHQL_MAX_QUERY_DEPTH', 3);
$validations = GraphQL::getStandardValidationRules();
$validations[] = new QueryComplexity($maxComplexity);
$validations[] = new QueryDepth($maxDepth);
$validations[] = new DisableIntrospection();
}
$promise = GraphQL::promiseToExecute(
$promiseAdapter,
@ -117,10 +112,11 @@ function graphqlRequest(
validationRules: $validations
);
// Blocking wait while queries resolve asynchronously
// Blocking wait while queries resolve
$wg = new WaitGroup();
$wg->add();
$promise->then(function ($result) use ($response, $debugFlags, $wg) {
$promise->then(
function ($result) use ($response, $debugFlags, $wg) {
$result = $result->toArray($debugFlags);
\var_dump("Result:" . $result);
if (isset($result['errors'])) {
@ -134,6 +130,7 @@ function graphqlRequest(
function ($error) use ($response, $wg) {
$response->text(\json_encode(['errors' => [\json_encode($error)]]));
$wg->done();
});
}
);
$wg->wait();
}

View file

@ -865,7 +865,7 @@ App::setResource('promiseAdapter', function ($register) {
return $register->get('promiseAdapter');
}, ['register']);
App::setResource('gqlSchema', function ($utopia, $request, $response, $register, $dbForProject) {
return Builder::buildSchema($utopia, $request, $response, $register, $dbForProject);
}, ['utopia', 'request', 'response', 'register', 'dbForProject']);
App::setResource('gqlSchema', function ($utopia, $request, $response, $register, $dbForProject, $user) {
return Builder::buildSchema($utopia, $request, $response, $register, $dbForProject, $user);
}, ['utopia', 'request', 'response', 'register', 'dbForProject', 'user']);

View file

@ -29,6 +29,7 @@ class Exception extends \Exception
* - Keys
* - Platform
* - Domain
* - GraphQL
*/
/** General */
@ -160,6 +161,8 @@ class Exception extends \Exception
const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
/** GraphqQL */
const GRAPHQL_NO_QUERY = 'graphql_no_query';
private $type = '';

View file

@ -6,11 +6,13 @@ use Appwrite\GraphQL\Types\JsonType;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response\Model\User;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use Swoole\Coroutine\WaitGroup;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Database;
@ -20,6 +22,8 @@ use Utopia\Registry\Registry;
use Utopia\Route;
use Utopia\Validator;
use function \Co\go;
class Builder
{
protected static ?JsonType $jsonParser = null;
@ -268,11 +272,12 @@ class Builder
Request $request,
Response $response,
Registry &$register,
Database $dbForProject
Database $dbForProject,
Document $user,
): Schema
{
$apiSchema = self::buildAPISchema($utopia, $request, $response, $register);
$db = self::buildCollectionsSchema($utopia, $request, $response, $dbForProject);
$db = self::buildCollectionsSchema($utopia, $request, $response, $dbForProject, $user);
$queryFields = \array_merge_recursive($apiSchema['query'], $db['query']);
$mutationFields = \array_merge_recursive($apiSchema['mutation'], $db['mutation']);
@ -307,23 +312,27 @@ class Builder
App $utopia,
Request $request,
Response $response,
Database $dbForProject
Database $dbForProject,
?Document $user = null,
): array
{
$start = microtime(true);
$userId = $user?->getId();
$collections = [];
$queryFields = [];
$mutationFields = [];
$limit = 1000 * swoole_cpu_num();
$limit = 1000;
$offset = 0;
$wg = new WaitGroup();
while (!empty($attrs = Authorization::skip(fn() => $dbForProject->find(
'attributes',
limit: $limit,
offset: $offset
)))) {
//go(function() use ($utopia, $request, $response, $dbForProject, &$collections, &$queryFields, &$mutationFields, $limit, &$offset, $attrs) {
$wg->add();
go(function () use ($utopia, $request, $response, $dbForProject, &$collections, &$queryFields, &$mutationFields, $limit, &$offset, $attrs, $userId, $wg) {
foreach ($attrs as $attr) {
$collectionId = $attr->getAttribute('collectionId');
if ($attr->getAttribute('status') !== 'available') {
@ -377,12 +386,14 @@ class Builder
$attributes['read'] = [
'type' => Type::listOf(Type::string()),
'defaultValue' => ["user:$userId"],
'resolve' => function ($object, $args, $context, $info) use ($collectionId) {
return $object->getAttribute('$read');
}
];
$attributes['write'] = [
'type' => Type::listOf(Type::string()),
'defaultValue' => ["user:$userId"],
'resolve' => function ($object, $args, $context, $info) use ($collectionId) {
return $object->getAttribute('$write');
}
@ -414,11 +425,11 @@ class Builder
'resolve' => self::mutateDelete($collectionId, $dbForProject)
];
}
//});
$wg->done();
});
$offset += $limit;
}
$wg->wait();
$time_elapsed_secs = (microtime(true) - $start) * 1000;
Console::info("[INFO] Built GraphQL Project Collection Schema in ${time_elapsed_secs}ms");
@ -455,6 +466,7 @@ class Builder
$utopia
->setRoute($route)
->execute($route, $request);
$resolve($response->getPayload());
} catch (\Throwable $e) {
$reject($e);

View file

@ -25,9 +25,6 @@ class CoroutinePromise
if (\is_null($executor)) {
return;
}
if (!\extension_loaded('swoole')) {
throw new \RuntimeException('Swoole ext missing!');
}
$resolve = function ($value) {
$this->setResult($value);
$this->setState(self::STATE_FULFILLED);
@ -141,11 +138,9 @@ class CoroutinePromise
foreach ($promises as $promise) {
if (!$promise instanceof CoroutinePromise) {
$channel->close();
throw new \RuntimeException(
'Supported only Appwrite\GraphQL\SwoolePromise instance'
);
throw new \RuntimeException('Not an Appwrite\GraphQL\CoroutinePromise');
}
$promise->then(function ($value) use ($key, $result, $channel) {
$promise->then(function ($value) use ($key, &$result, $channel) {
$result[$key] = $value;
$channel->push(true);
return $value;
@ -166,6 +161,7 @@ class CoroutinePromise
$reject($firstError);
return;
}
$resolve($result);
});
}

View file

@ -68,27 +68,22 @@ class CoroutinePromiseAdapter implements PromiseAdapter
$result = [];
foreach ($promisesOrValues as $index => $promiseOrValue) {
go(function ($index, $promiseOrValue, $all, $total, &$count, $result) {
if (!($promiseOrValue instanceof CoroutinePromise)) {
if (!($promiseOrValue instanceof Promise)) {
$result[$index] = $promiseOrValue;
$count++;
return;
break;
}
$result[$index] = null;
$promiseOrValue->then(
function ($value) use ($index, &$count, $total, &$result, $all): void {
$result[$index] = $value;
if ($count++ === $total) {
$count++;
if ($count === $total) {
$all->resolve($result);
}
},
[$all, 'reject']
);
}, $index, $promiseOrValue, $all, $total, $count, $result);
}
if ($count === $total) {
$all->resolve($result);
}
return new Promise($all, $this);

View file

@ -3,6 +3,7 @@
namespace Appwrite\Utopia;
use Exception;
use Swoole\Http\Request as SwooleRequest;
use Utopia\Swoole\Response as SwooleResponse;
use Swoole\Http\Response as SwooleHTTPResponse;
use Utopia\Database\Document;
@ -307,6 +308,11 @@ class Response extends SwooleResponse
parent::__construct($response);
}
public function getSwoole(): SwooleHTTPResponse
{
return $this->swoole;
}
/**
* HTTP content types
*/