lock: Always cancel the returned Get(R)Lock context (#12162)

* lock: Always cancel the returned Get(R)Lock context

There is a leak with cancel created inside the locking mechanism. The
cancel purpose was to cancel operations such erasure get/put that are
holding non-refreshable locks.

This PR will ensure the created context.Cancel is passed to the unlock
API so it will cleanup and avoid leaks.

* locks: Avoid returning nil cancel in local lockers

Since there is no Refresh mechanism in the local locking mechanism, we
do not generate a new context or cancel. Currently, a nil cancel
function is returned but this can cause a crash. Return a dummy function
instead.
This commit is contained in:
Anis Elleuch
2021-04-28 00:12:50 +01:00
committed by GitHub
parent fbdfa11f76
commit 9e797532dc
12 changed files with 146 additions and 129 deletions

View File

@@ -65,12 +65,13 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
defer ObjectPathUpdated(pathJoin(dstBucket, dstObject))
if !dstOpts.NoLock {
var cancel context.CancelFunc
lk := er.NewNSLock(dstBucket, dstObject)
ctx, err = lk.GetLock(ctx, globalOperationTimeout)
ctx, cancel, err = lk.GetLock(ctx, globalOperationTimeout)
if err != nil {
return oi, err
}
defer lk.Unlock()
defer lk.Unlock(cancel)
}
// Read metadata associated with the object from all disks.
storageDisks := er.getDisks()
@@ -145,20 +146,21 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
// Acquire lock
if lockType != noLock {
var cancel context.CancelFunc
lock := er.NewNSLock(bucket, object)
switch lockType {
case writeLock:
ctx, err = lock.GetLock(ctx, globalOperationTimeout)
ctx, cancel, err = lock.GetLock(ctx, globalOperationTimeout)
if err != nil {
return nil, err
}
nsUnlocker = lock.Unlock
nsUnlocker = func() { lock.Unlock(cancel) }
case readLock:
ctx, err = lock.GetRLock(ctx, globalOperationTimeout)
ctx, cancel, err = lock.GetRLock(ctx, globalOperationTimeout)
if err != nil {
return nil, err
}
nsUnlocker = lock.RUnlock
nsUnlocker = func() { lock.RUnlock(cancel) }
}
unlockOnDefer = true
}
@@ -213,11 +215,11 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
func (er erasureObjects) GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) {
// Lock the object before reading.
lk := er.NewNSLock(bucket, object)
ctx, err = lk.GetRLock(ctx, globalOperationTimeout)
ctx, cancel, err := lk.GetRLock(ctx, globalOperationTimeout)
if err != nil {
return err
}
defer lk.RUnlock()
defer lk.RUnlock(cancel)
// Start offset cannot be negative.
if startOffset < 0 {
@@ -376,13 +378,14 @@ func (er erasureObjects) getObject(ctx context.Context, bucket, object string, s
// GetObjectInfo - reads object metadata and replies back ObjectInfo.
func (er erasureObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (info ObjectInfo, err error) {
if !opts.NoLock {
var cancel context.CancelFunc
// Lock the object before reading.
lk := er.NewNSLock(bucket, object)
ctx, err = lk.GetRLock(ctx, globalOperationTimeout)
ctx, cancel, err = lk.GetRLock(ctx, globalOperationTimeout)
if err != nil {
return ObjectInfo{}, err
}
defer lk.RUnlock()
defer lk.RUnlock(cancel)
}
return er.getObjectInfo(ctx, bucket, object, opts)
@@ -732,12 +735,13 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
if !opts.NoLock {
var err error
var cancel context.CancelFunc
lk := er.NewNSLock(bucket, object)
ctx, err = lk.GetLock(ctx, globalOperationTimeout)
ctx, cancel, err = lk.GetLock(ctx, globalOperationTimeout)
if err != nil {
return ObjectInfo{}, err
}
defer lk.Unlock()
defer lk.Unlock(cancel)
}
for i, w := range writers {
@@ -1043,11 +1047,11 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
}
// Acquire a write lock before deleting the object.
lk := er.NewNSLock(bucket, object)
ctx, err = lk.GetLock(ctx, globalDeleteOperationTimeout)
ctx, cancel, err := lk.GetLock(ctx, globalDeleteOperationTimeout)
if err != nil {
return ObjectInfo{}, err
}
defer lk.Unlock()
defer lk.Unlock(cancel)
storageDisks := er.getDisks()
writeQuorum := len(storageDisks)/2 + 1
@@ -1154,14 +1158,13 @@ func (er erasureObjects) addPartial(bucket, object, versionID string) {
}
func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
var err error
// Lock the object before updating tags.
lk := er.NewNSLock(bucket, object)
ctx, err = lk.GetLock(ctx, globalOperationTimeout)
ctx, cancel, err := lk.GetLock(ctx, globalOperationTimeout)
if err != nil {
return ObjectInfo{}, err
}
defer lk.Unlock()
defer lk.Unlock(cancel)
disks := er.getDisks()
@@ -1205,14 +1208,13 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s
// PutObjectTags - replace or add tags to an existing object
func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object string, tags string, opts ObjectOptions) (ObjectInfo, error) {
var err error
// Lock the object before updating tags.
lk := er.NewNSLock(bucket, object)
ctx, err = lk.GetLock(ctx, globalOperationTimeout)
ctx, cancel, err := lk.GetLock(ctx, globalOperationTimeout)
if err != nil {
return ObjectInfo{}, err
}
defer lk.Unlock()
defer lk.Unlock(cancel)
disks := er.getDisks()
@@ -1302,11 +1304,11 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
}
// Acquire write lock before starting to transition the object.
lk := er.NewNSLock(bucket, object)
ctx, err = lk.GetLock(ctx, globalDeleteOperationTimeout)
ctx, cancel, err := lk.GetLock(ctx, globalDeleteOperationTimeout)
if err != nil {
return err
}
defer lk.Unlock()
defer lk.Unlock(cancel)
fi, metaArr, onlineDisks, err := er.getObjectFileInfo(ctx, bucket, object, opts, true)
if err != nil {