From 074d52a9fb5092d9a4a01f2d381e0d7095d94e73 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 12 Jan 2024 13:47:16 +0000 Subject: [PATCH] optimize resolve routine * The `resolve` routine was an async recursive function. * Because this function gets called for every value, the number of async tasks can become extremely large for large or deeply nested GraphQL responses. * Python functions have overheads, async tasks have additional overheads, this created a severe performance bottleneck. * Simple solution, make the function iterative rather than recursive. --- graphql_ws/base_async.py | 41 +++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/graphql_ws/base_async.py b/graphql_ws/base_async.py index a21ca5e..b1b681d 100644 --- a/graphql_ws/base_async.py +++ b/graphql_ws/base_async.py @@ -41,25 +41,28 @@ async def resolve( Recursively wait on any awaitable children of a data element and resolve any Promises. """ - if is_awaitable(data): - data = await data - if isinstance(data, Promise): - data = data.value # type: Any - if _container is not None: - _container[_key] = data - if isinstance(data, dict): - items = data.items() - elif isinstance(data, list): - items = enumerate(data) - else: - items = None - if items is not None: - children = [ - asyncio.ensure_future(resolve(child, _container=data, _key=key)) - for key, child in items - ] - if children: - await asyncio.wait(children) + stack = [(data, _container, _key)] + + while stack: + data, _container, _key = stack.pop() + + if is_awaitable(data): + data = await data + if isinstance(data, Promise): + data = data.value # type: Any + if _container is not None: + _container[_key] = data + if isinstance(data, dict): + items = data.items() + elif isinstance(data, list): + items = enumerate(data) + else: + items = None + if items is not None: + stack.extend([ + (child, data, key) + for key, child in items + ]) class BaseAsyncConnectionContext(base.BaseConnectionContext, ABC):