restic/internal/ui/progress/counter.go
Alexander Neumann 6c514adb8a ui/progress: Use mutex instead of atomic
The counter value needs to be aligned to 64 bit in memory for the
atomic functions to work on some platform (such as 32 bit ARM).

The atomic package says in its documentation:

> These functions require great care to be used correctly. Except for
> special, low-level applications, synchronization is better done with
> channels or the facilities of the sync package.

This commit replaces the atomic functions with a simple sync.Mutex, so
we don't have to care about alignment.
2020-12-22 21:03:27 +01:00

110 lines
2.2 KiB
Go

package progress
import (
"os"
"sync"
"time"
"github.com/restic/restic/internal/debug"
)
// A Func is a callback for a Counter.
//
// The final argument is true if Counter.Done has been called,
// which means that the current call will be the last.
type Func func(value uint64, runtime time.Duration, final bool)
// A Counter tracks a running count and controls a goroutine that passes its
// value periodically to a Func.
//
// The Func is also called when SIGUSR1 (or SIGINFO, on BSD) is received.
type Counter struct {
report Func
start time.Time
stopped chan struct{} // Closed by run.
stop chan struct{} // Close to stop run.
tick *time.Ticker
valueMutex sync.Mutex
value uint64
}
// New starts a new Counter.
func New(interval time.Duration, report Func) *Counter {
signals.Once.Do(func() {
signals.ch = make(chan os.Signal, 1)
setupSignals()
})
c := &Counter{
report: report,
start: time.Now(),
stopped: make(chan struct{}),
stop: make(chan struct{}),
tick: time.NewTicker(interval),
}
go c.run()
return c
}
// Add v to the Counter. This method is concurrency-safe.
func (c *Counter) Add(v uint64) {
if c == nil {
return
}
c.valueMutex.Lock()
c.value += v
c.valueMutex.Unlock()
}
// Done tells a Counter to stop and waits for it to report its final value.
func (c *Counter) Done() {
if c == nil {
return
}
c.tick.Stop()
close(c.stop)
<-c.stopped // Wait for last progress report.
*c = Counter{} // Prevent reuse.
}
func (c *Counter) get() uint64 {
c.valueMutex.Lock()
v := c.value
c.valueMutex.Unlock()
return v
}
func (c *Counter) run() {
defer close(c.stopped)
defer func() {
// Must be a func so that time.Since isn't called at defer time.
c.report(c.get(), time.Since(c.start), true)
}()
for {
var now time.Time
select {
case now = <-c.tick.C:
case sig := <-signals.ch:
debug.Log("Signal received: %v\n", sig)
now = time.Now()
case <-c.stop:
return
}
c.report(c.get(), now.Sub(c.start), false)
}
}
// XXX The fact that signals is a single global variable means that only one
// Counter receives each incoming signal.
var signals struct {
ch chan os.Signal
sync.Once
}