forked from TrueCloudLab/restic
6c514adb8a
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.
109 lines
2.2 KiB
Go
109 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
|
|
}
|