Skip to content

chore: get rid of possible recursion when unwinding structured reply #5012

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

Merged
merged 1 commit into from
Apr 30, 2025

Conversation

romange
Copy link
Collaborator

@romange romange commented Apr 27, 2025

No description provided.

@romange romange requested a review from vyavdoshenko April 27, 2025 17:18
auto& last = stack_.top();
last.first->arr.push_back(std::move(val));
if (collapse_if_needed && last.second-- == 1) {
CollapseFilledCollections();
Copy link
Contributor

Choose a reason for hiding this comment

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

Logically, we still have conditional recursion.
It appears more dangerous because the unconditional collapsing collections must process all elements.
Now it should skip part of them. Correct?

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure I understand this. Imagine a stack of size 2. We enter the body here and call CollapseFilledCollection() which pops the top of stack_ and calls Capture(). Now Capture() calls again CollapseFilledCollection which pops the top of the stack_ and calls Capture() again. The sequence we get is Capture -> Collapse -> Capture -> Collapse. This recursion can blow out the stack of the fiber. By passing false to Capture() within CollapseFilledCollections we enforce that we drain the stack_ within the body of CollapseFilledCollections(). So now we would get something like: Capture -> CollapseFilledCollections iteration 1 -> iteration 2 -> done.

}

void CapturingReplyBuilder::CollapseFilledCollections() {
while (!stack_.empty() && stack_.top().second == 0) {
auto pl = std::move(stack_.top());
stack_.pop();
Capture(std::move(pl.first));
Capture(std::move(pl.first), false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it mean that we always have a 1-level nested collection?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, it can have several levels. My changes should not change functionality. Are you saying they do?

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess it changes the behavior, because Capture(std::move(pl.first), false); call never causes call CollapseFilledCollections(); inside Capture(). But CollapseFilledCollections(); was called unconditionally before.

Copy link
Contributor

Choose a reason for hiding this comment

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

@vyavdoshenko CollapseFilledCollections body is executed only when !stack_empty(). The function applies only for the branch if (!stack_.empty()) { of Capture() above.

@romange
Copy link
Collaborator Author

romange commented Apr 27, 2025 via email

auto& last = stack_.top();
last.first->arr.push_back(std::move(val));
if (collapse_if_needed && last.second-- == 1) {
CollapseFilledCollections();
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure I understand this. Imagine a stack of size 2. We enter the body here and call CollapseFilledCollection() which pops the top of stack_ and calls Capture(). Now Capture() calls again CollapseFilledCollection which pops the top of the stack_ and calls Capture() again. The sequence we get is Capture -> Collapse -> Capture -> Collapse. This recursion can blow out the stack of the fiber. By passing false to Capture() within CollapseFilledCollections we enforce that we drain the stack_ within the body of CollapseFilledCollections(). So now we would get something like: Capture -> CollapseFilledCollections iteration 1 -> iteration 2 -> done.

}

void CapturingReplyBuilder::CollapseFilledCollections() {
while (!stack_.empty() && stack_.top().second == 0) {
auto pl = std::move(stack_.top());
stack_.pop();
Capture(std::move(pl.first));
Capture(std::move(pl.first), false);
Copy link
Contributor

Choose a reason for hiding this comment

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

@vyavdoshenko CollapseFilledCollections body is executed only when !stack_empty(). The function applies only for the branch if (!stack_.empty()) { of Capture() above.

stack_.top().second--;
auto& last = stack_.top();
last.first->arr.push_back(std::move(val));
if (collapse_if_needed && last.second-- == 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

post increment and 1 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the bug is here

@romange romange requested a review from vyavdoshenko April 30, 2025 05:13
@romange romange merged commit befb36d into main Apr 30, 2025
10 checks passed
@romange romange deleted the Pr6 branch April 30, 2025 15:17
romange added a commit that referenced this pull request May 14, 2025
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.

3 participants