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 race between startup and shutdown in task reader #5522

Merged
merged 2 commits into from
Dec 21, 2023
Merged

Conversation

Groxx
Copy link
Contributor

@Groxx Groxx commented Dec 21, 2023

Nothing ensures all goroutines are started before shutdown, and in tests that's somewhat likely to occur.
Risk to production is probably very low, but definitely best to fix there too.

As a general statement for any readers:

func (s *self) thing() {
  s.wg.Add(1)
  defer s.wg.Done()
  ...
}

// elsewhere
go s.thing()

^ this pattern is almost always unsafe, at least on a technical level.

All adds need to occur before any Wait() can possibly finish, so they should always be added in the "parent" thread, never in the goroutine that things will wait on.

Nothing ensures all goroutines are started before shutdown, and in tests that's somewhat likely to occur.
Risk to production is probably very low, but definitely best to fix there too.

As a general statement for any readers:
```go
func (s *self) thing() {
  s.wg.Add(1)
  defer s.wg.Done()
  ...
}

// elsewhere
go s.thing()
```
^ this pattern is *almost always* unsafe, at least on a technical level.

All adds need to occur before any Wait() can possibly finish, so they should **always** be added in the "parent" thread, never in the goroutine that things will wait on.
Comment on lines +131 to +135
tr.stopWg.Add(1)
go func() {
defer tr.stopWg.Done()
tr.dispatchBufferedTasks(g)
}()
Copy link
Contributor Author

@Groxx Groxx Dec 21, 2023

Choose a reason for hiding this comment

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

there are other ways to achieve this, but tbh I'm fond of "always make concurrency controls visible where it is made concurrent". it simplifies the method doing things, and makes "you added go, did you take care of X?" questions trivial.

alternatively, a backgroundDispatch(g) that does this internally (and you do not go backgroundDispatch(g)) works pretty well. that also makes you relatively immune to mutating range vars.

Copy link
Contributor

@3vilhamster 3vilhamster left a comment

Choose a reason for hiding this comment

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

Looks weird, but should be good.

@Groxx
Copy link
Contributor Author

Groxx commented Dec 21, 2023

Looks weird, ...

yea. tbh this is my biggest complaint with Go concurrency: it's "intrusive". you essentially must always put concurrency controls in by hand, because the whole system prevents you from building both robust and performant abstractions around it like Java has had for many years. Even just stuff like "there is no thread.Join" causes a LOT of error-prone needs like this.

Generics can largely address that now, but there's a massive pile of history that will take a long time to upgrade / will influence habits for probably the next decade :\

@Groxx Groxx merged commit e610bbb into uber:master Dec 21, 2023
16 checks passed
@Groxx Groxx deleted the fixrace branch December 21, 2023 19:12
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.

None yet

2 participants