2019-09-05 16:40:52 +01:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package audit
|
|
|
|
|
|
|
|
import (
|
2020-07-13 23:24:15 +01:00
|
|
|
"context"
|
2019-09-05 16:40:52 +01:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ErrEmptyQueue is used to indicate that the queue is empty.
|
|
|
|
var ErrEmptyQueue = errs.Class("empty audit queue")
|
|
|
|
|
2020-12-14 12:54:22 +00:00
|
|
|
// Queue is a list of segments to audit, shared between the reservoir chore and audit workers.
|
2020-08-20 14:29:02 +01:00
|
|
|
// It is not safe for concurrent use.
|
2019-09-05 16:40:52 +01:00
|
|
|
type Queue struct {
|
2020-12-14 12:54:22 +00:00
|
|
|
queue []Segment
|
2019-09-05 16:40:52 +01:00
|
|
|
}
|
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
// NewQueue creates a new audit queue.
|
2020-12-14 12:54:22 +00:00
|
|
|
func NewQueue(segments []Segment) *Queue {
|
2020-08-20 14:29:02 +01:00
|
|
|
return &Queue{
|
2020-12-14 12:54:22 +00:00
|
|
|
queue: segments,
|
2020-07-13 23:24:15 +01:00
|
|
|
}
|
2020-08-20 14:29:02 +01:00
|
|
|
}
|
2020-07-13 23:24:15 +01:00
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
// Next gets the next item in the queue.
|
2020-12-14 12:54:22 +00:00
|
|
|
func (q *Queue) Next() (Segment, error) {
|
2020-07-13 23:24:15 +01:00
|
|
|
if len(q.queue) == 0 {
|
2020-12-14 12:54:22 +00:00
|
|
|
return Segment{}, ErrEmptyQueue.New("")
|
2020-07-13 23:24:15 +01:00
|
|
|
}
|
2019-09-05 16:40:52 +01:00
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
next := q.queue[0]
|
|
|
|
q.queue = q.queue[1:]
|
|
|
|
|
|
|
|
return next, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size returns the size of the queue.
|
|
|
|
func (q *Queue) Size() int {
|
|
|
|
return len(q.queue)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrPendingQueueInProgress means that a chore attempted to add a new pending queue when one was already being added.
|
|
|
|
var ErrPendingQueueInProgress = errs.Class("pending queue already in progress")
|
|
|
|
|
|
|
|
// Queues is a shared resource that keeps track of the next queue to be fetched
|
|
|
|
// and swaps with a new queue when ready.
|
|
|
|
type Queues struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
nextQueue *Queue
|
|
|
|
swapQueue func()
|
|
|
|
queueSwapped chan struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewQueues creates a new Queues object.
|
|
|
|
func NewQueues() *Queues {
|
|
|
|
queues := &Queues{
|
2020-12-14 12:54:22 +00:00
|
|
|
nextQueue: NewQueue([]Segment{}),
|
2020-07-13 23:24:15 +01:00
|
|
|
}
|
2020-08-20 14:29:02 +01:00
|
|
|
return queues
|
|
|
|
}
|
2020-07-13 23:24:15 +01:00
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
// Fetch gets the active queue, clears it, and swaps a pending queue in as the new active queue if available.
|
|
|
|
func (queues *Queues) Fetch() *Queue {
|
|
|
|
queues.mu.Lock()
|
|
|
|
defer queues.mu.Unlock()
|
2020-07-13 23:24:15 +01:00
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
if queues.nextQueue.Size() == 0 && queues.swapQueue != nil {
|
|
|
|
queues.swapQueue()
|
2020-07-13 23:24:15 +01:00
|
|
|
}
|
2020-08-20 14:29:02 +01:00
|
|
|
active := queues.nextQueue
|
|
|
|
|
|
|
|
if queues.swapQueue != nil {
|
|
|
|
queues.swapQueue()
|
|
|
|
} else {
|
2020-12-14 12:54:22 +00:00
|
|
|
queues.nextQueue = NewQueue([]Segment{})
|
2020-08-20 14:29:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return active
|
2019-09-05 16:40:52 +01:00
|
|
|
}
|
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
// Push waits until the next queue has been fetched (if not empty), then swaps it with the provided pending queue.
|
|
|
|
// Push adds a pending queue to be swapped in when ready.
|
|
|
|
// If nextQueue is empty, it immediately replaces the queue. Otherwise it creates a swapQueue callback to be called when nextQueue is fetched.
|
|
|
|
// Only one call to Push is permitted at a time, otherwise it will return ErrPendingQueueInProgress.
|
2020-12-14 12:54:22 +00:00
|
|
|
func (queues *Queues) Push(pendingQueue []Segment) error {
|
2020-08-20 14:29:02 +01:00
|
|
|
queues.mu.Lock()
|
|
|
|
defer queues.mu.Unlock()
|
|
|
|
|
|
|
|
// do not allow multiple concurrent calls to Push().
|
|
|
|
// only one audit chore should exist.
|
|
|
|
if queues.swapQueue != nil {
|
|
|
|
return ErrPendingQueueInProgress.New("")
|
|
|
|
}
|
2019-09-05 16:40:52 +01:00
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
if queues.nextQueue.Size() == 0 {
|
|
|
|
queues.nextQueue = NewQueue(pendingQueue)
|
|
|
|
return nil
|
2019-09-05 16:40:52 +01:00
|
|
|
}
|
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
queues.queueSwapped = make(chan struct{})
|
2019-09-05 16:40:52 +01:00
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
queues.swapQueue = func() {
|
|
|
|
queues.nextQueue = NewQueue(pendingQueue)
|
|
|
|
queues.swapQueue = nil
|
|
|
|
close(queues.queueSwapped)
|
|
|
|
}
|
|
|
|
return nil
|
2019-09-05 16:40:52 +01:00
|
|
|
}
|
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
// WaitForSwap blocks until the swapQueue callback is called or context is canceled.
|
|
|
|
// If there is no pending swap, it returns immediately.
|
|
|
|
func (queues *Queues) WaitForSwap(ctx context.Context) error {
|
|
|
|
queues.mu.Lock()
|
|
|
|
if queues.swapQueue == nil {
|
|
|
|
queues.mu.Unlock()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
queues.mu.Unlock()
|
2019-09-05 16:40:52 +01:00
|
|
|
|
2020-08-20 14:29:02 +01:00
|
|
|
// wait for swapQueue to be called or for context canceled
|
|
|
|
select {
|
|
|
|
case <-queues.queueSwapped:
|
|
|
|
case <-ctx.Done():
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctx.Err()
|
2019-09-05 16:40:52 +01:00
|
|
|
}
|