-
Notifications
You must be signed in to change notification settings - Fork 120
fix: Support multiple messages to same task after non-blocking sendMe… #331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ssage Add test for non-blocking multi-message pattern where multiple sendMessage calls to same taskId continue processing. Test uncovered EventConsumer polling loops never terminating, causing resource leaks. Fix: Close EventQueue at appropriate times to stop infinite polling: - EventConsumer.close() explicitly closes queue - InMemoryQueueManager.close() fails fast on missing queue - DefaultRequestHandler cleanup: streaming (always close) vs non-blocking (close only if task final, for multi-message support) - Refactor ResultAggregator to use AsyncUtils.consumer() consistently
Summary of ChangesHello @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 critical resource leak issues arising from infinite polling loops in the event consumption mechanism, especially when handling non-blocking messages. By introducing precise queue closure logic and refining event aggregation, it ensures that multiple messages can be sent to the same task without exhausting resources, thereby supporting a more robust 'fire-and-forget' pattern. The changes improve the stability and efficiency of task processing by preventing unmanaged event queues. Highlights
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this 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 provides a comprehensive fix for resource leaks and event loop blocking issues related to event queue consumption. The changes correctly introduce logic to close event queues at appropriate times, supporting multi-message patterns for non-blocking calls by keeping queues open for non-final tasks. The refactoring of ResultAggregator to run polling on a background thread is a critical improvement. The addition of thorough unit and integration tests to validate the new behavior is excellent.
server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java
Show resolved
Hide resolved
|
/gemini review |
There was a problem hiding this 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 resource leak issue with non-terminating EventConsumer loops by introducing explicit queue closing mechanisms. The refactoring in ResultAggregator to run consumption on a background thread is a crucial improvement to prevent blocking worker threads. The new integration tests are comprehensive and provide good coverage for the new multi-message and non-blocking scenarios.
I've identified a potential logic issue in ResultAggregator for blocking calls that could prevent them from returning as early as expected, and a minor improvement for error handling in DefaultRequestHandler. Overall, this is a solid fix for a complex problem.
server-common/src/main/java/io/a2a/server/tasks/ResultAggregator.java
Outdated
Show resolved
Hide resolved
server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java
Show resolved
Hide resolved
|
/gemini review |
There was a problem hiding this 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 important fixes for handling multiple messages to a single task in non-blocking scenarios and resolves a resource leak by ensuring event queues are closed properly. The changes are well-structured, particularly the refactoring in ResultAggregator to handle consumption in a background thread, which improves robustness. The addition of specific tests for these scenarios is also a great improvement.
I have a couple of suggestions to improve code maintainability by reducing duplication and simplifying conditional logic. Overall, this is a solid contribution that addresses a critical part of the server's behavior.
| // For non-streaming calls, only close queue if task is in final state | ||
| // For non-final states, queue must stay open for potential future messages to same taskId | ||
| // so we can handle the "fire and forget' case used e.g. in the TCK | ||
| Task task = taskStore.get(taskId); | ||
| if (task != null && task.getStatus() != null && task.getStatus().state().isFinal()) { | ||
| LOGGER.debug("Task in final state, closing queue for task {}", taskId); | ||
| closeQueue = true; | ||
| } else { | ||
| LOGGER.debug("Task not in final state, keeping queue open for task {}", taskId); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic to check if a task is in a final state is duplicated here and in the whenComplete block of registerAndExecuteAgentAsync (lines 481-483).
To improve maintainability and reduce code duplication, consider extracting this check into a private helper method.
For example:
private boolean isTaskFinal(String taskId) {
Task task = taskStore.get(taskId);
return task != null && task.getStatus() != null && task.getStatus().state().isFinal();
}You could then simplify the logic in both places.
In cleanupProducer:
// ...
} else {
// For non-streaming calls, only close queue if task is in final state
if (isTaskFinal(taskId)) {
LOGGER.debug("Task in final state, closing queue for task {}", taskId);
closeQueue = true;
} else {
LOGGER.debug("Task not in final state, keeping queue open for task {}", taskId);
}
}
// ...In registerAndExecuteAgentAsync:
// ...
} else {
// Only close queue if task is in a final state
if (isTaskFinal(taskId)) {
queue.close();
} else {
LOGGER.debug("Task {} not in final state or not yet created, keeping queue open", taskId);
}
}
// ...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is clearer the way I have it. I removed the task.getStatus() != null bit though since it isn't necessary
| } | ||
| } | ||
|
|
||
| private boolean isTaskFinalInTaskStore(String taskId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not used, should we remove it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this could be used on lines 481 and 533
| } | ||
| return existing == null ? newQueue : existing.tap(); | ||
| EventQueue result = existing == null ? newQueue : existing.tap(); | ||
| if (existing == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to check, can this be null here? Let me know if I'm missing something.
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.api.Disabled; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.Timeout; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just minor, looks like this isn't needed right?
|
Replaced by #333 @amithsourya ^^ contains this and the second half of what I was mentioning. It undos some of the changes in this PR |
…ssage
Add test for non-blocking multi-message pattern where multiple sendMessage calls to same taskId continue processing. Test uncovered EventConsumer polling loops never terminating, causing resource leaks. Fix: Close EventQueue at appropriate times to stop infinite polling: