19
19
20
20
package org .logstash .common ;
21
21
22
+ import org .hamcrest .Matchers ;
22
23
import org .jruby .RubyArray ;
23
24
import org .jruby .RubyString ;
24
25
import org .jruby .runtime .ThreadContext ;
27
28
import org .junit .Test ;
28
29
import org .logstash .RubyTestBase ;
29
30
import org .logstash .RubyUtil ;
31
+ import org .logstash .util .JavaVersion ;
30
32
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 ;
31
38
import java .util .List ;
32
39
33
40
import static org .hamcrest .MatcherAssert .assertThat ;
34
41
import 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 ;
36
46
import static org .logstash .RubyUtil .RUBY ;
37
47
38
48
@ SuppressWarnings ("unchecked" )
@@ -115,27 +125,67 @@ public void giveMultipleSegmentsThatGeneratesMultipleBufferFullErrorsThenIsAbleT
115
125
}
116
126
117
127
@ 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
+
119
142
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 ());
121
144
122
145
// re-init the tokenizer with big sizeLimit
123
146
initSUTWithSizeLimit ((int ) (2L * GB ) - 3 );
124
147
// 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 );
127
150
128
151
// add another small fragment to trigger int overflow
129
152
// 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
131
154
// but because of overflow it's negative and happens to be < sizeLimit
132
155
Exception thrownException = assertThrows (IllegalStateException .class , () -> {
133
- sut .extract (context , RubyUtil . RUBY . newString ( generateString ("a" , 1024 + 2 ) ));
156
+ sut .extract (context , generateString ("a" , 1024 + 2 ));
134
157
});
135
158
assertThat (thrownException .getMessage (), containsString ("input buffer full" ));
136
159
}
137
160
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 ();
140
190
}
141
191
}
0 commit comments