Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Poor interaction with threads (python 2.7) #43

Open
melancholy opened this issue May 17, 2018 · 3 comments
Open

Poor interaction with threads (python 2.7) #43

melancholy opened this issue May 17, 2018 · 3 comments

Comments

@melancholy
Copy link

melancholy commented May 17, 2018

the interaction between this library, graphql-core, and promise appears to allow execution to hop threads especially (only?) when using a middleware and nested resolvers. this is inconsistent with flasks threading model and the ability to access the request/g thread locals

with the attached (very contrived) example, when submitting the query with concurrent requests the requests frequently fail because the key created in get_context doesn't exist on the threadlocal flask.g object in the resolvers. This happens when a thread accesses the promise.async_instance which isn't thread local, and resolves a promise that was created on a different thread.
query.txt

from flask import Flask
from flask_graphql import GraphQLView


app = Flask(__name__)

import graphene
import threading
import time
from flask import g

def get_user(info):
    return g.get(info.context['key']) 

class User(graphene.ObjectType):
    id = graphene.ID()
    name = graphene.String()
    friend = graphene.Field(lambda: User)
    age = graphene.Int()
    apple = graphene.String()

    @classmethod
    def resolve_friend(cls, root, info):
        time.sleep(.1)
        x = get_user(info)
        return User(id=id(root), name=':'.join([x.name, threading.current_thread().name]))
    
    @classmethod
    def resolve_age(cls, root, info):
        time.sleep(.1)
	return 5

    @classmethod
    def resolve_apple(cls, root, info):
        time.sleep(.1)
        return "Apple"

class Query(graphene.ObjectType):
    me = graphene.Field(User)

    def resolve_me(self, info):
        time.sleep(.1)
        return get_user(info)

schema = graphene.Schema(query=Query)

ahh = {}
def dummy_middleware(next, root, info, **args):
    return_value = next(root, info, **args)
    return return_value

import random
random.seed()

class TestQLView(GraphQLView):
    def get_context(self, request):
	# set a random key in g to be used by resolvers
        key = str(random.randint(0,50))
	name = threading.current_thread().name
        user = User(id=key, name=name)
        setattr(g, key, user)
	return {
	    'key': key
        }

app.add_url_rule('/graphql', view_func=TestQLView.as_view('graphql', schema=schema, graphiql=True, middleware=[dummy_middleware]))
@leebenson
Copy link

I tested this with 2.7.15 and 3.7 - here are my query results:

2.7.15:

screen shot 2018-07-22 at 07 20 25

3.7:

screen shot 2018-07-22 at 07 21 46

Are you seeing something different, @melancholy ?

@melancholy
Copy link
Author

Yes. With concurrent requests I would frequently see errors in the logs. This only occurs when threading is on and requests are concurrent, so a single screenshot of a result won't reflect a proper step for reproduction.

I also ended up tracking this down and working around it. It turns out the promise library isn't thread safe. I mentioned it in an issue for the promise library and described my workarounds.

here's the issue: syrusakbary/promise#57

@jimzer
Copy link

jimzer commented May 8, 2020

Any news on how to access context in resolvers with the ThreadExecutor ?
I keep getting "Working outside of request context" when accessing "info.context" in resolver when I use the ThreadExecutor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants