Skip to content

Commit d898982

Browse files
committed
Race in LookPool may cause used Resources to be evicted
1 parent 6d99656 commit d898982

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

dora/core/common/src/main/java/alluxio/collections/LockPool.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import alluxio.resource.LockResource;
1717
import alluxio.resource.RWLockResource;
1818
import alluxio.resource.RefCountLockResource;
19+
import alluxio.util.AlluxioFaultInjector;
1920
import alluxio.util.ThreadFactoryUtils;
2021

2122
import com.google.common.annotations.VisibleForTesting;
@@ -165,8 +166,12 @@ private void awaitAndEvict() throws InterruptedException {
165166
candidate.mIsAccessed = false;
166167
} else {
167168
if (candidate.mRefCount.compareAndSet(0, Integer.MIN_VALUE)) {
168-
mIterator.remove();
169-
numToEvict--;
169+
AlluxioFaultInjector.get().blockUtilAllocatedNewResource();
170+
Resource tmpResource = mPool.compute(candidateMapEntry.getKey(),
171+
(k, v) -> v == candidate ? null : v);
172+
if (tmpResource == null) {
173+
numToEvict--;
174+
}
170175
}
171176
}
172177
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
3+
* (the "License"). You may not use this work except in compliance with the License, which is
4+
* available at www.apache.org/licenses/LICENSE-2.0
5+
*
6+
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
7+
* either express or implied, as more fully set forth in the License.
8+
*
9+
* See the NOTICE file distributed with this work for information regarding copyright ownership.
10+
*/
11+
12+
package alluxio.util;
13+
14+
import com.google.common.annotations.VisibleForTesting;
15+
16+
/**
17+
* Used for injecting faults in Alluxio tests.
18+
* Calls into this are a no-op in production code.
19+
*/
20+
@VisibleForTesting
21+
public class AlluxioFaultInjector {
22+
private static AlluxioFaultInjector instance = new AlluxioFaultInjector();
23+
24+
public static AlluxioFaultInjector get() {
25+
return instance;
26+
}
27+
28+
public static void set(AlluxioFaultInjector injector) {
29+
instance = injector;
30+
}
31+
32+
/**
33+
* Used as a hook to inject intercept when evicting unused resource from pool.
34+
*/
35+
public void blockUtilAllocatedNewResource() {
36+
37+
}
38+
}

dora/core/common/src/test/java/alluxio/collections/LockPoolTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

1717
import alluxio.concurrent.LockMode;
1818
import alluxio.resource.LockResource;
19+
import alluxio.util.AlluxioFaultInjector;
1920
import alluxio.util.CommonUtils;
2021

2122
import org.junit.After;
2223
import org.junit.Before;
2324
import org.junit.Test;
2425

26+
import java.util.ArrayList;
27+
import java.util.List;
28+
import java.util.concurrent.CountDownLatch;
2529
import java.util.concurrent.locks.ReentrantReadWriteLock;
2630

2731
/**
@@ -141,4 +145,53 @@ public void referencedLockTest() {
141145
assertTrue(lock1.hasSameLock(mPool.get(50, LockMode.READ)));
142146
assertTrue(lock2.hasSameLock(mPool.get(100, LockMode.READ)));
143147
}
148+
149+
@Test(timeout = 10000)
150+
public void evictorTest() throws InterruptedException {
151+
List<LockResource> unClosedLocks = new ArrayList<>();
152+
CountDownLatch mainThreadDownLatch = new CountDownLatch(1);
153+
CountDownLatch evictorDownLatch = new CountDownLatch(1);
154+
AlluxioFaultInjector.set(new AlluxioFaultInjector() {
155+
public void blockUtilAllocatedNewResource() {
156+
try {
157+
mainThreadDownLatch.countDown();
158+
evictorDownLatch.await();
159+
} catch (InterruptedException e) {
160+
throw new RuntimeException(e);
161+
}
162+
}
163+
});
164+
165+
// Acquire 16 locks and just release the lock-3.
166+
for (int i = 1; i <= 16; i++) {
167+
LockResource lock = mPool.get(i, LockMode.READ);
168+
if (i == 3) {
169+
lock.close();
170+
} else {
171+
unClosedLocks.add(lock);
172+
}
173+
}
174+
assertEquals(16, mPool.size());
175+
176+
// Acquire a new Lock to trigger evictor. And the Evictor will release the
177+
// unused Resource-3, but it will be blocked by evictorDownLatch.
178+
LockResource lock17 = mPool.get(17, LockMode.READ);
179+
unClosedLocks.add(lock17);
180+
181+
// Acquire a resource for key 3 again, currently the Evictor is releasing
182+
// the old Resource-3, but it is blocked right now.
183+
mainThreadDownLatch.await();
184+
LockResource lock3 = mPool.get(3, LockMode.READ);
185+
unClosedLocks.add(lock3);
186+
187+
// Allow the evictor to continue.
188+
evictorDownLatch.countDown();
189+
190+
// Sleep some millis to wait for the Evictor.
191+
Thread.sleep(100);
192+
193+
// The Resource 3 should be still in the mPool.
194+
assertEquals(17, mPool.size());
195+
unClosedLocks.forEach(k -> k.close());
196+
}
144197
}

0 commit comments

Comments
 (0)