Merge pull request #113 from colincross/parallel

Add concurrency to BottomUpMutators and cloneModules
This commit is contained in:
colincross 2016-08-11 11:18:15 -07:00 committed by GitHub
commit 056963fe71
3 changed files with 194 additions and 136 deletions

View file

@ -70,7 +70,7 @@ type Context struct {
modulesSorted []*moduleInfo
singletonInfo []*singletonInfo
mutatorInfo []*mutatorInfo
earlyMutatorInfo []*earlyMutatorInfo
earlyMutatorInfo []*mutatorInfo
variantMutatorNames []string
moduleNinjaNames map[string]*moduleGroup
@ -224,12 +224,7 @@ type mutatorInfo struct {
topDownMutator TopDownMutator
bottomUpMutator BottomUpMutator
name string
}
type earlyMutatorInfo struct {
// set during RegisterEarlyMutator
mutator EarlyMutator
name string
parallel bool
}
func (e *Error) Error() string {
@ -399,19 +394,35 @@ func (c *Context) RegisterTopDownMutator(name string, mutator TopDownMutator) {
//
// The mutator type names given here must be unique to all bottom up or early
// mutators in the Context.
func (c *Context) RegisterBottomUpMutator(name string, mutator BottomUpMutator) {
//
// Returns a BottomUpMutatorHandle, on which Parallel can be called to set
// the mutator to visit modules in parallel while maintaining ordering.
func (c *Context) RegisterBottomUpMutator(name string, mutator BottomUpMutator) BottomUpMutatorHandle {
for _, m := range c.variantMutatorNames {
if m == name {
panic(fmt.Errorf("mutator name %s is already registered", name))
}
}
c.mutatorInfo = append(c.mutatorInfo, &mutatorInfo{
info := &mutatorInfo{
bottomUpMutator: mutator,
name: name,
})
}
c.mutatorInfo = append(c.mutatorInfo, info)
c.variantMutatorNames = append(c.variantMutatorNames, name)
return info
}
type BottomUpMutatorHandle interface {
// Set the mutator to visit modules in parallel while maintaining ordering
Parallel() BottomUpMutatorHandle
}
func (mutator *mutatorInfo) Parallel() BottomUpMutatorHandle {
mutator.parallel = true
return mutator
}
// RegisterEarlyMutator registers a mutator that will be invoked to split
@ -436,8 +447,10 @@ func (c *Context) RegisterEarlyMutator(name string, mutator EarlyMutator) {
}
}
c.earlyMutatorInfo = append(c.earlyMutatorInfo, &earlyMutatorInfo{
mutator: mutator,
c.earlyMutatorInfo = append(c.earlyMutatorInfo, &mutatorInfo{
bottomUpMutator: func(mctx BottomUpMutatorContext) {
mutator(mctx)
},
name: name,
})
@ -995,11 +1008,6 @@ func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
newModules = append(newModules, newModule)
// Insert the new variant into the global module map. If this is the first variant then
// it reuses logicModule from the original module, which causes this to replace the
// original module in the global module map.
c.moduleInfo[newModule.logicModule] = newModule
newErrs := c.convertDepsToVariation(newModule, mutatorName, variationName)
if len(newErrs) > 0 {
errs = append(errs, newErrs...)
@ -1139,7 +1147,12 @@ func (c *Context) addModule(module *moduleInfo) []error {
// the modules depended upon are defined and that no circular dependencies
// exist.
func (c *Context) ResolveDependencies(config interface{}) []error {
errs := c.runMutators(config)
errs := c.updateDependencies()
if len(errs) > 0 {
return errs
}
errs = c.runMutators(config)
if len(errs) > 0 {
return errs
}
@ -1351,6 +1364,16 @@ func (c *Context) addInterVariantDependency(origModule *moduleInfo, tag Dependen
fromInfo.directDeps = append(fromInfo.directDeps, depInfo{toInfo, tag})
}
func (c *Context) visitAllBottomUp(visit func(group *moduleInfo) bool) {
for _, module := range c.modulesSorted {
if visit(module) {
return
}
}
}
// Calls visit on each module, guaranteeing that visit is not called on a module until visit on all
// of its dependencies has finished.
func (c *Context) parallelVisitAllBottomUp(visit func(group *moduleInfo) bool) {
doneCh := make(chan *moduleInfo)
count := 0
@ -1571,69 +1594,17 @@ func (c *Context) PrepareBuildActions(config interface{}) (deps []string, errs [
return deps, nil
}
func (c *Context) runEarlyMutators(config interface{}) (errs []error) {
for _, mutator := range c.earlyMutatorInfo {
for _, group := range c.moduleGroups {
newModules := make([]*moduleInfo, 0, len(group.modules))
for _, module := range group.modules {
mctx := &mutatorContext{
baseModuleContext: baseModuleContext{
context: c,
config: config,
module: module,
},
name: mutator.name,
}
func() {
defer func() {
if r := recover(); r != nil {
in := fmt.Sprintf("early mutator %q for %s", mutator.name, module)
if err, ok := r.(panicError); ok {
err.addIn(in)
mctx.error(err)
} else {
mctx.error(newPanicErrorf(r, in))
}
}
}()
mutator.mutator(mctx)
}()
if len(mctx.errs) > 0 {
errs = append(errs, mctx.errs...)
return errs
}
if module.splitModules != nil {
newModules = append(newModules, module.splitModules...)
} else {
newModules = append(newModules, module)
}
}
group.modules = newModules
}
}
errs = c.updateDependencies()
if len(errs) > 0 {
return errs
}
return nil
}
func (c *Context) runMutators(config interface{}) (errs []error) {
errs = c.runEarlyMutators(config)
if len(errs) > 0 {
return errs
}
var mutators []*mutatorInfo
for _, mutator := range c.mutatorInfo {
mutators = append(mutators, c.earlyMutatorInfo...)
mutators = append(mutators, c.mutatorInfo...)
for _, mutator := range mutators {
if mutator.topDownMutator != nil {
errs = c.runTopDownMutator(config, mutator.name, mutator.topDownMutator)
errs = c.runTopDownMutator(config, mutator)
} else if mutator.bottomUpMutator != nil {
errs = c.runBottomUpMutator(config, mutator.name, mutator.bottomUpMutator)
errs = c.runBottomUpMutator(config, mutator)
} else {
panic("no mutator set on " + mutator.name)
}
@ -1645,8 +1616,7 @@ func (c *Context) runMutators(config interface{}) (errs []error) {
return nil
}
func (c *Context) runTopDownMutator(config interface{},
name string, mutator TopDownMutator) (errs []error) {
func (c *Context) runTopDownMutator(config interface{}, mutator *mutatorInfo) (errs []error) {
for i := 0; i < len(c.modulesSorted); i++ {
module := c.modulesSorted[len(c.modulesSorted)-1-i]
@ -1656,12 +1626,12 @@ func (c *Context) runTopDownMutator(config interface{},
config: config,
module: module,
},
name: name,
name: mutator.name,
}
func() {
defer func() {
if r := recover(); r != nil {
in := fmt.Sprintf("top down mutator %q for %s", name, module)
in := fmt.Sprintf("top down mutator %q for %s", mutator.name, module)
if err, ok := r.(panicError); ok {
err.addIn(in)
mctx.error(err)
@ -1670,7 +1640,7 @@ func (c *Context) runTopDownMutator(config interface{},
}
}
}()
mutator(mctx)
mutator.topDownMutator(mctx)
}()
if len(mctx.errs) > 0 {
@ -1682,14 +1652,27 @@ func (c *Context) runTopDownMutator(config interface{},
return errs
}
type reverseDep struct {
module *moduleInfo
dep depInfo
}
func (c *Context) runBottomUpMutator(config interface{},
name string, mutator BottomUpMutator) (errs []error) {
mutator *mutatorInfo) (errs []error) {
newModuleInfo := make(map[Module]*moduleInfo)
for k, v := range c.moduleInfo {
newModuleInfo[k] = v
}
reverseDeps := make(map[*moduleInfo][]depInfo)
for _, module := range c.modulesSorted {
newModules := make([]*moduleInfo, 0, 1)
errsCh := make(chan []error)
reverseDepsCh := make(chan []reverseDep)
newModulesCh := make(chan []*moduleInfo)
done := make(chan bool)
visit := func(module *moduleInfo) bool {
if module.splitModules != nil {
panic("split module found in sorted module list")
}
@ -1700,14 +1683,13 @@ func (c *Context) runBottomUpMutator(config interface{},
config: config,
module: module,
},
name: name,
reverseDeps: reverseDeps,
name: mutator.name,
}
func() {
defer func() {
if r := recover(); r != nil {
in := fmt.Sprintf("bottom up mutator %q for %s", name, module)
in := fmt.Sprintf("bottom up mutator %q for %s", mutator.name, module)
if err, ok := r.(panicError); ok {
err.addIn(in)
mctx.error(err)
@ -1716,28 +1698,76 @@ func (c *Context) runBottomUpMutator(config interface{},
}
}
}()
mutator(mctx)
mutator.bottomUpMutator(mctx)
}()
if len(mctx.errs) > 0 {
errs = append(errs, mctx.errs...)
errsCh <- errs
return true
}
if len(mctx.newModules) > 0 {
newModulesCh <- mctx.newModules
}
if len(mctx.reverseDeps) > 0 {
reverseDepsCh <- mctx.reverseDeps
}
return false
}
// Process errs and reverseDeps in a single goroutine
go func() {
for {
select {
case newErrs := <-errsCh:
errs = append(errs, newErrs...)
case newReverseDeps := <-reverseDepsCh:
for _, r := range newReverseDeps {
reverseDeps[r.module] = append(reverseDeps[r.module], r.dep)
}
case newModules := <-newModulesCh:
for _, m := range newModules {
newModuleInfo[m.logicModule] = m
}
case <-done:
return
}
}
}()
if mutator.parallel {
c.parallelVisitAllBottomUp(visit)
} else {
c.visitAllBottomUp(visit)
}
done <- true
if len(errs) > 0 {
return errs
}
c.moduleInfo = newModuleInfo
for _, group := range c.moduleGroups {
for i := 0; i < len(group.modules); i++ {
module := group.modules[i]
// Update module group to contain newly split variants
if module.splitModules != nil {
group.modules, i = spliceModules(group.modules, i, module.splitModules)
}
// Fix up any remaining dependencies on modules that were split into variants
// by replacing them with the first variant
for i, dep := range module.directDeps {
for j, dep := range module.directDeps {
if dep.module.logicModule == nil {
module.directDeps[i].module = dep.module.splitModules[0]
module.directDeps[j].module = dep.module.splitModules[0]
}
}
if module.splitModules != nil {
newModules = append(newModules, module.splitModules...)
} else {
newModules = append(newModules, module)
}
module.group.modules = spliceModules(module.group.modules, module, newModules)
}
for module, deps := range reverseDeps {
@ -1745,6 +1775,7 @@ func (c *Context) runBottomUpMutator(config interface{},
module.directDeps = append(module.directDeps, deps...)
}
// TODO(ccross): update can be elided if no dependencies were modified
errs = c.updateDependencies()
if len(errs) > 0 {
return errs
@ -1757,26 +1788,30 @@ func (c *Context) runBottomUpMutator(config interface{},
// a mutator sets a non-property member variable on a module, which works until a later mutator
// creates variants of that module.
func (c *Context) cloneModules() {
type update struct {
orig Module
clone *moduleInfo
}
ch := make(chan update, 100)
for _, m := range c.modulesSorted {
go func(m *moduleInfo) {
origLogicModule := m.logicModule
m.logicModule, m.moduleProperties = c.cloneLogicModule(m)
delete(c.moduleInfo, origLogicModule)
c.moduleInfo[m.logicModule] = m
ch <- update{origLogicModule, m}
}(m)
}
for i := 0; i < len(c.modulesSorted); i++ {
update := <-ch
delete(c.moduleInfo, update.orig)
c.moduleInfo[update.clone.logicModule] = update.clone
}
}
func spliceModules(modules []*moduleInfo, origModule *moduleInfo,
newModules []*moduleInfo) []*moduleInfo {
for i, m := range modules {
if m == origModule {
return spliceModulesAtIndex(modules, i, newModules)
}
}
panic("failed to find original module to splice")
}
func spliceModulesAtIndex(modules []*moduleInfo, i int, newModules []*moduleInfo) []*moduleInfo {
// Removes modules[i] from the list and inserts newModules... where it was located, returning
// the new slice and the index of the last inserted element
func spliceModules(modules []*moduleInfo, i int, newModules []*moduleInfo) ([]*moduleInfo, int) {
spliceSize := len(newModules)
newLen := len(modules) + spliceSize - 1
var dest []*moduleInfo
@ -1794,7 +1829,7 @@ func spliceModulesAtIndex(modules []*moduleInfo, i int, newModules []*moduleInfo
// Copy the new modules into the slice
copy(dest[i:], newModules)
return dest
return dest, i + spliceSize - 1
}
func (c *Context) initSpecialVariables() {

View file

@ -429,7 +429,8 @@ func (m *moduleContext) GetMissingDependencies() []string {
type mutatorContext struct {
baseModuleContext
name string
reverseDeps map[*moduleInfo][]depInfo
reverseDeps []reverseDep
newModules []*moduleInfo
}
type baseMutatorContext interface {
@ -460,7 +461,7 @@ type TopDownMutatorContext interface {
}
type BottomUpMutatorContext interface {
TopDownMutatorContext
baseMutatorContext
AddDependency(module Module, tag DependencyTag, name ...string)
AddReverseDependency(module Module, tag DependencyTag, name string)
@ -541,6 +542,11 @@ func (mctx *mutatorContext) createVariations(variationNames []string, local bool
}
}
if mctx.newModules != nil {
panic("module already has variations from this mutator")
}
mctx.newModules = modules
if len(ret) != len(variationNames) {
panic("oops!")
}
@ -582,8 +588,10 @@ func (mctx *mutatorContext) AddReverseDependency(module Module, tag DependencyTa
return
}
mctx.reverseDeps[destModule] = append(mctx.reverseDeps[destModule],
depInfo{mctx.context.moduleInfo[module], tag})
mctx.reverseDeps = append(mctx.reverseDeps, reverseDep{
destModule,
depInfo{mctx.context.moduleInfo[module], tag},
})
}
// AddVariationDependencies adds deps as dependencies of the current module, but uses the variations

View file

@ -30,73 +30,82 @@ var (
var spliceModulesTestCases = []struct {
in []*moduleInfo
replace *moduleInfo
at int
with []*moduleInfo
out []*moduleInfo
outAt int
reallocate bool
}{
{
// Insert at the beginning
in: []*moduleInfo{testModuleA, testModuleB, testModuleC},
replace: testModuleA,
at: 0,
with: []*moduleInfo{testModuleD, testModuleE},
out: []*moduleInfo{testModuleD, testModuleE, testModuleB, testModuleC},
outAt: 1,
reallocate: true,
},
{
// Insert in the middle
in: []*moduleInfo{testModuleA, testModuleB, testModuleC},
replace: testModuleB,
at: 1,
with: []*moduleInfo{testModuleD, testModuleE},
out: []*moduleInfo{testModuleA, testModuleD, testModuleE, testModuleC},
outAt: 2,
reallocate: true,
},
{
// Insert at the end
in: []*moduleInfo{testModuleA, testModuleB, testModuleC},
replace: testModuleC,
at: 2,
with: []*moduleInfo{testModuleD, testModuleE},
out: []*moduleInfo{testModuleA, testModuleB, testModuleD, testModuleE},
outAt: 3,
reallocate: true,
},
{
// Insert over a single element
in: []*moduleInfo{testModuleA},
replace: testModuleA,
at: 0,
with: []*moduleInfo{testModuleD, testModuleE},
out: []*moduleInfo{testModuleD, testModuleE},
outAt: 1,
reallocate: true,
},
{
// Insert at the beginning without reallocating
in: []*moduleInfo{testModuleA, testModuleB, testModuleC, nil}[0:3],
replace: testModuleA,
at: 0,
with: []*moduleInfo{testModuleD, testModuleE},
out: []*moduleInfo{testModuleD, testModuleE, testModuleB, testModuleC},
outAt: 1,
reallocate: false,
},
{
// Insert in the middle without reallocating
in: []*moduleInfo{testModuleA, testModuleB, testModuleC, nil}[0:3],
replace: testModuleB,
at: 1,
with: []*moduleInfo{testModuleD, testModuleE},
out: []*moduleInfo{testModuleA, testModuleD, testModuleE, testModuleC},
outAt: 2,
reallocate: false,
},
{
// Insert at the end without reallocating
in: []*moduleInfo{testModuleA, testModuleB, testModuleC, nil}[0:3],
replace: testModuleC,
at: 2,
with: []*moduleInfo{testModuleD, testModuleE},
out: []*moduleInfo{testModuleA, testModuleB, testModuleD, testModuleE},
outAt: 3,
reallocate: false,
},
{
// Insert over a single element without reallocating
in: []*moduleInfo{testModuleA, nil}[0:1],
replace: testModuleA,
at: 0,
with: []*moduleInfo{testModuleD, testModuleE},
out: []*moduleInfo{testModuleD, testModuleE},
outAt: 1,
reallocate: false,
},
}
@ -106,15 +115,21 @@ func TestSpliceModules(t *testing.T) {
in := make([]*moduleInfo, len(testCase.in), cap(testCase.in))
copy(in, testCase.in)
origIn := in
got := spliceModules(in, testCase.replace, testCase.with)
got, gotAt := spliceModules(in, testCase.at, testCase.with)
if !reflect.DeepEqual(got, testCase.out) {
t.Errorf("test case: %v, %v -> %v", testCase.in, testCase.replace, testCase.with)
t.Errorf("test case: %v, %v -> %v", testCase.in, testCase.at, testCase.with)
t.Errorf("incorrect output:")
t.Errorf(" expected: %v", testCase.out)
t.Errorf(" got: %v", got)
}
if gotAt != testCase.outAt {
t.Errorf("test case: %v, %v -> %v", testCase.in, testCase.at, testCase.with)
t.Errorf("incorrect index:")
t.Errorf(" expected: %d", testCase.outAt)
t.Errorf(" got: %d", gotAt)
}
if sameArray(origIn, got) != !testCase.reallocate {
t.Errorf("test case: %v, %v -> %v", testCase.in, testCase.replace, testCase.with)
t.Errorf("test case: %v, %v -> %v", testCase.in, testCase.at, testCase.with)
not := ""
if !testCase.reallocate {
not = " not"