task.go (2018B)
1 package main 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/hashicorp/go-multierror" 8 ) 9 10 // Task is an independent unit of work. 11 type Task struct { 12 Name string 13 Op func() error 14 Requires []string 15 } 16 17 // FanOut executes all the independant tasks in parallel and waits for them to 18 // finish. 19 type FanOut []Task 20 21 // Run all the tasks. 22 func (tasks FanOut) Run() (err error) { 23 var ( 24 failures = make(chan error) 25 wg = &sync.WaitGroup{} 26 // index contains all the Tasks and allows us to resolve a Task 27 // object from it's name alone. 28 // This is used for building the graph. 29 index = map[string]Task{} 30 // Each chain is a serialised list of tasks to execute. 31 // Chains can be executed independently because they do not 32 // share dependencies - thus parallelism. 33 chains [][]Task 34 ) 35 for _, t := range tasks { 36 index[t.Name] = t 37 } 38 g := &Graph{} 39 for _, t := range tasks { 40 g.Append(taskNode{Task: t, Index: index}) 41 } 42 resolved := g.Resolve() 43 // Since the resolved slice is an ordered list of tasks, we can slice it 44 // into independent chains delimited by tasks that have no dependencies. 45 for _, n := range resolved { 46 if len(n.Requires()) > 0 { 47 // Append to the current chain. 48 current := len(chains) - 1 49 chains[current] = append(chains[current], index[n.ID()]) 50 } else { 51 // Start a new chain. 52 chains = append(chains, []Task{index[n.ID()]}) 53 } 54 } 55 wg.Add(len(chains)) 56 for _, chain := range chains { 57 chain := chain 58 go func() { 59 defer wg.Done() 60 for _, t := range chain { 61 fmt.Printf("run: %v\n", t.Name) 62 if err := t.Op(); err != nil { 63 failures <- TaskError{Task: t, Err: err} 64 break 65 } 66 } 67 }() 68 } 69 go func() { 70 wg.Wait() 71 close(failures) 72 }() 73 for failure := range failures { 74 err = multierror.Append(err, failure) 75 } 76 return err 77 } 78 79 // TaskError associates an error with the task that produced it. 80 type TaskError struct { 81 Task Task 82 Err error 83 } 84 85 func (err TaskError) Error() string { 86 return fmt.Sprintf("task %q: %v", err.Task.Name, err.Err) 87 }