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.

Persistence of state from lifespan events to requests

See original GitHub issue

ASGI lifespan events are designed to handle setup and subsequent teardown of resource like a database connection, but does not provide any persistence to store this state to requests.

Starlette implements an Application.state namespace (docs) which modifies the application object in place. Quart suggest to store data in the app’s namespace directly. This is not ideal because it gives an otherwise stateless thing state, and there is also no correlation between this state and the event loop / ASGI state, which can lead to inconsistent state. Here’s an artificial but not absurd example of how this could lead to a confusing user experience:

import asyncio
from contextlib import asynccontextmanager
from dataclasses import dataclass
from typing import AsyncIterator

from starlette.applications import Starlette
from starlette.testclient import TestClient
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route


@dataclass
class Connection:
    loop: asyncio.AbstractEventLoop

    async def run(self) -> None:
        if asyncio.get_event_loop() is not self.loop:
            raise Exception("Some obtuse error")


@asynccontextmanager
async def lifespan(app: Starlette) -> AsyncIterator[None]:
    app.state.db = Connection(asyncio.get_event_loop())
    yield


async def endpoint(request: Request) -> Response:
    await request.app.state.db.run()
    return Response()


app = Starlette(
    routes=[Route("/", endpoint)],
    lifespan=lifespan,
)


def some_test_using_lifespans() -> None:
    with TestClient(app):
        pass


def some_test_where_the_user_forgets_to_use_the_lifespan() -> None:
    TestClient(app).get("/")


if __name__ == "__main__":
    some_test_using_lifespans()
    some_test_where_the_user_forgets_to_use_the_lifespan()

Here an honest mistake ends up leaking state between tests, and maybe giving the user an obtuse error about event loops and such.

I think it would be beneficial if the spec provided a namespace scoped to each ASGI lifespan / request. This namespace would basically be an empty dict that gets persisted from the lifespan event into each request. I think it would make sense to model this like contextvar propagation into tasks: every request gets a copy of the namespace from the lifespan (unless there was no lifespan, in which it’s a brand new dict or maybe None). Then state can be naturally stored in this namespace. Since the ASGI server manages this namespace (and it already manages it’s own state because of lifespans), the application can be stateless and neither the framework nor the users have to worry about clearing state at the end of a lifespan or anything like.

This can easily be backwards compatible: it’s a new key in the scope and we bump the minor version of the ASGI version so that frameworks can check if this is supported or not.

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:12 (12 by maintainers)

github_iconTop GitHub Comments

1reaction
andrewgodwincommented, Dec 16, 2022

I’ve looked at it already once, I just haven’t been in the right headspace to sit down and think about it as a spec - might take a few weeks or so!

0reactions
adriangbcommented, Dec 15, 2022

@andrewgodwin would you mind taking a look at #354? Thanks!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Skill Attributes | Alexa Skills Kit - Amazon Developer
Session attributes persist throughout the lifespan of the current skill session. Session attributes are available for use with any in-session request.
Read more >
Chapter 4. Dealing with Persistence, State, and Clients - O'Reilly
A command may result in state changes that are persisted as events, representing the effect of the command. The current state is not...
Read more >
Durable Objects - Workers - Cloudflare Docs
A Durable Object may be evicted from memory any time, causing a loss of all transient (in-memory) state. To persistently store state your ......
Read more >
Session Basics — SQLAlchemy 2.0 Documentation
The Session begins in a mostly stateless form. Once queries are issued or other objects are persisted with it, it requests a connection...
Read more >
The activity lifecycle | Android Developers
Persistent work. Overview. Getting started. Getting started · Define your work requests. How to. Work states · Manage work ...
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