f-29: stop: add a proper stop
All checks were successful
Release Pipeline / upload-assets (metadata@.service, systemd/metadata@.service) (push) Successful in 4s
Release Pipeline / upload-assets (run-dnsmasq-in-netns.sh, scripts/run-dnsmasq-in-netns.sh) (push) Successful in 4s
Release Pipeline / build (agent, amd64, linux) (push) Successful in 0s
Release Pipeline / build (metadata, amd64, linux) (push) Successful in 0s
Release Pipeline / checksums (push) Successful in 4s
Release Pipeline / set-release-target (push) Successful in 2s
Release Pipeline / upload-assets (agent.service, systemd/agent.service) (push) Successful in 4s
Release Pipeline / upload-assets (dnsmasq@.service, systemd/dnsmasq@.service) (push) Successful in 4s
Release Pipeline / release (push) Successful in 11s
Release Pipeline / publish (push) Successful in 0s
Release Pipeline / build (push) Successful in 1m30s
All checks were successful
Release Pipeline / upload-assets (metadata@.service, systemd/metadata@.service) (push) Successful in 4s
Release Pipeline / upload-assets (run-dnsmasq-in-netns.sh, scripts/run-dnsmasq-in-netns.sh) (push) Successful in 4s
Release Pipeline / build (agent, amd64, linux) (push) Successful in 0s
Release Pipeline / build (metadata, amd64, linux) (push) Successful in 0s
Release Pipeline / checksums (push) Successful in 4s
Release Pipeline / set-release-target (push) Successful in 2s
Release Pipeline / upload-assets (agent.service, systemd/agent.service) (push) Successful in 4s
Release Pipeline / upload-assets (dnsmasq@.service, systemd/dnsmasq@.service) (push) Successful in 4s
Release Pipeline / release (push) Successful in 11s
Release Pipeline / publish (push) Successful in 0s
Release Pipeline / build (push) Successful in 1m30s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
1ce84ffd58
commit
cdbb5012b1
9 changed files with 333 additions and 29 deletions
|
|
@ -1,13 +1,19 @@
|
|||
package worker
|
||||
|
||||
import "log"
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Task is a function to be executed asynchronously by a worker.
|
||||
type Task func()
|
||||
|
||||
// Queue is a FIFO channel-backed task queue consumed by worker goroutines.
|
||||
type Queue struct {
|
||||
tasks chan Task
|
||||
tasks chan Task
|
||||
wg sync.WaitGroup
|
||||
mu sync.RWMutex
|
||||
stopped bool
|
||||
}
|
||||
|
||||
// New creates a Queue with the given channel buffer size.
|
||||
|
|
@ -15,8 +21,16 @@ func New(bufferSize int) *Queue {
|
|||
return &Queue{tasks: make(chan Task, bufferSize)}
|
||||
}
|
||||
|
||||
// Submit enqueues a task. Blocks if the queue is full.
|
||||
// Submit enqueues a task. Blocks if the queue is full. Tasks submitted after
|
||||
// Stop are rejected and logged rather than enqueued.
|
||||
func (q *Queue) Submit(t Task) {
|
||||
q.mu.RLock()
|
||||
defer q.mu.RUnlock()
|
||||
|
||||
if q.stopped {
|
||||
log.Print("worker: queue stopped, task rejected")
|
||||
return
|
||||
}
|
||||
q.tasks <- t
|
||||
}
|
||||
|
||||
|
|
@ -24,10 +38,28 @@ func (q *Queue) Submit(t Task) {
|
|||
func (q *Queue) Start(n int) {
|
||||
log.Printf("worker: starting %d workers", n)
|
||||
for i := range n {
|
||||
q.wg.Add(1)
|
||||
go func(id int) {
|
||||
defer q.wg.Done()
|
||||
for task := range q.tasks {
|
||||
task()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop rejects new tasks, then waits for the queued and in-flight ones to
|
||||
// finish. The write lock is what makes closing the channel safe: it is only
|
||||
// taken once every in-flight Submit has released its read lock.
|
||||
func (q *Queue) Stop() {
|
||||
q.mu.Lock()
|
||||
if q.stopped {
|
||||
q.mu.Unlock()
|
||||
return
|
||||
}
|
||||
q.stopped = true
|
||||
close(q.tasks)
|
||||
q.mu.Unlock()
|
||||
|
||||
q.wg.Wait()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,3 +101,79 @@ func TestQueue_SubmitBlocksWhenFull(t *testing.T) {
|
|||
t.Error("Submit aurait dû se débloquer après démarrage d'un worker")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStop_AttendLesTachesEnCours(t *testing.T) {
|
||||
q := New(10)
|
||||
q.Start(2)
|
||||
|
||||
var mu sync.Mutex
|
||||
done := 0
|
||||
for range 5 {
|
||||
q.Submit(func() {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
mu.Lock()
|
||||
done++
|
||||
mu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
q.Stop()
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if done != 5 {
|
||||
t.Errorf("Stop devrait attendre les 5 tâches, %d terminées", done)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStop_RejetteLesTachesSuivantes(t *testing.T) {
|
||||
q := New(10)
|
||||
q.Start(1)
|
||||
q.Stop()
|
||||
|
||||
executed := make(chan struct{}, 1)
|
||||
q.Submit(func() { executed <- struct{}{} })
|
||||
|
||||
select {
|
||||
case <-executed:
|
||||
t.Error("une tâche soumise après Stop ne doit pas être exécutée")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
func TestStop_Idempotent(t *testing.T) {
|
||||
q := New(10)
|
||||
q.Start(1)
|
||||
q.Stop()
|
||||
q.Stop()
|
||||
}
|
||||
|
||||
func TestStop_SansTacheEnCours(t *testing.T) {
|
||||
q := New(10)
|
||||
q.Start(3)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() { q.Stop(); close(done) }()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Stop n'a pas rendu la main sur une file vide")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmit_ConcurrentAvecStop(t *testing.T) {
|
||||
q := New(100)
|
||||
q.Start(4)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range 20 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
q.Submit(func() {})
|
||||
}()
|
||||
}
|
||||
q.Stop()
|
||||
wg.Wait()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue