forked from open-feature/java-sdk-contrib
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: improve wait logic to a more elegant solution open-feature#1160
Signed-off-by: christian.lutnik <[email protected]>
- Loading branch information
Showing
6 changed files
with
201 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 0 additions & 39 deletions
39
...ers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/common/Util.java
This file was deleted.
Oops, something went wrong.
72 changes: 72 additions & 0 deletions
72
...ers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/common/Wait.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package dev.openfeature.contrib.providers.flagd.resolver.common; | ||
|
||
import dev.openfeature.sdk.exceptions.GeneralError; | ||
|
||
/** | ||
* A helper class to wait for events. | ||
*/ | ||
public class Wait { | ||
private volatile boolean isFinished; | ||
|
||
/** | ||
* Create a new Wait object. | ||
*/ | ||
public Wait() {} | ||
|
||
private Wait(boolean isFinished) { | ||
this.isFinished = isFinished; | ||
} | ||
|
||
/** | ||
* Blocks the calling thread until either {@link Wait#onFinished()} is called or the deadline is exceeded, whatever | ||
* happens first. | ||
* If the deadline is exceeded, a GeneralError will be thrown. | ||
* | ||
* @param deadline the maximum time in ms to wait | ||
* @throws GeneralError when the deadline is exceeded before {@link Wait#onFinished()} is called on this object | ||
*/ | ||
public void waitUntilFinished(long deadline) { | ||
long start = System.currentTimeMillis(); | ||
long end = start + deadline; | ||
while (!isFinished) { | ||
long now = System.currentTimeMillis(); | ||
// if wait(0) is called, the thread would wait forever, so we abort when this would happen | ||
if (now >= end) { | ||
throw new GeneralError(String.format( | ||
"Deadline exceeded. Condition did not complete within the %d ms deadline", deadline)); | ||
} | ||
long remaining = end - now; | ||
synchronized (this) { | ||
if (isFinished) { // might have changed in the meantime | ||
return; | ||
} | ||
try { | ||
this.wait(remaining); | ||
} catch (InterruptedException e) { | ||
// try again. Leave the continue to make PMD happy | ||
continue; | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Wake up all threads that have called {@link Wait#waitUntilFinished(long)}. | ||
*/ | ||
public void onFinished() { | ||
synchronized (this) { | ||
isFinished = true; | ||
this.notifyAll(); | ||
} | ||
} | ||
|
||
/** | ||
* Create a new Wait object that is already finished. Calls to {@link Wait#waitUntilFinished(long)} will return | ||
* immediately. | ||
* | ||
* @return an already finished Wait object | ||
*/ | ||
public static Wait finished() { | ||
return new Wait(true); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
...flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/common/WaitTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package dev.openfeature.contrib.providers.flagd.resolver.common; | ||
|
||
import dev.openfeature.sdk.exceptions.GeneralError; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.Timeout; | ||
|
||
class WaitTest { | ||
private static final long PERMISSIBLE_EPSILON = 20; | ||
|
||
@Timeout(2) | ||
@Test | ||
void waitUntilFinished_failsWhenDeadlineElapses() { | ||
final Wait wait = new Wait(); | ||
Assertions.assertThrows(GeneralError.class, () -> wait.waitUntilFinished(10)); | ||
} | ||
|
||
@Timeout(2) | ||
@Test | ||
void waitUntilFinished_WaitsApproxForDeadline() { | ||
final Wait wait = new Wait(); | ||
final AtomicLong start = new AtomicLong(); | ||
final AtomicLong end = new AtomicLong(); | ||
final long deadline = 45; | ||
Assertions.assertThrows(GeneralError.class, () -> { | ||
start.set(System.currentTimeMillis()); | ||
try { | ||
wait.waitUntilFinished(deadline); | ||
} catch (Exception e) { | ||
end.set(System.currentTimeMillis()); | ||
throw e; | ||
} | ||
}); | ||
final long elapsed = end.get() - start.get(); | ||
// should wait at least for the deadline | ||
Assertions.assertTrue(elapsed >= deadline); | ||
// should not wait much longer than the deadline | ||
Assertions.assertTrue(elapsed < deadline + PERMISSIBLE_EPSILON); | ||
} | ||
|
||
@Timeout(2) | ||
@Test | ||
void interruptingWaitingThread_isIgnored() throws InterruptedException { | ||
final AtomicBoolean isWaiting = new AtomicBoolean(); | ||
final Wait wait = new Wait(); | ||
final long deadline = 500; | ||
Thread t0 = new Thread(() -> { | ||
long start = System.currentTimeMillis(); | ||
isWaiting.set(true); | ||
wait.waitUntilFinished(deadline); | ||
long end = System.currentTimeMillis(); | ||
long duration = end - start; | ||
// even though thread was interrupted, it still waited for the deadline | ||
Assertions.assertTrue(duration >= deadline); | ||
Assertions.assertTrue(duration < deadline + PERMISSIBLE_EPSILON); | ||
}); | ||
t0.start(); | ||
|
||
while (!isWaiting.get()) { | ||
Thread.yield(); | ||
} | ||
|
||
Thread.sleep(10); // t0 should have started waiting in the meantime | ||
|
||
for (int i = 0; i < 50; i++) { | ||
t0.interrupt(); | ||
Thread.sleep(10); | ||
} | ||
|
||
t0.join(); | ||
} | ||
|
||
@Timeout(2) | ||
@Test | ||
void callingOnFinished_wakesUpWaitingThread() throws InterruptedException { | ||
final AtomicBoolean isWaiting = new AtomicBoolean(); | ||
final Wait wait = new Wait(); | ||
Thread t0 = new Thread(() -> { | ||
long start = System.currentTimeMillis(); | ||
isWaiting.set(true); | ||
wait.waitUntilFinished(10000); | ||
long end = System.currentTimeMillis(); | ||
long duration = end - start; | ||
Assertions.assertTrue(duration < PERMISSIBLE_EPSILON); | ||
}); | ||
t0.start(); | ||
|
||
while (!isWaiting.get()) { | ||
Thread.yield(); | ||
} | ||
|
||
Thread.sleep(10); // t0 should have started waiting in the meantime | ||
|
||
wait.onFinished(); | ||
|
||
t0.join(); | ||
} | ||
|
||
@Timeout(2) | ||
@Test | ||
void waitingOnFinished_returnsInstantly() { | ||
Wait finished = Wait.finished(); | ||
long start = System.currentTimeMillis(); | ||
finished.waitUntilFinished(10000); | ||
long end = System.currentTimeMillis(); | ||
// do not use PERMISSIBLE_EPSILON here, this should happen faster than that | ||
Assertions.assertTrue(start + 2 > end); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters