JDK-8179334 : Empty while loops do not terminate when condition is satisfied
  • Type: Bug
  • Component: hotspot
  • Sub-Component: compiler
  • Affected Version: 8,9
  • Priority: P3
  • Status: Closed
  • Resolution: Not an Issue
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2017-04-20
  • Updated: 2019-07-31
  • Resolved: 2017-04-26
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java -jre7
java -1.8.0_121
java -1.8.0_131


ADDITIONAL OS VERSION INFORMATION :
Windows 7 Professional x64 

EXTRA RELEVANT SYSTEM CONFIGURATION :
N/A - Tested on multiple windows systems

A DESCRIPTION OF THE PROBLEM :
while(!condition) {
 //empty body no code
}

Loop does not terminate when condition is satisfied. 

condition = true

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
public class Test {

	public static void main(String[] args) throws Exception {
		new Thread(new Runnable() {

			@Override
			public void run() {
				start();
			}

		}).start();

		Thread.sleep(100);
		stopRunning = true;
		System.out.println("But it didn't end?");
	}

	private static boolean stopRunning = false;

	public static void start() {
		while (!stopRunning) {

		}
		System.out.println("Method ended.");
	}

}


Please see that System.out.println("Method ended."); does not execute and application will not terminate

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I was trying to pause the application until a condition was achieved. 
ACTUAL -
System.out.println("But it didn't end?"); printed however System.out.println("Method ended."); did not. I can see that if you call the method again under System.out.println("But it didn't end?"); the method will terminate however the issue will still be present with the next execution of the method. 

ERROR MESSAGES/STACK TRACES THAT OCCUR :
No errors, no crashes. 

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
public class Test {

	public static void main(String[] args) throws Exception {
		new Thread(new Runnable() {

			@Override
			public void run() {
				start();
			}

		}).start();

		Thread.sleep(100);
		stopRunning = true;
		System.out.println("But it didn't end?");
	}

	private static boolean stopRunning = false;

	public static void start() {
		while (!stopRunning) {

		}
		System.out.println("Method ended.");
	}

}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Not bypass has been found


Comments
The program submitted by the user is incorrectly synchronized (i.e., it contains a data race). Therefore, the behavior reported by the user is expected. In the example program, stopRunning is a shared variable, however, it is neither marked as volatile, nor is it accessed in a synchronized block. When compiling the start() method, the C2 compiler therefore assumes that the variable stopRunning is only accessed locally in the start() method. (That is, the C2 compiler does not consider the possibility that the variable is changed outside the method start(), for example, while the method is being executed). The C2 compiler generates the following code: 000 N49: # B1 <- BLOCK HEAD IS JUNK Freq: 1 000 # breakpoint nop # 11 bytes pad for loops and calls 010 B1: # B3 B2 <- BLOCK HEAD IS JUNK Freq: 1 010 # stack bang (112 bytes) pushq rbp # Save rbp subq rsp, #16 # Create frame 01c movq RDI, RSI # spill 01f call_leaf,runtime OSR_migration_end No JVM State Info # 02c movq R10, java/lang/Class:exact * # ptr 036 movzbl R11, [R10 + #112 (8-bit)] # ubyte ! Field: TestOriginal.stopRunning 03b testl R11, R11 03e jne,s B3 P=0.000001 C=-1.000000 03e 040 B2: # B2 <- B1 B2 top-of-loop Freq: 1e-35 040 testl rax, [rip + #offset_to_poll_page] # Safepoint: poll for GC # TestOriginal::start @ bci:6 # OopMap{off=64} 046 jmp,s B2 046 048 B3: # N49 <- B1 Freq: 1e-06 048 movl RSI, #-122 # int nop # 2 bytes pad for loops and calls 04f call,static wrapper for: uncommon_trap(reason='predicate' action='maybe_recompile' debug_id='0') # TestOriginal::start @ bci:0 # OopMap{off=84} 054 int3 # ShouldNotReachHere 054 At offset 02c, the compiled code reads field stopRunning. At 03b the code checks if the field is true or false. If it is false, it eenters the loop (offsets 040 and 046). The llop contains only a safepoint check and a jump to the beginning the loop (i.e., the safepoint check). The loop does not re-read the field because it assumes it's only locally accessed. If the code is changed to access the field in a synchronized block or by marking stopRunning as volatile, the program behaves as expected by the user. (See the attached TestSynchronized.java and TestVolatile.java examples). I'm closing this issue as "Not an issue".
26-04-2017

This is an issue, compiler is not generating conditional checks for empty loops, adding simple statement makes it work Below is the result. ../java_re/jdk/9/ea/166/binaries/linux-x64/bin/java -Xint Test Method ended. But it didn't end? ../java_re/jdk/9/ea/166/binaries/linux-x64/bin/java Test But it didn't end? Loop is not ending Issue seems to be existing for a long, Suprised to see how it was not detected so far...
26-04-2017