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

fix: make EventuallyWithT concurrency safe #1395

Merged
merged 2 commits into from Oct 13, 2023

Conversation

czeslavo
Copy link
Contributor

@czeslavo czeslavo commented Jun 2, 2023

Summary

In assert.EventuallyWithT, assert.CollectT is used in a concurrent context which may lead to races as explained in #1391.

Changes

We create a CollectT per every condition tick and send its errors over a channel. On the reader side of the channel, we cache the lastTickErrs so they can be copied over to t once the timeout is fired. The only access to lastTickErrs happens in the reader case and the timeout case which both run in the same goroutine.

Motivation

We cannot use EventuallyWithT family of functions/methods in our codebase due to data races it causes.

Related issues

Closes #1391.

@czeslavo czeslavo marked this pull request as draft June 2, 2023 21:28
@czeslavo czeslavo force-pushed the eventually-with-t-race-fix branch from 4ce8b36 to 7767496 Compare June 2, 2023 21:34
@czeslavo czeslavo marked this pull request as ready for review June 2, 2023 21:40
@dolmen dolmen added pkg-assert Change related to package testify/assert concurrency pkg-suite Change related to package testify/suite labels Jul 5, 2023
Copy link
Collaborator

@brackendawson brackendawson left a comment

Choose a reason for hiding this comment

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

I was thinking whether this can be fixed by enforcing that condition funcs can never overlap but the documentation is clear that it is checked every tick, so that would be a breaking change. The test relies on the race detector, I don't see a better way to do it.

brackendawson
brackendawson previously approved these changes Jul 20, 2023
assert/assertions.go Outdated Show resolved Hide resolved
@dolmen
Copy link
Collaborator

dolmen commented Jul 22, 2023

So while this effectively serializes calls to CollectT methods (#1418), I'm not convinced this really fixes the core issue.

This implementation doesn't fix the real problem which that we may have multiple instances of the condition function running at the same time (overlapping) but we only use a single CollectT instance to collect the results, with calls to Reset which could alter the result of conditions.

I think we will merge this (with HasErrors made private), but work on EventuallyWithT must not be considered done.

@czeslavo czeslavo force-pushed the eventually-with-t-race-fix branch 3 times, most recently from b93dd46 to 596062c Compare July 28, 2023 21:20
@czeslavo
Copy link
Contributor Author

So while this effectively serializes calls to CollectT methods (#1418), I'm not convinced this really fixes the core issue.

This implementation doesn't fix the real problem which that we may have multiple instances of the condition function running at the same time (overlapping) but we only use a single CollectT instance to collect the results, with calls to Reset which could alter the result of conditions.

I think we will merge this (with HasErrors made private), but work on EventuallyWithT must not be considered done.

@dolmen Thanks for the comment, you're right! I've taken a look into that again and came up with a solution that doesn't involve a mutex. It also doesn't allow a situation where we end up with an empty CollectT on timeout in case of timeout firing up just after collect.Reset() call. We create a CollectT per every condition tick and send its errors over a channel. On the reader side of the channel, we cache the lastTickErrs so they can be copied over to t once the timeout is fired. The only access to lastTickErrs happens in the reader case and the timeout case which both run in the same goroutine.

@czeslavo czeslavo requested a review from dolmen July 28, 2023 21:34
@czeslavo czeslavo changed the title fix: make assert.CollectT concurrency safe fix: make EventuallyWithT concurrency safe Jul 28, 2023
Copy link
Collaborator

@dolmen dolmen left a comment

Choose a reason for hiding this comment

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

This looks very good!

This is the solution I want!

Also it will allow us to get rid of CollectT which was a design error of the original implementation (a separate work for which I am studying plans since a week).

assert/assertions_test.go Outdated Show resolved Hide resolved
@dolmen
Copy link
Collaborator

dolmen commented Jul 29, 2023

As the Reset and Copy methods of CollectT are not needed anymore, and they should have been made private in the first place, please replace their implementation with a panic and add a // Deprecated: comment.

@dolmen
Copy link
Collaborator

dolmen commented Jul 29, 2023

@czeslavo Note for the future, that completely replacing your original implementation on which at least 2 other people worked too (reviewers) with a completely different one, is not a good practice. It would have been better to open a separate PR and close this one.

Anyway, in this case, the original implementation can be forgotten.

@czeslavo czeslavo requested a review from dolmen July 29, 2023 08:29
assert/assertions.go Outdated Show resolved Hide resolved
assert/assertions.go Outdated Show resolved Hide resolved
assert/assertions.go Outdated Show resolved Hide resolved
assert/assertions.go Outdated Show resolved Hide resolved
assert/assertions.go Outdated Show resolved Hide resolved
assert/assertions.go Outdated Show resolved Hide resolved
@dolmen
Copy link
Collaborator

dolmen commented Jul 29, 2023

"address review comments" is never a good commit message. It is completely useless when looking at the Git log or with "git blame". A good commit message must give a summary of what changes, and, if necessary, why. A commit that ONLY says the why is never good.

In the present case squashing the commits is the best thing to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
assert.Eventually About assert.Eventually/EventuallyWithT concurrency hacktoberfest-accepted Hacktoberfest pkg-assert Change related to package testify/assert
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Data race when using EventuallyWithT()
6 participants