2121
2222import java .lang .reflect .Constructor ;
2323import java .lang .reflect .Field ;
24+ import java .nio .charset .StandardCharsets ;
25+ import java .nio .file .Path ;
2426import java .util .ArrayList ;
2527import java .util .HashSet ;
2628import java .util .List ;
@@ -66,6 +68,14 @@ void shouldStartBackgroundMonitorThreadForHighMemoryPressure() throws Exception
6668 }
6769 }
6870
71+ @ Test
72+ void shouldDeriveDefaultFreeMemoryThresholdsFromMaxHeap () throws Exception {
73+ Thresholds thresholds = readDefaultThresholdsFromFreshJvm ("12801m" );
74+
75+ assertEquals (257 , thresholds .warnFreeMb );
76+ assertEquals (129 , thresholds .highFreeMb );
77+ }
78+
6979 @ Test
7080 void shouldThrottleExecutionContextCheckpointToEvery16384Calls () throws Exception {
7181 PropertiesScope properties = new PropertiesScope ().with (QueryCircuitBreaker .ENABLED_PROPERTY , "false" );
@@ -221,6 +231,25 @@ void shouldHoldCriticalStateUntilRecoveryCooldownExpires() {
221231 assertEquals ("NORMAL" , breaker .snapshotStatus ().getState ());
222232 }
223233
234+ @ Test
235+ void shouldThrottleCheckpointsOnlyAtHighOrAbove () {
236+ Fixture fixture = new Fixture ();
237+ QueryCircuitBreaker breaker = fixture .breaker (configuration (true , 100 , 200 , 300 , 400 , 300 , 200 , 25 , 10 , 1000 ,
238+ 7 ), 1000 );
239+ QueryCircuitBreakerHandle handle = breaker .register (QueryCircuitBreakerHandle .Source .SERVER , "repo" ,
240+ "checkpoint-query" );
241+
242+ fixture .freeMemoryMb .set (350 );
243+ breaker .checkpoint (handle , "JOIN" );
244+ assertEquals (List .of (), fixture .sleeps );
245+
246+ fixture .freeMemoryMb .set (250 );
247+ breaker .checkpoint (handle , "JOIN" );
248+ assertEquals (List .of (10L ), fixture .sleeps );
249+
250+ breaker .complete (handle );
251+ }
252+
224253 @ Test
225254 void shouldDelayWarnAdmissionAndRejectHighAdmission () {
226255 Fixture fixture = new Fixture ();
@@ -388,6 +417,23 @@ private static QueryPressureState waitForCurrentState(QueryCircuitBreaker breake
388417 return state ;
389418 }
390419
420+ private static Thresholds readDefaultThresholdsFromFreshJvm (String maxHeap ) throws Exception {
421+ String classpath = System .getProperty ("java.class.path" );
422+ String javaExecutable = Path .of (System .getProperty ("java.home" ), "bin" , "java" ).toString ();
423+ Process process = new ProcessBuilder (javaExecutable , "-Xmx" + maxHeap ,
424+ "-D" + QueryCircuitBreaker .WARN_FREE_MB_PROPERTY + "=" ,
425+ "-D" + QueryCircuitBreaker .HIGH_FREE_MB_PROPERTY + "=" ,
426+ "-cp" , classpath , DefaultThresholdProbe .class .getName ())
427+ .redirectErrorStream (true )
428+ .start ();
429+ String output = new String (process .getInputStream ().readAllBytes (), StandardCharsets .UTF_8 ).trim ();
430+ int exitCode = process .waitFor ();
431+ assertEquals (0 , exitCode , output );
432+
433+ String [] parts = output .split ("," );
434+ return new Thresholds (Integer .parseInt (parts [0 ]), Integer .parseInt (parts [1 ]));
435+ }
436+
391437 private static final class PropertiesScope {
392438 private final List <String > keys = new ArrayList <>();
393439 private final List <String > previousValues = new ArrayList <>();
@@ -400,9 +446,21 @@ private PropertiesScope with(String key, String value) {
400446 return this ;
401447 }
402448
449+ private PropertiesScope without (String key ) {
450+ keys .add (key );
451+ previousValues .add (System .getProperty (key ));
452+ nextValues .add (null );
453+ return this ;
454+ }
455+
403456 private void apply () {
404457 for (int i = 0 ; i < keys .size (); i ++) {
405- System .setProperty (keys .get (i ), nextValues .get (i ));
458+ String nextValue = nextValues .get (i );
459+ if (nextValue == null ) {
460+ System .clearProperty (keys .get (i ));
461+ } else {
462+ System .setProperty (keys .get (i ), nextValue );
463+ }
406464 }
407465 }
408466
@@ -436,4 +494,23 @@ private QueryCircuitBreaker breaker(QueryCircuitBreaker.Configuration configurat
436494 recoveryCooldownMs , 1 , false );
437495 }
438496 }
497+
498+ public static final class DefaultThresholdProbe {
499+ public static void main (String [] args ) {
500+ QueryCircuitBreaker .Configuration configuration = QueryCircuitBreaker .Configuration .fromSystemProperties ();
501+ System .out .print (configuration .getWarnFreeMb ());
502+ System .out .print ("," );
503+ System .out .print (configuration .getHighFreeMb ());
504+ }
505+ }
506+
507+ private static final class Thresholds {
508+ private final int warnFreeMb ;
509+ private final int highFreeMb ;
510+
511+ private Thresholds (int warnFreeMb , int highFreeMb ) {
512+ this .warnFreeMb = warnFreeMb ;
513+ this .highFreeMb = highFreeMb ;
514+ }
515+ }
439516}
0 commit comments