Reactive blocking
This article explains the concept of reactive blocking, which is hookless equivalent of synchronous blocking. No actual thread blocking takes place. Reactive blocking is a metaphor, because it tends to be used in places where synchronous blocking would be used by synchronous code.
How things work without reactive blocking
It sometimes happens that reactive method cannot return the value you requested the first time you call it. It cannot block the thread until the result is available, because reactive computations usually run in shared executor and they are expected to complete quickly. So the reactive method will return fallback or throw instead. Once the real result is available, dependent computations are invalidated and as they refresh, the reactive method will return the real result. If the real result is obtained quickly enough, the worst thing user will ever see is a short UI flash with some fallbacks or a message.
Consider this (contrived) example:
var variable = new ReactiveVariable<>("placeholder value"); new ReactiveThread(() -> System.out.println(variable.get())).start(); variable.set("real value");
Depending on timing, the output might look like this:
placeholder value real value
So we get to see the initial (incorrect) value, but we eventually also see the actual (correct) value.
How reactive blocking improves the situation
But sometimes (actually, most of the time) you don't want that UI flash with fallback values. Other times, you cannot afford to return fallbacks, because you are dealing with single-shot reactive consumer like reactive servlet. In these situations, reactive blocking comes handy. Reactive blocking does not actually block anything. It merely flags the current result as draft that does not yet contain actual up-to-date data. Callers are then expected to refrain from committing to any action until the actual (non-draft) data is available.
This is how we can modify the above example to make use of reactive blocking:
var variable = new ReactiveVariable<>(new ReactiveValue<>("placeholder", true)); new ReactiveThread(() -> { var value = variable.get(); if (!CurrentReactiveScope.blocked()) System.out.println(variable.get()); }).start(); variable.set("real value");
Note the call to ReactiveValue(T, boolean)
constructor.
The second parameter is true
, which indicates the stored value is a "blocking" or draft value.
When we later read the blocking value in the ReactiveThread
,
CurrentReactiveScope.blocked()
will be true
and nothing gets printed. Only the subsequent non-blocking value will be printed:
real value
This seems a bit laborious, because this example is intended to be explicit,
but most of the time, reactive blocking is handled for you for free.
ReactiveLazy
, for example, will transparently propagate reactive blocking.
ReactiveWorker
will not overwrite previously computed non-blocking value
with blocking one and it will instead wait for new non-blocking value.
And standard reactive consumers like reactive servlets
will refrain from doing anything until non-blocking value is available.
The movie metaphor
Hookless reactive computation can be thought of as a sequence of video frames.
Every frame is rebuilt from scratch, so frames are independent data snapshots.
What if some of these frames are marked as blocking?
In the following figure, B
stands for blocking frame, N
for non-blocking one:
B1 B2 B3 B4 N5 B6 B7 N8 N9
What happens depends on the particual reactive object you are using. A good reactive UI would remove all blocking frames and show only non-blocking ones:
N5 N8 N9
But perhaps the first non-blocking result is unavailable for too long and the reactive UI decides to show the latest blocking value it has until non-blocking one is available:
B3 B4 N5 N8 N9
Or perhaps the reactive consumer is a one-shot like in servlets, in which case only the first non-blocking value is used:
N5
The initial train of blocking values is often useful.
UI can use it to show some fallback to the user if the non-blocking value takes too long to compute.
Complex code can use even partial result to initiate additional queries, which helps to collapse latencies and parallelize IO operations.
This is why a good reactive intermediary like ReactiveWorker
will generally propagate initial blocking values until first non-blocking value is encountered:
B1 B2 B3 B4 N5 N8 N9
Reactive blocking is a powerful feature of hookless. The above examples show how this simple concept can be used to implement many kinds of intelligent reactive behavior.
Reactive analog to synchronous blocking
Synchronous methods usually communicate results via return value or exception. They can also synchronously block, which can be seen as a form of communication that instructs caller to stop execution until result is available. Reactive blocking analogously instructs caller to stop until result is available, but, with reactive blocking, only dependent reactive computation is stopped instead of the entire thread.
Reactive blocking is therefore an integral part of ReactiveValue
which captures all the ways that reactive methods can communicate their results to callers.