From 1b98ebba31e486a9aa384a7aa1da6e2d3b983746 Mon Sep 17 00:00:00 2001 From: Chris Vest Date: Sat, 6 Jul 2024 11:50:39 -0700 Subject: [PATCH] Add a SlotInfo.getCreatedNanoTime and fix flaky test The flaky test was relying on overly precise SlotInfo.getAgeMillis, but it is not that uncommon for slots to age 100 milliseconds or more in the heat of a test. The SlotInfo.getCreatedNanoTime side-steps the racy check on age. --- src/main/java/stormpot/BSlot.java | 5 +++ src/main/java/stormpot/SlotInfo.java | 9 ++++++ .../java/blackbox/AllocatorBasedPoolTest.java | 31 ++++++++++++++++--- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/main/java/stormpot/BSlot.java b/src/main/java/stormpot/BSlot.java index 66e0cda4..ca5912dd 100644 --- a/src/main/java/stormpot/BSlot.java +++ b/src/main/java/stormpot/BSlot.java @@ -103,6 +103,11 @@ public long getAgeMillis() { return TimeUnit.NANOSECONDS.toMillis(NanoClock.elapsed(createdNanos)); } + @Override + public long getCreatedNanoTime() { + return createdNanos; + } + @Override public long getClaimCount() { return claims; diff --git a/src/main/java/stormpot/SlotInfo.java b/src/main/java/stormpot/SlotInfo.java index 13099031..07ab10b7 100644 --- a/src/main/java/stormpot/SlotInfo.java +++ b/src/main/java/stormpot/SlotInfo.java @@ -31,6 +31,15 @@ public interface SlotInfo { */ long getAgeMillis(); + /** + * Get the approximate {@link System#nanoTime()} timestamp for when the object + * was allocated. + * @return The object allocation {@link System#nanoTime()} timestamp. + */ + default long getCreatedNanoTime() { + return 0; + } + /** * Get the number of times the object has been claimed since it was * allocated. diff --git a/src/test/java/blackbox/AllocatorBasedPoolTest.java b/src/test/java/blackbox/AllocatorBasedPoolTest.java index 2d77f67f..cd89b58d 100644 --- a/src/test/java/blackbox/AllocatorBasedPoolTest.java +++ b/src/test/java/blackbox/AllocatorBasedPoolTest.java @@ -15,6 +15,7 @@ */ package blackbox; +import org.assertj.core.data.Offset; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -468,8 +469,15 @@ void slotInfoMustHaveAgeInMillis(Taps taps) throws InterruptedException { void slotInfoAgeMustResetAfterAllocation(Taps taps) throws InterruptedException { final AtomicBoolean hasExpired = new AtomicBoolean(); final AtomicLong age = new AtomicLong(); + final AtomicLong expectedAge = new AtomicLong(); + final AtomicLong createdNanoTime = new AtomicLong(); builder.setExpiration(expire( - $capture($age(age), $expiredIf(hasExpired)))); + $capture(info -> { + age.set(info.getAgeMillis()); + long created = info.getCreatedNanoTime(); + createdNanoTime.set(created); + expectedAge.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - created)); + }, $expiredIf(hasExpired)))); noBackgroundExpirationChecking(); // Reallocations will fail, causing the slot to be poisoned. // Then, the poisoned slot will not be reallocated again, but rather @@ -481,6 +489,8 @@ void slotInfoAgeMustResetAfterAllocation(Taps taps) throws InterruptedException Thread.sleep(100); // time transpires tap.claim(longTimeout).release(); long firstAge = age.get(); // age is now at least 5 ms + long firstCreatedNanoTime = createdNanoTime.get(); + assertThat(firstAge).isCloseTo(expectedAge.get(), Offset.offset(25L)); hasExpired.set(true); try { tap.claim(longTimeout).release(); @@ -491,26 +501,39 @@ void slotInfoAgeMustResetAfterAllocation(Taps taps) throws InterruptedException // new object should have a new age tap.claim(longTimeout).release(); long secondAge = age.get(); // age should be less than age of prev. obj. - assertThat(secondAge).isLessThan(firstAge); + assertThat(secondAge).isCloseTo(expectedAge.get(), Offset.offset(25L)); + long secondCreatedNanoTime = createdNanoTime.get(); + assertThat(secondCreatedNanoTime).isNotEqualTo(firstCreatedNanoTime); } @ParameterizedTest @EnumSource(Taps.class) void slotInfoAgeMustResetAfterReallocation(Taps taps) throws InterruptedException { final AtomicLong age = new AtomicLong(); + final AtomicLong expectedAge = new AtomicLong(); + final AtomicLong createdNanoTime = new AtomicLong(); builder.setExpiration(expire( - $capture($age(age), $fresh))); + $capture(info -> { + age.set(info.getAgeMillis()); + long created = info.getCreatedNanoTime(); + createdNanoTime.set(created); + expectedAge.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - created)); + }, $fresh))); createPool(); PoolTap tap = taps.get(this); tap.claim(longTimeout).release(); Thread.sleep(100); // time transpires GenericPoolable obj = tap.claim(longTimeout); long firstAge = age.get(); + long firstCreatedNanoTime = createdNanoTime.get(); + assertThat(firstAge).isCloseTo(expectedAge.get(), Offset.offset(25L)); obj.expire(); // cause reallocation obj.release(); tap.claim(longTimeout).release(); // new object, new age long secondAge = age.get(); - assertThat(secondAge).isLessThan(firstAge); + assertThat(secondAge).isCloseTo(expectedAge.get(), Offset.offset(25L)); + long secondCreatedNanoTime = createdNanoTime.get(); + assertThat(secondCreatedNanoTime).isNotEqualTo(firstCreatedNanoTime); } /**