Skip to content

Conversation

@kabir
Copy link
Collaborator

@kabir kabir commented Nov 3, 2025

…cking calls

Fixes race condition where DefaultRequestHandler.onMessageSend() returns before all task events are fully processed and persisted to TaskStore, resulting in incomplete Task objects being returned to clients (missing artifacts, incorrect state).

Root Cause:

  • Blocking calls interrupted immediately after first event and returned to client before background event consumption completed
  • Agent execution and event processing happened asynchronously in background
  • No synchronization to ensure all events were consumed and persisted before returning Task to client

Solution (4-step process):

  1. Wait for agent to finish enqueueing events (configurable timeout)
  2. Close the queue to signal consumption can complete (breaks dependency)
  3. Wait for consumption to finish processing events (configurable timeout)
  4. Fetch final task state from TaskStore (has all artifacts and correct state)

This ensures blocking calls return complete Task objects with all artifacts and correct state, including support for fire-and-forget tasks that never emit final state events.

Added unit tests:

  • testBlockingFireAndForgetReturnsNonFinalTask: Validates fire-and-forget pattern
  • testBlockingCallReturnsCompleteTaskWithArtifacts: Ensures all artifacts included

Fixes #428 🦕

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @kabir, I'm Gemini Code Assist1! 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 a critical race condition affecting blocking calls, ensuring that Task objects returned to clients are always complete and accurate. By introducing explicit synchronization steps, the system now reliably waits for all agent events to be processed and persisted before concluding a blocking operation, thereby preventing incomplete task states and missing artifacts. This enhancement improves the robustness and reliability of task handling, especially for scenarios involving asynchronous agent execution and 'fire-and-forget' patterns.

Highlights

  • Race Condition Fix: Addressed a race condition in DefaultRequestHandler.onMessageSend() where blocking calls would return incomplete Task objects due to asynchronous event processing. Previously, onMessageSend() would return after the first event, before all task events were fully processed and persisted to the TaskStore.
  • Synchronized Event Processing for Blocking Calls: Implemented a 4-step synchronization process for blocking calls: 1) Wait for the agent to finish enqueueing events (5s timeout), 2) Close the event queue to signal consumption completion, 3) Wait for event consumption to finish (2s timeout), and 4) Fetch the final Task state from the TaskStore. This ensures that returned Task objects are complete with all artifacts and the correct state.
  • Support for Fire-and-Forget Tasks: The fix now correctly handles 'fire-and-forget' tasks, where an agent might return without emitting a final state event. The new logic ensures that even in such cases, the blocking call waits for agent execution and event processing to complete, returning a complete Task object reflecting its current state and artifacts.
  • New Unit Tests: Added two new unit tests: testBlockingFireAndForgetReturnsNonFinalTask to validate the fire-and-forget pattern with blocking calls, and testBlockingCallReturnsCompleteTaskWithArtifacts to ensure all artifacts are included in the returned Task for blocking calls.
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 👍 and 👎 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.

Footnotes

  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.

@kabir
Copy link
Collaborator Author

kabir commented Nov 3, 2025

@flex-myeonghyeon I moved the work here

Note that for some reason, GitHub isn't showing the code I have locally, so if you want to check you might have to pull down the branch. This was my IDE not behaving. It should be the same now :-)

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses a critical race condition in blocking calls by introducing a synchronization mechanism to ensure all events are processed before returning a response. The solution is well-structured and the accompanying comments clearly explain the logic. The new unit tests are comprehensive and correctly validate both the fix for blocking calls and the fire-and-forget scenario. I've identified a few minor areas for improvement, mainly related to code style and clarity, such as replacing magic numbers with constants and simplifying some expressions.

@kabir
Copy link
Collaborator Author

kabir commented Nov 3, 2025

Replaces #430

@kabir kabir force-pushed the blocking-send-fixes branch from b79aee8 to c97d236 Compare November 3, 2025 18:47

private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRequestHandler.class);
private static final int AGENT_COMPLETION_TIMEOUT_SECONDS = 5;
private static final int CONSUMPTION_COMPLETION_TIMEOUT_SECONDS = 2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases, a longer wait time might be necessary.
Would it be possible to make this configurable — for example, by passing it as a parameter or injecting it through the DefaultRequestHandler constructor?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@flex-myeonghyeon Good idea, I've added some MP Config properties

@kabir kabir force-pushed the blocking-send-fixes branch from f487663 to 6e35990 Compare November 4, 2025 11:07
@flex-myeonghyeon
Copy link
Contributor

@kabir
Looks good to me!
Once the snapshot version is deployed and available, I’ll test it in my development environment.

@kabir kabir force-pushed the blocking-send-fixes branch from 6e35990 to 413b7ce Compare November 4, 2025 13:36
…cking calls

Fixes race condition where DefaultRequestHandler.onMessageSend() returns
before all task events are fully processed and persisted to TaskStore,
resulting in incomplete Task objects being returned to clients (missing
artifacts, incorrect state).

Root Cause:
- Blocking calls interrupted immediately after first event and returned
  to client before background event consumption completed
- Agent execution and event processing happened asynchronously in background
- No synchronization to ensure all events were consumed and persisted
  before returning Task to client

Solution (4-step process):
1. Wait for agent to finish enqueueing events (5s timeout)
2. Close the queue to signal consumption can complete (breaks dependency)
3. Wait for consumption to finish processing events (2s timeout)
4. Fetch final task state from TaskStore (has all artifacts and correct state)

This ensures blocking calls return complete Task objects with all artifacts
and correct state, including support for fire-and-forget tasks that never
emit final state events.

Added unit tests:
- testBlockingFireAndForgetReturnsNonFinalTask: Validates fire-and-forget pattern
- testBlockingCallReturnsCompleteTaskWithArtifacts: Ensures all artifacts included
@kabir kabir force-pushed the blocking-send-fixes branch from 413b7ce to ece9af8 Compare November 4, 2025 14:19
@kabir kabir force-pushed the blocking-send-fixes branch 2 times, most recently from 2e48d12 to e2498c7 Compare November 4, 2025 14:38
@kabir kabir merged commit 465a5e4 into a2aproject:main Nov 4, 2025
10 checks passed
kabir added a commit that referenced this pull request Nov 4, 2025
#441)

…cking calls

Fixes race condition where DefaultRequestHandler.onMessageSend() returns
before all task events are fully processed and persisted to TaskStore,
resulting in incomplete Task objects being returned to clients (missing
artifacts, incorrect state).

Root Cause:
- Blocking calls interrupted immediately after first event and returned
to client before background event consumption completed
- Agent execution and event processing happened asynchronously in
background
- No synchronization to ensure all events were consumed and persisted
before returning Task to client

Solution (4-step process):
1. Wait for agent to finish enqueueing events (5s timeout)
2. Close the queue to signal consumption can complete (breaks
dependency)
3. Wait for consumption to finish processing events (2s timeout)
4. Fetch final task state from TaskStore (has all artifacts and correct
state)

This ensures blocking calls return complete Task objects with all
artifacts and correct state, including support for fire-and-forget tasks
that never emit final state events.

Added unit tests:
- testBlockingFireAndForgetReturnsNonFinalTask: Validates
fire-and-forget pattern
- testBlockingCallReturnsCompleteTaskWithArtifacts: Ensures all
artifacts included


Upstream: #431
kabir added a commit to kabir/a2a-java that referenced this pull request Dec 23, 2025
a2aproject#431)

…cking calls

Fixes race condition where DefaultRequestHandler.onMessageSend() returns
before all task events are fully processed and persisted to TaskStore,
resulting in incomplete Task objects being returned to clients (missing
artifacts, incorrect state).

Root Cause:
- Blocking calls interrupted immediately after first event and returned
to client before background event consumption completed
- Agent execution and event processing happened asynchronously in
background
- No synchronization to ensure all events were consumed and persisted
before returning Task to client

Solution (4-step process):
1. Wait for agent to finish enqueueing events (configurable timeout)
2. Close the queue to signal consumption can complete (breaks
dependency)
3. Wait for consumption to finish processing events (configurable
timeout)
4. Fetch final task state from TaskStore (has all artifacts and correct
state)

This ensures blocking calls return complete Task objects with all
artifacts and correct state, including support for fire-and-forget tasks
that never emit final state events.

Added unit tests:
- testBlockingFireAndForgetReturnsNonFinalTask: Validates
fire-and-forget pattern
- testBlockingCallReturnsCompleteTaskWithArtifacts: Ensures all
artifacts included

Fixes a2aproject#428 🦕
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: DefaultRequestHandler does not block until final state when blocking=true

3 participants