code.gitea.io/gitea@v1.19.3/modules/queue/unique_queue_disk_channel_test.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package queue 5 6 import ( 7 "fmt" 8 "os" 9 "strconv" 10 "sync" 11 "testing" 12 "time" 13 14 "code.gitea.io/gitea/modules/log" 15 16 "github.com/stretchr/testify/assert" 17 ) 18 19 func TestPersistableChannelUniqueQueue(t *testing.T) { 20 if os.Getenv("CI") != "" { 21 t.Skip("Skipping because test is flaky on CI") 22 } 23 tmpDir := t.TempDir() 24 fmt.Printf("TempDir %s\n", tmpDir) 25 _ = log.NewLogger(1000, "console", "console", `{"level":"warn","stacktracelevel":"NONE","stderr":true}`) 26 27 // Common function to create the Queue 28 newQueue := func(name string, handle func(data ...Data) []Data) Queue { 29 q, err := NewPersistableChannelUniqueQueue(handle, 30 PersistableChannelUniqueQueueConfiguration{ 31 Name: name, 32 DataDir: tmpDir, 33 QueueLength: 200, 34 MaxWorkers: 1, 35 BlockTimeout: 1 * time.Second, 36 BoostTimeout: 5 * time.Minute, 37 BoostWorkers: 1, 38 Workers: 0, 39 }, "task-0") 40 assert.NoError(t, err) 41 return q 42 } 43 44 // runs the provided queue and provides some timer function 45 type channels struct { 46 readyForShutdown chan struct{} // closed when shutdown functions have been assigned 47 readyForTerminate chan struct{} // closed when terminate functions have been assigned 48 signalShutdown chan struct{} // Should close to signal shutdown 49 doneShutdown chan struct{} // closed when shutdown function is done 50 queueTerminate []func() // list of atTerminate functions to call atTerminate - need to be accessed with lock 51 } 52 runQueue := func(q Queue, lock *sync.Mutex) *channels { 53 chans := &channels{ 54 readyForShutdown: make(chan struct{}), 55 readyForTerminate: make(chan struct{}), 56 signalShutdown: make(chan struct{}), 57 doneShutdown: make(chan struct{}), 58 } 59 go q.Run(func(atShutdown func()) { 60 go func() { 61 lock.Lock() 62 select { 63 case <-chans.readyForShutdown: 64 default: 65 close(chans.readyForShutdown) 66 } 67 lock.Unlock() 68 <-chans.signalShutdown 69 atShutdown() 70 close(chans.doneShutdown) 71 }() 72 }, func(atTerminate func()) { 73 lock.Lock() 74 defer lock.Unlock() 75 select { 76 case <-chans.readyForTerminate: 77 default: 78 close(chans.readyForTerminate) 79 } 80 chans.queueTerminate = append(chans.queueTerminate, atTerminate) 81 }) 82 83 return chans 84 } 85 86 // call to shutdown and terminate the queue associated with the channels 87 doTerminate := func(chans *channels, lock *sync.Mutex) { 88 <-chans.readyForTerminate 89 90 lock.Lock() 91 callbacks := []func(){} 92 callbacks = append(callbacks, chans.queueTerminate...) 93 lock.Unlock() 94 95 for _, callback := range callbacks { 96 callback() 97 } 98 } 99 100 mapLock := sync.Mutex{} 101 executedInitial := map[string][]string{} 102 hasInitial := map[string][]string{} 103 104 fillQueue := func(name string, done chan struct{}) { 105 t.Run("Initial Filling: "+name, func(t *testing.T) { 106 lock := sync.Mutex{} 107 108 startAt100Queued := make(chan struct{}) 109 stopAt20Shutdown := make(chan struct{}) // stop and shutdown at the 20th item 110 111 handle := func(data ...Data) []Data { 112 <-startAt100Queued 113 for _, datum := range data { 114 s := datum.(string) 115 mapLock.Lock() 116 executedInitial[name] = append(executedInitial[name], s) 117 mapLock.Unlock() 118 if s == "task-20" { 119 close(stopAt20Shutdown) 120 } 121 } 122 return nil 123 } 124 125 q := newQueue(name, handle) 126 127 // add 100 tasks to the queue 128 for i := 0; i < 100; i++ { 129 _ = q.Push("task-" + strconv.Itoa(i)) 130 } 131 close(startAt100Queued) 132 133 chans := runQueue(q, &lock) 134 135 <-chans.readyForShutdown 136 <-stopAt20Shutdown 137 close(chans.signalShutdown) 138 <-chans.doneShutdown 139 _ = q.Push("final") 140 141 // check which tasks are still in the queue 142 for i := 0; i < 100; i++ { 143 if has, _ := q.(UniqueQueue).Has("task-" + strconv.Itoa(i)); has { 144 mapLock.Lock() 145 hasInitial[name] = append(hasInitial[name], "task-"+strconv.Itoa(i)) 146 mapLock.Unlock() 147 } 148 } 149 if has, _ := q.(UniqueQueue).Has("final"); has { 150 mapLock.Lock() 151 hasInitial[name] = append(hasInitial[name], "final") 152 mapLock.Unlock() 153 } else { 154 assert.Fail(t, "UnqueQueue %s should have \"final\"", name) 155 } 156 doTerminate(chans, &lock) 157 mapLock.Lock() 158 assert.Equal(t, 101, len(executedInitial[name])+len(hasInitial[name])) 159 mapLock.Unlock() 160 }) 161 close(done) 162 } 163 164 doneA := make(chan struct{}) 165 doneB := make(chan struct{}) 166 167 go fillQueue("QueueA", doneA) 168 go fillQueue("QueueB", doneB) 169 170 <-doneA 171 <-doneB 172 173 executedEmpty := map[string][]string{} 174 hasEmpty := map[string][]string{} 175 emptyQueue := func(name string, done chan struct{}) { 176 t.Run("Empty Queue: "+name, func(t *testing.T) { 177 lock := sync.Mutex{} 178 stop := make(chan struct{}) 179 180 // collect the tasks that have been executed 181 handle := func(data ...Data) []Data { 182 lock.Lock() 183 for _, datum := range data { 184 mapLock.Lock() 185 executedEmpty[name] = append(executedEmpty[name], datum.(string)) 186 mapLock.Unlock() 187 if datum.(string) == "final" { 188 close(stop) 189 } 190 } 191 lock.Unlock() 192 return nil 193 } 194 195 q := newQueue(name, handle) 196 chans := runQueue(q, &lock) 197 198 <-chans.readyForShutdown 199 <-stop 200 close(chans.signalShutdown) 201 <-chans.doneShutdown 202 203 // check which tasks are still in the queue 204 for i := 0; i < 100; i++ { 205 if has, _ := q.(UniqueQueue).Has("task-" + strconv.Itoa(i)); has { 206 mapLock.Lock() 207 hasEmpty[name] = append(hasEmpty[name], "task-"+strconv.Itoa(i)) 208 mapLock.Unlock() 209 } 210 } 211 doTerminate(chans, &lock) 212 213 mapLock.Lock() 214 assert.Equal(t, 101, len(executedInitial[name])+len(executedEmpty[name])) 215 assert.Equal(t, 0, len(hasEmpty[name])) 216 mapLock.Unlock() 217 }) 218 close(done) 219 } 220 221 doneA = make(chan struct{}) 222 doneB = make(chan struct{}) 223 224 go emptyQueue("QueueA", doneA) 225 go emptyQueue("QueueB", doneB) 226 227 <-doneA 228 <-doneB 229 230 mapLock.Lock() 231 t.Logf("TestPersistableChannelUniqueQueue executedInitiallyA=%v, executedInitiallyB=%v, executedToEmptyA=%v, executedToEmptyB=%v", 232 len(executedInitial["QueueA"]), len(executedInitial["QueueB"]), len(executedEmpty["QueueA"]), len(executedEmpty["QueueB"])) 233 234 // reset and rerun 235 executedInitial = map[string][]string{} 236 hasInitial = map[string][]string{} 237 executedEmpty = map[string][]string{} 238 hasEmpty = map[string][]string{} 239 mapLock.Unlock() 240 241 doneA = make(chan struct{}) 242 doneB = make(chan struct{}) 243 244 go fillQueue("QueueA", doneA) 245 go fillQueue("QueueB", doneB) 246 247 <-doneA 248 <-doneB 249 250 doneA = make(chan struct{}) 251 doneB = make(chan struct{}) 252 253 go emptyQueue("QueueA", doneA) 254 go emptyQueue("QueueB", doneB) 255 256 <-doneA 257 <-doneB 258 259 mapLock.Lock() 260 t.Logf("TestPersistableChannelUniqueQueue executedInitiallyA=%v, executedInitiallyB=%v, executedToEmptyA=%v, executedToEmptyB=%v", 261 len(executedInitial["QueueA"]), len(executedInitial["QueueB"]), len(executedEmpty["QueueA"]), len(executedEmpty["QueueB"])) 262 mapLock.Unlock() 263 }