Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eventually fixed #4018

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open

Conversation

mirageoasis
Copy link
Contributor

@mirageoasis mirageoasis commented May 14, 2024

will resolve #3988

this is draft for eventually

I made some senario for this new eventually

  1. duration is set to positve -> works well
  2. duration is set to minus-> error (this pr)
  3. retries is set to minus -> error (this pr)
  4. user do not set duration -> which make infinite loop (this pr)

and there are some other feature I introduced in this pr

  1. if duration millisecond overflowed it is set to Duration.INFINITE and prints out waring

by the way can we change retries to Long? I think it doesn't matter to change Int to Long

sorry found out reason why retries cannot be Long in ExponentialIntervalFn.kt

@mirageoasis
Copy link
Contributor Author

currently working on test code
Any suggestions are welcome!

@@ -205,7 +211,7 @@ object NoopEventuallyListener : EventuallyListener {
private class EventuallyControl(val config: EventuallyConfiguration) {

val start = timeInMillis()
val end = start + config.duration.inWholeMilliseconds
val end = addSafely()
Copy link
Contributor

@OliverO2 OliverO2 May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried to run this? The local start variable is not visible in addSafely(), right?
[Edit: It is visible. My bad. Just needed to look at the whole file instead of the GitHub diff.]

What about expressing more clearly what happens, so that readers don't have to look up the implementation of addSafely()? For example, mirroring the coerce... functions in the stdilb, we could use:

val end = (start + config.duration.inWholeMilliseconds).orInfinityIfNegative()

with:

fun Long.orInfinityIfNegative() = if (this < 0) Duration.INFINITE.inWholeMilliseconds else this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks to your advice I refactored code! thanks a lot!

fun hasAttemptsRemaining() = timeInMillis() < end && iterations < config.retries
fun hasAttemptsRemaining() : Boolean {
// If the duration is infinite, it is considered as default value, which means it will never stop
val isInfinite = config.duration == INFINITE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this case already covered by isWithinDuration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well

Isn't this case already covered by isWithinDuration?

isWithinDuration only checks time depletion
isInfinte checks if config.duration if is set to default which is INFINITE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh maybe since Duration.INFINITE is almost same as INFINITE I think I can rollback this expression

@@ -121,7 +121,7 @@ data class EventuallyConfiguration(
}

object EventuallyConfigurationDefaults {
var duration: Duration = Duration.INFINITE
var duration: Duration = INFINITE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am concerned over this approach in general. This thing is shared (because object) and mutable (because var). As such, there will be race conditions. We should refactor away the very need for shared mutable things. WDYT @OliverO2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is default setting I think it is ok to change var to val

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlexCue987 I agree.

All properties should really be val (though I wonder how this would affect backward compatibility in this case). Global state is almost never a good idea and in particular with the option of running parallel tests and concurrent coroutines, any context-induced configuration should be coroutine-local. See #2791 for an example.

I also wonder why that object is part of the public API. It might better be private.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its appropriate to make another pr and deal with this issue
on this pr I will only focus on eventually's function
I will raise another pr for this issue!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense. To make this PR ready for review, we'd need some test code covering the proposed fixes. Would also be good to update the initial PR comment with references to issues resolved by this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its appropriate to make another pr and deal with this issue on this pr I will only focus on eventually's function I will raise another pr for this issue!

sure - this is a preexisting condition, not something introduced in this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense. To make this PR ready for review, we'd need some test code covering the proposed fixes. Would also be good to update the initial PR comment with references to issues resolved by this PR.

done with test!

@mirageoasis mirageoasis changed the title draft for eventually eventually refactored May 19, 2024
Copy link
Contributor

@kshired kshired left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mirageoasis

  • I commented some suggestions.
  • Why not modify the comment of EventuallyConfigurationBuilder.duration that the constraint has been added?

return when {
this in 0..INFINITE.inWholeMilliseconds -> this
else -> INFINITE.inWholeMilliseconds.also {
println("[WARN] end value $this is out of the valid range (0 to ${INFINITE.inWholeMilliseconds}), value is fixed to Duration.INFINITE.inWholeMilliseconds")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
println("[WARN] end value $this is out of the valid range (0 to ${INFINITE.inWholeMilliseconds}), value is fixed to Duration.INFINITE.inWholeMilliseconds")
println("[WARN] end value $this is out of the valid range (0 to $it), value is fixed to Duration.INFINITE.inWholeMilliseconds")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not just error out and throw if the duration is invalid?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think throwing error is more clear then changing value since people will not set duration close to inifnite

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And no one is going to spot the warn in the logs unless they look closely.
Failing the test makes more sense if its a bug, which having an incorrect duration is.

@mirageoasis
Copy link
Contributor Author

Can someone help me with test case to trigger Duration overflow?

}

test("when duration is set to default it cannot end test until iteration is done") {
val finalCount = 10000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also work with a count of 10, and faster, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

@@ -205,7 +211,7 @@ object NoopEventuallyListener : EventuallyListener {
private class EventuallyControl(val config: EventuallyConfiguration) {

val start = timeInMillis()
val end = start + config.duration.inWholeMilliseconds
val end = (start.milliseconds + config.duration).inWholeMilliseconds
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's how it works

Duration's single value cannot be Bigger than 2 ^ 63 - 1

if we add two Duration and if it's value's result is bigger than 2 ^ 63 -1 then we set Duration to Duration.INFINITE

so

this

val end = (start.milliseconds + config.duration).inWholeMilliseconds
is 100% safe!

I've come a long way round :(

@mirageoasis mirageoasis changed the title eventually refactored eventually fixed May 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

eventually without customization never runs test method
5 participants