adk-go icon indicating copy to clipboard operation
adk-go copied to clipboard

fix error:stale session error: last update time from request is older…

Open huangbug opened this issue 4 weeks ago • 10 comments

Fix: Truncate event timestamp to millisecond precision to prevent stale session errors

The MySQL datetime(3) column type only supports millisecond precision (3 decimal places). When storing timestamps with nanosecond precision, MySQL rounds the value, which can cause the stored time to be slightly greater than the original in-memory time.

This rounding issue triggers false "stale session error" because the comparison in applyEvent() uses UnixNano():

if storageUpdateTime > sessionUpdateTime {
    return fmt.Errorf("stale session error: ...")
}

Solution: Truncate the event timestamp to millisecond precision before persisting, ensuring consistency between in-memory and database values:

event.Timestamp = time.UnixMilli(event.Timestamp.UnixMilli())

This eliminates sub-millisecond precision that would otherwise be lost (and potentially rounded up) during database storage.

huangbug avatar Dec 09 '25 10:12 huangbug

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

google-cla[bot] avatar Dec 09 '25 10:12 google-cla[bot]

Summary of Changes

Hello @huangbug, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where a 'stale session error' would incorrectly trigger due to how timestamps are handled. Go's time.Time can store nanosecond precision, but MySQL's datetime(3) column type only supports millisecond precision. When a nanosecond timestamp was stored, MySQL would round it, potentially making the stored value slightly greater than the original in-memory timestamp. This discrepancy led to false positives during comparison checks. The fix ensures that event timestamps are truncated to millisecond precision before being written to the database, thereby aligning the precision and preventing these erroneous stale session errors.

Highlights

  • Stale Session Error Fix: Addresses a 'stale session error' caused by a precision mismatch between Go's nanosecond timestamps and MySQL's millisecond-precision datetime(3) column type.
  • Timestamp Truncation: The solution involves explicitly truncating event timestamps to millisecond precision before persistence, ensuring consistency between in-memory values and database storage.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

[^1]: Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

gemini-code-assist[bot] avatar Dec 09 '25 10:12 gemini-code-assist[bot]

Hi @huangbug, thank you for bringing this up to our attention. Instead of truncating the saved value, we would prefer to add the respective tolerance to the "stale session" validation, since this does not affect every database? Alternatively does the MYSQL driver you're using allow for configuring datetime precision?

baptmont avatar Dec 09 '25 13:12 baptmont

CREATE TABLE `sessions` (
  `app_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `user_id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `state` longtext COLLATE utf8mb4_unicode_ci,
  `create_time` datetime(3) DEFAULT NULL,
  `update_time` datetime(3) DEFAULT NULL,
  PRIMARY KEY (`app_name`,`user_id`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
image

Hi @baptmont , from the table definition, we can see that the update_time field in the default sessions table only supports millisecond precision, and MySQL’s DATETIME type does not support nanosecond precision. Without fixing this issue, the error mentioned above will frequently occur when executing runner.run(xxx).

huangbug avatar Dec 09 '25 14:12 huangbug

What if we do this conversion to miliseconds only in the applyEvent()?

if storageUpdateTime > sessionUpdateTime {
    return fmt.Errorf("stale session error: ...")
}

This way we will support the case when db provider has no nanos support.

dpasiukevich avatar Dec 09 '25 14:12 dpasiukevich

Hi @dpasiukevich, to ensure that the update_time field in the sessions table maintains millisecond precision, the timestamp must be converted to millisecond precision before executing applyEvent(). Otherwise, inserting an event and executing applyEvent() to update update_time will result in rounding, causing a loss of precision.

For example:

UPDATE sessions 
SET update_time = '2025-12-09 16:30:28.4915' 
WHERE app_name = 'role-play-chatbot-dialogue' 
  AND user_id = 'test_1765269021' 
  AND id = 'test_1765269021';
image

After execution, the update_time in the sessions table will be updated to:

2025-12-09 16:30:28.492

As shown, the portion exceeding millisecond precision is rounded.

huangbug avatar Dec 09 '25 14:12 huangbug

Honestly, I don't like the idea to have millisecond precision. If there are 2 agents working on the same session (e.g. main and sub agent), and they generated 2 events it will have a high chance to race (when event from one agent overwrites the event from the other, because both are in the same millisecond).

@huangbug could you verify if you use e.g precision 6 for time? https://github.com/go-gorm/mysql by setting mysql.Config. DefaultDatetimePrecision?

And if it sounds ok, for database provider we can set this precision in gorm model tag for session timestamp: https://github.com/google/adk-go/blob/0d0062df20e343382169205a1bd15a48b873062a/session/database/storage_session.go#L35

image

@huangbug @baptmont WDYT?

dpasiukevich avatar Dec 10 '25 12:12 dpasiukevich

The bumping precision example: https://github.com/google/adk-go/compare/main...dpasiukevich:adk-go:db_session_precision

@huangbug could you check if this looks good?

dpasiukevich avatar Dec 10 '25 13:12 dpasiukevich

Hi @dpasiukevich, If the update_time field is defined as datetime(6), you must ensure that the value written to the sessions table uses microsecond precision rather than nanosecond precision. Otherwise, MySQL will still perform rounding during insertion, causing precision loss and leading to the same issue. Importantly, the rounding is performed by MySQL itself, not by GORM.

huangbug avatar Dec 10 '25 14:12 huangbug

Correct.

I've created a branch with the required changes: https://github.com/google/adk-go/compare/main...dpasiukevich:adk-go:db_session_precision

  1. It has gorm:"precision:6" for time.Time type for all entities. So now mysql driver should create datetime(6) with microsecond precision.
  2. There's a check in the current code which verifies if in-memory ADK object is in-sync with stored session. Which is failing right now in your case because MySQL ceiled it up. So in-memory object is treated as "outdated" because it kept the nanosecond precision in go code. I've updated this check to round both stored_session and inmemory object to be both in milliseconds to fix this.

Could I ask to PTAL the code if possible?

Thanks!

dpasiukevich avatar Dec 10 '25 14:12 dpasiukevich

Hi @dpasiukevich, I've reviewed the changes and everything looks good to me.

huangbug avatar Dec 11 '25 06:12 huangbug

@huangbug I've added https://github.com/google/adk-go/pull/414 to explicitly set microsecond level precision. Thank you for noticing this!

dpasiukevich avatar Dec 11 '25 09:12 dpasiukevich