-
Notifications
You must be signed in to change notification settings - Fork 207
Attempt to fix some race conditions in local subincludes #3354
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
Attempt to fix some race conditions in local subincludes #3354
Conversation
I think it should be possible to do this without the hack. The sequence of events should be:
The problem is that step 14 hangs. We have some logic to sync subincludes to avoid duplicate work. The subinclude for "defs_1" is held by the preload thread by the time parse thread 1 gets there. I'm not sure how to work around this. Maybe we can just pay the price of having multiple things parse subincludes until it's written to the map:
I think maybe the existing solution here is acceptable too though. |
src/parse/asp/builtins.go
Outdated
// sleep can be used to induce race conditions in please | ||
func sleep(s *scope, _ []pyObject) pyObject { | ||
time.Sleep(100 * time.Millisecond) | ||
return None | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can probably remove this. It's useful to debug these kind of things though.
@@ -71,6 +71,10 @@ func (m *Map[K, V]) Get(key K) V { | |||
return v | |||
} | |||
|
|||
func (m *Map[K, V]) Contains(key K) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As noted irl, this is a potential performance hit as we'll do this for every build target, but for now it's a price we're willing to pay. We'll keep an eye on the performance test
src/core/state.go
Outdated
@@ -893,11 +893,13 @@ func (state *BuildState) WaitForPackage(l, dependent BuildLabel, mode ParseMode) | |||
} | |||
|
|||
func (state *BuildState) WaitForBuiltTarget(l, dependent BuildLabel, mode ParseMode) *BuildTarget { | |||
if t := state.Graph.Target(l); t != nil { | |||
t := state.Graph.Target(l) | |||
if t != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I reading this right that the behaviour hasn't actually changed here? You don't appear to be referring to t
later in the function, so this could be:
if t := state.Graph.Target(l); t != nil && t.State().IsBuilt {
return t
}
Could also just change back to what it was before to avoid the no-op change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops yeah I changed this to debug log some stuff.
@@ -0,0 +1,2 @@ | |||
[Parse] | |||
PreloadSubincludes = //a:defs_2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitty mcnitface: plz add trailing newline
Overview
This is a followup to #3305
Please has a race condition where it will erroneously report that a subinclude must appear after the target it is subincluding. This happens during preloaded subincludes, because the base package is nil, but we use the context package (i.e. the package the subinclude target was defined in) to check if a subinclude is local. This means that the pre-load is considered a local subinclude, which seems erroneous.
This was introduced with this PR that attempted to resolve a subinclude lockup:
#3305
The lockup would happen under the following conditions:
:def_2
subincludes:def_1
:def_1
:def_2
, reaching thesubinclude(":def_1")
from there before registering:def_1
to the build graph.The main BUILD file would have registered
:def_1
to the build graph in an inactive state. Normally we would queue:def_1
up again insubincludeTarget()
(link), and it would be activated in the parse step (link). This doesn't happen because we have a map where we sync parses of subincludesb (link). This means the target would never be activated, and nothing can make progress.Race condition breakdown
The sequence of events before this PR are:
Go-routine 1:
Preload(":def_2")
:def_2
(triggers Go-routine to parse//a:all
for:defs_2
)def_2.build_defs
into the preload scopedefs_2.build_defs
, evalsubinclude(":defs_1")
:defs_1
hasn't been added to the build graph yet (due to the sleep we added to induce this race), and this is considered a local subincludeGo-routine 2:
//a:all
for:defs_2
:defs_2
, which is built successfullysleep(100)
:defs_1
With the fix to
isLocal
, we get the following change to go-routine 1:5) Wait for built target (defs_1) because it's not local anymore
6) Add pending target and wait for build as the package already exists
7) Wait forever because the target was never activated
To fix this, when adding a target to the build graph, we check if the target is a pending target. If it is, we activate it. This means that at step 4, we activate
:defs_1
, and it gets built successfully unblocking the build. This is a bit grungy as we loose the original target when activating the target.