Is Eclipse's $SWITCH_TABLE$ thread-safe?

Viewed 198

It appears when Eclipse compiles an enum whose values are used in a switch statement, it adds the following to the enum class file:

  • synthetic field: private static int[] $SWITCH_TABLE$mypackage$MyEnum;
  • synthetic method: static int[] $SWITCH_TABLE$mypackage$MyEnum()
    Lazily initializes $SWITCH_TABLE$mypackage$MyEnum field with respective enum ordinal values.

And at every place where a switch on the enum values occurs it calls $SWITCH_TABLE$mypackage$MyEnum()[e.ordinal()].

Source code:

package mypackage;

public enum MyEnum {
    FIRST;

    public int get() {
        switch (this) {
            case FIRST:
                return 2;
            default:
                return -1;
        }
    }
}

Decompiled code:

package mypackage;

public enum MyEnum {
   FIRST;

   // $FF: synthetic field
   private static int[] $SWITCH_TABLE$mypackage$MyEnum;

   public int get() {
      switch($SWITCH_TABLE$mypackage$MyEnum()[this.ordinal()]) {
      case 1:
         return 2;
      default:
         return -1;
      }
   }

   // $FF: synthetic method
   static int[] $SWITCH_TABLE$mypackage$MyEnum() {
      int[] var10000 = $SWITCH_TABLE$mypackage$MyEnum;
      if (var10000 != null) {
         return var10000;
      } else {
         int[] var0 = new int[values().length];

         try {
            var0[FIRST.ordinal()] = 1;
         } catch (NoSuchFieldError var1) {
         }

         $SWITCH_TABLE$mypackage$MyEnum = var0;
         return var0;
      }
   }
}

However, as you can see here neither the field $SWITCH_TABLE$mypackage$MyEnum is volatile nor $SWITCH_TABLE$mypackage$MyEnum() is performing any sychronization.

The question is now: Is this actually thread-safe? If I understand the Java Memory Model correctly, there is no guarantee that if one thread initializes the switch table (through a call to get()), a second thread sees an up to date switch table value. Instead it might see a stale value which does not have the correct elements.

Followup question: Since there are no happens-before relations, would a JVM also be allowed to reorder the assignment to the $SWITCH_TABLE$ in front of the element changes? If this was possible there could be race conditions where one thread sees the array while the other thread is still modifying the elements.

1 Answers

I run some tests with jcstress on Windows 7/OpenJDK 13. The local variables are important!

This code should mimic posted code, running 3 threads, multiple times:

// concurrency stress test
@JCStressTest

// expected outcome (III_Result::toString)
@Outcome(id = "1, 1, 1", expect = Expect.ACCEPTABLE, desc = "Correctly initialized.")
@Outcome(id = ".+, .+, .+", expect = Expect.FORBIDDEN, desc = "Some Thread did see a not initialized array")

// test data
@State
public class EnumInitOK {

    int[] array;

    int[] switchTable() {
        int[] v1 = array;
        if (v1 != null) {
            return v1;
        } else {
            int[] v2 = new int[1];
            v2[0] = 1;
            array = v2;
            return v2;
        }
    }

    // each called only by one particular Thread, once per State instance
    @Actor public void test1(III_Result r) {
        r.r1 = switchTable()[0];
    }

    @Actor public void test2(III_Result r) {
        r.r2 = switchTable()[0];
    }

    @Actor public void test3(III_Result r) {
        r.r3 = switchTable()[0];
    }
}

Results from all runs with different settings, not one initialization error (almost 400M tests):

Java Concurrency Stress Tests
---------------------------------------------------------------------------------
Rev: null, built by cfh with 12.0.2 at null

Probing what VM modes are available:
 (failures are non-fatal, but may miss some interesting cases)

----- [OK] [-Xint]
----- [OK] [-XX:TieredStopAtLevel=1]
----- [OK] []
----- [OK] [-XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM]
----- [OK] [-XX:-TieredCompilation]
----- [OK] [-XX:-TieredCompilation, -XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM]

Initializing and probing the target VM: 
 (all failures are non-fatal, but may affect testing accuracy)

----- [OK] Unlocking diagnostic VM options
Burning up to figure out the exact CPU count....... done!

----- [OK] Trimming down the default VM heap size to 1/4-th of max RAM
----- [OK] Trimming down the number of compiler threads
----- [OK] Trimming down the number of parallel GC threads
----- [OK] Trimming down the number of concurrent GC threads
----- [OK] Trimming down the number of G1 concurrent refinement GC threads
----- [OK] Testing @Contended works on all results and infra objects
----- [OK] Unlocking Whitebox API for online de-optimization
----- [OK] Testing allocation profiling
----- [OK] Trying Thread.onSpinWait

  Hardware threads in use/available: 4/4, using Thread.onSpinWait()
  Test preset mode: "default"
  Writing the test results to "jcstress-results-2019-10-22-23-32-40.bin.gz"
  Parsing results to "results/"
  Running each test matching "cfh.jcstress.EnumInitOK" for 1 forks, 5 iterations, 1000 ms each
  Each JVM would execute at most 5 tests in the row.
  Solo stride size will be autobalanced within [10, 10000] elements, but taking no more than 100 Mb.

... [output deleted] ...

RUN COMPLETE.

RUN RESULTS:
------------------------------------------------------------------------------------------------------------------------

*** INTERESTING tests
  Some interesting behaviors observed. This is for the plain curiosity.

  0 matching test results. 

*** FAILED tests
  Strong asserts were violated. Correct implementations should have no assert failures here.

  0 matching test results. 

*** ERROR tests
  Tests break for some reason, other than failing the assert. Correct implementations should have none.

  0 matching test results. 

*** All remaining tests
  Tests that do not fall into any of the previous categories.

  6 matching test results. 
      [OK] cfh.jcstress.EnumInitOK
    (JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM])
  Observed state   Occurrences   Expectation  Interpretation                                              
      .+, .+, .+             0     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1    76.194.931    ACCEPTABLE  Correctly initialized.                                      

      [OK] cfh.jcstress.EnumInitOK
    (JVM args: [-XX:-TieredCompilation, -XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM])
  Observed state   Occurrences   Expectation  Interpretation                                              
      .+, .+, .+             0     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1    76.756.011    ACCEPTABLE  Correctly initialized.                                      

      [OK] cfh.jcstress.EnumInitOK
    (JVM args: [-XX:-TieredCompilation])
  Observed state   Occurrences   Expectation  Interpretation                                              
      .+, .+, .+             0     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1    78.453.381    ACCEPTABLE  Correctly initialized.                                      

      [OK] cfh.jcstress.EnumInitOK
    (JVM args: [-XX:TieredStopAtLevel=1])
  Observed state   Occurrences   Expectation  Interpretation                                              
      .+, .+, .+             0     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1    73.608.821    ACCEPTABLE  Correctly initialized.                                      

      [OK] cfh.jcstress.EnumInitOK
    (JVM args: [-Xint])
  Observed state   Occurrences   Expectation  Interpretation                                              
      .+, .+, .+             0     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1     8.740.641    ACCEPTABLE  Correctly initialized.                                      

      [OK] cfh.jcstress.EnumInitOK
    (JVM args: [])
  Observed state   Occurrences   Expectation  Interpretation                                              
      .+, .+, .+             0     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1    86.069.111    ACCEPTABLE  Correctly initialized.                                      


------------------------------------------------------------------------------------------------------------------------

To cross-check, I tried the same code accessing the array directly, without using the local variables, in the switchTable method:

//concurrency stress test
@JCStressTest

//expected outcome (III_Result::toString)
@Outcome(id = "1, 1, 1", expect = Expect.ACCEPTABLE, desc = "Correctly initialized.")
@Outcome(id = ".+, .+, .+", expect = Expect.FORBIDDEN, desc = "Some Thread did see a not initialized array")

//test data
@State
public class EnumInitErr {

    int[] array;

    int[] switchTable() {
        if (array != null) {
            return array;
        } else {
            array = new int[1];
            array[0] = 1;
            return array;
        }
    }

    // each called only by one particular Thread, once per State instance
    @Actor public void test1(III_Result r) {
        r.r1 = switchTable()[0];
    }

    @Actor public void test2(III_Result r) {
        r.r2 = switchTable()[0];
    }

    @Actor public void test3(III_Result r) {
        r.r3 = switchTable()[0];
    }
}

This time we got some results with uninitialized arrays:

... [output deleted] ...

RUN COMPLETE.

RUN RESULTS:
------------------------------------------------------------------------------------------------------------------------

*** INTERESTING tests
  Some interesting behaviors observed. This is for the plain curiosity.

  0 matching test results. 

*** FAILED tests
  Strong asserts were violated. Correct implementations should have no assert failures here.

  6 matching test results. 
  [FAILED] cfh.jcstress.EnumInitErr
    (JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM])
  Observed state   Occurrences   Expectation  Interpretation                                              
         0, 0, 1           565     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 0         1.089     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 1         2.835     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 0           792     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 1         3.272     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 0         2.394     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1    70.441.694    ACCEPTABLE  Correctly initialized.                                      

  [FAILED] cfh.jcstress.EnumInitErr
    (JVM args: [-XX:-TieredCompilation, -XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM])
  Observed state   Occurrences   Expectation  Interpretation                                              
         0, 0, 1           481     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 0           604     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 1         2.373     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 0           901     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 1         2.908     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 0         3.310     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1    69.456.024    ACCEPTABLE  Correctly initialized.                                      

  [FAILED] cfh.jcstress.EnumInitErr
    (JVM args: [-XX:-TieredCompilation])
  Observed state   Occurrences   Expectation  Interpretation                                              
         0, 0, 1         1.332     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 0           948     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 1         4.212     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 0         1.071     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 1         5.216     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 0         5.052     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1    64.580.410    ACCEPTABLE  Correctly initialized.                                      

  [FAILED] cfh.jcstress.EnumInitErr
    (JVM args: [-XX:TieredStopAtLevel=1])
  Observed state   Occurrences   Expectation  Interpretation                                              
         0, 0, 1         8.766     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 0        11.470     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 1        21.761     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 0        11.280     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 1        28.461     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 0        27.634     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1    71.079.569    ACCEPTABLE  Correctly initialized.                                      

  [FAILED] cfh.jcstress.EnumInitErr
    (JVM args: [-Xint])
  Observed state   Occurrences   Expectation  Interpretation                                              
         0, 0, 1            77     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 0           117     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 1         2.417     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 0            83     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 1         2.291     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 0         1.789     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1     5.674.177    ACCEPTABLE  Correctly initialized.                                      

  [FAILED] cfh.jcstress.EnumInitErr
    (JVM args: [])
  Observed state   Occurrences   Expectation  Interpretation                                              
         0, 0, 1           785     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 0           804     FORBIDDEN  Some Thread did see a not initialized array                 
         0, 1, 1         2.717     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 0         1.250     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 0, 1         4.041     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 0         5.500     FORBIDDEN  Some Thread did see a not initialized array                 
         1, 1, 1    63.510.464    ACCEPTABLE  Correctly initialized.                                      


*** ERROR tests
  Tests break for some reason, other than failing the assert. Correct implementations should have none.

  0 matching test results. 

*** All remaining tests
  Tests that do not fall into any of the previous categories.

  0 matching test results. 

------------------------------------------------------------------------------------------------------------------------

first time using jcstress, sorry for any errors

Related