ARTEMIS-3802 Avoiding races on deleteWithID and Iterator.remove
This commit is contained in:
parent
502461fc9b
commit
1946351fce
|
@ -103,7 +103,7 @@ public class LinkedListImpl<E> implements LinkedList<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public E removeWithID(String listID, long id) {
|
public synchronized E removeWithID(String listID, long id) {
|
||||||
assert nodeStore != null; // it is assumed the code will call setNodeStore before callin removeWithID
|
assert nodeStore != null; // it is assumed the code will call setNodeStore before callin removeWithID
|
||||||
|
|
||||||
Node<E> node = nodeStore.getNode(listID, id);
|
Node<E> node = nodeStore.getNode(listID, id);
|
||||||
|
@ -422,67 +422,77 @@ public class LinkedListImpl<E> implements LinkedList<E> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext() {
|
public boolean hasNext() {
|
||||||
Node<E> e = getNode();
|
synchronized (LinkedListImpl.this) {
|
||||||
|
Node<E> e = getNode();
|
||||||
|
|
||||||
if (e != null && (e != last || repeat)) {
|
if (e != null && (e != last || repeat)) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return canAdvance();
|
||||||
}
|
}
|
||||||
|
|
||||||
return canAdvance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public E next() {
|
public E next() {
|
||||||
Node<E> e = getNode();
|
synchronized (LinkedListImpl.this) {
|
||||||
|
Node<E> e = getNode();
|
||||||
|
|
||||||
if (repeat) {
|
if (repeat) {
|
||||||
repeat = false;
|
repeat = false;
|
||||||
|
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
return e.val();
|
return e.val();
|
||||||
} else {
|
} else {
|
||||||
|
if (canAdvance()) {
|
||||||
|
advance();
|
||||||
|
|
||||||
|
e = getNode();
|
||||||
|
|
||||||
|
return e.val();
|
||||||
|
} else {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e == null || e == last) {
|
||||||
if (canAdvance()) {
|
if (canAdvance()) {
|
||||||
advance();
|
advance();
|
||||||
|
|
||||||
e = getNode();
|
e = getNode();
|
||||||
|
|
||||||
return e.val();
|
|
||||||
} else {
|
} else {
|
||||||
throw new NoSuchElementException();
|
throw new NoSuchElementException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last = e;
|
||||||
|
|
||||||
|
repeat = false;
|
||||||
|
|
||||||
|
return e.val();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e == null || e == last) {
|
|
||||||
if (canAdvance()) {
|
|
||||||
advance();
|
|
||||||
|
|
||||||
e = getNode();
|
|
||||||
} else {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
last = e;
|
|
||||||
|
|
||||||
repeat = false;
|
|
||||||
|
|
||||||
return e.val();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void remove() {
|
public void remove() {
|
||||||
if (last == null) {
|
synchronized (LinkedListImpl.this) {
|
||||||
throw new NoSuchElementException();
|
if (last == null) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node<E> prev = current.prev;
|
||||||
|
|
||||||
|
if (prev != null) {
|
||||||
|
LinkedListImpl.this.removeAfter(prev);
|
||||||
|
|
||||||
|
last = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current == null) {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkedListImpl.this.removeAfter(current.prev);
|
|
||||||
|
|
||||||
last = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -22,6 +22,12 @@ import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.CyclicBarrier;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import io.netty.util.collection.LongObjectHashMap;
|
import io.netty.util.collection.LongObjectHashMap;
|
||||||
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
||||||
|
@ -313,6 +319,83 @@ public class LinkedListTest extends ActiveMQTestBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRaceRemovingID() throws Exception {
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||||
|
|
||||||
|
int elements = 1000;
|
||||||
|
try {
|
||||||
|
LinkedListImpl<ObservableNode> objs = new LinkedListImpl<>();
|
||||||
|
ListNodeStore nodeStore = new ListNodeStore();
|
||||||
|
objs.setNodeStore(nodeStore);
|
||||||
|
final String serverID = RandomUtil.randomString();
|
||||||
|
|
||||||
|
for (int i = 0; i < elements; i++) {
|
||||||
|
objs.addHead(new ObservableNode(serverID, i));
|
||||||
|
}
|
||||||
|
Assert.assertEquals(elements, objs.size());
|
||||||
|
|
||||||
|
CyclicBarrier barrier = new CyclicBarrier(2);
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(2);
|
||||||
|
AtomicInteger errors = new AtomicInteger(0);
|
||||||
|
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
barrier.await(10, TimeUnit.SECONDS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < elements; i++) {
|
||||||
|
objs.removeWithID(serverID, i);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
errors.incrementAndGet();
|
||||||
|
} finally {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
executor.execute(() -> {
|
||||||
|
LinkedListIterator iterator = objs.iterator();
|
||||||
|
|
||||||
|
try {
|
||||||
|
barrier.await(10, TimeUnit.SECONDS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Object value = iterator.next();
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (e instanceof NoSuchElementException) {
|
||||||
|
// this is ok
|
||||||
|
} else {
|
||||||
|
e.printStackTrace();
|
||||||
|
errors.incrementAndGet();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
Assert.assertEquals(0, objs.size());
|
||||||
|
|
||||||
|
Assert.assertEquals(0, errors.get());
|
||||||
|
} finally {
|
||||||
|
executor.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddHeadAndRemove() {
|
public void testAddHeadAndRemove() {
|
||||||
LinkedListImpl<ObservableNode> objs = new LinkedListImpl<>();
|
LinkedListImpl<ObservableNode> objs = new LinkedListImpl<>();
|
||||||
|
|
Loading…
Reference in New Issue