You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by jp...@apache.org on 2019/10/14 17:57:31 UTC

[lucene-solr] 02/05: LUCENE-9001: Fix race condition in SetOnce (#931)

This is an automated email from the ASF dual-hosted git repository.

jpountz pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit dad15ba15e00df4fef024dae3b101b7969775ed6
Author: Przemko Robakowski <pr...@elastic.co>
AuthorDate: Mon Oct 14 18:33:46 2019 +0200

    LUCENE-9001: Fix race condition in SetOnce (#931)
---
 lucene/CHANGES.txt                                 |  4 +--
 .../src/java/org/apache/lucene/util/SetOnce.java   | 36 +++++++++++++++-------
 .../test/org/apache/lucene/util/TestSetOnce.java   |  9 ++++++
 3 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 0a986ab..4315a7d 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -25,8 +25,8 @@ Optimizations
   (Ignacio Vera, Adrien Grand)
 
 Bug Fixes
----------------------
-(No changes)
+
+* LUCENE-9001: Fix race condition in SetOnce. (Przemko Robakowski)
 
 Other
 ---------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/SetOnce.java b/lucene/core/src/java/org/apache/lucene/util/SetOnce.java
index 9be88ec..3c3f277 100644
--- a/lucene/core/src/java/org/apache/lucene/util/SetOnce.java
+++ b/lucene/core/src/java/org/apache/lucene/util/SetOnce.java
@@ -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;
   }
 }
diff --git a/lucene/core/src/test/org/apache/lucene/util/TestSetOnce.java b/lucene/core/src/test/org/apache/lucene/util/TestSetOnce.java
index 0d93b88..ae12321 100644
--- a/lucene/core/src/test/org/apache/lucene/util/TestSetOnce.java
+++ b/lucene/core/src/test/org/apache/lucene/util/TestSetOnce.java
@@ -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 {