mirror of
https://github.com/go-task/task.git
synced 2026-07-01 08:34:19 +00:00
fix: orderedmap race condition (#1972)
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -12,13 +13,25 @@ import (
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
)
|
||||
|
||||
// Tasks represents a group of tasks
|
||||
type Tasks struct {
|
||||
om *orderedmap.OrderedMap[string, *Task]
|
||||
}
|
||||
|
||||
type TaskElement orderedmap.Element[string, *Task]
|
||||
type (
|
||||
// Tasks is an ordered map of task names to Tasks.
|
||||
Tasks struct {
|
||||
om *orderedmap.OrderedMap[string, *Task]
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
// A TaskElement is a key-value pair that is used for initializing a Tasks
|
||||
// structure.
|
||||
TaskElement orderedmap.Element[string, *Task]
|
||||
// MatchingTask represents a task that matches a given call. It includes the
|
||||
// task itself and a list of wildcards that were matched.
|
||||
MatchingTask struct {
|
||||
Task *Task
|
||||
Wildcards []string
|
||||
}
|
||||
)
|
||||
|
||||
// NewTasks creates a new instance of Tasks and initializes it with the provided
|
||||
// set of elements, if any. The elements are added in the order they are passed.
|
||||
func NewTasks(els ...*TaskElement) *Tasks {
|
||||
tasks := &Tasks{
|
||||
om: orderedmap.NewOrderedMap[string, *Task](),
|
||||
@@ -29,20 +42,31 @@ func NewTasks(els ...*TaskElement) *Tasks {
|
||||
return tasks
|
||||
}
|
||||
|
||||
// Len returns the number of variables in the Tasks map.
|
||||
func (tasks *Tasks) Len() int {
|
||||
if tasks == nil || tasks.om == nil {
|
||||
return 0
|
||||
}
|
||||
defer tasks.mutex.RUnlock()
|
||||
tasks.mutex.RLock()
|
||||
return tasks.om.Len()
|
||||
}
|
||||
|
||||
// Get returns the value the the task with the provided key and a boolean that
|
||||
// indicates if the value was found or not. If the value is not found, the
|
||||
// returned task is a zero value and the bool is false.
|
||||
func (tasks *Tasks) Get(key string) (*Task, bool) {
|
||||
if tasks == nil || tasks.om == nil {
|
||||
return &Task{}, false
|
||||
}
|
||||
defer tasks.mutex.RUnlock()
|
||||
tasks.mutex.RLock()
|
||||
return tasks.om.Get(key)
|
||||
}
|
||||
|
||||
// Set sets the value of the task with the provided key to the provided value.
|
||||
// If the task already exists, its value is updated. If the task does not exist,
|
||||
// it is created.
|
||||
func (tasks *Tasks) Set(key string, value *Task) bool {
|
||||
if tasks == nil {
|
||||
tasks = NewTasks()
|
||||
@@ -50,9 +74,14 @@ func (tasks *Tasks) Set(key string, value *Task) bool {
|
||||
if tasks.om == nil {
|
||||
tasks.om = orderedmap.NewOrderedMap[string, *Task]()
|
||||
}
|
||||
defer tasks.mutex.Unlock()
|
||||
tasks.mutex.Lock()
|
||||
return tasks.om.Set(key, value)
|
||||
}
|
||||
|
||||
// Range calls the provided function for each task in the map. The function
|
||||
// receives the task's key and value as arguments. If the function returns an
|
||||
// error, the iteration stops and the error is returned.
|
||||
func (tasks *Tasks) Range(f func(k string, v *Task) error) error {
|
||||
if tasks == nil || tasks.om == nil {
|
||||
return nil
|
||||
@@ -65,10 +94,13 @@ func (tasks *Tasks) Range(f func(k string, v *Task) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Keys returns a slice of all the keys in the Tasks map.
|
||||
func (tasks *Tasks) Keys() []string {
|
||||
if tasks == nil {
|
||||
return nil
|
||||
}
|
||||
defer tasks.mutex.RUnlock()
|
||||
tasks.mutex.RLock()
|
||||
var keys []string
|
||||
for pair := tasks.om.Front(); pair != nil; pair = pair.Next() {
|
||||
keys = append(keys, pair.Key)
|
||||
@@ -76,10 +108,13 @@ func (tasks *Tasks) Keys() []string {
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns a slice of all the values in the Tasks map.
|
||||
func (tasks *Tasks) Values() []*Task {
|
||||
if tasks == nil {
|
||||
return nil
|
||||
}
|
||||
defer tasks.mutex.RUnlock()
|
||||
tasks.mutex.RLock()
|
||||
var values []*Task
|
||||
for pair := tasks.om.Front(); pair != nil; pair = pair.Next() {
|
||||
values = append(values, pair.Value)
|
||||
@@ -87,11 +122,10 @@ func (tasks *Tasks) Values() []*Task {
|
||||
return values
|
||||
}
|
||||
|
||||
type MatchingTask struct {
|
||||
Task *Task
|
||||
Wildcards []string
|
||||
}
|
||||
|
||||
// FindMatchingTasks returns a list of tasks that match the given call. A task
|
||||
// matches a call if its name is equal to the call's task name or if it matches
|
||||
// a wildcard pattern. The function returns a list of MatchingTask structs, each
|
||||
// containing a task and a list of wildcards that were matched.
|
||||
func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
if call == nil {
|
||||
return nil
|
||||
@@ -117,6 +151,8 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
}
|
||||
|
||||
func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars) error {
|
||||
defer t2.mutex.RUnlock()
|
||||
t2.mutex.RLock()
|
||||
err := t2.Range(func(name string, v *Task) error {
|
||||
// We do a deep copy of the task struct here to ensure that no data can
|
||||
// be changed elsewhere once the taskfile is merged.
|
||||
@@ -207,6 +243,9 @@ func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars)
|
||||
}
|
||||
|
||||
func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
|
||||
if t == nil || t.om == nil {
|
||||
*t = *NewTasks()
|
||||
}
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
// NOTE: orderedmap does not have an unmarshaler, so we have to decode
|
||||
|
||||
Reference in New Issue
Block a user