forked from TrueCloudLab/distribution
258345ba0d
In some conditions, regulator.exit may not send a signal to blocked regulator.enter. Let's assume we are in the critical section of regulator.exit and r.available is equal to 0. And there are three more gorotines. One goroutine also executes regulator.exit and waits for the lock. Rest run regulator.enter and wait for the signal. We send the signal, and after releasing the lock, there will be lock contention: 1. Wait from regulator.enter 2. Lock from regulator.exit If the winner is Lock from regulator.exit, we will not send another signal to unlock the second Wait. Signed-off-by: Oleg Bulatov <obulatov@redhat.com>
141 lines
4 KiB
Go
141 lines
4 KiB
Go
package base
|
|
|
|
import (
|
|
"io"
|
|
"sync"
|
|
|
|
"github.com/docker/distribution/context"
|
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
|
)
|
|
|
|
type regulator struct {
|
|
storagedriver.StorageDriver
|
|
*sync.Cond
|
|
|
|
available uint64
|
|
}
|
|
|
|
// NewRegulator wraps the given driver and is used to regulate concurrent calls
|
|
// to the given storage driver to a maximum of the given limit. This is useful
|
|
// for storage drivers that would otherwise create an unbounded number of OS
|
|
// threads if allowed to be called unregulated.
|
|
func NewRegulator(driver storagedriver.StorageDriver, limit uint64) storagedriver.StorageDriver {
|
|
return ®ulator{
|
|
StorageDriver: driver,
|
|
Cond: sync.NewCond(&sync.Mutex{}),
|
|
available: limit,
|
|
}
|
|
}
|
|
|
|
func (r *regulator) enter() {
|
|
r.L.Lock()
|
|
for r.available == 0 {
|
|
r.Wait()
|
|
}
|
|
r.available--
|
|
r.L.Unlock()
|
|
}
|
|
|
|
func (r *regulator) exit() {
|
|
r.L.Lock()
|
|
r.Signal()
|
|
r.available++
|
|
r.L.Unlock()
|
|
}
|
|
|
|
// Name returns the human-readable "name" of the driver, useful in error
|
|
// messages and logging. By convention, this will just be the registration
|
|
// name, but drivers may provide other information here.
|
|
func (r *regulator) Name() string {
|
|
r.enter()
|
|
defer r.exit()
|
|
|
|
return r.StorageDriver.Name()
|
|
}
|
|
|
|
// GetContent retrieves the content stored at "path" as a []byte.
|
|
// This should primarily be used for small objects.
|
|
func (r *regulator) GetContent(ctx context.Context, path string) ([]byte, error) {
|
|
r.enter()
|
|
defer r.exit()
|
|
|
|
return r.StorageDriver.GetContent(ctx, path)
|
|
}
|
|
|
|
// PutContent stores the []byte content at a location designated by "path".
|
|
// This should primarily be used for small objects.
|
|
func (r *regulator) PutContent(ctx context.Context, path string, content []byte) error {
|
|
r.enter()
|
|
defer r.exit()
|
|
|
|
return r.StorageDriver.PutContent(ctx, path, content)
|
|
}
|
|
|
|
// Reader retrieves an io.ReadCloser for the content stored at "path"
|
|
// with a given byte offset.
|
|
// May be used to resume reading a stream by providing a nonzero offset.
|
|
func (r *regulator) Reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
|
|
r.enter()
|
|
defer r.exit()
|
|
|
|
return r.StorageDriver.Reader(ctx, path, offset)
|
|
}
|
|
|
|
// Writer stores the contents of the provided io.ReadCloser at a
|
|
// location designated by the given path.
|
|
// May be used to resume writing a stream by providing a nonzero offset.
|
|
// The offset must be no larger than the CurrentSize for this path.
|
|
func (r *regulator) Writer(ctx context.Context, path string, append bool) (storagedriver.FileWriter, error) {
|
|
r.enter()
|
|
defer r.exit()
|
|
|
|
return r.StorageDriver.Writer(ctx, path, append)
|
|
}
|
|
|
|
// Stat retrieves the FileInfo for the given path, including the current
|
|
// size in bytes and the creation time.
|
|
func (r *regulator) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) {
|
|
r.enter()
|
|
defer r.exit()
|
|
|
|
return r.StorageDriver.Stat(ctx, path)
|
|
}
|
|
|
|
// List returns a list of the objects that are direct descendants of the
|
|
//given path.
|
|
func (r *regulator) List(ctx context.Context, path string) ([]string, error) {
|
|
r.enter()
|
|
defer r.exit()
|
|
|
|
return r.StorageDriver.List(ctx, path)
|
|
}
|
|
|
|
// Move moves an object stored at sourcePath to destPath, removing the
|
|
// original object.
|
|
// Note: This may be no more efficient than a copy followed by a delete for
|
|
// many implementations.
|
|
func (r *regulator) Move(ctx context.Context, sourcePath string, destPath string) error {
|
|
r.enter()
|
|
defer r.exit()
|
|
|
|
return r.StorageDriver.Move(ctx, sourcePath, destPath)
|
|
}
|
|
|
|
// Delete recursively deletes all objects stored at "path" and its subpaths.
|
|
func (r *regulator) Delete(ctx context.Context, path string) error {
|
|
r.enter()
|
|
defer r.exit()
|
|
|
|
return r.StorageDriver.Delete(ctx, path)
|
|
}
|
|
|
|
// URLFor returns a URL which may be used to retrieve the content stored at
|
|
// the given path, possibly using the given options.
|
|
// May return an ErrUnsupportedMethod in certain StorageDriver
|
|
// implementations.
|
|
func (r *regulator) URLFor(ctx context.Context, path string, options map[string]interface{}) (string, error) {
|
|
r.enter()
|
|
defer r.exit()
|
|
|
|
return r.StorageDriver.URLFor(ctx, path, options)
|
|
}
|