1919
2020package org .logstash .common ;
2121
22+ import org .hamcrest .Matchers ;
2223import org .jruby .RubyArray ;
2324import org .jruby .RubyString ;
2425import org .jruby .runtime .ThreadContext ;
2728import org .junit .Test ;
2829import org .logstash .RubyTestBase ;
2930import org .logstash .RubyUtil ;
31+ import org .logstash .util .JavaVersion ;
3032
33+ import javax .management .Attribute ;
34+ import javax .management .InstanceNotFoundException ;
35+ import javax .management .ReflectionException ;
36+ import java .lang .management .ManagementFactory ;
37+ import java .lang .management .OperatingSystemMXBean ;
3138import java .util .List ;
3239
3340import static org .hamcrest .MatcherAssert .assertThat ;
3441import static org .hamcrest .Matchers .containsString ;
35- import static org .junit .Assert .*;
42+ import static org .junit .Assert .assertEquals ;
43+ import static org .junit .Assert .assertThrows ;
44+ import static org .junit .Assume .assumeThat ;
45+ import static org .junit .Assume .assumeTrue ;
3646import static org .logstash .RubyUtil .RUBY ;
3747
3848@ SuppressWarnings ("unchecked" )
@@ -115,27 +125,67 @@ public void giveMultipleSegmentsThatGeneratesMultipleBufferFullErrorsThenIsAbleT
115125 }
116126
117127 @ Test
118- public void givenMaliciousInputExtractDoesntOverflow () {
128+ public void givenTooLongInputExtractDoesntOverflow () {
129+ // This test has proven to go OOM on JDK 11 and JDK 17, also if physical memory is 16GB.
130+ // JDK 21 successfully executes the test due to internal changes or efficiency of G1GC (the default GC).
131+ // Tested also others GC on JDK 11 without any success:
132+ // - ZGC
133+ // - Parallel GC
134+ // - CMS
135+ // remove this code when the minimal JDK version for Logstash is JDK 21 or greater.
136+
137+ assumeThat ("Expect at least JDK 21" , JavaVersion .CURRENT , Matchers .greaterThanOrEqualTo (JavaVersion .JAVA_21 ));
138+ long expectedNeedHeapMemory = 10L * GB ;
139+ // To understand the motivation of 10GB heap please read https://github.com/elastic/logstash/pull/17373#issuecomment-2750378212
140+ assumeTrue ("Skip the test because VM hasn't enough physical memory" , hasEnoughPhysicalMemory (expectedNeedHeapMemory ));
141+
119142 assertEquals ("Xmx must equals to what's defined in the Gradle's javaTests task" ,
120- 12L * GB , Runtime .getRuntime ().maxMemory ());
143+ expectedNeedHeapMemory , Runtime .getRuntime ().maxMemory ());
121144
122145 // re-init the tokenizer with big sizeLimit
123146 initSUTWithSizeLimit ((int ) (2L * GB ) - 3 );
124147 // Integer.MAX_VALUE is 2 * GB
125- String bigFirstPiece = generateString ("a" , Integer .MAX_VALUE - 1024 );
126- sut .extract (context , RubyUtil . RUBY . newString ( bigFirstPiece ) );
148+ RubyString bigFirstPiece = generateString ("a" , Integer .MAX_VALUE - 1024 );
149+ sut .extract (context , bigFirstPiece );
127150
128151 // add another small fragment to trigger int overflow
129152 // sizeLimit is (2^32-1)-3 first segment length is (2^32-1) - 1024 second is 1024 +2
130- // so the combined length of first and second is > sizeLimit and should throw an expection
153+ // so the combined length of first and second is > sizeLimit and should throw an exception
131154 // but because of overflow it's negative and happens to be < sizeLimit
132155 Exception thrownException = assertThrows (IllegalStateException .class , () -> {
133- sut .extract (context , RubyUtil . RUBY . newString ( generateString ("a" , 1024 + 2 ) ));
156+ sut .extract (context , generateString ("a" , 1024 + 2 ));
134157 });
135158 assertThat (thrownException .getMessage (), containsString ("input buffer full" ));
136159 }
137160
138- private String generateString (String fill , int size ) {
139- return fill .repeat (size );
161+ private RubyString generateString (String fill , int size ) {
162+ return RubyUtil .RUBY .newString (fill .repeat (size ));
163+ }
164+
165+ private boolean hasEnoughPhysicalMemory (long requiredPhysicalMemory ) {
166+ long physicalMemory ;
167+ try {
168+ physicalMemory = readPhysicalMemorySize ();
169+ } catch (InstanceNotFoundException | ReflectionException e ) {
170+ System .out .println ("Can't read attribute JMX OS bean" );
171+ return false ;
172+ } catch (IllegalStateException e ) {
173+ System .out .println (e .getMessage ());
174+ return false ;
175+ }
176+ System .out .println ("Physical memory on the VM is: " + physicalMemory + " bytes" );
177+ return physicalMemory > requiredPhysicalMemory ;
178+ }
179+
180+ private long readPhysicalMemorySize () throws ReflectionException , InstanceNotFoundException {
181+ OperatingSystemMXBean op = ManagementFactory .getOperatingSystemMXBean ();
182+
183+ List <Attribute > attributes = ManagementFactory .getPlatformMBeanServer ()
184+ .getAttributes (op .getObjectName (), new String []{"TotalPhysicalMemorySize" } ).asList ();
185+ if (attributes .isEmpty ()) {
186+ throw new IllegalStateException ("Attribute TotalPhysicalMemorySize is not available from JMX OS bean" );
187+ }
188+ Attribute a = attributes .get (0 );
189+ return (long ) (Long ) a .getValue ();
140190 }
141191}
0 commit comments