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

Pykka blocking fastapi to server other clients #99

Closed
bobfang1992 opened this issue Oct 21, 2020 · 5 comments
Closed

Pykka blocking fastapi to server other clients #99

bobfang1992 opened this issue Oct 21, 2020 · 5 comments

Comments

@bobfang1992
Copy link

Hi, I have an app that resembles the snippet code like follows:

from fastapi import FastAPI
import time

import pykka


class AnActor(pykka.ThreadingActor):
    field = 'this is the value of AnActor.field'

    def func(self):
        time.sleep(0.5)  # Block a bit to make it realistic
        return 'this was returned by AnActor.func() after a delay'

app = FastAPI()
actor = AnActor.start().proxy()

@app.get("/")
async def do_things():
    result = await actor.func()
    return {"message": result}

To my surprise that it seems that at the actor.func call the request handler blocks, instead of yielding the control back to fastapi to handle requests. I thought the await statement should achieve that and only return when the func function is done with its own business. Am I misunderstanding pykka's design goal here? Is there any way to achieve what I want to do?

@jodal
Copy link
Owner

jodal commented Oct 21, 2020

Even though Pykka's futures can be awaited using the await keyword, there is no support in Pykka for asyncio.

Pykka predates asyncio with several years and I have never spent the effort required to figure out how Pykka and asyncio should work together, if at all.

@jodal
Copy link
Owner

jodal commented Oct 22, 2020

That said, it is possible to have Pykka and asyncio cooperate, but then you must take care to do all blocking calls to Pykka in a threadpool, e.g. through asyncio's loop.run_in_executor().

I think the following updated example should work:

import asyncio
import time

import pykka
from fastapi import FastAPI

class AnActor(pykka.ThreadingActor):
    field = 'this is the value of AnActor.field'

    def func(self):
        time.sleep(0.5)  # Block a bit to make it realistic
        return 'this was returned by AnActor.func() after a delay'

app = FastAPI()
actor = AnActor.start().proxy()

@app.get("/")
async def do_things_long_version():
    loop = asyncio.get_running_loop()
    # This does not block, it just sends a message with the arguments to the actor:
    pykka_future = actor.func()
    # This does not block the asyncio loop, as the blocking call to `.get()` is done in an executor:
    asyncio_future = loop.run_in_executor(None, pykka_future.get)
    # Finally, yield to other tasks until the asyncio future has a result:
    result = await asyncio_future
    return {"message": result}

@app.get("/short")
async def do_things_short_version():
    return {"message": await get(actor.func())}

async def get(pykka_future):
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(None, pykka_future.get)

@bobfang1992
Copy link
Author

Hi I have tried it and apparently this is a working fix. I am just wondering is there anything to stop you to wrap this in a way that so await pkka_future will do this automatically? Thanks!

@jodal
Copy link
Owner

jodal commented Oct 22, 2020

It would be possible, but that'd be breaking the API for existing users. I want to defer a change like that until a complete and thought through story for Pykka+asyncio exists.

@bobfang1992
Copy link
Author

Cool give me a shout when you want to do this. I would love to review code for you or do anything that can contribute. I like Pykka as it is small, concise and to the point and really helped me for my work.

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

2 participants