Skip to content

Conversation

@kabir
Copy link
Collaborator

@kabir kabir commented Oct 9, 2025

…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

…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
@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 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

  • Resource Leak Fix: Addressed and fixed an issue where EventConsumer polling loops would never terminate, leading to resource leaks, particularly in non-blocking multi-message scenarios.
  • Multi-Message Support: Enabled proper handling of multiple non-blocking sendMessage calls to the same task ID, ensuring continuous processing without resource exhaustion.
  • Queue Management Enhancements: Implemented explicit queue closing mechanisms in EventConsumer and InMemoryQueueManager, and refined cleanup logic in DefaultRequestHandler to close queues appropriately based on streaming vs. non-blocking calls and task finality.
  • ResultAggregator Refactoring: Refactored ResultAggregator to consistently use AsyncUtils.consumer() and manage event consumption and interruption more robustly, including running subscriptions on background threads to prevent blocking.
  • New Test Cases: Introduced new test cases (testBlockingMessageContinueTask and testNonBlockingWithMultipleMessages) to validate the multi-message pattern and ensure correct queue behavior and task lifecycle management.
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.

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 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.

@kabir
Copy link
Collaborator Author

kabir commented Oct 9, 2025

/gemini review

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 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.

@kabir
Copy link
Collaborator Author

kabir commented Oct 9, 2025

/gemini review

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 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.

Comment on lines 530 to 539
// 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);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

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);
    }
}
// ...

Copy link
Collaborator Author

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) {
Copy link
Collaborator

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 ?

Copy link
Collaborator

@fjuma fjuma Oct 10, 2025

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) {
Copy link
Collaborator

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;
Copy link
Collaborator

@fjuma fjuma Oct 10, 2025

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?

@kabir
Copy link
Collaborator Author

kabir commented Oct 10, 2025

Replaced by #333

@amithsourya ^^ contains this and the second half of what I was mentioning. It undos some of the changes in this PR

@amithsourya
Copy link
Contributor

@kabir It took me a while to wrap my head around everything that's happening in #333 & #331, but I can really appreciate the elegance of the solution. This is exactly the kind of sophisticated code I hope to write someday 😃

@kabir kabir closed this Oct 13, 2025
@kabir kabir deleted the event-queues-take2 branch November 3, 2025 13:02
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.

4 participants