Skip to content

DatabaseSessionService.append_event raises MissingGreenlet with asyncpg after client disconnect (regression from da73e718ef) #5761

@KrishnaVemu

Description

@KrishnaVemu

Summary

With postgresql+asyncpg:// and DatabaseSessionService, every SSE/stream
client disconnect mid-run produces:

sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called;
can't call await_only() here.

Reproduces 100% of the time when the client closes the stream while
append_event is committing.

Version

  • google-adk: 1.32.0 (verified identical code on main / 1.33 / 1.34 / 2.0)
  • SQLAlchemy: 2.x
  • asyncpg: 0.31.0
  • Python: 3.12
  • Driver recommended by ADK docs: asyncpg

Root cause

  1. database_session_service.py:208 forces pool_pre_ping=True for any
    non-SQLite URL (added in da73e71 on 2026-02-03 — this is a regression
    for asyncpg users).
  2. append_event at L740 does await sql_session.commit(), then at L743
    accesses storage_session.update_time via
    storage_session.get_update_timestamp(is_sqlite).
  3. With async engine + commit, that attribute access lazy-loads → pool
    checkout → _do_ping_w_eventawait_only() outside the greenlet
    bridge → MissingGreenlet.
  4. Normally the request completes before this triggers. A client disconnect
    raises GeneratorExit through parallel_agent._merge_agent_run, which
    surfaces the bad code path.

Repro

  • Run any agent under an SSE endpoint with DatabaseSessionService(db_url="postgresql+asyncpg://...") (no extra kwargs — defaults only).
  • Disconnect the client mid-stream (close tab / abort fetch).
  • Server log shows the MissingGreenlet stack from append_event.

Stack trace (abridged)

File ".../google/adk/sessions/database_session_service.py", line 743, in append_event
    session.last_update_time = storage_session.get_update_timestamp(is_sqlite)
File ".../google/adk/sessions/schemas/v1.py", line 131, in get_update_timestamp
    return self.update_time.timestamp()
File ".../sqlalchemy/orm/attributes.py", line 569, in __get__
    return self.impl.get(state, dict_)
...
File ".../sqlalchemy/dialects/postgresql/asyncpg.py", line 816, in ping
    _ = self.await_(self._async_ping())
File ".../sqlalchemy/util/_concurrency_py3k.py", line 123, in await_only
    raise exc.MissingGreenlet(...)
sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called;
can't call await_only() here.

Workaround (confirmed)

Pass pool_pre_ping=False — kwargs are forwarded to create_async_engine:

DatabaseSessionService(db_url=url, pool_pre_ping=False)

But pool_pre_ping is the recommended setting for production, so
disabling it is not a real fix.

Prior reports (closed stale, not fixed)

Suggested fixes

  1. Refresh update_time explicitly before commit, so the post-commit
    access doesn't lazy-load:

    await sql_session.refresh(storage_session, ["update_time"])
    await sql_session.commit()
  2. Read update_time into a local before commit (cheapest fix):

    updated_at = storage_session.update_time
    await sql_session.commit()
    session.last_update_time = updated_at.timestamp()
  3. Use expire_on_commit=False consistently and guard the lazy-load path.

Happy to send a PR if a maintainer can confirm preferred approach.

Metadata

Metadata

Labels

request clarification[Status] The maintainer need clarification or more information from the authorservices[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions