HDDS-428. OzoneManager lock optimization.
Contributed by Nanda Kumar.
This commit is contained in:
parent
47b72c87eb
commit
6e2129cf4e
|
@ -100,6 +100,11 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<artifactId>disruptor</artifactId>
|
<artifactId>disruptor</artifactId>
|
||||||
<version>${disruptor.version}</version>
|
<version>${disruptor.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-pool2</artifactId>
|
||||||
|
<version>2.6.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
@ -81,4 +81,8 @@ public final class HddsConfigKeys {
|
||||||
"hdds.scm.chillmode.threshold.pct";
|
"hdds.scm.chillmode.threshold.pct";
|
||||||
public static final double HDDS_SCM_CHILLMODE_THRESHOLD_PCT_DEFAULT = 0.99;
|
public static final double HDDS_SCM_CHILLMODE_THRESHOLD_PCT_DEFAULT = 0.99;
|
||||||
|
|
||||||
|
public static final String HDDS_LOCK_MAX_CONCURRENCY =
|
||||||
|
"hdds.lock.max.concurrency";
|
||||||
|
public static final int HDDS_LOCK_MAX_CONCURRENCY_DEFAULT = 100;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with this
|
||||||
|
* work for additional information regarding copyright ownership. The ASF
|
||||||
|
* licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.ozone.lock;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock implementation which also maintains counter.
|
||||||
|
*/
|
||||||
|
public final class ActiveLock {
|
||||||
|
|
||||||
|
private Lock lock;
|
||||||
|
private AtomicInteger count;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use ActiveLock#newInstance to create instance.
|
||||||
|
*/
|
||||||
|
private ActiveLock() {
|
||||||
|
this.lock = new ReentrantLock();
|
||||||
|
this.count = new AtomicInteger(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of ActiveLock.
|
||||||
|
*
|
||||||
|
* @return new ActiveLock
|
||||||
|
*/
|
||||||
|
public static ActiveLock newInstance() {
|
||||||
|
return new ActiveLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquires the lock.
|
||||||
|
*
|
||||||
|
* <p>If the lock is not available then the current thread becomes
|
||||||
|
* disabled for thread scheduling purposes and lies dormant until the
|
||||||
|
* lock has been acquired.
|
||||||
|
*/
|
||||||
|
public void lock() {
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the lock.
|
||||||
|
*/
|
||||||
|
public void unlock() {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the active count of the lock.
|
||||||
|
*/
|
||||||
|
void incrementActiveCount() {
|
||||||
|
count.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrement the active count of the lock.
|
||||||
|
*/
|
||||||
|
void decrementActiveCount() {
|
||||||
|
count.decrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the active count on the lock.
|
||||||
|
*
|
||||||
|
* @return Number of active leases on the lock.
|
||||||
|
*/
|
||||||
|
int getActiveLockCount() {
|
||||||
|
return count.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the active count on the lock.
|
||||||
|
*/
|
||||||
|
void resetCounter() {
|
||||||
|
count.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return lock.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with this
|
||||||
|
* work for additional information regarding copyright ownership. The ASF
|
||||||
|
* licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.ozone.lock;
|
||||||
|
|
||||||
|
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hdds.HddsConfigKeys;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the locks on a given resource. A new lock is created for each
|
||||||
|
* and every unique resource. Uniqueness of resource depends on the
|
||||||
|
* {@code equals} implementation of it.
|
||||||
|
*/
|
||||||
|
public class LockManager<T> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(LockManager.class);
|
||||||
|
|
||||||
|
private final Map<T, ActiveLock> activeLocks = new ConcurrentHashMap<>();
|
||||||
|
private final GenericObjectPool<ActiveLock> lockPool =
|
||||||
|
new GenericObjectPool<>(new PooledLockFactory());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new LockManager instance.
|
||||||
|
*
|
||||||
|
* @param conf Configuration object
|
||||||
|
*/
|
||||||
|
public LockManager(Configuration conf) {
|
||||||
|
int maxPoolSize = conf.getInt(HddsConfigKeys.HDDS_LOCK_MAX_CONCURRENCY,
|
||||||
|
HddsConfigKeys.HDDS_LOCK_MAX_CONCURRENCY_DEFAULT);
|
||||||
|
lockPool.setMaxTotal(maxPoolSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquires the lock on given resource.
|
||||||
|
*
|
||||||
|
* <p>If the lock is not available then the current thread becomes
|
||||||
|
* disabled for thread scheduling purposes and lies dormant until the
|
||||||
|
* lock has been acquired.
|
||||||
|
*/
|
||||||
|
public void lock(T resource) {
|
||||||
|
activeLocks.compute(resource, (k, v) -> {
|
||||||
|
ActiveLock lock;
|
||||||
|
try {
|
||||||
|
if (v == null) {
|
||||||
|
lock = lockPool.borrowObject();
|
||||||
|
} else {
|
||||||
|
lock = v;
|
||||||
|
}
|
||||||
|
lock.incrementActiveCount();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.error("Unable to obtain lock.", ex);
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
return lock;
|
||||||
|
}).lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the lock on given resource.
|
||||||
|
*/
|
||||||
|
public void unlock(T resource) {
|
||||||
|
ActiveLock lock = activeLocks.get(resource);
|
||||||
|
if (lock == null) {
|
||||||
|
// Someone is releasing a lock which was never acquired. Log and return.
|
||||||
|
LOG.warn("Trying to release the lock on {}, which was never acquired.",
|
||||||
|
resource);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lock.unlock();
|
||||||
|
activeLocks.computeIfPresent(resource, (k, v) -> {
|
||||||
|
v.decrementActiveCount();
|
||||||
|
if (v.getActiveLockCount() != 0) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
lockPool.returnObject(v);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with this
|
||||||
|
* work for additional information regarding copyright ownership. The ASF
|
||||||
|
* licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.ozone.lock;
|
||||||
|
|
||||||
|
import org.apache.commons.pool2.BasePooledObjectFactory;
|
||||||
|
import org.apache.commons.pool2.PooledObject;
|
||||||
|
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pool factory to create {@code ActiveLock} instances.
|
||||||
|
*/
|
||||||
|
public class PooledLockFactory extends BasePooledObjectFactory<ActiveLock> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActiveLock create() throws Exception {
|
||||||
|
return ActiveLock.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PooledObject<ActiveLock> wrap(ActiveLock activeLock) {
|
||||||
|
return new DefaultPooledObject<>(activeLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void activateObject(PooledObject<ActiveLock> pooledObject) {
|
||||||
|
pooledObject.getObject().resetCounter();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with this
|
||||||
|
* work for additional information regarding copyright ownership. The ASF
|
||||||
|
* licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.ozone.lock;
|
||||||
|
/*
|
||||||
|
This package contains the lock related classes.
|
||||||
|
*/
|
|
@ -1168,4 +1168,15 @@
|
||||||
(in compressed format), but doesn't require fast io access such as SSD.
|
(in compressed format), but doesn't require fast io access such as SSD.
|
||||||
</description>
|
</description>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
|
<property>
|
||||||
|
<name>hdds.lock.max.concurrency</name>
|
||||||
|
<value>100</value>
|
||||||
|
<tag>HDDS</tag>
|
||||||
|
<description>Locks in HDDS/Ozone uses object pool to maintain active locks
|
||||||
|
in the system, this property defines the max limit for the locks that
|
||||||
|
will be maintained in the pool.
|
||||||
|
</description>
|
||||||
|
</property>
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with this
|
||||||
|
* work for additional information regarding copyright ownership. The ASF
|
||||||
|
* licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.ozone.lock;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test-cases to test LockManager.
|
||||||
|
*/
|
||||||
|
public class TestLockManager {
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
public void testWithDifferentResource() {
|
||||||
|
LockManager<String> manager = new LockManager<>(new OzoneConfiguration());
|
||||||
|
manager.lock("/resourceOne");
|
||||||
|
// This should work, as they are different resource.
|
||||||
|
manager.lock("/resourceTwo");
|
||||||
|
manager.unlock("/resourceOne");
|
||||||
|
manager.unlock("/resourceTwo");
|
||||||
|
Assert.assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithSameResource() throws Exception {
|
||||||
|
LockManager<String> manager = new LockManager<>(new OzoneConfiguration());
|
||||||
|
manager.lock("/resourceOne");
|
||||||
|
AtomicBoolean gotLock = new AtomicBoolean(false);
|
||||||
|
new Thread(() -> {
|
||||||
|
manager.lock("/resourceOne");
|
||||||
|
gotLock.set(true);
|
||||||
|
manager.unlock("/resourceOne");
|
||||||
|
}).start();
|
||||||
|
// Let's give some time for the new thread to run
|
||||||
|
Thread.sleep(100);
|
||||||
|
// Since the new thread is trying to get lock on same object, it will wait.
|
||||||
|
Assert.assertFalse(gotLock.get());
|
||||||
|
manager.unlock("/resourceOne");
|
||||||
|
// Since we have released the lock, the new thread should have the lock
|
||||||
|
// now
|
||||||
|
// Let's give some time for the new thread to run
|
||||||
|
Thread.sleep(100);
|
||||||
|
Assert.assertTrue(gotLock.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with this
|
||||||
|
* work for additional information regarding copyright ownership. The ASF
|
||||||
|
* licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.ozone.lock;
|
||||||
|
/*
|
||||||
|
This package contains the lock related test classes.
|
||||||
|
*/
|
|
@ -79,9 +79,10 @@ public class BucketManagerImpl implements BucketManager {
|
||||||
@Override
|
@Override
|
||||||
public void createBucket(OmBucketInfo bucketInfo) throws IOException {
|
public void createBucket(OmBucketInfo bucketInfo) throws IOException {
|
||||||
Preconditions.checkNotNull(bucketInfo);
|
Preconditions.checkNotNull(bucketInfo);
|
||||||
metadataManager.writeLock().lock();
|
|
||||||
String volumeName = bucketInfo.getVolumeName();
|
String volumeName = bucketInfo.getVolumeName();
|
||||||
String bucketName = bucketInfo.getBucketName();
|
String bucketName = bucketInfo.getBucketName();
|
||||||
|
metadataManager.getLock().acquireVolumeLock(volumeName);
|
||||||
|
metadataManager.getLock().acquireBucketLock(volumeName, bucketName);
|
||||||
try {
|
try {
|
||||||
byte[] volumeKey = metadataManager.getVolumeKey(volumeName);
|
byte[] volumeKey = metadataManager.getVolumeKey(volumeName);
|
||||||
byte[] bucketKey = metadataManager.getBucketKey(volumeName, bucketName);
|
byte[] bucketKey = metadataManager.getBucketKey(volumeName, bucketName);
|
||||||
|
@ -118,7 +119,8 @@ public class BucketManagerImpl implements BucketManager {
|
||||||
}
|
}
|
||||||
throw ex;
|
throw ex;
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseBucketLock(volumeName, bucketName);
|
||||||
|
metadataManager.getLock().releaseVolumeLock(volumeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +135,7 @@ public class BucketManagerImpl implements BucketManager {
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Preconditions.checkNotNull(volumeName);
|
Preconditions.checkNotNull(volumeName);
|
||||||
Preconditions.checkNotNull(bucketName);
|
Preconditions.checkNotNull(bucketName);
|
||||||
metadataManager.readLock().lock();
|
metadataManager.getLock().acquireBucketLock(volumeName, bucketName);
|
||||||
try {
|
try {
|
||||||
byte[] bucketKey = metadataManager.getBucketKey(volumeName, bucketName);
|
byte[] bucketKey = metadataManager.getBucketKey(volumeName, bucketName);
|
||||||
byte[] value = metadataManager.getBucketTable().get(bucketKey);
|
byte[] value = metadataManager.getBucketTable().get(bucketKey);
|
||||||
|
@ -151,7 +153,7 @@ public class BucketManagerImpl implements BucketManager {
|
||||||
}
|
}
|
||||||
throw ex;
|
throw ex;
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.readLock().unlock();
|
metadataManager.getLock().releaseBucketLock(volumeName, bucketName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,18 +166,11 @@ public class BucketManagerImpl implements BucketManager {
|
||||||
@Override
|
@Override
|
||||||
public void setBucketProperty(OmBucketArgs args) throws IOException {
|
public void setBucketProperty(OmBucketArgs args) throws IOException {
|
||||||
Preconditions.checkNotNull(args);
|
Preconditions.checkNotNull(args);
|
||||||
metadataManager.writeLock().lock();
|
|
||||||
String volumeName = args.getVolumeName();
|
String volumeName = args.getVolumeName();
|
||||||
String bucketName = args.getBucketName();
|
String bucketName = args.getBucketName();
|
||||||
|
metadataManager.getLock().acquireBucketLock(volumeName, bucketName);
|
||||||
try {
|
try {
|
||||||
byte[] bucketKey = metadataManager.getBucketKey(volumeName, bucketName);
|
byte[] bucketKey = metadataManager.getBucketKey(volumeName, bucketName);
|
||||||
//Check if volume exists
|
|
||||||
if (metadataManager.getVolumeTable()
|
|
||||||
.get(metadataManager.getVolumeKey(volumeName)) == null) {
|
|
||||||
LOG.debug("volume: {} not found ", volumeName);
|
|
||||||
throw new OMException("Volume doesn't exist",
|
|
||||||
OMException.ResultCodes.FAILED_VOLUME_NOT_FOUND);
|
|
||||||
}
|
|
||||||
byte[] value = metadataManager.getBucketTable().get(bucketKey);
|
byte[] value = metadataManager.getBucketTable().get(bucketKey);
|
||||||
//Check if bucket exist
|
//Check if bucket exist
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -230,7 +225,7 @@ public class BucketManagerImpl implements BucketManager {
|
||||||
}
|
}
|
||||||
throw ex;
|
throw ex;
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseBucketLock(volumeName, bucketName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,16 +261,8 @@ public class BucketManagerImpl implements BucketManager {
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Preconditions.checkNotNull(volumeName);
|
Preconditions.checkNotNull(volumeName);
|
||||||
Preconditions.checkNotNull(bucketName);
|
Preconditions.checkNotNull(bucketName);
|
||||||
metadataManager.writeLock().lock();
|
metadataManager.getLock().acquireBucketLock(volumeName, bucketName);
|
||||||
try {
|
try {
|
||||||
//Check if volume exists
|
|
||||||
if (metadataManager.getVolumeTable()
|
|
||||||
.get(metadataManager.getVolumeKey(volumeName)) == null) {
|
|
||||||
LOG.debug("volume: {} not found ", volumeName);
|
|
||||||
throw new OMException("Volume doesn't exist",
|
|
||||||
OMException.ResultCodes.FAILED_VOLUME_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if bucket exists
|
//Check if bucket exists
|
||||||
byte[] bucketKey = metadataManager.getBucketKey(volumeName, bucketName);
|
byte[] bucketKey = metadataManager.getBucketKey(volumeName, bucketName);
|
||||||
if (metadataManager.getBucketTable().get(bucketKey) == null) {
|
if (metadataManager.getBucketTable().get(bucketKey) == null) {
|
||||||
|
@ -297,7 +284,7 @@ public class BucketManagerImpl implements BucketManager {
|
||||||
}
|
}
|
||||||
throw ex;
|
throw ex;
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseBucketLock(volumeName, bucketName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,12 +296,8 @@ public class BucketManagerImpl implements BucketManager {
|
||||||
String startBucket, String bucketPrefix, int maxNumOfBuckets)
|
String startBucket, String bucketPrefix, int maxNumOfBuckets)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Preconditions.checkNotNull(volumeName);
|
Preconditions.checkNotNull(volumeName);
|
||||||
metadataManager.readLock().lock();
|
return metadataManager.listBuckets(
|
||||||
try {
|
volumeName, startBucket, bucketPrefix, maxNumOfBuckets);
|
||||||
return metadataManager.listBuckets(
|
|
||||||
volumeName, startBucket, bucketPrefix, maxNumOfBuckets);
|
|
||||||
} finally {
|
|
||||||
metadataManager.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,44 +139,38 @@ public class KeyManagerImpl implements KeyManager {
|
||||||
public OmKeyLocationInfo allocateBlock(OmKeyArgs args, long clientID)
|
public OmKeyLocationInfo allocateBlock(OmKeyArgs args, long clientID)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Preconditions.checkNotNull(args);
|
Preconditions.checkNotNull(args);
|
||||||
metadataManager.writeLock().lock();
|
|
||||||
String volumeName = args.getVolumeName();
|
String volumeName = args.getVolumeName();
|
||||||
String bucketName = args.getBucketName();
|
String bucketName = args.getBucketName();
|
||||||
String keyName = args.getKeyName();
|
String keyName = args.getKeyName();
|
||||||
|
validateBucket(volumeName, bucketName);
|
||||||
|
byte[] openKey = metadataManager.getOpenKeyBytes(
|
||||||
|
volumeName, bucketName, keyName, clientID);
|
||||||
|
|
||||||
try {
|
byte[] keyData = metadataManager.getOpenKeyTable().get(openKey);
|
||||||
validateBucket(volumeName, bucketName);
|
if (keyData == null) {
|
||||||
byte[] openKey = metadataManager.getOpenKeyBytes(
|
LOG.error("Allocate block for a key not in open status in meta store" +
|
||||||
volumeName, bucketName, keyName, clientID);
|
" /{}/{}/{} with ID {}", volumeName, bucketName, keyName, clientID);
|
||||||
|
throw new OMException("Open Key not found",
|
||||||
byte[] keyData = metadataManager.getOpenKeyTable().get(openKey);
|
OMException.ResultCodes.FAILED_KEY_NOT_FOUND);
|
||||||
if (keyData == null) {
|
|
||||||
LOG.error("Allocate block for a key not in open status in meta store" +
|
|
||||||
" /{}/{}/{} with ID {}", volumeName, bucketName, keyName, clientID);
|
|
||||||
throw new OMException("Open Key not found",
|
|
||||||
OMException.ResultCodes.FAILED_KEY_NOT_FOUND);
|
|
||||||
}
|
|
||||||
OmKeyInfo keyInfo =
|
|
||||||
OmKeyInfo.getFromProtobuf(KeyInfo.parseFrom(keyData));
|
|
||||||
AllocatedBlock allocatedBlock =
|
|
||||||
scmBlockClient.allocateBlock(scmBlockSize, keyInfo.getType(),
|
|
||||||
keyInfo.getFactor(), omId);
|
|
||||||
OmKeyLocationInfo info = new OmKeyLocationInfo.Builder()
|
|
||||||
.setBlockID(allocatedBlock.getBlockID())
|
|
||||||
.setShouldCreateContainer(allocatedBlock.getCreateContainer())
|
|
||||||
.setLength(scmBlockSize)
|
|
||||||
.setOffset(0)
|
|
||||||
.build();
|
|
||||||
// current version not committed, so new blocks coming now are added to
|
|
||||||
// the same version
|
|
||||||
keyInfo.appendNewBlocks(Collections.singletonList(info));
|
|
||||||
keyInfo.updateModifcationTime();
|
|
||||||
metadataManager.getOpenKeyTable().put(openKey,
|
|
||||||
keyInfo.getProtobuf().toByteArray());
|
|
||||||
return info;
|
|
||||||
} finally {
|
|
||||||
metadataManager.writeLock().unlock();
|
|
||||||
}
|
}
|
||||||
|
OmKeyInfo keyInfo =
|
||||||
|
OmKeyInfo.getFromProtobuf(KeyInfo.parseFrom(keyData));
|
||||||
|
AllocatedBlock allocatedBlock =
|
||||||
|
scmBlockClient.allocateBlock(scmBlockSize, keyInfo.getType(),
|
||||||
|
keyInfo.getFactor(), omId);
|
||||||
|
OmKeyLocationInfo info = new OmKeyLocationInfo.Builder()
|
||||||
|
.setBlockID(allocatedBlock.getBlockID())
|
||||||
|
.setShouldCreateContainer(allocatedBlock.getCreateContainer())
|
||||||
|
.setLength(scmBlockSize)
|
||||||
|
.setOffset(0)
|
||||||
|
.build();
|
||||||
|
// current version not committed, so new blocks coming now are added to
|
||||||
|
// the same version
|
||||||
|
keyInfo.appendNewBlocks(Collections.singletonList(info));
|
||||||
|
keyInfo.updateModifcationTime();
|
||||||
|
metadataManager.getOpenKeyTable().put(openKey,
|
||||||
|
keyInfo.getProtobuf().toByteArray());
|
||||||
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -186,7 +180,7 @@ public class KeyManagerImpl implements KeyManager {
|
||||||
String bucketName = args.getBucketName();
|
String bucketName = args.getBucketName();
|
||||||
validateBucket(volumeName, bucketName);
|
validateBucket(volumeName, bucketName);
|
||||||
|
|
||||||
metadataManager.writeLock().lock();
|
metadataManager.getLock().acquireBucketLock(volumeName, bucketName);
|
||||||
String keyName = args.getKeyName();
|
String keyName = args.getKeyName();
|
||||||
ReplicationFactor factor = args.getFactor();
|
ReplicationFactor factor = args.getFactor();
|
||||||
ReplicationType type = args.getType();
|
ReplicationType type = args.getType();
|
||||||
|
@ -286,17 +280,17 @@ public class KeyManagerImpl implements KeyManager {
|
||||||
throw new OMException(ex.getMessage(),
|
throw new OMException(ex.getMessage(),
|
||||||
OMException.ResultCodes.FAILED_KEY_ALLOCATION);
|
OMException.ResultCodes.FAILED_KEY_ALLOCATION);
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseBucketLock(volumeName, bucketName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void commitKey(OmKeyArgs args, long clientID) throws IOException {
|
public void commitKey(OmKeyArgs args, long clientID) throws IOException {
|
||||||
Preconditions.checkNotNull(args);
|
Preconditions.checkNotNull(args);
|
||||||
metadataManager.writeLock().lock();
|
|
||||||
String volumeName = args.getVolumeName();
|
String volumeName = args.getVolumeName();
|
||||||
String bucketName = args.getBucketName();
|
String bucketName = args.getBucketName();
|
||||||
String keyName = args.getKeyName();
|
String keyName = args.getKeyName();
|
||||||
|
metadataManager.getLock().acquireBucketLock(volumeName, bucketName);
|
||||||
try {
|
try {
|
||||||
validateBucket(volumeName, bucketName);
|
validateBucket(volumeName, bucketName);
|
||||||
byte[] openKey = metadataManager.getOpenKeyBytes(volumeName, bucketName,
|
byte[] openKey = metadataManager.getOpenKeyBytes(volumeName, bucketName,
|
||||||
|
@ -329,17 +323,17 @@ public class KeyManagerImpl implements KeyManager {
|
||||||
throw new OMException(ex.getMessage(),
|
throw new OMException(ex.getMessage(),
|
||||||
OMException.ResultCodes.FAILED_KEY_ALLOCATION);
|
OMException.ResultCodes.FAILED_KEY_ALLOCATION);
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseBucketLock(volumeName, bucketName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OmKeyInfo lookupKey(OmKeyArgs args) throws IOException {
|
public OmKeyInfo lookupKey(OmKeyArgs args) throws IOException {
|
||||||
Preconditions.checkNotNull(args);
|
Preconditions.checkNotNull(args);
|
||||||
metadataManager.writeLock().lock();
|
|
||||||
String volumeName = args.getVolumeName();
|
String volumeName = args.getVolumeName();
|
||||||
String bucketName = args.getBucketName();
|
String bucketName = args.getBucketName();
|
||||||
String keyName = args.getKeyName();
|
String keyName = args.getKeyName();
|
||||||
|
metadataManager.getLock().acquireBucketLock(volumeName, bucketName);
|
||||||
try {
|
try {
|
||||||
byte[] keyBytes = metadataManager.getOzoneKeyBytes(
|
byte[] keyBytes = metadataManager.getOzoneKeyBytes(
|
||||||
volumeName, bucketName, keyName);
|
volumeName, bucketName, keyName);
|
||||||
|
@ -357,7 +351,7 @@ public class KeyManagerImpl implements KeyManager {
|
||||||
throw new OMException(ex.getMessage(),
|
throw new OMException(ex.getMessage(),
|
||||||
OMException.ResultCodes.FAILED_KEY_NOT_FOUND);
|
OMException.ResultCodes.FAILED_KEY_NOT_FOUND);
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseBucketLock(volumeName, bucketName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,7 +369,7 @@ public class KeyManagerImpl implements KeyManager {
|
||||||
ResultCodes.FAILED_INVALID_KEY_NAME);
|
ResultCodes.FAILED_INVALID_KEY_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
metadataManager.writeLock().lock();
|
metadataManager.getLock().acquireBucketLock(volumeName, bucketName);
|
||||||
try {
|
try {
|
||||||
// fromKeyName should exist
|
// fromKeyName should exist
|
||||||
byte[] fromKey = metadataManager.getOzoneKeyBytes(
|
byte[] fromKey = metadataManager.getOzoneKeyBytes(
|
||||||
|
@ -431,17 +425,17 @@ public class KeyManagerImpl implements KeyManager {
|
||||||
throw new OMException(ex.getMessage(),
|
throw new OMException(ex.getMessage(),
|
||||||
ResultCodes.FAILED_KEY_RENAME);
|
ResultCodes.FAILED_KEY_RENAME);
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseBucketLock(volumeName, bucketName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteKey(OmKeyArgs args) throws IOException {
|
public void deleteKey(OmKeyArgs args) throws IOException {
|
||||||
Preconditions.checkNotNull(args);
|
Preconditions.checkNotNull(args);
|
||||||
metadataManager.writeLock().lock();
|
|
||||||
String volumeName = args.getVolumeName();
|
String volumeName = args.getVolumeName();
|
||||||
String bucketName = args.getBucketName();
|
String bucketName = args.getBucketName();
|
||||||
String keyName = args.getKeyName();
|
String keyName = args.getKeyName();
|
||||||
|
metadataManager.getLock().acquireBucketLock(volumeName, bucketName);
|
||||||
try {
|
try {
|
||||||
byte[] objectKey = metadataManager.getOzoneKeyBytes(
|
byte[] objectKey = metadataManager.getOzoneKeyBytes(
|
||||||
volumeName, bucketName, keyName);
|
volumeName, bucketName, keyName);
|
||||||
|
@ -470,7 +464,7 @@ public class KeyManagerImpl implements KeyManager {
|
||||||
throw new OMException(ex.getMessage(), ex,
|
throw new OMException(ex.getMessage(), ex,
|
||||||
ResultCodes.FAILED_KEY_DELETION);
|
ResultCodes.FAILED_KEY_DELETION);
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseBucketLock(volumeName, bucketName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,12 +500,8 @@ public class KeyManagerImpl implements KeyManager {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<BlockGroup> getExpiredOpenKeys() throws IOException {
|
public List<BlockGroup> getExpiredOpenKeys() throws IOException {
|
||||||
metadataManager.readLock().lock();
|
return metadataManager.getExpiredOpenKeys();
|
||||||
try {
|
|
||||||
return metadataManager.getExpiredOpenKeys();
|
|
||||||
} finally {
|
|
||||||
metadataManager.readLock().unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.apache.hadoop.utils.db.Table;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OM metadata manager interface.
|
* OM metadata manager interface.
|
||||||
|
@ -51,18 +50,11 @@ public interface OMMetadataManager {
|
||||||
DBStore getStore();
|
DBStore getStore();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the read lock used on Metadata DB.
|
* Returns the OzoneManagerLock used on Metadata DB.
|
||||||
*
|
*
|
||||||
* @return readLock
|
* @return OzoneManagerLock
|
||||||
*/
|
*/
|
||||||
Lock readLock();
|
OzoneManagerLock getLock();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the write lock used on Metadata DB.
|
|
||||||
*
|
|
||||||
* @return writeLock
|
|
||||||
*/
|
|
||||||
Lock writeLock();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a volume return the corresponding DB key.
|
* Given a volume return the corresponding DB key.
|
||||||
|
|
|
@ -52,9 +52,6 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.apache.hadoop.hdds.server.ServerUtils.getOzoneMetaDirPath;
|
import static org.apache.hadoop.hdds.server.ServerUtils.getOzoneMetaDirPath;
|
||||||
|
@ -102,9 +99,7 @@ public class OmMetadataManagerImpl implements OMMetadataManager {
|
||||||
|
|
||||||
private final DBStore store;
|
private final DBStore store;
|
||||||
|
|
||||||
// TODO: Make this lock move into Table instead of *ONE* lock for the whole
|
private final OzoneManagerLock lock;
|
||||||
// DB.
|
|
||||||
private final ReadWriteLock lock;
|
|
||||||
private final long openKeyExpireThresholdMS;
|
private final long openKeyExpireThresholdMS;
|
||||||
|
|
||||||
private final Table userTable;
|
private final Table userTable;
|
||||||
|
@ -116,7 +111,7 @@ public class OmMetadataManagerImpl implements OMMetadataManager {
|
||||||
|
|
||||||
public OmMetadataManagerImpl(OzoneConfiguration conf) throws IOException {
|
public OmMetadataManagerImpl(OzoneConfiguration conf) throws IOException {
|
||||||
File metaDir = getOzoneMetaDirPath(conf);
|
File metaDir = getOzoneMetaDirPath(conf);
|
||||||
this.lock = new ReentrantReadWriteLock();
|
this.lock = new OzoneManagerLock(conf);
|
||||||
this.openKeyExpireThresholdMS = 1000 * conf.getInt(
|
this.openKeyExpireThresholdMS = 1000 * conf.getInt(
|
||||||
OZONE_OPEN_KEY_EXPIRE_THRESHOLD_SECONDS,
|
OZONE_OPEN_KEY_EXPIRE_THRESHOLD_SECONDS,
|
||||||
OZONE_OPEN_KEY_EXPIRE_THRESHOLD_SECONDS_DEFAULT);
|
OZONE_OPEN_KEY_EXPIRE_THRESHOLD_SECONDS_DEFAULT);
|
||||||
|
@ -280,23 +275,13 @@ public class OmMetadataManagerImpl implements OMMetadataManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the read lock used on Metadata DB.
|
* Returns the OzoneManagerLock used on Metadata DB.
|
||||||
*
|
*
|
||||||
* @return readLock
|
* @return OzoneManagerLock
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Lock readLock() {
|
public OzoneManagerLock getLock() {
|
||||||
return lock.readLock();
|
return lock;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the write lock used on Metadata DB.
|
|
||||||
*
|
|
||||||
* @return writeLock
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Lock writeLock() {
|
|
||||||
return lock.writeLock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with this
|
||||||
|
* work for additional information regarding copyright ownership. The ASF
|
||||||
|
* licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.ozone.om;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.ozone.lock.LockManager;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX;
|
||||||
|
import static org.apache.hadoop.ozone.OzoneConsts.OM_USER_PREFIX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides different locks to handle concurrency in OzoneMaster.
|
||||||
|
* We also maintain lock hierarchy, based on the weight.
|
||||||
|
*
|
||||||
|
* <table>
|
||||||
|
* <tr>
|
||||||
|
* <td><b> WEIGHT </b></td> <td><b> LOCK </b></td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td> 0 </td> <td> User Lock </td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td> 1 </td> <td> Volume Lock </td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td> 2 </td> <td> Bucket Lock </td>
|
||||||
|
* </tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* One cannot obtain a lower weight lock while holding a lock with higher
|
||||||
|
* weight. The other way around is possible. <br>
|
||||||
|
* <br>
|
||||||
|
* <p>
|
||||||
|
* For example:
|
||||||
|
* <br>
|
||||||
|
* -> acquireVolumeLock (will work)<br>
|
||||||
|
* +-> acquireBucketLock (will work)<br>
|
||||||
|
* +--> acquireUserLock (will throw Exception)<br>
|
||||||
|
* </p>
|
||||||
|
* <br>
|
||||||
|
*
|
||||||
|
* To acquire a user lock you should not hold any Volume/Bucket lock. Similarly
|
||||||
|
* to acquire a Volume lock you should not hold any Bucket lock.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public final class OzoneManagerLock {
|
||||||
|
|
||||||
|
private static final String VOLUME_LOCK = "volumeLock";
|
||||||
|
private static final String BUCKET_LOCK = "bucketLock";
|
||||||
|
|
||||||
|
|
||||||
|
private final LockManager<String> manager;
|
||||||
|
|
||||||
|
// To maintain locks held by current thread.
|
||||||
|
private final ThreadLocal<Map<String, AtomicInteger>> myLocks =
|
||||||
|
ThreadLocal.withInitial(() -> ImmutableMap.of(
|
||||||
|
VOLUME_LOCK, new AtomicInteger(0),
|
||||||
|
BUCKET_LOCK, new AtomicInteger(0)));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new OzoneManagerLock instance.
|
||||||
|
* @param conf Configuration object
|
||||||
|
*/
|
||||||
|
public OzoneManagerLock(Configuration conf) {
|
||||||
|
manager = new LockManager<>(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquires user lock on the given resource.
|
||||||
|
*
|
||||||
|
* <p>If the lock is not available then the current thread becomes
|
||||||
|
* disabled for thread scheduling purposes and lies dormant until the
|
||||||
|
* lock has been acquired.
|
||||||
|
*
|
||||||
|
* @param user User on which the lock has to be acquired
|
||||||
|
*/
|
||||||
|
public void acquireUserLock(String user) {
|
||||||
|
// Calling thread should not hold any volume or bucket lock.
|
||||||
|
if (hasAnyVolumeLock() || hasAnyBucketLock()) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Thread '" + Thread.currentThread().getName() +
|
||||||
|
"' cannot acquire user lock" +
|
||||||
|
" while holding volume/bucket lock(s).");
|
||||||
|
}
|
||||||
|
manager.lock(OM_USER_PREFIX + user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the user lock on given resource.
|
||||||
|
*/
|
||||||
|
public void releaseUserLock(String user) {
|
||||||
|
manager.unlock(OM_USER_PREFIX + user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquires volume lock on the given resource.
|
||||||
|
*
|
||||||
|
* <p>If the lock is not available then the current thread becomes
|
||||||
|
* disabled for thread scheduling purposes and lies dormant until the
|
||||||
|
* lock has been acquired.
|
||||||
|
*
|
||||||
|
* @param volume Volume on which the lock has to be acquired
|
||||||
|
*/
|
||||||
|
public void acquireVolumeLock(String volume) {
|
||||||
|
// Calling thread should not hold any bucket lock.
|
||||||
|
if (hasAnyBucketLock()) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Thread '" + Thread.currentThread().getName() +
|
||||||
|
"' cannot acquire volume lock while holding bucket lock(s).");
|
||||||
|
}
|
||||||
|
manager.lock(OM_KEY_PREFIX + volume);
|
||||||
|
myLocks.get().get(VOLUME_LOCK).incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the volume lock on given resource.
|
||||||
|
*/
|
||||||
|
public void releaseVolumeLock(String volume) {
|
||||||
|
manager.unlock(OM_KEY_PREFIX + volume);
|
||||||
|
myLocks.get().get(VOLUME_LOCK).decrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquires bucket lock on the given resource.
|
||||||
|
*
|
||||||
|
* <p>If the lock is not available then the current thread becomes
|
||||||
|
* disabled for thread scheduling purposes and lies dormant until the
|
||||||
|
* lock has been acquired.
|
||||||
|
*
|
||||||
|
* @param bucket Bucket on which the lock has to be acquired
|
||||||
|
*/
|
||||||
|
public void acquireBucketLock(String volume, String bucket) {
|
||||||
|
manager.lock(OM_KEY_PREFIX + volume + OM_KEY_PREFIX + bucket);
|
||||||
|
myLocks.get().get(BUCKET_LOCK).incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the bucket lock on given resource.
|
||||||
|
*/
|
||||||
|
public void releaseBucketLock(String volume, String bucket) {
|
||||||
|
manager.unlock(OM_KEY_PREFIX + volume + OM_KEY_PREFIX + bucket);
|
||||||
|
myLocks.get().get(BUCKET_LOCK).decrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the current thread holds any volume lock.
|
||||||
|
* @return true if current thread holds volume lock, else false
|
||||||
|
*/
|
||||||
|
private boolean hasAnyVolumeLock() {
|
||||||
|
return myLocks.get().get(VOLUME_LOCK).get() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the current thread holds any bucket lock.
|
||||||
|
* @return true if current thread holds bucket lock, else false
|
||||||
|
*/
|
||||||
|
private boolean hasAnyBucketLock() {
|
||||||
|
return myLocks.get().get(BUCKET_LOCK).get() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -126,7 +126,8 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
@Override
|
@Override
|
||||||
public void createVolume(OmVolumeArgs args) throws IOException {
|
public void createVolume(OmVolumeArgs args) throws IOException {
|
||||||
Preconditions.checkNotNull(args);
|
Preconditions.checkNotNull(args);
|
||||||
metadataManager.writeLock().lock();
|
metadataManager.getLock().acquireUserLock(args.getOwnerName());
|
||||||
|
metadataManager.getLock().acquireVolumeLock(args.getVolume());
|
||||||
try {
|
try {
|
||||||
byte[] dbVolumeKey = metadataManager.getVolumeKey(args.getVolume());
|
byte[] dbVolumeKey = metadataManager.getVolumeKey(args.getVolume());
|
||||||
byte[] volumeInfo = metadataManager.getVolumeTable().get(dbVolumeKey);
|
byte[] volumeInfo = metadataManager.getVolumeTable().get(dbVolumeKey);
|
||||||
|
@ -177,7 +178,8 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
throw (IOException) ex;
|
throw (IOException) ex;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseVolumeLock(args.getVolume());
|
||||||
|
metadataManager.getLock().releaseUserLock(args.getOwnerName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +194,8 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
public void setOwner(String volume, String owner) throws IOException {
|
public void setOwner(String volume, String owner) throws IOException {
|
||||||
Preconditions.checkNotNull(volume);
|
Preconditions.checkNotNull(volume);
|
||||||
Preconditions.checkNotNull(owner);
|
Preconditions.checkNotNull(owner);
|
||||||
metadataManager.writeLock().lock();
|
metadataManager.getLock().acquireUserLock(owner);
|
||||||
|
metadataManager.getLock().acquireVolumeLock(volume);
|
||||||
try {
|
try {
|
||||||
byte[] dbVolumeKey = metadataManager.getVolumeKey(volume);
|
byte[] dbVolumeKey = metadataManager.getVolumeKey(volume);
|
||||||
byte[] volInfo = metadataManager.getVolumeTable().get(dbVolumeKey);
|
byte[] volInfo = metadataManager.getVolumeTable().get(dbVolumeKey);
|
||||||
|
@ -235,7 +238,8 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
throw (IOException) ex;
|
throw (IOException) ex;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseVolumeLock(volume);
|
||||||
|
metadataManager.getLock().releaseUserLock(owner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +252,7 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
*/
|
*/
|
||||||
public void setQuota(String volume, long quota) throws IOException {
|
public void setQuota(String volume, long quota) throws IOException {
|
||||||
Preconditions.checkNotNull(volume);
|
Preconditions.checkNotNull(volume);
|
||||||
metadataManager.writeLock().lock();
|
metadataManager.getLock().acquireVolumeLock(volume);
|
||||||
try {
|
try {
|
||||||
byte[] dbVolumeKey = metadataManager.getVolumeKey(volume);
|
byte[] dbVolumeKey = metadataManager.getVolumeKey(volume);
|
||||||
byte[] volInfo = metadataManager.getVolumeTable().get(dbVolumeKey);
|
byte[] volInfo = metadataManager.getVolumeTable().get(dbVolumeKey);
|
||||||
|
@ -279,7 +283,7 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
}
|
}
|
||||||
throw ex;
|
throw ex;
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseVolumeLock(volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +295,7 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
*/
|
*/
|
||||||
public OmVolumeArgs getVolumeInfo(String volume) throws IOException {
|
public OmVolumeArgs getVolumeInfo(String volume) throws IOException {
|
||||||
Preconditions.checkNotNull(volume);
|
Preconditions.checkNotNull(volume);
|
||||||
metadataManager.readLock().lock();
|
metadataManager.getLock().acquireVolumeLock(volume);
|
||||||
try {
|
try {
|
||||||
byte[] dbVolumeKey = metadataManager.getVolumeKey(volume);
|
byte[] dbVolumeKey = metadataManager.getVolumeKey(volume);
|
||||||
byte[] volInfo = metadataManager.getVolumeTable().get(dbVolumeKey);
|
byte[] volInfo = metadataManager.getVolumeTable().get(dbVolumeKey);
|
||||||
|
@ -310,7 +314,7 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
}
|
}
|
||||||
throw ex;
|
throw ex;
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.readLock().unlock();
|
metadataManager.getLock().releaseVolumeLock(volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,7 +327,15 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
@Override
|
@Override
|
||||||
public void deleteVolume(String volume) throws IOException {
|
public void deleteVolume(String volume) throws IOException {
|
||||||
Preconditions.checkNotNull(volume);
|
Preconditions.checkNotNull(volume);
|
||||||
metadataManager.writeLock().lock();
|
String owner;
|
||||||
|
metadataManager.getLock().acquireVolumeLock(volume);
|
||||||
|
try {
|
||||||
|
owner = getVolumeInfo(volume).getOwnerName();
|
||||||
|
} finally {
|
||||||
|
metadataManager.getLock().releaseVolumeLock(volume);
|
||||||
|
}
|
||||||
|
metadataManager.getLock().acquireUserLock(owner);
|
||||||
|
metadataManager.getLock().acquireVolumeLock(volume);
|
||||||
try {
|
try {
|
||||||
|
|
||||||
byte[] dbVolumeKey = metadataManager.getVolumeKey(volume);
|
byte[] dbVolumeKey = metadataManager.getVolumeKey(volume);
|
||||||
|
@ -359,7 +371,8 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
throw (IOException) ex;
|
throw (IOException) ex;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.writeLock().unlock();
|
metadataManager.getLock().releaseVolumeLock(volume);
|
||||||
|
metadataManager.getLock().releaseUserLock(owner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,7 +388,7 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Preconditions.checkNotNull(volume);
|
Preconditions.checkNotNull(volume);
|
||||||
Preconditions.checkNotNull(userAcl);
|
Preconditions.checkNotNull(userAcl);
|
||||||
metadataManager.readLock().lock();
|
metadataManager.getLock().acquireVolumeLock(volume);
|
||||||
try {
|
try {
|
||||||
byte[] dbVolumeKey = metadataManager.getVolumeKey(volume);
|
byte[] dbVolumeKey = metadataManager.getVolumeKey(volume);
|
||||||
byte[] volInfo = metadataManager.getVolumeTable().get(dbVolumeKey);
|
byte[] volInfo = metadataManager.getVolumeTable().get(dbVolumeKey);
|
||||||
|
@ -395,7 +408,7 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
}
|
}
|
||||||
throw ex;
|
throw ex;
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.readLock().unlock();
|
metadataManager.getLock().releaseVolumeLock(volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,12 +418,12 @@ public class VolumeManagerImpl implements VolumeManager {
|
||||||
@Override
|
@Override
|
||||||
public List<OmVolumeArgs> listVolumes(String userName,
|
public List<OmVolumeArgs> listVolumes(String userName,
|
||||||
String prefix, String startKey, int maxKeys) throws IOException {
|
String prefix, String startKey, int maxKeys) throws IOException {
|
||||||
metadataManager.readLock().lock();
|
metadataManager.getLock().acquireUserLock(userName);
|
||||||
try {
|
try {
|
||||||
return metadataManager.listVolumes(
|
return metadataManager.listVolumes(
|
||||||
userName, prefix, startKey, maxKeys);
|
userName, prefix, startKey, maxKeys);
|
||||||
} finally {
|
} finally {
|
||||||
metadataManager.readLock().unlock();
|
metadataManager.getLock().releaseUserLock(userName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with this
|
||||||
|
* work for additional information regarding copyright ownership. The ASF
|
||||||
|
* licenses this file to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.ozone.om;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains test-cases to verify OzoneManagerLock.
|
||||||
|
*/
|
||||||
|
public class TestOzoneManagerLock {
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
public void testDifferentUserLock() {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireUserLock("userOne");
|
||||||
|
lock.acquireUserLock("userTwo");
|
||||||
|
lock.releaseUserLock("userOne");
|
||||||
|
lock.releaseUserLock("userTwo");
|
||||||
|
Assert.assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSameUserLock() throws Exception {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireUserLock("userOne");
|
||||||
|
AtomicBoolean gotLock = new AtomicBoolean(false);
|
||||||
|
new Thread(() -> {
|
||||||
|
lock.acquireUserLock("userOne");
|
||||||
|
gotLock.set(true);
|
||||||
|
lock.releaseUserLock("userOne");
|
||||||
|
}).start();
|
||||||
|
// Let's give some time for the new thread to run
|
||||||
|
Thread.sleep(100);
|
||||||
|
// Since the new thread is trying to get lock on same user, it will wait.
|
||||||
|
Assert.assertFalse(gotLock.get());
|
||||||
|
lock.releaseUserLock("userOne");
|
||||||
|
// Since we have released the lock, the new thread should have the lock
|
||||||
|
// now
|
||||||
|
// Let's give some time for the new thread to run
|
||||||
|
Thread.sleep(100);
|
||||||
|
Assert.assertTrue(gotLock.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
public void testDifferentVolumeLock() {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireVolumeLock("volOne");
|
||||||
|
lock.acquireVolumeLock("volTwo");
|
||||||
|
lock.releaseVolumeLock("volOne");
|
||||||
|
lock.releaseVolumeLock("volTwo");
|
||||||
|
Assert.assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSameVolumeLock() throws Exception {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireVolumeLock("volOne");
|
||||||
|
AtomicBoolean gotLock = new AtomicBoolean(false);
|
||||||
|
new Thread(() -> {
|
||||||
|
lock.acquireVolumeLock("volOne");
|
||||||
|
gotLock.set(true);
|
||||||
|
lock.releaseVolumeLock("volOne");
|
||||||
|
}).start();
|
||||||
|
// Let's give some time for the new thread to run
|
||||||
|
Thread.sleep(100);
|
||||||
|
// Since the new thread is trying to get lock on same user, it will wait.
|
||||||
|
Assert.assertFalse(gotLock.get());
|
||||||
|
lock.releaseVolumeLock("volOne");
|
||||||
|
// Since we have released the lock, the new thread should have the lock
|
||||||
|
// now
|
||||||
|
// Let's give some time for the new thread to run
|
||||||
|
Thread.sleep(100);
|
||||||
|
Assert.assertTrue(gotLock.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
public void testDifferentBucketLock() {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireBucketLock("volOne", "bucketOne");
|
||||||
|
lock.acquireBucketLock("volOne", "bucketTwo");
|
||||||
|
lock.releaseBucketLock("volOne", "bucketTwo");
|
||||||
|
lock.releaseBucketLock("volOne", "bucketOne");
|
||||||
|
Assert.assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSameBucketLock() throws Exception {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireBucketLock("volOne", "bucketOne");
|
||||||
|
AtomicBoolean gotLock = new AtomicBoolean(false);
|
||||||
|
new Thread(() -> {
|
||||||
|
lock.acquireBucketLock("volOne", "bucketOne");
|
||||||
|
gotLock.set(true);
|
||||||
|
lock.releaseBucketLock("volOne", "bucketOne");
|
||||||
|
}).start();
|
||||||
|
// Let's give some time for the new thread to run
|
||||||
|
Thread.sleep(100);
|
||||||
|
// Since the new thread is trying to get lock on same user, it will wait.
|
||||||
|
Assert.assertFalse(gotLock.get());
|
||||||
|
lock.releaseBucketLock("volOne", "bucketOne");
|
||||||
|
// Since we have released the lock, the new thread should have the lock
|
||||||
|
// now
|
||||||
|
// Let's give some time for the new thread to run
|
||||||
|
Thread.sleep(100);
|
||||||
|
Assert.assertTrue(gotLock.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
public void testVolumeLockAfterUserLock() {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireUserLock("userOne");
|
||||||
|
lock.acquireVolumeLock("volOne");
|
||||||
|
lock.releaseVolumeLock("volOne");
|
||||||
|
lock.releaseUserLock("userOne");
|
||||||
|
Assert.assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
public void testBucketLockAfterVolumeLock() {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireVolumeLock("volOne");
|
||||||
|
lock.acquireBucketLock("volOne", "bucketOne");
|
||||||
|
lock.releaseBucketLock("volOne", "bucketOne");
|
||||||
|
lock.releaseVolumeLock("volOne");
|
||||||
|
Assert.assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
public void testBucketLockAfterVolumeLockAfterUserLock() {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireUserLock("userOne");
|
||||||
|
lock.acquireVolumeLock("volOne");
|
||||||
|
lock.acquireBucketLock("volOne", "bucketOne");
|
||||||
|
lock.releaseBucketLock("volOne", "bucketOne");
|
||||||
|
lock.releaseVolumeLock("volOne");
|
||||||
|
lock.releaseUserLock("userOne");
|
||||||
|
Assert.assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserLockAfterVolumeLock() {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireVolumeLock("volOne");
|
||||||
|
try {
|
||||||
|
lock.acquireUserLock("userOne");
|
||||||
|
Assert.fail();
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
String msg =
|
||||||
|
"cannot acquire user lock while holding volume/bucket lock(s).";
|
||||||
|
Assert.assertTrue(ex.getMessage().contains(msg));
|
||||||
|
}
|
||||||
|
lock.releaseVolumeLock("volOne");
|
||||||
|
Assert.assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVolumeLockAfterBucketLock() {
|
||||||
|
OzoneManagerLock lock = new OzoneManagerLock(new OzoneConfiguration());
|
||||||
|
lock.acquireBucketLock("volOne", "bucketOne");
|
||||||
|
try {
|
||||||
|
lock.acquireVolumeLock("volOne");
|
||||||
|
Assert.fail();
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
String msg =
|
||||||
|
"cannot acquire volume lock while holding bucket lock(s).";
|
||||||
|
Assert.assertTrue(ex.getMessage().contains(msg));
|
||||||
|
}
|
||||||
|
lock.releaseBucketLock("volOne", "bucketOne");
|
||||||
|
Assert.assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue