105 lines
2.6 KiB
Go
105 lines
2.6 KiB
Go
package lifecycle
|
|
|
|
import "sync"
|
|
|
|
// Resource keeps track of references and has some compile time debug hooks
|
|
// to help diagnose leaks. It keeps track of if it is open or not and allows
|
|
// blocking until all references are released.
|
|
type Resource struct {
|
|
stmu sync.Mutex // protects state transitions
|
|
chmu sync.RWMutex // protects channel mutations
|
|
ch chan struct{} // signals references to close
|
|
wg sync.WaitGroup // counts outstanding references
|
|
}
|
|
|
|
// Open marks the resource as open.
|
|
func (res *Resource) Open() {
|
|
res.stmu.Lock()
|
|
defer res.stmu.Unlock()
|
|
|
|
res.chmu.Lock()
|
|
res.ch = make(chan struct{})
|
|
res.chmu.Unlock()
|
|
}
|
|
|
|
// Close waits for any outstanding references and marks the resource as closed
|
|
// so that Acquire returns an error.
|
|
func (res *Resource) Close() {
|
|
res.stmu.Lock()
|
|
defer res.stmu.Unlock()
|
|
|
|
res.chmu.Lock()
|
|
if res.ch != nil {
|
|
close(res.ch) // signal any references.
|
|
res.ch = nil // stop future Acquires
|
|
}
|
|
res.chmu.Unlock()
|
|
|
|
res.wg.Wait() // wait for any acquired references
|
|
}
|
|
|
|
// Opened returns true if the resource is currently open. It may be immediately
|
|
// false in the presence of concurrent Open and Close calls.
|
|
func (res *Resource) Opened() bool {
|
|
res.chmu.RLock()
|
|
opened := res.ch != nil
|
|
res.chmu.RUnlock()
|
|
|
|
return opened
|
|
}
|
|
|
|
// Acquire returns a Reference used to keep alive some resource.
|
|
func (res *Resource) Acquire() (*Reference, error) {
|
|
res.chmu.RLock()
|
|
defer res.chmu.RUnlock()
|
|
|
|
ch := res.ch
|
|
if ch == nil {
|
|
return nil, resourceClosed()
|
|
}
|
|
|
|
res.wg.Add(1)
|
|
return live.track(&Reference{wg: &res.wg, ch: ch}), nil
|
|
}
|
|
|
|
// Reference is an open reference for some resource.
|
|
type Reference struct {
|
|
once sync.Once
|
|
wg *sync.WaitGroup
|
|
ch <-chan struct{}
|
|
id uint64
|
|
}
|
|
|
|
// Closing returns a channel that will be closed when the associated resource begins closing.
|
|
func (ref *Reference) Closing() <-chan struct{} { return ref.ch }
|
|
|
|
// Release causes the Reference to be freed. It is safe to call multiple times.
|
|
func (ref *Reference) Release() {
|
|
ref.once.Do(func() {
|
|
live.untrack(ref)
|
|
ref.wg.Done()
|
|
})
|
|
}
|
|
|
|
// Close makes a Reference an io.Closer. It is safe to call multiple times.
|
|
func (ref *Reference) Close() error {
|
|
ref.Release()
|
|
return nil
|
|
}
|
|
|
|
// References is a helper to aggregate a group of references.
|
|
type References []*Reference
|
|
|
|
// Release releases all of the references. It is safe to call multiple times.
|
|
func (refs References) Release() {
|
|
for _, ref := range refs {
|
|
ref.Release()
|
|
}
|
|
}
|
|
|
|
// Close makes References an io.Closer. It is safe to call multiple times.
|
|
func (refs References) Close() error {
|
|
refs.Release()
|
|
return nil
|
|
}
|