github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/lightning/config/configlist.go (about) 1 // Copyright 2019 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package config 15 16 import ( 17 "container/list" 18 "context" 19 "sync" 20 "time" 21 ) 22 23 // List is a goroutine-safe FIFO list of *Config, which supports removal 24 // from the middle. The list is not expected to be very long. 25 type List struct { 26 cond *sync.Cond 27 taskIDMap map[int64]*list.Element 28 nodes list.List 29 30 // lastID records the largest task ID being Push()'ed to the List. 31 // In the rare case where two Push() are executed in the same nanosecond 32 // (or the not-so-rare case where the clock's precision is lower than CPU 33 // speed), we'll need to manually force one of the task to use the ID as 34 // lastID + 1. 35 lastID int64 36 } 37 38 // NewConfigList creates a new ConfigList instance. 39 func NewConfigList() *List { 40 return &List{ 41 cond: sync.NewCond(new(sync.Mutex)), 42 taskIDMap: make(map[int64]*list.Element), 43 } 44 } 45 46 // Push adds a configuration to the end of the list. The field `cfg.TaskID` will 47 // be modified to include a unique ID to identify this task. 48 func (cl *List) Push(cfg *Config) { 49 id := time.Now().UnixNano() 50 cl.cond.L.Lock() 51 defer cl.cond.L.Unlock() 52 if id <= cl.lastID { 53 id = cl.lastID + 1 54 } 55 cfg.TaskID = id 56 cl.lastID = id 57 cl.taskIDMap[id] = cl.nodes.PushBack(cfg) 58 cl.cond.Broadcast() 59 } 60 61 // Pop removes a configuration from the front of the list. If the list is empty, 62 // this method will block until either another goroutines calls Push() or the 63 // input context expired. 64 // 65 // If the context expired, the error field will contain the error from context. 66 func (cl *List) Pop(ctx context.Context) (*Config, error) { 67 res := make(chan *Config) 68 69 go func() { 70 cl.cond.L.Lock() 71 defer cl.cond.L.Unlock() 72 for { 73 if front := cl.nodes.Front(); front != nil { 74 cfg := front.Value.(*Config) 75 delete(cl.taskIDMap, cfg.TaskID) 76 cl.nodes.Remove(front) 77 res <- cfg 78 break 79 } 80 cl.cond.Wait() 81 } 82 }() 83 84 select { 85 case cfg := <-res: 86 return cfg, nil 87 case <-ctx.Done(): 88 return nil, ctx.Err() 89 } 90 } 91 92 // Remove removes a task from the list given its task ID. Returns true if a task 93 // is successfully removed, false if the task ID did not exist. 94 func (cl *List) Remove(taskID int64) bool { 95 cl.cond.L.Lock() 96 defer cl.cond.L.Unlock() 97 element, ok := cl.taskIDMap[taskID] 98 if !ok { 99 return false 100 } 101 delete(cl.taskIDMap, taskID) 102 cl.nodes.Remove(element) 103 return true 104 } 105 106 // Get obtains a task from the list given its task ID. If the task ID did not 107 // exist, the returned bool field will be false. 108 func (cl *List) Get(taskID int64) (*Config, bool) { 109 cl.cond.L.Lock() 110 defer cl.cond.L.Unlock() 111 element, ok := cl.taskIDMap[taskID] 112 if !ok { 113 return nil, false 114 } 115 return element.Value.(*Config), true 116 } 117 118 // AllIDs returns a list of all task IDs in the list. 119 func (cl *List) AllIDs() []int64 { 120 cl.cond.L.Lock() 121 defer cl.cond.L.Unlock() 122 res := make([]int64, 0, len(cl.taskIDMap)) 123 for element := cl.nodes.Front(); element != nil; element = element.Next() { 124 res = append(res, element.Value.(*Config).TaskID) 125 } 126 return res 127 } 128 129 // MoveToFront moves a task to the front of the list. Returns true if the task 130 // is successfully moved (including no-op), false if the task ID did not exist. 131 func (cl *List) MoveToFront(taskID int64) bool { 132 cl.cond.L.Lock() 133 defer cl.cond.L.Unlock() 134 element, ok := cl.taskIDMap[taskID] 135 if !ok { 136 return false 137 } 138 cl.nodes.MoveToFront(element) 139 return true 140 } 141 142 // MoveToBack moves a task to the back of the list. Returns true if the task is 143 // successfully moved (including no-op), false if the task ID did not exist. 144 func (cl *List) MoveToBack(taskID int64) bool { 145 cl.cond.L.Lock() 146 defer cl.cond.L.Unlock() 147 element, ok := cl.taskIDMap[taskID] 148 if !ok { 149 return false 150 } 151 cl.nodes.MoveToBack(element) 152 return true 153 }