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  }