Skip to content

fix: return task immediately when blocking=False in on_message_send#952

Open
ruimgf wants to merge 4 commits intoa2aproject:mainfrom
ruimgf:fix/non-blocking-immediate-response
Open

fix: return task immediately when blocking=False in on_message_send#952
ruimgf wants to merge 4 commits intoa2aproject:mainfrom
ruimgf:fix/non-blocking-immediate-response

Conversation

@ruimgf
Copy link
Copy Markdown

@ruimgf ruimgf commented Apr 8, 2026

Summary

  • When blocking=False, return the Task immediately without waiting for executor events
  • Process event consumption and push notifications entirely in background
  • Blocking path remains unchanged for backward compatibility

Problem

Even with blocking=False, on_message_send waits for the first event from the EventConsumer polling loop (0.5s timeout per iteration). When the event loop is busy with background work from previous requests, this causes 5-7s delays before the HTTP response is sent, leading to client timeouts.

The root cause is that consume_and_break_on_interrupt polls the EventQueue waiting for the executor to emit its first event. The executor runs via asyncio.create_task but may not be scheduled by the event loop between consumer polls, especially under load.

Solution

Add a non-blocking fast path at the top of on_message_send:

  1. Call _setup_message_execution (creates task, starts executor)
  2. Return the task in submitted state immediately
  3. Spawn a background task that consumes events and sends push notifications

This reduced HTTP response times from 5-7s to ~3ms in production.

Test plan

  • Verify blocking=True (default) behavior is unchanged
  • Verify blocking=False returns task immediately without waiting for events
  • Verify push notifications are still sent for all task state transitions
  • Verify task artifacts are properly delivered via push notifications
  • Test with concurrent requests to confirm no event loop contention

Fixes #951

🤖 Generated with Claude Code

When `blocking=False` is set in MessageSendConfiguration, the handler
now returns the Task object immediately without waiting for executor
events. Event consumption and push notifications are processed entirely
in the background.

Previously, even with `blocking=False`, the handler waited for the first
event from the EventConsumer polling loop (0.5s timeout per iteration).
When the event loop was busy with background work, this caused 5-7s
delays before the HTTP response was sent, leading to client timeouts.

The new non-blocking fast path:
1. Creates the task and starts the executor (via _setup_message_execution)
2. Returns the task in 'submitted' state immediately
3. Consumes events and sends push notifications in a background task

The blocking path remains unchanged for backward compatibility.

Fixes a2aproject#951

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ruimgf ruimgf requested a review from a team as a code owner April 8, 2026 21:38
Copy link
Copy Markdown
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 introduces a non-blocking execution path for message handling, allowing tasks to be returned immediately while processing continues in the background. A critical issue was identified where the task state is not persisted to the store before returning to the client, which may cause race conditions during subsequent task lookups.

ruimgf and others added 2 commits April 8, 2026 22:49
- Extract non-blocking logic to _on_message_send_non_blocking to fix
  PLR0915 (too many statements) lint error
- Update test_on_message_send_with_push_notification_in_non_blocking_request
  to test the new immediate-return behavior instead of mocking
  consume_and_break_on_interrupt
- Update test_on_message_send_non_blocking history assertion to account
  for the immediate return having only the initial user message

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move background consume logic to _consume_and_notify_in_background
  method to reduce argument count and keep non-blocking path inline
  in on_message_send
- Fix test assertion: use push_store.get_info() instead of
  assert_awaited_once_with since InMemoryPushNotificationConfigStore
  is not a mock

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

🧪 Code Coverage (vs main)

⬇️ Download Full Report

Base PR Delta
src/a2a/server/request_handlers/default_request_handler.py 97.34% 95.83% 🔴 -1.51%
src/a2a/server/tasks/result_aggregator.py 100.00% 97.14% 🔴 -2.86%
Total 91.72% 91.62% 🔴 -0.10%

Generated by coverage-comment.yml

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.

on_message_send blocks HTTP response even with blocking=False

1 participant