-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Replace Timeout implementation with interface, introduce StartTime interface #1194
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Orca Security Scan Summary
Status | Check | Issues by priority | |
---|---|---|---|
![]() |
Infrastructure as Code | ![]() ![]() ![]() ![]() |
View in Orca |
![]() |
Vulnerabilities | ![]() ![]() ![]() ![]() |
View in Orca |
![]() |
Secrets | ![]() ![]() ![]() ![]() |
View in Orca |
} | ||
long remainingNanos = timeout.remainingOrInfinite(NANOSECONDS); | ||
while (permits == 0 | ||
// the absence of short-circuiting is of importance | ||
& !stateAndGeneration.throwIfClosedOrPaused() | ||
& (availableConnection = tryGetAvailable ? tryGetAvailableConnection() : null) == null) { | ||
if (Timeout.expired(remainingNanos)) { | ||
throw createTimeoutException(timeout); | ||
if (timeout.hasExpired()) { | ||
throw createTimeoutException(startTime); | ||
} | ||
remainingNanos = awaitNanos(permitAvailableOrHandedOverOrClosedOrPausedCondition, remainingNanos); | ||
timeout.awaitOn(permitAvailableOrHandedOverOrClosedOrPausedCondition); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the timeout is now checked directly, rather than the value from await.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack
* @return The remaining duration as per {@link Timeout#remainingOrInfinite(TimeUnit)} if waiting ended early either | ||
* spuriously or because of receiving a signal. | ||
*/ | ||
private long awaitNanos(final Condition condition, final long timeoutNanos) throws MongoInterruptedException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been moved into the Deadline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack
/** | ||
* @see TimePoint | ||
*/ | ||
public interface Deadline { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created the Deadline and StartPoint interfaces on TimePoint. These hide TimePoint methods that are not relevant or used, and also make it clear what "type of" TimePoint we are using.
* The number of whole time units that remain until this TimePoint | ||
* {@link #hasExpired()}. This should not be used to check for expiry, | ||
* but can be used to supply a remaining value, in the finest-grained | ||
* TimeUnit available, to some method that may time out. | ||
* This method must not be used with infinite TimePoints. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changes the behaviour of the remaining
method.
Previously, if there were 100ns remaining, and you asked how many hours were remaining, it would return 1 hour, because returning 0 might suggest that the deadline is expired. However, this does not match the behaviour of Duration, and I do not think rounding up is correct, for the expected usage. For example, I might get the remaining time in hours because that is the granularity of some method that I will invoke, and get back 1, and wait on the method for the next hour, which is not correct. As far as "hours" go, the deadline is indeed expired, and I do not think we should undertake operations that might (because of granularity differences) exceed the timeout. The same logic applies to milliseconds, though less intuitively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spoke with @stIncMale about this, and he explained that the existing rounding-up behaviour is generally consistent with the timeout interpretation elsewhere in Java, of "try at least this long and then time out as fast as possible". I agree this is not an incorrect interpretation, but we also agree that the appropriate way to address this particular issue is to use nanoseconds (as Java SE does), rather than rounding the value up and down to some granularity. I won't address this in this PR (that is, refactoring our existing methods). I will leave this rounding-up interpretation, since it is consistent with how "elapsed" works, and the inconsistency with Java SE is not crucial (and should ideally be addressed in a different way regardless).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack
Set<Class<?>> encountered = new HashSet<>(); | ||
while (true) { | ||
Object element; | ||
if (timeout.isImmediate()) { | ||
if (deadline.hasExpired()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This removes the concept of "immediate" timepoints: they are just expired.
/** | ||
* Returns an {@linkplain Deadline#isInfinite() infinite} deadline if | ||
* {@code timeoutValue} is negative, an expired deadline if | ||
* {@code timeoutValue} is 0, otherwise a deadline in {@code durationNanos}. | ||
* <p> | ||
* Note that some code might ignore a deadline, and attempt to perform | ||
* the operation in question at least once.</p> | ||
* <p> | ||
* Note that the contract of this method is also used in some places to | ||
* specify the behavior of methods that accept {@code (long timeout, TimeUnit unit)}, | ||
* e.g., {@link com.mongodb.internal.connection.ConcurrentPool#get(long, TimeUnit)}, | ||
* so it cannot be changed without updating those methods.</p> | ||
* | ||
* @see TimePoint#fromNowOrInfiniteIfNegative(long, TimeUnit) | ||
*/ | ||
Deadline fromNowOrInfiniteIfNegative(long timeoutValue, TimeUnit timeUnit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think perhaps this method should not be added, but I have included it because otherwise, it seems I would need to change existing locations that take long/TimeUnit in this PR. I have at least made the name long and noticeable.
c0e5a15
to
65ceb29
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good - would be worthwhile having @stIncMale review as well as he authored the original Timeout class.
} | ||
long remainingNanos = timeout.remainingOrInfinite(NANOSECONDS); | ||
while (permits == 0 | ||
// the absence of short-circuiting is of importance | ||
& !stateAndGeneration.throwIfClosedOrPaused() | ||
& (availableConnection = tryGetAvailable ? tryGetAvailableConnection() : null) == null) { | ||
if (Timeout.expired(remainingNanos)) { | ||
throw createTimeoutException(timeout); | ||
if (timeout.hasExpired()) { | ||
throw createTimeoutException(startTime); | ||
} | ||
remainingNanos = awaitNanos(permitAvailableOrHandedOverOrClosedOrPausedCondition, remainingNanos); | ||
timeout.awaitOn(permitAvailableOrHandedOverOrClosedOrPausedCondition); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack
* @return The remaining duration as per {@link Timeout#remainingOrInfinite(TimeUnit)} if waiting ended early either | ||
* spuriously or because of receiving a signal. | ||
*/ | ||
private long awaitNanos(final Condition condition, final long timeoutNanos) throws MongoInterruptedException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack
* The number of whole time units that remain until this TimePoint | ||
* {@link #hasExpired()}. This should not be used to check for expiry, | ||
* but can be used to supply a remaining value, in the finest-grained | ||
* TimeUnit available, to some method that may time out. | ||
* This method must not be used with infinite TimePoints. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch
} else { | ||
// the deadline will track this remaining time | ||
//noinspection ResultOfMethodCallIgnored | ||
condition.awaitNanos(remaining(NANOSECONDS)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the documentation should be clarified to ensure that hasExpired
is checked after calling awaitOn
as the condition may not be satisfied with in the deadline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added docs. It seems it is usually checked before calling await (though, this is generally in a loop).
I considered changing the method to perform the check, but there were some complications that I ran into elsewhere (that might be noted in a future PR).
The idea behind JAVA-5112 was that instead of having two sets of APIs, As far as I can see, this PR does not do the improvement that was intended by JAVA-5112. What is the motivation behind this PR? |
65ceb29
to
ed06cc0
Compare
(From earlier discussion, to track responses to the points above.) It seems beneficial, whatever we do, to have a single implementation (more maintainable and testable), and also a single abstraction (a plain point in time), and my opinion is that these are the primary benefits. The outstanding point is whether we should differentiate two "types of" TimePoint (how we decide what counts as a single API is tangential). I believe there is little harm in differentiating because there is no significant API duplication between the interfaces, and a benefit is that the compiler now prevents us from confusing the two, or using methods from one unsuitable in the other (that is, TimePoints are always strictly either Deadlines or StartTimes). |
I renamed Deadlines to Timeouts because, while the term "deadline" is clearer, it would be burdensome to change existing usages, and would conflict with public-facing terminology. This is a naming change only - Timeouts are still trimmed down, no longer have start-times built in, etc. Also changed target to the CSOT feature branch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good - main comment is to add a little more back ground to StartTime but I like it :)
* The number of whole time units that remain until this TimePoint | ||
* {@link #hasExpired()}. This should not be used to check for expiry, | ||
* but can be used to supply a remaining value, in the finest-grained | ||
* TimeUnit available, to some method that may time out. | ||
* This method must not be used with infinite TimePoints. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack
@@ -75,7 +75,7 @@ public boolean hasTimeoutMS() { | |||
* @return true if the timeout has been set and it has expired | |||
*/ | |||
public boolean expired() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should change this to hasExpired at some point - just to keep the nomenclature consistent. Doesn't have to be done in this PR.
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* @see TimePoint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think perhaps there should be more docs - just explaining why this interface exists.
As TimePoint.now
could be added and all functionality is covered by Timepoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added. Both StartTime and Timeout are intended to remove functionality to provide the (now-documented) typesafe guarantees, and to clarify the purpose or intended use of any value of that type.
@katcharov I think you need to push a commit? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
JAVA-5112