org.aksw.commons.rx.util.RxUtils Maven / Gradle / Ivy
package org.aksw.commons.rx.util;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Streams;
import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.FlowableEmitter;
import io.reactivex.rxjava3.core.FlowableOnSubscribe;
import io.reactivex.rxjava3.core.FlowableSubscriber;
import io.reactivex.rxjava3.core.FlowableTransformer;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Scheduler;
import io.reactivex.rxjava3.core.Scheduler.Worker;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.operators.SimpleQueue;
import io.reactivex.rxjava3.operators.SpscArrayQueue;
public class RxUtils {
private static final Logger logger = LoggerFactory.getLogger(RxUtils.class);
/**
* Create a stream that must eventually be closed from a Flowable.
* If closing cannot be ensured then it is most likely preferrable to use
* {@code flowable.toList().blockingGet().stream()}.
*
* Example Usage:
*
* try (Stream stream : RxUtils.stream(flowable)) {
* ...
* }
*
**/
public static Stream stream(Flowable flowable) {
Iterator it = flowable.blockingIterable().iterator();
Disposable disposable = (Disposable) it;
Stream result = Streams.stream(it).onClose(disposable::dispose);
return result;
}
/**
* If something goes wrong when running the wrapped action
* then log an error return an empty maybe
*
* @param action A callable encapsulating some action
* @return A maybe with the action's return value or empty
*/
public static Maybe safeMaybe(Callable action) {
Maybe result;
try {
T value = action.call();
result = Maybe.just(value);
} catch (Exception e) {
logger.warn("An exception occurred; trying to continue", e);
result = Maybe.empty();
}
return result;
}
/**
* A 'poison' is an object that serves as an end marker on blocking queues
*/
public static final Object POISON = new Object();
@SuppressWarnings("unchecked")
public static T poison() {
return (T)POISON;
}
public static Map nameMap = new ConcurrentHashMap<>();
public static FlowableTransformer counter(String name, long interval) {
return RxUtils.createTransformer(emitter -> new FlowBase(emitter) {
int id;
long i[] = {0};
long startTimeMillis;
public void onSubscribe(Subscription s) {
AtomicInteger n = nameMap.computeIfAbsent(name, k -> new AtomicInteger());
id = n.incrementAndGet();
startTimeMillis = System.currentTimeMillis();
super.onSubscribe(s);
};
@Override
public void onNext(T item) {
long elapsed = System.currentTimeMillis() - startTimeMillis;
if(i[0] % interval == 0) {
System.err.println("On " + name + "-" + id + " seen item count = " + i[0] + " - throughput: " + (i[0] / (elapsed * 0.001f)));
}
++i[0];
emitter.onNext(item);
}
});
}
// public static FlowableTransformer queuedObserveOn(Scheduler scheduler, int capacity) {
// return upstream -> upstream
//// .map(x -> x)
// .compose(queueProducer(capacity))
// .doOnNext(x -> System.err.println("Passing on queue: " + x.size()))
//// .observeOn(Schedulers.newThread())
// .observeOn(scheduler)
// .compose(queueConsumer());
// }
public static void put(SimpleQueue queue, T item) throws InterruptedException {
if(!queue.offer(item)) {
synchronized (queue) {
while(!queue.offer(item)) {
queue.notifyAll();
queue.wait();
}
}
}
}
public static T take(SimpleQueue queue) throws Throwable {
T result = queue.poll();
if(result == null) {
synchronized(queue) {
queue.notifyAll();
while((result = queue.poll()) == null) {
// System.out.println("Waiting for items");
queue.wait(100);
queue.notifyAll();
break;
}
}
}
return result;
}
public static FlowableTransformer queuedObserveOn(Scheduler scheduler, int capacity) {
return upstream -> Flowable.create(new FlowableOnSubscribe() {
@Override
public void subscribe(FlowableEmitter downstream) throws Exception {
//BlockingQueue queue = new LinkedBlockingQueue(capacity);
// BlockingQueue queue = new ArrayBlockingQueue<>(capacity);
SimpleQueue queue = new SpscArrayQueue<>(capacity);
Disposable[] disposable = {null};
Worker worker = scheduler.createWorker();
Runnable action = () -> {
while(!Thread.interrupted()) {
T item;
try {
// If the worker is backed by a thread pool, this may give that pool
// the opportunity to pick another task if no items were delivered in time
// This may prevent dead locks however at the cost of greatly degraded performance
//item = queue.poll(100, TimeUnit.MILLISECONDS);
item = take(queue);
} catch (Throwable e1) {
throw new RuntimeException(e1);
}
// System.out.println("Queue state of " + System.identityHashCode(queue) + ": " + queue.size() + " items seen: " + (++i[0]));
if(item == POISON) {
// System.out.println("Queue completed");
downstream.onComplete();
worker.dispose();
break;
}
else if(item == null) {
break;
}
else {
downstream.onNext(item);
// if(queue.remainingCapacity() == 0) {
// System.err.println("WARN: Consumer too slow");
// }
}
}
};
worker.schedulePeriodically(action, 0, 0, TimeUnit.MILLISECONDS);
// worker.schedule(action);
disposable[0] = upstream
// .delay(0, TimeUnit.MILLISECONDS)
.subscribe(
x -> {
// Thread.sleep(1);
// System.out.println("Putting on queue " + System.identityHashCode(queue) + ": " + queue.size());
//queue.put(x);
put(queue, x);
},
e -> downstream.onError(e),
() -> {
//queue.put((T)POISON);
put(queue, (T)POISON);
synchronized (queue) {
queue.notifyAll();
}
}
);
downstream.setDisposable(disposable[0]);
}
}, BackpressureStrategy.ERROR);
// Flowable.>generate(
// () -> queue,
// (q, e) -> {
// T item = q.take();
//// T item = q.poll();
//// System.out.println("Queue state of " + System.identityHashCode(queue) + ": " + queue.size() + " items seen: " + (++i[0]));
// if(item == POISON) {
//// System.out.println("Queue completed");
// e.onComplete();
// }
// else if(item == null) {
// // nothing to do
// }
// else {
// e.onNext(item);
// }
// },
// q -> { disposable[0].dispose(); }
// )
// .subscribeOn(scheduler)
//// .delay(0, TimeUnit.MILLISECONDS)
// .subscribe(x -> downstream.onNext(x), t -> downstream.onError(t), () -> downstream.onComplete());
// ;
}
/**
* Map each item to the same blocking queue instance
* thereby appending that item to the queue.
*
* @param
* @param capacity
* @return
*/
public static FlowableTransformer> queueProducer(int capacity) {
BlockingQueue queue = new ArrayBlockingQueue(capacity);
return upstream -> upstream
.map(item -> {
// if(queue.remainingCapacity() == 0) {
// System.err.println("Capacity exhausted");
// }
System.err.println("Putting to queue " + System.identityHashCode(queue) + " state: " + queue.size());
queue.put(item);
System.err.println("Returned (and possibly woke up) from put");
return queue;
})
.doOnComplete(() -> queue.put((T)POISON))
;
}
public static Flowable fromBlockingQueue(BlockingQueue queue, Predicate super T> isPoison) {
return Flowable.generate(
() -> queue,
(q, e) -> {
T item = q.take();
if (isPoison.test(item)) {
e.onComplete();
} else {
e.onNext(item);
}
},
q -> {}
);
}
/**
* Take items from the blocking queue and pass them on to the subscriber
*
* @param
* @return
*/
public static FlowableTransformer, T> queueConsumer() {
return upstream -> {
return Flowable.create(new FlowableOnSubscribe() {
@Override
public void subscribe(FlowableEmitter child) throws Exception {
upstream.subscribe(new FlowableSubscriber>() {
// protected Subscription s;
@Override
public void onSubscribe(Subscription s) {
// this.s = s;
child.setCancellable(s::cancel);
s.request(Long.MAX_VALUE);
// s.request(1);
}
// BlockingQueue queue = null;
public void drain(BlockingQueue queue) throws InterruptedException {
// this.queue = queue;
T item;
// while(!queue.isEmpty() && !child.isCancelled()) {
while((item = queue.take()) != null && !child.isCancelled()) {
// System.err.println("" + Thread.currentThread() + "- QueueState " + System.identityHashCode(queue) + ": " + queue.size());
// try {
//// item = queue.poll()();
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
if(item == POISON) {
System.err.println("POISON seen");
// child.onComplete();
} else {
System.out.println("Passed on an item " + "- QueueState " + System.identityHashCode(queue) + ": " + queue.size());
child.onNext(item);
}
}
// s.request(1);
}
@Override
public void onNext(BlockingQueue queue) {
try {
drain(queue);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public void onError(Throwable t) {
child.onError(t);
}
@Override
public void onComplete() {
// if(queue != null) {
// drain(queue);
// }
System.err.println("On complete called");
child.onComplete();
}
});
}
}, BackpressureStrategy.ERROR);
};
}
/**
* Utils method to create a transformer from a function that takes a FlowableEmitter and
* yields a FlowableSubscriber from it. Used to slightly reduce boilerplate.
*
* @param
* @param
* @param fsSupp
* @return
*/
public static FlowableTransformer createTransformer(Function super FlowableEmitter, ? extends FlowableSubscriber> fsSupp) {
return createTransformer(fsSupp, BackpressureStrategy.ERROR);
}
public static FlowableTransformer createTransformer(
Function super FlowableEmitter, ? extends FlowableSubscriber> fsSupp,
BackpressureStrategy backpressureStrategy) {
return upstream -> {
Flowable result = Flowable.create(new FlowableOnSubscribe() {
@Override
public void subscribe(FlowableEmitter emitter) throws Exception {
FlowableSubscriber subscriber = fsSupp.apply(emitter);
upstream.subscribe(subscriber);
}
}, backpressureStrategy);
return result;
};
}
/**
* Consume a flow by mapping it to empty maybes as long as there is no error.
* On error emit a maybe that holds the occurred exception.
* This method underneath uses blockingGet on the single result.
*
* @param flowable
*/
public static void consume(Flowable> flowable) {
Flowable tmp = flowable
//.mapOptional(x -> Optional.empty())
.concatMapMaybe(x -> Maybe.empty())
.onErrorReturn(t -> t);
Throwable e = tmp.singleElement().blockingGet();
if(e != null) {
throw new RuntimeException(e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy