question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[BUG] Support for ASGI startup events

See original GitHub issue

Investigative information

Please provide the following:
  • Timestamp: 2021-11-10T10:58:00
  • Function App name: (debugging locally)
  • Function name(s) (as appropriate):(debugging locally)
  • Core Tools version: 3.0.3785 Commit hash: db6fe71b2f05d09757179d5618a07bba4b28826f (64-bit)

Repro steps

Provide the steps required to reproduce the problem:
  1. Setup a new Function App, with an HTTP trigger and the following code in func/__init__.py:
import logging

import azure.functions as func
from azure.functions import AsgiMiddleware
from api_fastapi import app

IS_INITED = False


def run_setup(app, loop):
    """Workaround to run Starlette startup events on Azure Function Workers."""
    global IS_INITED
    if not IS_INITED:
        loop.run_until_complete(app.router.startup())
        IS_INITED = True


def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    asgi_middleware = AsgiMiddleware(app)
    run_setup(app, asgi_middleware._loop)
    return asgi_middleware.handle(req, context)

  1. After the 1st request, you will get the following exception:
[2021-11-10T09:58:11.178Z] Executed 'Functions.api_az_function' (Failed, Id=50cbe047-48bd-42be-9314-0194c2e17ec9, Duration=65ms)
[2021-11-10T09:58:11.181Z] System.Private.CoreLib: Exception while executing function: Functions.api_az_function. System.Private.CoreLib: Result: Failure
Exception: RuntimeError: Task <Task pending name='Task-39' coro=<AsgiResponse.from_app() running at C:\Program Files\Microsoft\Azure Functions Core Tools\workers\python\3.8/WINDOWS/X64\azure\functions\_http_asgi.py:65> cb=[_run_until_complete_cb() at C:\Users\User\AppData\Local\Programs\Python\Python38\lib\asyncio\base_events.py:184]> got Future <Future pending> attached to a different loop

Expected behavior

Provide a description of the expected behavior.
  • The Function should be able to fulfil any number of requests.

Actual behavior

Provide a description of the actual behavior observed.
  • Because the AsgiMiddleware is instantiated for each call, the event loop is not reused and thus we get the exception.

Known workarounds

Provide a description of any known workarounds.
  • Have a global AsgiMiddleware object as shown here:
import logging

import azure.functions as func
from azure.functions import AsgiMiddleware
from api_fastapi import app

IS_INITED = False
ASGI_MIDDLEWARE = AsgiMiddleware(app)


def run_setup(app, loop):
   """Workaround to run Starlette startup events on Azure Function Workers."""
   global IS_INITED
   if not IS_INITED:
       loop.run_until_complete(app.router.startup())
       IS_INITED = True


def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
   run_setup(app, ASGI_MIDDLEWARE._loop)
   return ASGI_MIDDLEWARE.handle(req, context)

Contents of the requirements.txt file:

Provide the requirements.txt file to help us find out module related issues.
azure-functions==1.7.2
fastapi==0.70.0

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:1
  • Comments:6

github_iconTop GitHub Comments

3reactions
rbudnarcommented, Jun 28, 2022

I’ve run across a different issue with the AsgiMiddleware and arrived at basically the same solution as posted above. I noticed that simple async functions do not behave as I would expect an async application should. For example, I tested the following:

from __app__.app import app
import azure.functions as func
import asyncio
import nest_asyncio
nest_asyncio.apply() # as suggested in docs

@app.get("/api/test")
async def test(code: Optional[str] = None):
    await asyncio.sleep(5)
    return "OK"

async def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    return await AsgiMiddleware(app).handle(req, context)

If you hit this endpoint above with a few concurrent requests, you’ll notice that they execute serially instead of concurrently (I assume because the AsgiMiddleware blocks with a call to run_until_complete here). E.g., if I hit this endpoint twice in rapid succession, the second request will take ~10 seconds to complete instead of ~5 seconds.

Using a plain azure function without the middleware works as expected (e.g., the above scenario completes in ~5 seconds with two concurrent requests):

async def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    await asyncio.sleep(5)
    return "OK"

As mentioned, the posted solution resolves this, but I’m concerned about the implications of this as mentioned above. It would be great to see a robust solution for this in the near future if possible.

1reaction
thomasfrederikhoeckcommented, Oct 25, 2022

@rbudnar This blocking problem is fixed now here https://github.com/Azure/azure-functions-python-library/pull/143 which also removes the need for nest_asyncio. But it doesn’t solve this initial issue here.

Read more comments on GitHub >

github_iconTop Results From Across the Web

asgi-lifespan - PyPI
asgi -lifespan provides a LifespanManager to programmatically send ASGI lifespan events into an ASGI app. This can be used to programmatically startup/shutdown ...
Read more >
falcon.asgi.app — Falcon 3.1.1 documentation
The interface provides support for handling both ASGI worker lifespan events and per-request events. However, because lifespan events are an optional part ...
Read more >
tiangolo/fastapi - Gitter
on_event("startup") , it is reported as INFO: ASGI 'lifespan' protocol appears unsupported. with no more information. Is that a FastAPI bug or a...
Read more >
Django Channels, ASGI, and Heroku | by Matt Basta - Medium
The first thing to do was get the service up and running. ... Daphne is a HTTP/websocket protocol server for ASGI, maintained by...
Read more >
Release Notes - Starlette
Add WebSocketException and support for WebSocket exception handlers #1263. Add middleware parameter to ... Add reason to WebSocket close ASGI event #1417.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found