-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Clarify CyclicTimeouts API #12308
Comments
The 3 examples you link are examples of a new entity being scheduled, so they are not about cancelling timeouts. The new entry is scheduled not added because the collection is managed elsewhere, and the scheduling has the only purpose of letting So the semantic of "schedule when created", with subsequent recalculation of the earliest wakeup based on pushed-to-the-future The other usage is when an existing entity changes its expiration, possibly earlier than expected. The collection of entities is managed elsewhere, and Since the collection of entities is managed elsewhere, In summary, I think the "add" semantic is not the right one, it is really a "schedule" semantic, and I would not make changes to the APIs. |
@sbordet sorry but I totally disagree. I wrote the original CyclicTimeout class and I'm confused by this API. What hope do other have. The ability for a new entity to be scheduled via inclusion in the iterator, without I agree At the very least, this class needs more javadoc. |
the following "test" illustrates the confusion with the API: {
long now = NanoTime.now();
List<CyclicTimeouts.Expirable> entities = new ArrayList<>(List.of(
new SimpleExpirable("one", now + 1),
new SimpleExpirable("two", now + 2),
new SimpleExpirable("three", now + 3)
));
CountDownLatch latch = new CountDownLatch(3);
AtomicReference<Runnable> update = new AtomicReference<>();
CyclicTimeouts<CyclicTimeouts.Expirable> timeouts = new CyclicTimeouts<>(scheduler)
{
{
update.set(this::onTimeoutExpired);
}
@Override
protected Iterator<Expirable> iterator()
{
return entities.iterator();
}
@Override
protected boolean onExpired(Expirable expirable)
{
System.err.println(expirable);
latch.countDown();
return true;
}
};
//0
//1 timeouts.schedule(entities.get(0));
//2 timeouts.schedule(new SimpleExpirable("fake", now));
//3 timeouts.schedule(new SimpleExpirable("fake", now + TimeUnit.SECONDS.toNanos(10)));
//4 update.get().run();
assertTrue(latch.await(1, TimeUnit.SECONDS));
} If schedule is:
So this illustrates that while the class is working, the
So it is really hard to know when you should call
That is hard to explain. Perhaps the method should be called "update(Expirable)" and we say that it should be called whenever an expirable changes when it is scheduled? But then the passed expirable is still mostly ignored. Or better yet, should we just add a new method called "update()" (//4), that scans the iterator and sets the next expiry time. This method should be called whenever there is a substantive change to the collection that was not done by expiry. |
@gregw I agree with you, I find this API definitely confusing and error-prone. The fact that Personally, I'd rather have an API where once |
The collection is managed externally, that is the use case of
A Instead, What would the same code do with a plain It would: A) add the entity to the collection (so yes, the collection is external and not managed by the scheduler), and B) call Do you get confused by |
@lorban and I discussed this:
We could rename
|
Because a
The problem is that is very hard to write the javadoc of exactly when Currently
This can be simplified to
But that results in calling
What about renaming /**
* Update the internal schedule of this {@code CyclicTimeouts} if the passed {@link Expirable} is the earliest expiring element of the {@link #iterator()}.
* Should be called whenever adding a new element or mutating the expiry time outside of the scope of {@link Expirable#onExpired(Expirable)}.
*/
public void update(T expirable)
{
assert StreamSupport.stream(((Iterable<T>)this::iterator).spliterator(), false).anyMatch(e -> e.equals(expirable));
|
Jetty version(s)
12.1.x
Enhancement Description
Currently the API for
CyclicTimeouts
is used in two different ways.Some usages are modelled on the
CyclicTimeout
class, where theschedule
method needs to be called to cancel the current timeout and set a new one into the future:Other usages just call
schedule
once and then mutate the value returned fromgetExpireNanoTime
in the knowledge that by moving it into the future, it can cancel the current expire, or schedule a new one even if it has expired.Both usages are problematic. The first usage is most similar to the style of
CyclicTimeout
and feels natural to use. However, because theIterator<Expirable>
is not controlled by theCyclicTimeouts
class itself (other than to call remove), it is entirely possible that anExpirable
to be "scheduled" without ever being passed in a call toschedule(Expirable)
. If it is returned by the iterator, then it can be scheduled by returning a value fromgetExpireNanoTime
.The second style of usage is also strange, because a call to
schedule(Expirable)
also doesn't really need theExpirable
to be passed. That will provoke a first look at thatExpirable
, but it was just added to the iterator, then it would eventually be seen and scheduled regardless of any call toschedule
.Perhaps
schedule(Expirable)
should be renamedadd(Expirable)
? Or maybe more extensive API changes are necessary?The text was updated successfully, but these errors were encountered: