-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Description
Reviewing the Scheduler interface changes of 0.17 with @headinthebox revealed that we're not 100% happy with the outcome, particularly after learning that Java 8 does not allow referencing this from within a lambda.
The Scheduler interface as of 0.17 is:
class Scheduler {
public abstract Subscription schedule(Action1<Scheduler.Inner> action);
public abstract Subscription schedule(Action1<Scheduler.Inner> action, final long delayTime, final TimeUnit unit);
public Subscription scheduleRecursive(Action1<Recurse> action);
public Subscription schedulePeriodically(Action1<Scheduler.Inner> action, long initialDelay, long period, TimeUnit unit);
public int degreeOfParallelism();
public long now();
public static final class Recurse {
public void schedule() {
public void schedule(long delay, TimeUnit unit) {
}
public abstract static class Inner implements Subscription {
public abstract void schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
public abstract void schedule(Action1<Scheduler.Inner> action);
public long now();
}
}We have determined two problems with this:
- Inner/Outer Dance
In practice we have found that usage is always one of two things, either you just interact with the outer and don't care about the Inner, or you immediately need the Inner and have to do an awkward first scheduling just to get access to the Inner. (See here and weep.)
- Recursion
The Action1<Scheduler.Inner> signature was chosen and put on both outer and inner so that an inner class could refer to itself using this to simply reschedule itself from the outer onto the inner.
It was assumed this would work in Java 8 lambdas but unfortunately we did not prove it.
This works with anonymous classes:
Schedulers.newThread().schedule(new Action1<Inner>() {
@Override
public void call(Inner inner) {
System.out.println("do stuff");
// recurse
inner.schedule(this);
}
});but this does not with lambdas:
Schedulers.newThread().schedule((inner) -> {
System.out.println("do stuff");
inner.schedule(this); // doesn't compile
});So we end up with this:
Schedulers.newThread().scheduleRecursive((recurse) -> {
System.out.println("do stuff");
recurse.schedule();
});At that point it's clear that Inner is not working well and we have Recurse to fix the problem.
Thus, the proposed changes (breaking again) are:
class Scheduler {
public final Subscription schedule(Action1<Recurse> action);
public final Subscription schedule(Action1<Recurse> action, final long delayTime, final TimeUnit unit);
public final Subscription schedulePeriodically(Action1<Recurse> action, long initialDelay, long period, TimeUnit unit);
public abstract Inner createInner(); // for advanced use cases like `observeOn`
public int degreeOfParallelism();
public long now();
// now the primary interface
public static final class Recurse {
public final void schedule();
public final void schedule(long delay, TimeUnit unit);
public final void schedule(Action1<Recurse> action);
public final void schedule(Action1<Recurse> action, final long delayTime, final TimeUnit unit);
}
// now mostly an implementation detail except for advanced use cases
public abstract static class Inner implements Subscription {
public abstract void schedule(Action1<Recurse> action, long delayTime, TimeUnit unit);
public abstract void schedule(Action1<Recurse> action);
public long now();
}
}The name of Recurse is up for debate. It may be possible to merge Recurse and Inner but I haven't figured it out yet. The reason is that Inner is a single instance representing a thread or event-loop whereas Recurse represents an Action or work. Thus a given Inner could have multiple Recurse actions scheduled on to it. It is being an Action that allows it to recurse by invoking schedule() that just reschedules itself.
This would make it better support Java 8 lambdas and simply recursion, while also better supporting (via the createInner() method) the more complicated use cases like observeOn where current code is very awkward.
This needs to be the last refactor of this so we nail it down and stop breaking things and can get to 1.0.
Let the discussion begin ...