LUCENE-9001: Fix race condition in SetOnce (#931)

This commit is contained in:
Przemko Robakowski 2019-10-14 18:33:46 +02:00 committed by Adrien Grand
parent da73a6aff5
commit dad15ba15e
3 changed files with 36 additions and 13 deletions

View File

@ -25,8 +25,8 @@ Optimizations
(Ignacio Vera, Adrien Grand)
Bug Fixes
---------------------
(No changes)
* LUCENE-9001: Fix race condition in SetOnce. (Przemko Robakowski)
Other
---------------------

View File

@ -16,7 +16,7 @@
*/
package org.apache.lucene.util;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
@ -36,16 +36,24 @@ public final class SetOnce<T> implements Cloneable {
super("The object cannot be set twice!");
}
}
private volatile T obj = null;
private final AtomicBoolean set;
/** Holding object and marking that it was already set */
private static final class Wrapper<T> {
private T object;
private Wrapper(T object) {
this.object = object;
}
}
private final AtomicReference<Wrapper<T>> set;
/**
* A default constructor which does not set the internal object, and allows
* setting it by calling {@link #set(Object)}.
*/
public SetOnce() {
set = new AtomicBoolean(false);
set = new AtomicReference<>();
}
/**
@ -57,21 +65,27 @@ public final class SetOnce<T> implements Cloneable {
* @see #set(Object)
*/
public SetOnce(T obj) {
this.obj = obj;
set = new AtomicBoolean(true);
set = new AtomicReference<>(new Wrapper<>(obj));
}
/** Sets the given object. If the object has already been set, an exception is thrown. */
public final void set(T obj) {
if (set.compareAndSet(false, true)) {
this.obj = obj;
} else {
if (!trySet(obj)) {
throw new AlreadySetException();
}
}
/** Sets the given object if none was set before.
*
* @return true if object was set successfully, false otherwise
* */
public final boolean trySet(T obj) {
return set.compareAndSet(null, new Wrapper<>(obj));
}
/** Returns the object set by {@link #set(Object)}. */
public final T get() {
return obj;
Wrapper<T> wrapper = set.get();
return wrapper == null ? null : wrapper.object;
}
}

View File

@ -69,6 +69,15 @@ public class TestSetOnce extends LuceneTestCase {
assertEquals(5, set.get().intValue());
set.set(7);
}
@Test
public void testTrySet() {
SetOnce<Integer> set = new SetOnce<>();
assertTrue(set.trySet(5));
assertEquals(5, set.get().intValue());
assertFalse(set.trySet(7));
assertEquals(5, set.get().intValue());
}
@Test
public void testSetMultiThreaded() throws Exception {