RxJava: Emit most recent item from upstream periodically - rx-java2

I'm looking for a solution that would emit the most recent item from the upstream periodically. ThrottleLatest is not exactly what I need, as I would like to emit the most recent item that was ever received from the upstream, not just in the last throttling interval.
Flowable.interval(3, TimeUnit.SECONDS)
.operatorThatEmitsMostRecentItemFromUpstream(1, TimeUnit.SECONDS)
.subscribe { println(it) }
Desired output: 0, 0, 0, 1, 1, 1, 2, 2, 2, ...

There is no standard operator for this but you can peek into a sequence, save the current item, and in the timer sequence, keep reading this saved item:
Flowable<T> source = ...
Flowable<Long> sampler = Flowable.interval(1, TimeUnit.SECONDS);
Flowable.defer(() -> {
var item = new AtomicReference<T>();
return source.doOnNext(item::set)
.ignoreElements()
.toFlowable()
.mergeWith(
sampler
.filter(v -> item.get() != null)
.map(v -> item.get())
);
});

Related

Rxjava takeuntil wait until first stream finishes

I got a simulator class that emit items, it can be controlled to start emitting by:
Simulator.emiting -> Observable, true -> started emitting, false, stopped emitting
The items are exposed: Simulator.items -> Observable
The items will be processed, processing happen much slower than emitting (processing potentially occurs on another thread)
I am trying to get an observable that signal when "emitting+processing" starts and ends, so:
from: start emitting , 1, 2, ,3, end emitting
to: start emitting and processing, 1------, 2-----, 3-----, end emitting and processing
how can I get the emitting+processing observable ? I tried using
simulator.items.map { process(it) }.takeUntil(simulator.emitting.filter { it == false }) but this will stop before processing finishes.
So it looks like this is a trivial problem, using zip operator
val stoppedEmitting = simulator.emitting.filter { it == false }
val emitted = simulator.items.takeUntil(stoppedEmitting )
val processed = emitted.map { item -> process(item) }
then "zip" op will wait until the last item get processed:
val processingFlow = emitted.zipWith(processed) { item, processedItem -> ... }
processing.subscribe { }

How can I change the period for Flowable.interval

Is there a way to change the Flowable.interval period at runtime?
LOGGER.info("Start generating bullshit for 7 seconds:");
Flowable.interval(3, TimeUnit.SECONDS)
.map(tick -> random.nextInt(100))
.subscribe(tick -> LOGGER.info("tick = " + tick));
TimeUnit.SECONDS.sleep(7);
LOGGER.info("Change interval to 2 seconds:");
I have a workaround, but the best way would be to create a new operator.
How does this solution work?
You have a trigger source, which will provide values, when to start start a new interval. The source is switchMapped with an interval as inner-stream. The inner-stream takes an input value for the upstream source for setting the new interval time.
switchMap
When the source emits a time (Long), the switchMap lambda is invoked and the returned Flowable will be subscribed to immediately. When a new value arrives at the switchMap, the inner subscribed Flowable interval will be unsubscribed from and the lambda will be invoked once again. The returned Inverval-Flowable will be re-subscribed.
This means, that on each emit from the source, a new Inveral is created.
How does it behave?
When the inveral is subscribed to and is about to emit a new value and a new value is emitted from the source, the inner-stream (inverval) is unsubscribed from. Therefore the value is not emitted anymore. The new Interval-Flowable is subscribed to and will emit a value to it's configuration.
Solution
lateinit var scheduler: TestScheduler
#Before
fun init() {
scheduler = TestScheduler()
}
#Test
fun `62232235`() {
val trigger = PublishSubject.create<Long>()
val switchMap = trigger.toFlowable(BackpressureStrategy.LATEST)
// make sure, that a value is emitted from upstream, in order to make sure, that at least one interval emits values, when the upstream-sources does not provide a seed value.
.startWith(3)
.switchMap {
Flowable.interval(it, TimeUnit.SECONDS, scheduler)
.map { tick: Long? ->
tick
}
}
val test = switchMap.test()
scheduler.advanceTimeBy(10, TimeUnit.SECONDS)
test.assertValues(0, 1, 2)
// send new onNext value at absolute time 10
trigger.onNext(10)
// the inner stream is unsubscribed and a new stream with inverval(10) is subscribed to. Therefore the first vale will be emitted at 20 (current: 10 + 10 configured)
scheduler.advanceTimeTo(21, TimeUnit.SECONDS)
// if the switch did not happen, there would be 7 values
test.assertValues(0, 1, 2, 0)
}

Using `onBackpressureLatest` to drop intermediate messages in blocking Flowable

I have a chain where I do some blocking IO calls (e.g. HTTP-call). I want the blocking call to consume a value, proceed without interrupting, but drop everything that is piling up meanwhile, and then consume the next value in the same manner.
Consider the following example:
fun main() {
Flowable.interval(100, TimeUnit.MILLISECONDS).onBackpressureLatest().map {
Thread.sleep(1000)
it
}.blockingForEach { println(it) }
}
From a naive point of view, I would it expect to print something like 0, 10, 20, ..., but it prints 0, 1, 2, ....
What am I doing wrong?
EDIT:
I thought about naively adding debounce to eat up the incoming stream:
fun main() {
Flowable.interval(100, TimeUnit.MILLISECONDS)
.debounce(0, TimeUnit.MILLISECONDS)
.map {
Thread.sleep(1000)
it
}
.blockingForEach { println(it) }
}
But, now I get a java.lang.InterruptedException: sleep interrupted.
EDIT:
What seems to work is the following:
fun main() {
Flowable.interval(100, TimeUnit.MILLISECONDS)
.throttleLast(0, TimeUnit.MILLISECONDS)
.map {
Thread.sleep(1000)
it
}
.blockingForEach { println(it) }
}
The output is as expected 0, 10, 20, ...!!
Is that the correct way?
I noted that throttleLast will switch to the Computation-Scheduler. Is there a way to go back to the original scheduler?
EDIT:
I also get an occasional java.lang.InterruptedException: sleep interrupted with that variant.
The most simple approach to solve the problem is:
fun <T> Flowable<T>.lossy() : Flowable<T> {
return onBackpressureLatest().observeOn(Schedulers.io(), false, 1)
}
By calling lossy on a Flowable it starts to drop all element that are coming in faster than the downstream consumer can process.

Using range in zipWith also emits all items from range sequence before zipper function applied

The question is about RxJava2.
Noticed that zipping Throwable that comes from retryWhen with range emits all items from Observable.range before zipper function has been applied. Also, range emits sequence even if zipWith wasn't called. For example this source code
Observable.create<String> {
println("subscribing")
it.onError(RuntimeException("always fails"))
}
.retryWhen {
it.zipWith(Observable.range(1, 3).doOnNext { println("range $it") },
BiFunction { t: Throwable, i: Int -> i })
.flatMap {
System.out.println("delay retry by $it + second(s)")
Observable.timer(it.toLong(), TimeUnit.SECONDS)
}
}./*subscribe*/
gives the following result
range 1
range 2
range 3
subscribing
delay retry by 1 + second(s)
subscribing
delay retry by 2 + second(s)
subscribing
delay retry by 3 + second(s)
subscribing
onComplete
Replacing onError in observable creation also don't eliminate emitting range items. So the question is why it's happening as Range is cold.
Observables in 2.x don't have backpressure thus a range operator will emit all its items as soon as it can. Your case, however, can use a normal counter incremented along the error notification of the retry handler:
source.retryWhen(e -> {
int[] counter = { 0 };
return e.takeWhile(v -> ++counter[0] < 4)
.flatMap(v -> Observable.timer(counter[0], TimeUnit.SECONDS));
})

Timeout with per item fallback (no completing after fallback)

If I have items coming from a source periodically, how can I put timeout on each item to be able to substitute a missing item with a fallback (which is a function of previous item), and then keep streaming from the original source? Note, that if an item is not coming even after fallback, the same timeout policy should apply (that is timeout interval restarts from the latest fallback)
Existing operator timeout(timeoutSelector, other) is not suitable, as the sequence terminates after the fallback (other).
Trying to split the source into window(1) and then applying timeout() on each window does not work either, as there is no previous item available to to feed the timeoutSelector.
Is there any graceful way to do this?
You can achieve this via publish(Func1) trick:
TestScheduler s = Schedulers.test();
Scheduler.Worker w = s.createWorker();
PublishSubject<Long> source = PublishSubject.<Long>create();
PublishSubject<Long> other = PublishSubject.create();
source
.publish(o -> {
AtomicReference<Long> last = new AtomicReference<>();
return o
.doOnNext(last::set)
.doOnCompleted(() -> other.onCompleted())
.timeout(75, TimeUnit.MILLISECONDS, s)
.doOnError(e -> {
if (last.get() != null) {
other.onNext(- last.get());
}
})
.retry();
}
).mergeWith(other)
.forEach(System.out::println);
w.schedule(() -> source.onNext(1L), 0, TimeUnit.MILLISECONDS);
w.schedule(() -> source.onNext(2L), 50, TimeUnit.MILLISECONDS);
w.schedule(() -> source.onNext(3L), 150, TimeUnit.MILLISECONDS);
w.schedule(() -> source.onNext(4L), 200, TimeUnit.MILLISECONDS);
w.schedule(() -> source.onNext(5L), 500, TimeUnit.MILLISECONDS);
w.schedule(() -> source.onNext(6L), 550, TimeUnit.MILLISECONDS);
s.advanceTimeBy(1, TimeUnit.SECONDS);
You may need to apply onBackpressureXXX before publish and within mergeWith.