Skip to content

Commit 12820aa

Browse files
committed
fix(sessions): Order database sessions deterministically
DatabaseSessionService.list_sessions previously returned rows without an ORDER BY, so callers using sessions[-1] could receive an older session depending on database heap order. Order rows by create_time with stable primary-key tie breakers so the newest created session is last. Fixes #6272
1 parent dec2182 commit 12820aa

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

src/google/adk/sessions/database_session_service.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,11 @@ async def list_sessions(
663663
)
664664
if user_id is not None:
665665
stmt = stmt.filter(schema.StorageSession.user_id == user_id)
666+
stmt = stmt.order_by(
667+
schema.StorageSession.create_time.asc(),
668+
schema.StorageSession.user_id.asc(),
669+
schema.StorageSession.id.asc(),
670+
)
666671

667672
result = await sql_session.execute(stmt)
668673
results = result.scalars().all()

tests/unittests/sessions/test_session_service.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import pytest
3636
from sqlalchemy import delete
3737
from sqlalchemy import text
38+
from sqlalchemy import update
3839
from sqlalchemy.ext.asyncio import create_async_engine
3940
from sqlalchemy.pool import StaticPool
4041

@@ -404,6 +405,51 @@ async def test_create_and_list_sessions(session_service):
404405
assert session.state == {'key': 'value' + session.id}
405406

406407

408+
@pytest.mark.asyncio
409+
async def test_database_session_service_list_sessions_orders_by_create_time_then_id():
410+
"""Database list_sessions returns oldest sessions first, with stable ties."""
411+
service = DatabaseSessionService('sqlite+aiosqlite:///:memory:')
412+
try:
413+
app_name = 'my_app'
414+
user_id = 'test_user'
415+
session_ids = ['newest', 'oldest_b', 'middle', 'oldest_a']
416+
for session_id in session_ids:
417+
await service.create_session(
418+
app_name=app_name,
419+
user_id=user_id,
420+
session_id=session_id,
421+
)
422+
423+
schema = service._get_schema_classes()
424+
create_times = {
425+
'oldest_a': datetime(2026, 1, 1, tzinfo=timezone.utc),
426+
'oldest_b': datetime(2026, 1, 1, tzinfo=timezone.utc),
427+
'middle': datetime(2026, 1, 2, tzinfo=timezone.utc),
428+
'newest': datetime(2026, 1, 3, tzinfo=timezone.utc),
429+
}
430+
async with service.database_session_factory() as sql_session:
431+
for session_id, create_time in create_times.items():
432+
await sql_session.execute(
433+
update(schema.StorageSession)
434+
.where(schema.StorageSession.app_name == app_name)
435+
.where(schema.StorageSession.user_id == user_id)
436+
.where(schema.StorageSession.id == session_id)
437+
.values(create_time=create_time)
438+
)
439+
await sql_session.commit()
440+
441+
response = await service.list_sessions(app_name=app_name, user_id=user_id)
442+
443+
assert [session.id for session in response.sessions] == [
444+
'oldest_a',
445+
'oldest_b',
446+
'middle',
447+
'newest',
448+
]
449+
finally:
450+
await service.close()
451+
452+
407453
@pytest.mark.asyncio
408454
async def test_list_sessions_all_users(session_service):
409455
app_name = 'my_app'

0 commit comments

Comments
 (0)