Understanding RxPHP Schedulers (part 2)

Jakub Tapuć
6 min readOct 4, 2019

--

It’s time to stop!

Introduction

In the previous article we discussed how the immediate scheduler works in RxPHP. In this post we are going to cover the virtual time scheduler and then its descendant — the event loop scheduler which opens up a brave new world of asynchronous possibilities to us. Of course, writing code that schedules things asynchronously by hand in PHP would be quite tiresome so we’re gonna use Amp. I’m purposefully not using ReactPHP as I’m more familiar with Amp and its yield-philosophy just appeals to me much more.

Entering the bizarre world of virtual time

As mentioned above the two schedulers we’re gonna cover in this post are based on the concept of virtual time. Understanding it is a crucial step towards grasping the event loop scheduler that we’re going to talk about later on.

So, without further ado let me take you on a journey through time. Remember when we talked about the three main things that make up a scheduler?

Schedulers know when and where to execute actions.

We’re going to focus on the when part of this sentence. First of all, it is really important to realize that a virtual time scheduler does not need to deal with real system clock at all. Instead, depending on the implementation it can be initially set to 0. This holds true if it look at VirtualTimeScheduler’s constructor.

VirtualTimeScheduler’s constructor

The good thing about schedulers is that we can use them in separation. Let us then look at some real code.

Standalone scheduler

Here, we create a new scheduler instance and pass 0 as the starting time. What is this weird callback though? It’s a comparer function. While useless in the context of the immediate scheduler it is indispensable in the realm of virtual time. The scheduler will use the callback every time it needs to know how two timestamps relate to each other.

Next, we schedule a hello world callback to be executed later. If we were to leave out the last line no output would have shown up. This is another thing that’s different from the immediate scheduler which was processing actions as soon as they arrived.

Now you probably remember that with the immediate scheduler we weren’t able to delay our actions — for obvious reasons, of course. Since the virtual time scheduler can compare two timestamps for us, we can try to kindly ask it to do so. The easiest way is to create an observable with a delay. Think about it — when the action is enqueued the scheduler needs to calculate if the callback is ready to be invoked or not by comparing two timestamps. How about really taking it on another time level and making it fire after 10 minutes?

Timeout of 10 minutes

Let’s run it.

Wait, what?

No, I did not accelerate the video. The output is immediate. Wait a second — did I just lie to you again? Well, just a tiny wee bit. Why do we get to see the result right away? Because virtual time is not human time. What for you seems like ten minutes for the virtual time scheduler might just as well take a couple of milliseconds. The next snippet should make it even clearer.

Looks interesting
This is actually lame

In the code above we created two objects — a simple ReturnObservable and an IntervalObservable. Things are starting to get a little more interesting at this point. On one hand the code was executed immediately once again but on the other the results from the two subscriptions were interleaved just as expected.

This proves my point exactly.

Virtual time is not human time.

The main concern of the virtual time scheduler is to make sure that every action is carried out in a timely fashion but it’s implemented in a way that leaves out the need to schedule work asynchronously. Apart from that, it neglects the absolute time values —it only cares about temporal relations, that is what comes before and what comes after. VirtualTimeScheduler is a fusion between the immediate and event loop schedulers— it supports delaying, interval observables and other ReactiveX goodies, however it’s synchronous by design. In fact, the event loop scheduler is based off of it and simply adds the missing async layer.

When the async kicks in

Photo by Bill Oxford on Unsplash

The real journey begins now. It’s time we met the event loop scheduler. Up until now we’ve only dealt with synchronous code. The difference between the virtual time scheduler and the event loop scheduler is actually quite subtle. As we already know there is an inheritance relation between the two and most of the functionality is basically the same with respect to enqueuing actions and then dequeuing them to invoke them when the time comes. But the event loop scheduler is capable of receiving an action and forwarding it to the actual event loop. It’s like two gears — a smaller one (scheduler) and a bigger one (event loop).

Amp’s event loop

To show you how the scheduler works — I’ll be using Amp. It is actually a whole ecosystem of libraries and tools for concurrent and async programming. Coroutines, promises, await (by using the yield keyword). Everything you need to start with asynchronous programming in PHP and much more than that.

Willy Wonka & the Time Factory

Every time we wanted to use a particular scheduler in RxPHP we needed to provide a factory function that returned the desired instance. This is true for the event loop scheduler as well but this time we will also need to write a delay callback. I’m assuming you have already worked with RxPHP and asynchronous programming at least a little bit in the past so the following snippet should definitely ring a bell. But if your first reaction is along the lines of Chronos almighty! it’s probably a sign you should read up on what an event loop actually is and what it’s used for.

Delay callback

Here we create a new scheduler and pass it a callback that will be used every time a new action needs to be forwarded to the event loop. For this purpose we use the static delay function. It simply takes a callable (our future action) and throws it into the loop. It’s like moving everything one cog forward in our mechanical analogy and at the same time emitting a unit of made up energy that will be passed from the smaller gear to the bigger one. It’s just as easy.

A scheduler’s post-mortem

As you can see this time there was no need to manually start the scheduler. It’s all due to the fact that it has the ability to schedule its own start-up. The only thing left for us to do is to set the event loop in motion by calling Loop::run(). Now we will dissect the event loop scheduler and try to answer the question — how can it run all by itself?

Tracing it all down

Every time subscribe is called on an observable, eventually it’ll all boil down to calling _subscribe which uses the scheduler to enqueue an action using a variety of methods like schedule, scheduleRecursive or schedulePeriodic.

ArrayObservable::_subscribe

Since EventLoopScheduler can’t respond to schedule by itself its parent’s implementation is used. It wraps our action in another callback that returns an EmptyDisposable.

VirtualTimeScheduler::schedule

Then EventLoopScheduler builds a CompositeDisposable with scheduleAbsoluteWithState from both its own implementation and from its parent.

EventLoopScheduler::scheduleAbsoluteWithState

If the action is due it calls EventLoopScheduler::scheduleStartup which in turn calls EventLoopScheduler::start. It checks for new actions to dequeue and uses the provided delay callback to call its start method again. There is no way for the scheduler to start using too many resources as it only runs as long as its queue is not empty. When it’s done the cycle will be repeated only if another action is requested to be scheduled via an observable’s _subscribe method.

Summary

This post should give you a thorough understanding of how RxPHP works with an event loop scheduler. We covered Amp but any event loop implementation should do as long as a proper factory method is provided. By now you should know how the event loop lets your RxPHP application be fully asynchronous without exposing the low-level internals (even though they’re worth learning).

--

--

Jakub Tapuć

Hi! I’m a developer based in Kraków, Poland. I’m passionate about programming, programming languages and their pragmatics.