Skip to content

Conversation

@errose28
Copy link
Owner

What changes were proposed in this pull request?

Prototype for the granular locking framework OBS implementation. The following aspects are considered in this design:

  • OmLockInfo will be consumed by all of the ~100 OM requests, so it should be as minimal as possible.
  • OmRequestGatekeeper provides one public method with a simple contract, and an initial implementation that is as simple as possible.
    • If we want to create a generic utility class to handle bins of striped locks, we can do that at a later time with no affect outside of this class.
    • For this reason, we should not add a generic component like this until it is needed.
  • Granular locking of Ozone's namespace will always be based on volume, buckets, keys, and later prefixes.
    • We do not need an enum of lock types, polymorphism, or anything more generic. The added complexity will provide no benefit.
  • Locks will always be of type read or write.
    • We do not need an enum of arbitrary lock types, because there is no underlying implementation for them to map to.
  • Timeouts provide an extra safety precaution
    • The current OM request flow does not have timeouts because there is no point prior to a request starting where we can wait and/or cleanly fail it.
    • This is a feature we gain with the gatekeeper, so we should take advantage of it by using timeouts to defensively handle issues.
    • The timeout logic fits in ~10 lines of code, so the minor complexity addition seems worth it for the added safety.

Since this is prototype/demonstration code, see inline for comments on how the pieces will fit together.

What is the link to the Apache JIRA

HDDS-12356

How was this patch tested?

N/A, for demonstration and discussion only.

Copy link

@szetszwo szetszwo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@errose28 , thanks for the suggestion! Please see the comments inlined.

*/
public final class LockInfo implements Comparable<LockInfo> {
private final String key;
private final boolean isWriteLock;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +73 to +107
public AutoCloseable lock(OmLockInfo lockInfo) throws InterruptedException, TimeoutException {
// Common case is 1 volume, 1 bucket, and at most 2 key locks.
List<Lock> locks = new ArrayList<>(4);

Optional<LockInfo> optionalVolumeLock = lockInfo.getVolumeLock();
Optional<LockInfo> optionalBucketLock = lockInfo.getBucketLock();
Optional<Set<LockInfo>> optionalKeyLocks = lockInfo.getKeyLocks();

if (optionalVolumeLock.isPresent()) {
LockInfo volumeLockInfo = optionalVolumeLock.get();
if (volumeLockInfo.isWriteLock()) {
locks.add(volumeLocks.get(volumeLockInfo.getKey()).writeLock());
} else {
locks.add(volumeLocks.get(volumeLockInfo.getKey()).readLock());
}
}

if (optionalBucketLock.isPresent()) {
LockInfo bucketLockInfo = optionalBucketLock.get();
if (bucketLockInfo.isWriteLock()) {
locks.add(bucketLocks.get(bucketLockInfo.getKey()).writeLock());
} else {
locks.add(bucketLocks.get(bucketLockInfo.getKey()).readLock());
}
}

if (optionalKeyLocks.isPresent()) {
for (Lock keyLock: keyLocks.bulkGet(optionalKeyLocks.get())) {
locks.add(keyLock);
}
}

acquireLocks(locks);
return () -> releaseLocks(locks);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lock overhead probably is quite high in this method.


@Override
public int compareTo(@NotNull LockInfo other) {
return Integer.compare(hashCode(), other.hashCode());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort by hashCode won't work with guava Striped. For example

  • the number of stripe: 1024.
  • hash code A = 1
  • hash code B = 1025.

compareTo will return non-zero but they are mapped to the same lock.

Comment on lines +40 to +42
private final LockInfo volumeLock;
private final LockInfo bucketLock;
private final Set<LockInfo> keyLocks;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we require locking on two or more volumes/buckets later on, it will require a lot of code change.

Comment on lines +118 to +135
private void acquireLocks(List<Lock> locks) throws TimeoutException, InterruptedException {
List<Lock> acquiredLocks = new ArrayList<>(locks.size());
for (Lock lock: locks) {
if (lock.tryLock(timeoutMillis, TimeUnit.MILLISECONDS)) {
try {
acquiredLocks.add(lock);
} catch (Throwable e) {
// We acquired this lock but were unable to add it to our acquired locks list.
lock.unlock();
releaseLocks(acquiredLocks);
throw e;
}
} else {
releaseLocks(acquiredLocks);
throw new TimeoutException("Failed to acquire lock after the given timeout.");
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concurrently, if the lock.tryLock(..) is interrupted, the partial lock won't be released. Getting everything right with timeout is not easy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants