Skip to content

HDDS-15909. Refactor OzoneManagerLock.#10852

Open
szetszwo wants to merge 2 commits into
apache:masterfrom
szetszwo:HDDS-15909
Open

HDDS-15909. Refactor OzoneManagerLock.#10852
szetszwo wants to merge 2 commits into
apache:masterfrom
szetszwo:HDDS-15909

Conversation

@szetszwo

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

//OzoneManagerLock
  private final Map<Class<? extends Resource>,
      Pair<Map<Resource, Striped<ReadWriteLock>>, ResourceLockTracker>> resourcelockMap;

The resourcelockMap above is

  • inefficient: use a Map (the outer map) for only two elements.
  • hard to read/understand: too many layers
  • Apache Commons Pair (see HDDS-15908).
  • Not type safe: ResourceLockTracker is generic

What is the link to the Apache JIRA

HDDS-15909

How was this patch tested?

By existing tests.

@szetszwo
szetszwo requested a review from spacemonkd July 23, 2026 16:34
private OMLockMetrics omLockMetrics;
private final OMLockMetrics omLockMetrics = OMLockMetrics.create();

class ResourceLocks<R extends Resource> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a question. Here we are having three levels of nested classes:
OzoneManagerLock { ResourceLocks { Context }}

As far as I understand ResourceLocks only access the outer class for the metrics.

Could extracting this to a separate private class be better? Or maybe making it a static class and passing the metrics object might make it easier to read?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Context is not very useful. Let's remove it.

return locks;
}

private <T extends Resource> ResourceLocks<T> getResourceLocks(Resource instance) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: Is it better to declare this as -

Suggested change
private <T extends Resource> ResourceLocks<T> getResourceLocks(Resource instance) {
private ResourceLocks<?> getResourceLocks(Resource instance) {

Because if we say it is a generic T then "T" has no relationship with instance. The compiler is picking T from wherever the call is being made for this method.

So if we have a scenario like (this won't happen I understand and it is hypothetical):

ResourceLocks<LeveledResource> locks = getResourceLocks(DAGLeveledResource.BOOTSTRAP_LOCK);

then T = LeveledResource and no ClassCastException is thrown here.

We do check in cast():

 resource.getClass() != tracker.getResourceClass()

which I guess gives us the safety, so this is just a nit from my end.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's a good point! ResourceLocks<?> seems better

Map<LeveledResource, Striped<ReadWriteLock>> stripedLockMap = new EnumMap<>(LeveledResource.class);
for (LeveledResource r : LeveledResource.values()) {
stripedLockMap.put(r, createStripeLock(r, conf));
private <T extends Resource> ResourceLocks<T> cast(ResourceLocks<?> resourceLocks) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: double space

Suggested change
private <T extends Resource> ResourceLocks<T> cast(ResourceLocks<?> resourceLocks) {
private <T extends Resource> ResourceLocks<T> cast(ResourceLocks<?> resourceLocks) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The method is removed now.

.flatMap(rlm -> ((ResourceLockTracker<? extends Resource>)rlm).getCurrentLockedResources())
.map(Resource::getName)
.collect(Collectors.toList());
int getCurrentLockSizeForTesting() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This simplifies the method, but then in tests we will only see the count.
For example it might be something like: expected 1 but got 2.

Previously I think it would have given the list of the locks as well to help debug any test issues. This is removing that ability. Do you think it might be good to return the locks that are held as well to debug later on?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Previously I think it would have given the list ...

No, the test currently never uses the List.

+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/lock/TestKeyPathLock.java
@@ -216,7 +216,7 @@ private void testDiffKeyPathWriteLockMultiThreadingUtil(
     // Waiting for all the threads to be instantiated/to reach
     // acquireWriteLock.
     countDown.countDown();
-    assertEquals(1, lock.getCurrentLocks().size());
+    assertEquals(1, lock.getCurrentLockSizeForTesting());

}

long getWaitingTimeNanos() {
return Time.monotonicNowNanos() - startWaitingTimeNanos;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As far as I understand this is inheriting the previous behaviour where all lock time was summed up.

So:
lock 1: 10ms
lock 2: 20ms
lock 3: 10ms

This would return the total time instead of per lock waiting time. However with the context class I would expect per lock wait to be accounted for. Maybe we can add a comment saying that this is still sum of the total wait time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Context is removed, so the code is reverted back as before.

@spacemonkd spacemonkd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR @szetszwo. Overall it looks good to me, just had some questions, could you take a look?

@szetszwo

Copy link
Copy Markdown
Contributor Author

@spacemonkd , thanks for review this! Just pushed a change to address your comments.

OMLockDetails acquire(Resource resource, boolean isRead,
Function<Striped<ReentrantReadWriteLock>, Iterable<ReentrantReadWriteLock>> getLocks) {
final R r = assertAcquire(resource);
final long startWaitingTimeNanos = Time.monotonicNow();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

monotonicNow() is in miliseconds, but we need to have nanoseconds?

Suggested change
final long startWaitingTimeNanos = Time.monotonicNow();
final long startWaitingTimeNanos = Time.monotonicNowNanos();

@spacemonkd spacemonkd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the update @szetszwo , just one comment. Apart from that this looks good to me.

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.

2 participants