-
Notifications
You must be signed in to change notification settings - Fork 7.6k
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
Scheduler Outer/Inner [Preview] #797
Scheduler Outer/Inner [Preview] #797
Conversation
RxJava-pull-requests #718 FAILURE |
Absolutely love this new design, it fixes a lot of the issues that the previous design inherited from the old Rx.NET. Win 1) The explicit C funcptr style of passing the state separately from the function pointer did not pan out and made everyones brain hurt. Those using the Scala bindings know that there the schedulers where exposed without the separate state parameters already. Win 2a) The old design as Ben says deeply confused inner and outer schedulers. In .NET it is even worse since there the extension methods for scheduling using Win 2b) The conceptual model is now much clearer and closer to Java's executors. The additional feature is that you can schedule iteratively. Win 3) One of the big mistakes in hindsight in .NET That said, there is still a date-related type in Remark A) I think it would be better to move Remark B) The interface for outer Remark C) The semantic difference is subtle, but I think we can remove The only price is that when you want to delay schedule you need to write a bit more code, but I think smaller interface trumps that. Remark D) Wrt to keeping or dropping The post goes down into all the gory details about how hard it is to deal with time in computing. The short story is that a lot of the complication in the current Rx schedulers implementation and the reason for having If I were to do the .NET thing again, which is really what we are doing with RxJava grin :->, I would not let that pollute the whole implementation and design of schedulers. Instead for the unlikely event that people want to do ultra-precise and super long scheduling, I would build a completely seperate implementation of a special high-precision scheduler (like |
Thanks @headinthebox for the feedback. Good point regarding
I found it became very awkward on some operators to have to schedule something before getting access to now(). So having it on both is perhaps better. An example is
It does greatly simplify usage of the API keeping this. It also leaves a nice symmetry between outer and inner: public abstract class Scheduler {
public abstract <T> Subscription schedule(Action1<Scheduler.Inner> action);
public <T> Subscription schedule(Action1<Scheduler.Inner> action, long delayTime, final TimeUnit unit);
public abstract class Inner implements Subscription {
public abstract void schedule(Action1<Scheduler.Inner> action);
public abstract void schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
}
} For example, this schedules the first execution 100ms in the future then 500ms each time thereafter and uses Schedulers.newThread().schedule(new Action1<Inner>() {
@Override
public void call(Inner inner) {
doWork();
// recurse until unsubscribed ... but delay the recursion
inner.schedule(this, 500, TimeUnit.MILLISECONDS);
}
}, 100, TimeUnit.MILLISECONDS);
I'll read that post to better understand. We already support the implementation, and it works fairly well. It takes into account the time taken by the task being executed periodically and schedules in the future for the diff of periodic time - time taken for task. I'm open for keeping or removing it. |
Of note, we do have a handful of operators in rxjava-core that use |
RxJava-pull-requests #719 FAILURE |
Works for me.
No issues with that, but in that case we should also add absolute time.
No real opinion about this, the implementation indeed does useful work, and since other operators rely on it, so let's keep it. |
Why do you want to add absolute time, I thought we wanted to eliminate absolute time so it was always based off of These are the only signatures currently: public abstract class Scheduler {
public <T> Subscription schedule(Action1<Scheduler.Inner> action);
public <T> Subscription schedule(Action1<Scheduler.Inner> action, long delayTime, final TimeUnit unit);
public long now();
public int degreeOfParallelism();
public abstract class Inner implements Subscription {
public void schedule(Action1<Scheduler.Inner> action);
public void schedule(Action1<Scheduler.Inner> action, long delayTime, TimeUnit unit);
public void schedulePeriodically(final Action1<Scheduler.Inner> action, long initialDelay, long period, TimeUnit unit);
public long now();
}
} |
I was assuming the absolute time one would look like
|
In Java the time value is always paired with All of these are absolute times that are then scheduled relative to |
As of commit 4bdf08d I now have all rxjava-core unit tests passing, and tests in most other modules. The Scala bindings are currently broken ... I'll try looking at them this evening but if anyone else has time and interest to submit a PR to my branch fixing them that would help as Scala is not my strength. |
RxJava-pull-requests #724 FAILURE |
Yup ... fixing the Scala bindings is complex enough with traits, inner classes/traits, Java interop etc that I'm not going to learn Scala fast enough tonight to fix that. I would appreciate someone submitting a PR to me with the Scala changes. |
Is there a reason only Inner has the |
I left it out for simplicity as it seems to only make sense calling it on the outer. The point is to start it and let it run. The inner is used for manual recursion. All use cases for schedulePeriodically I refactored or considered only made sense on the outer. Since it's easy to add and hard to remove I left it out until a use case is shown. Do you have one? |
No worries, working on it. (One day I hope to convince you to switch from Groovy to Scala ....) |
@akarnokd We should be careful with that, before you know it outer is as complex as it was before ... Erik's razor is actually still a bit eager to cut out the delay one from outer. |
Wrt to now, should we let now return http://docs.oracle.com/javase/7/docs/api/java/lang/System.html#nanoTime() instead of milliSeconds? i.e. Now should reflect the most accurate time you can measure. In any case, since The latter is not a big deal, but I do think we should seriously look at nanoTime. Interestingly, if you read the description it has the same goal as now in a scheduler, to provide local time, that is only relevant in a closed context. |
@benjchristensen I don't have any use case for that, but moving the schedulePeriodically to the outer is okay. I guess the reduced surface again means that there won't be any Action0 overloads, right? @headinthebox The delayed version is necessary for most timed operators (take, skip, delay, etc.). Nanotime would be great (at least for the time-windowed replay). |
@akarnokd My proposal is to always go via Action0 on outer. That is what delay must do anyway, so it feel purer to make that explicit. |
@headinthebox but that way, you can't use the same action in the outer and inner as the inner has to get the While we are at it, some operators use |
That's fine with me; the outer just dispatches to inner. Agree with |
It already has an implicit unit, milliseconds, but in Java duration always goes with TimeUnit and is hen used to convert to whatever is desired.
a) you can only get relative time since the JVM started, not real time If we change from millis to nanos it will break any code assuming millis. Issue (a) is the one I'm most concerned with as you could no longer take Scheduler.now() and turn it into a real date. This also means historical schedulers would be useless because the values from Scheduler.now() would not be meaningful on a Calendar. |
Correct. I'm keeping it as simple as can be. Without all of the Func2 and state overloads it is very simple now. Also, this Scheduler interface doesn't have type-erasure issues so the methods can be targeted with lambdas. Overloads with Action0/Action1 conflict. More importantly though, we want the Action1 interface common on all of them for two reasons:
|
That should be no problem, if you try that, you are going against the spirit of |
I really like the changes I'm seeing. The scheduling code was quite difficult to follow and it's a lot cleaner now. One thing I'd still like to see (but might be well outside the scope of this PR) is a way to skip to first wrap around for recursive scheduling when going through What's happening is that for the first notification that arrives, instead of processing that notification directly using the given scheduler, it's deferred into a function which is then piped through that scheduler again. That means, it's not the notification that will get processed during the first call to schedule, but instead another call to schedule which then again schedules the actual notification. For schedulers like the one we use for Android, this means an extra (unnecessary) round through the message loop, thus prolonging the RTT for the first notification. Any idea why it is implemented the way it is? I fail to see the point I guess. |
Agreed that the passage of time is virtual, not sure we want the value to be virtual though. What should the |
Here is a broader use case on the Say I have multiple machines all processing events and writing an event log. They use We could make In short, if we're not going to use actual time, then I think we would need to eliminate |
@mttkay Glad you like the changes.
Interesting point, I hadn't paid attention to that before. I'm pretty sure we can eliminate that. It's more about the code organization for invoking 'processQueue` than a reason for re-scheduling on the first pass. We can pursue fixing this in a separate PR. Thanks for pointing that out! |
I'm proceeding with the merge as this is a major change that will conflict with almost all other pull requests. The Scala fixes (and any others needed) can be done against master. |
Scheduler Outer/Inner [Preview]
|
||
@Override | ||
public Thread newThread(Runnable r) { | ||
return new Thread(r, "RxNewThreadScheduler-" + count.incrementAndGet()); |
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.
hi @benjchristensen , you forgot t.setDaemon(true);
for NewThreadScheduler
. I just find NewThreadScheduler
can not exit automatically.
Following are proposed changes to the
Scheduler
signature based on discussions between @headinthebox and I intended to simplify scheduling and make it easier to do the right thing.This originates from three findings:
To solve this the new design explicitly has the outer
Scheduler
and thenScheduler.Inner
for recursion.In this new design all state passing signatures have been removed. This was determined while implementing a
RemoteScheduler
that attempted to useobserveOn
to transition execution from one machine to another. This does not work because of the requirement for serializing/deserializing the state of the entire execution stack. Migration of work over the network has been bound to be better suited to explicit boundaries established by Subjects. Thus, the complications within the Schedulers are unnecessary.This new design removes all but the essential and simplest methods.
This is the new signature for
Scheduler
:The simplest execution of a single task is:
Recursion is easily invoked:
The use of
Action1<Inner>
on both the outer and inner levels makes it so recursion that refer tothis
and it works easily.Similar to the new
lift
/create
pattern withSubscriber
theInner
is also aSubscription
so it allows efficient loops withunsubscribe
support:An action can now
unsubscribe
theScheduler.Inner
:Typically just stopping is sufficient:
but if other work in other tasks is being done and you want to unsubscribe conditionally you could:
and the recursion can be delayed:
The methods on the
Inner
never return aSubscription
because they are always a single thread/event-loop/actor/etc and controlled by theSubscription
returned by the initialScheduler.schedule
method. This is part of clarifying the contract.Thus an
unsubscribe
controlled from the outside would be done like this:I'd appreciate feedback on this design direction.
NOTE: This pull request is not yet complete. I have not modified the language adaptors or other modules, and there are 3 unit tests in core failing (related to
buffer
andwindow
).