Skip to content
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

sql: elide recursion in the row iterator #131155

Merged
merged 1 commit into from
Sep 24, 2024

Conversation

mw5h
Copy link
Contributor

@mw5h mw5h commented Sep 21, 2024

Previously, the row iterator would self-recurse to process partial results and would sometimes blow up the stack as a result. Attempts were made to limit the amount of recursion, but would still sometimes explode the stack. In this patch, we rewrite the function using a retry loop. Along the way, we simplify the logic to make the function more readable.

Fixes: #129963
Release note: None

Copy link

blathers-crl bot commented Sep 21, 2024

It looks like your PR touches production code but doesn't add or edit any test code. Did you consider adding tests to your PR?

🦉 Hoot! I am a Blathers, a bot for CockroachDB. My owner is dev-inf.

@cockroach-teamcity
Copy link
Member

This change is Reviewable

Copy link
Collaborator

@mgartner mgartner left a comment

Choose a reason for hiding this comment

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

:lgtm: Nice find! I find this much easier to understand. I left a few suggestions, mostly optional so let me know if you disagree with any.

Reviewed 1 of 1 files at r1, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @mw5h)


pkg/sql/internal.go line 537 at r1 (raw file):

func (r *rowsIterator) Next(ctx context.Context) (bool, error) {
	for !r.done && r.lastErr == nil {

super nit: r.done is never set inside the loop. An explicit if r.done { return false r.lastErr } block before the loop might be easier to read.


pkg/sql/internal.go line 549 at r1 (raw file):

			if err != nil || done {
				r.lastErr = err
				break

super nit: An explicit break is used here to exit the loop, but in cases below where r.lastErr is set the for condition is used to break the loop. I find the inconsistency a bit confusing. But I don't feel too strongly. I suppose that would alter the semantics a bit, since before a call to Next after iteration is done would re-invoke r.Close()... I'm hoping that was unintentional though, since it seems wrong.


pkg/sql/internal.go line 573 at r1 (raw file):

		if data.cols != nil {
			r.lastErr = errors.AssertionFailedf("unexpectedly receieved non-nil cols in Next: %v", data)

nit: receieved => received


pkg/sql/internal.go line 582 at r1 (raw file):

	r.done = true
	_ = r.Close()

nit: the previous comment for ignoring this error was helpful, so maybe add it back?

// We can ignore the returned error because Close() will update
// r.lastErr if necessary.

pkg/sql/internal.go line 585 at r1 (raw file):

	if r.errCallback != nil {
		r.lastErr = r.errCallback(r.lastErr)
		r.errCallback = nil

The previous semantics are a bit mind-bending, but I believe the logic around errCallback here is the same as before - errCallback is always called for the last iteration, regardless of the presence of an error or not, and then nilled so it is never called again. ✔️

That being said, it doesn't exactly match the comment for errCallback:

	// errCallback is an optional callback that will be called exactly once
	// before an error is returned by Next() or Close().

It might be good to update the comment to make it explicit that errCallback is always called once iteration is complete, even if the err is nil. Or perhaps better, change this to only call the callback if the err is not nil.


pkg/sql/internal.go line 585 at r1 (raw file):

			r.rowsAffected++
			// No need to make a copy because streamingCommandResult does that
			// for us.

nit: leave this comment about the copy

Copy link
Collaborator

@mgartner mgartner left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @mw5h)


pkg/sql/internal.go line 537 at r1 (raw file):
Meant to add this here:

I suppose that would alter the semantics a bit, since before a call to Next after iteration is done would re-invoke r.Close()... I'm hoping that was unintentional though, since it seems wrong.


pkg/sql/internal.go line 549 at r1 (raw file):

I suppose that would alter the semantics a bit, since before a call to Next after iteration is done would re-invoke r.Close()... I'm hoping that was unintentional though, since it seems wrong.

Ignore this I mangled up my comments. It belonged in another one.

@mgartner mgartner requested a review from a team September 23, 2024 14:32
Copy link
Contributor Author

@mw5h mw5h left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @mgartner)


pkg/sql/internal.go line 537 at r1 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

Meant to add this here:

I suppose that would alter the semantics a bit, since before a call to Next after iteration is done would re-invoke r.Close()... I'm hoping that was unintentional though, since it seems wrong.

I like this comment and implemented it, but it doesn't work because the iterator initialization code just sets r.done and doesn't actually close the iterator (or return an error). It's pretty confusing and I may refactor that next, but it's more complicated so I'd like to keep that separate. So, we do need to hit that Close(). In my brain, r.done == true should mean that we've already closed.


pkg/sql/internal.go line 585 at r1 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

The previous semantics are a bit mind-bending, but I believe the logic around errCallback here is the same as before - errCallback is always called for the last iteration, regardless of the presence of an error or not, and then nilled so it is never called again. ✔️

That being said, it doesn't exactly match the comment for errCallback:

	// errCallback is an optional callback that will be called exactly once
	// before an error is returned by Next() or Close().

It might be good to update the comment to make it explicit that errCallback is always called once iteration is complete, even if the err is nil. Or perhaps better, change this to only call the callback if the err is not nil.

I'm going with "let's make the comment correct" and we'll see what the tests tell us.

@mw5h mw5h marked this pull request as ready for review September 23, 2024 17:25
Previously, the row iterator would self-recurse to process partial
results and would sometimes blow up the stack as a result. Attempts were
made to limit the amount of recursion, but would still sometimes
explode the stack. In this patch, we rewrite the function using a retry
loop. Along the way, we simplify the logic to make the function more
readable.

Fixes: cockroachdb#129963
Release note: None
Copy link
Collaborator

@mgartner mgartner left a comment

Choose a reason for hiding this comment

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

:lgtm:

Reviewed 1 of 1 files at r3, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @mw5h)


pkg/sql/internal.go line 537 at r1 (raw file):

Previously, mw5h (Matt White) wrote…

I like this comment and implemented it, but it doesn't work because the iterator initialization code just sets r.done and doesn't actually close the iterator (or return an error). It's pretty confusing and I may refactor that next, but it's more complicated so I'd like to keep that separate. So, we do need to hit that Close(). In my brain, r.done == true should mean that we've already closed.

Ahh, I guess that happens here:

if first, r.done, r.lastErr = rw.firstResult(ctx); !r.done {

Good catch, I missed that. SGTM to leave as-is for now.


pkg/sql/internal.go line 585 at r1 (raw file):

Previously, mw5h (Matt White) wrote…

I'm going with "let's make the comment correct" and we'll see what the tests tell us.

👍

@mw5h
Copy link
Contributor Author

mw5h commented Sep 24, 2024

bors r+

@craig craig bot merged commit 368f4e7 into cockroachdb:master Sep 24, 2024
23 checks passed
Copy link
Member

@yuzefovich yuzefovich left a comment

Choose a reason for hiding this comment

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

Thanks for the nice cleanup! I also realized that this PR fixed a bug - #133734.

Reviewed 1 of 1 files at r3, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained

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.

kv/kvserver: TestTenantRateLimiter failed
4 participants