Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8153980

Hotspot JIT compiler omits a field write after optimisation

XMLWordPrintable

    • x86_64
    • linux

      FULL PRODUCT VERSION :
      java version "1.8.0_60"
      Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
      Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

      Also testing on java version "1.8.0_77"


      FULL OS VERSION :
      Linux stem 4.4.6-201.fc22.x86_64 #1 SMP Wed Mar 30 18:30:16 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

      A DESCRIPTION OF THE PROBLEM :
      When using the Disruptor library (https://github.com/LMAX-Exchange/disruptor), the test code in the executable test case throws a null pointer exception (NPE) in a location where it should not. In an early pass of the compiler it is possible to see the field being updated.

        # {method} {0x00007ffa86c70d58} 'lambda$test$0' '(JLjava/lang/Integer;Ljava/lang/String;Ldisruptorbug/DisruptorTest$Event;J)V' in 'disruptorbug/DisruptorTest'
      ... lines omitted
        0x00007ffa8d192688: mov %rsi,0x10(%r8) ;*putfield id
                                                      ; - disruptorbug.DisruptorTest$Event::publish@2 (line 93)
                                                      ; - disruptorbug.DisruptorTest::lambda$test$0@5 (line 59)

        0x00007ffa8d19268c: mov %rdx,%r10
        0x00007ffa8d19268f: shr $0x3,%r10
        0x00007ffa8d192693: mov %r10d,0xc(%r8)
        0x00007ffa8d192697: mov %r8,%rsi
        0x00007ffa8d19269a: shr $0x9,%rsi
        0x00007ffa8d19269e: mov $0x7ffa9d7e4000,%rdi
        0x00007ffa8d1926a8: movb $0x0,(%rsi,%rdi,1) ;*putfield obj
                                                      ; - disruptorbug.DisruptorTest$Event::publish@7 (line 94)
                                                      ; - disruptorbug.DisruptorTest::lambda$test$0@5 (line 59)

        0x00007ffa8d1926ac: mov %rcx,%r10
        0x00007ffa8d1926af: shr $0x3,%r10
        0x00007ffa8d1926b3: mov %r10d,0x18(%r8)
        0x00007ffa8d1926b7: shr $0x9,%r8
        0x00007ffa8d1926bb: movb $0x0,(%r8,%rdi,1) ;*putfield str
                                                      ; - disruptorbug.D
      ...lines omitted.

      However in a later compilation pass the putfield of 'obj' field is dropped.

        # {method} {0x00007ffa86c70ba8} 'test' '()V' in 'disruptorbug/DisruptorTest'
      ... lines omitted
        0x00007ffa8d1b7f03: mov 0x50(%rsp),%r8
        0x00007ffa8d1b7f08: mov %r8,0x10(%r11) ;*putfield id
                                                      ; - disruptorbug.DisruptorTest$Event::publish@2 (line 93)
                                                      ; - disruptorbug.DisruptorTest::lambda$test$0@5 (line 59)
                                                      ; - disruptorbug.DisruptorTest$$Lambda$3/2094548358::translateTo@17
                                                      ; - com.lmax.disruptor.RingBuffer::translateAndPublish@7 (line 931)
                                                      ; - com.lmax.disruptor.RingBuffer::publishEvent@13 (line 445)
                                                      ; - com.lmax.disruptor.dsl.Disruptor::publishEvent@5 (line 256)
                                                      ; - disruptorbug.DisruptorTest::test@36 (line 58)

      **** The putfield obj is not here!!!!! ****

        0x00007ffa8d1b7f0c: movl $0xe3443067,0x18(%r11) ; {oop("str")}
        0x00007ffa8d1b7f14: shr $0x9,%r11
        0x00007ffa8d1b7f18: mov $0x7ffa9d7e4000,%r8
        0x00007ffa8d1b7f22: mov %r12b,(%r8,%r11,1) ;*putfield str
                                                      ; - disruptorbug.DisruptorTest$Event::publish@13 (line 95)
                                                      ; - disruptorbug.DisruptorTest::lambda$test$0@5 (line 59)
                                                      ; - disruptorbug.DisruptorTest$$Lambda$3/2094548358::translateTo@17
                                                      ; - com.lmax.disruptor.RingBuffer::translateAndPublish@7 (line 931)
                                                      ; - com.lmax.disruptor.RingBuffer::publishEvent@13 (line 445)
                                                      ; - com.lmax.disruptor.dsl.Disruptor::publishEvent@5 (line 256)
                                                      ; - disruptorbug.DisruptorTest::test@36 (line 58)


      THE PROBLEM WAS REPRODUCIBLE WITH -Xint FLAG: No

      THE PROBLEM WAS REPRODUCIBLE WITH -server FLAG: Yes

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1) Download the LMAX disruptor library from http://repo1.maven.org/maven2/com/lmax/disruptor/3.3.2/disruptor-3.3.2.jar
      2) Compile the executable test case with the disruptor library in the class path
      3) Execute the executable test case (it has a main method) with the disruptor library in the class path

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      Expected: The test should run indefinitely without error.
      Actual: The code fails part way through with an NPE.
      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      The exception trace will look like:

      id = 135222 Event{id=22965387954243, obj=null, str='str'}
      java.lang.NullPointerException
      at disruptorbug.DisruptorTest$Event.testNPE(DisruptorTest.java:100)
      at disruptorbug.DisruptorTest.handler1(DisruptorTest.java:37)
      at disruptorbug.DisruptorTest$$Lambda$2/1329552164.onEvent(Unknown Source)
      at com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:128)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
      at java.lang.Thread.run(Thread.java:745)


      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import com.lmax.disruptor.BusySpinWaitStrategy;
      import com.lmax.disruptor.dsl.Disruptor;
      import com.lmax.disruptor.dsl.ProducerType;
      import org.junit.Before;

      import java.util.Objects;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;


      public class DisruptorTest
      {
          private static final int disruptorSize = 4;

          private Disruptor<Event> disruptor;

          private volatile long opId;


          @Before
          public void setUp() throws Exception
          {
              ExecutorService executor = Executors.newCachedThreadPool();

              disruptor = new Disruptor<>(Event::new, disruptorSize, executor, ProducerType.SINGLE, new BusySpinWaitStrategy());
              disruptor.handleEventsWith(this::handler1);
              disruptor.start();
          }

          private void handler1(Event event, long sequence, boolean endOfBatch)
          {
              try
              {
                  event.testNPE();
              }
              catch (Exception e)
              {
                  System.err.println("id = " + opId + " " + event);
                  e.printStackTrace();
                  System.exit(-1);
              }
          }


          public void test() throws Exception
          {
              opId = 0L;
              while (true)
              {
                  long finalOpId = System.nanoTime();

                  Integer obj = new Integer(123);
                  String str = "str";

                  disruptor.publishEvent(
                          (event, sequence) -> event.publish(finalOpId, obj, str)
                  );

                  opId++;
              }
          }

          public static void main(String[] args) throws Exception
          {
              final DisruptorTest disruptorTest = new DisruptorTest();
              disruptorTest.setUp();
              disruptorTest.test();
          }

          static class Event
          {
              long id;
              Object obj;
              String str;

              @Override
              public String toString() {
                  return "Event{" +
                          "id=" + id +
                          ", obj=" + obj +
                          ", str='" + str + '\'' +
                          '}';
              }

              void publish(long id, Object obj, String str)
              {
      // Objects.requireNonNull(obj);

                  this.id = id;
                  this.obj = obj;
                  this.str = str;
              }

              void testNPE()
              {
                  obj.toString();
                  obj = null;
              }
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Inject an Objects.requireNonNull(obj) call as the first line in the publish method.

            thartmann Tobias Hartmann
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: