sq/libsq/core/cleanup/cleanup.go
Neil O'Toole 58ccfc9ded
Json driver; refactoring of core packages (#66)
* Type Detector refactor

* json driver impl; refactoring of source.Files reader et al

* working on kind detector

* significant switcheroo of packages

* partway throught refactoring Kind

* major package switcheroo for Kind
2020-08-23 04:42:15 -06:00

138 lines
2.7 KiB
Go

// Package cleanup provides functionality for executing
// cleanup functions.
package cleanup
import (
"io"
"sync"
"github.com/neilotoole/sq/libsq/core/errz"
)
// Nop is a no-op cleanup.Func.
var Nop = func() error { return nil }
// New returns a new Cleanup instance.
func New() *Cleanup {
return &Cleanup{}
}
// Cleanup encapsulates a slice of cleanup funcs. The
// funcs are executed by Run in reverse order to which
// they are added.
// Cleanup is safe for concurrent use.
type Cleanup struct {
mu sync.Mutex
fns []func() error
}
// Len returns the count of cleanup funcs.
func (cu *Cleanup) Len() int {
if cu == nil {
return 0
}
cu.mu.Lock()
defer cu.mu.Unlock()
return len(cu.fns)
}
// Append c's cleanup funcs to cu.
func (cu *Cleanup) Append(c *Cleanup) *Cleanup {
if c == nil || c == cu {
return cu
}
c.mu.Lock()
defer c.mu.Unlock()
cu.mu.Lock()
defer cu.mu.Unlock()
cu.fns = append(cu.fns, c.fns...)
return cu
}
// Add adds a cleanup func.
func (cu *Cleanup) Add(fn func()) *Cleanup {
if fn == nil {
// For convenience in avoiding boilerplate nil checks
// in the caller, we just ignore the nil arg.
return cu
}
cu.mu.Lock()
defer cu.mu.Unlock()
cu.fns = append(cu.fns, func() error {
fn()
return nil
})
return cu
}
// AddE adds an error-returning cleanup func.
func (cu *Cleanup) AddE(fn func() error) *Cleanup {
if fn == nil {
// For convenience in avoiding boilerplate nil checks
// in the caller, we just ignore the nil arg.
return cu
}
cu.mu.Lock()
defer cu.mu.Unlock()
cu.fns = append(cu.fns, fn)
return cu
}
// AddC adds c.Close as a cleanup func. This method
// is no-op if c is nil.
func (cu *Cleanup) AddC(c io.Closer) *Cleanup {
if c == nil {
// For convenience in avoiding boilerplate nil checks
// in the caller, we just ignore the nil arg.
return cu
}
cu.mu.Lock()
defer cu.mu.Unlock()
cu.fns = append(cu.fns, c.Close)
return cu
}
// Run executes the cleanup funcs in reverse order to which
// they were added. All funcs are executed, even in the presence of
// an error from a func. Any errors are combined into a single error.
// The set of cleanup funcs is removed when Run returns.
//
// TODO: Consider renaming Run to Close so that Cleanup
// implements io.Closer?
func (cu *Cleanup) Run() error {
if cu == nil {
return nil
}
cu.mu.Lock()
defer cu.mu.Unlock()
if len(cu.fns) == 0 {
return nil
}
// Capture any cleanup func errors
var err error
// Run cleanups in reverse order
for i := len(cu.fns) - 1; i >= 0; i-- {
fn := cu.fns[i]
if fn == nil {
// skip any nil fns
continue
}
err = errz.Append(err, fn())
}
// Set fns to nil so that the cleanup funcs
// can't get run twice.
cu.fns = nil
return err
}