Every interval I retrieve tweets with a certain query.
These tweets have to be passed to services which calculate and manipulate those tweets.
So these services are subscribed to my publisher. So publisher.hasSubscribers() returns true. But the submit or offer function does not invoke the onNext of my subscriber.
So as a "fix", I cycle through my subscribers and invoke it myself. But that shouldn't be the case.
This is the constructor of my publisher.
public TwitterStreamer(Executor executor, int maxBufferCapacity, long period, TimeUnit unit, String searchQuery){
super(executor, maxBufferCapacity);
this.searchQuery = searchQuery;
scheduler = new ScheduledThreadPoolExecutor(1);
this.tweetGetter = scheduler.scheduleAtFixedRate(
() -> {
List<String> tweets = getTweets(searchQuery);
/* this.lastCall = LocalDateTime.now();
for(Flow.Subscriber sub : this.getSubscribers()){
sub.onNext(tweets);
}*/
this.submit(tweets);
if(tweets.size() >= 20) this.close();
}, 0, period, unit);
}
This is my subscriber
package myFlowAPI;
import Interfaces.IProcess;
import Services.LogToFileService;
import java.util.List;
import java.util.concurrent.Flow;
import java.util.concurrent.atomic.AtomicInteger;
public class MySubscriber implements Flow.Subscriber<List<String>> {
private Flow.Subscription subscription;
private AtomicInteger count;
private IProcess processor;
private String name;
private int DEMAND = 0;
public MySubscriber(String name, IProcess processor){
this.name = name;
this.processor = processor;
}
#Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
}
#Override
public void onNext(List<String> item) {
Object result = this.processor.process(item);
this.readResult(result);
switch (this.processor.getClass().getSimpleName()){
case "CalculateTweetStatsService":
if((Integer) result >= 20){
this.subscription.cancel();
}
break;
}
}
#Override
public void onError(Throwable throwable) {
System.out.println("Error is thrown " + throwable.getMessage());
}
#Override
public void onComplete() {
if(this.processor instanceof LogToFileService){
((LogToFileService) processor).closeResource();
}
System.out.println("complete");
}
private void readResult(Object result){
System.out.println("Result of " + this.processor.getClass().getSimpleName() + " processor is " + result.toString());
}
}
This is the main where I subscribe to the publisher
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
String searchQuery;
try{
searchQuery = args[0] != null ? args[0] : "#capgemini50";
}catch (ArrayIndexOutOfBoundsException ex){
searchQuery = "#capgemini50";
}
TwitterStreamer streamer = new TwitterStreamer(executor, 5, 15L, SECONDS, searchQuery);
MySubscriber subscriber1 = new MySubscriber("LogFileSubscriber", new LogToFileService("./tweetsLogger.txt"));
MySubscriber subscriber2 = new MySubscriber("TotalTweetSubscriber",new CalculateTweetStatsService());
streamer.subscribe(subscriber1);
streamer.subscribe(subscriber2);
}
You need the subscriber to explicitly request data e.g. upon subscription (see https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/Flow.Subscription.html#request-long-):
#Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
this.subscription.request(1);
}
Same upon processing in onNext() to request the next item.
Related
I am testing gRPC with a list of a million of items and sending this million of item by an stream.
I have this code on client:
on my test host: "Localhost", ipPort = 7777
ManagedChannel comunicationChanel = ManagedChannelBuilder.forAddress(host, ipPort)
.enableFullStreamDecompression().compressorRegistry(CompressorRegistry.getDefaultInstance())
.decompressorRegistry(DecompressorRegistry.getDefaultInstance()).usePlaintext(true)
.maxInboundMessageSize(200888896).build();
ListMessageSRVStub asyncStub = ListMessageSRVGrpc.newStub(comunicationChanel);
List<MessageValue> millionMessages = new ArrayList<MessageValue>();
for (long i = 0l; i < 1000000; i++) {
millionMessages.add(MessageValue.newBuilder().build());
}
long before = System.currentTimeMillis();
StreamObserver<MessageValue> requestObserver = asyncStub.recievetonm(responseObserverTonMessages);
long i = 0;
for (MessageValue messageValue : millionMessages) {
requestObserver.onNext(messageValue);
i++;
if (i % 50000 == 0) {
LOG.info("Sended: " + i);
}
}
requestObserver.onCompleted();
long total = System.currentTimeMillis() - before;
LOG.info("Time = " + total);
but I have this Exception:
Exception in thread "main" io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 16777216 byte(s) of direct memory (used: 1879048487, max: 1894252544)
at io.netty.util.internal.PlatformDependent.incrementMemoryCounter(PlatformDependent.java:640)
at io.netty.util.internal.PlatformDependent.allocateDirectNoCleaner(PlatformDependent.java:594)
at io.netty.buffer.PoolArena$DirectArena.allocateDirect(PoolArena.java:764)
at io.netty.buffer.PoolArena$DirectArena.newChunk(PoolArena.java:740)
at io.netty.buffer.PoolArena.allocateNormal(PoolArena.java:244)
at io.netty.buffer.PoolArena.allocate(PoolArena.java:214)
at io.netty.buffer.PoolArena.allocate(PoolArena.java:146)
at io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:324)
at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:185)
at io.netty.buffer.AbstractByteBufAllocator.buffer(AbstractByteBufAllocator.java:121)
at io.grpc.netty.NettyWritableBufferAllocator.allocate(NettyWritableBufferAllocator.java:51)
at io.grpc.internal.MessageFramer.writeKnownLengthUncompressed(MessageFramer.java:226)
at io.grpc.internal.MessageFramer.writeUncompressed(MessageFramer.java:167)
at io.grpc.internal.MessageFramer.writePayload(MessageFramer.java:140)
at io.grpc.internal.AbstractStream.writeMessage(AbstractStream.java:52)
at io.grpc.internal.ClientCallImpl.sendMessage(ClientCallImpl.java:438)
at io.grpc.ForwardingClientCall.sendMessage(ForwardingClientCall.java:52)
at io.grpc.ForwardingClientCall.sendMessage(ForwardingClientCall.java:52)
at io.grpc.stub.ClientCalls$CallToStreamObserverAdapter.onNext(ClientCalls.java:320)
at com.oesia.grpgtest.server.TestClient.tonsofMSG(TestClient.java:130)
at com.oesia.grpgtest.server.TestClient.main(TestClient.java:146)
Any way to solve the problem sending that amounght of data?
Have you tried writing to with respect to slow receivers:
public class GracefulWriteHandler extends ChannelInboundHandlerAdapter {
#Override
public void channelActive(ChannelHandlerContext ctx) {
writeIfPossible(ctx.channel());
}
#Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) {
writeIfPossible(ctx.channel());
}
private void writeIfPossible(Channel channel) {
while(needsToWrite && channel.isWritable()) {
channel.writeAndFlush(createMessage());
}
}
}
I solve with this class, usin my own observer:
class OwnClientResponseObserver implements ClientResponseObserver<MessageValue, MessageValue> {
private ClientCallStreamObserver<MessageValue> requestStream;
private CountDownLatch countDownLatch;
Iterator<MessageValue> iterator;
public OwnClientResponseObserver(CountDownLatch countDownLatch, final Iterator<MessageValue> iterator) {
this.countDownLatch = countDownLatch;
this.iterator = iterator;
}
#Override
public void onNext(MessageValue value) {
LOG.info(value.toString());
}
#Override
public void onError(Throwable t) {
LOG.log(Level.SEVERE, "An savage error apears", t);
}
#Override
public void onCompleted() {
LOG.info("Finalized!!!");
}
#Override
public void beforeStart(ClientCallStreamObserver<MessageValue> requestStream) {
this.requestStream = requestStream;
this.requestStream.disableAutoInboundFlowControl();
this.requestStream.setOnReadyHandler(new Runnable() {
#Override
public void run() {
long i = 1L;
while (requestStream.isReady()) {
if (iterator.hasNext()) {
requestStream.onNext(iterator.next());
if (i % 1000 == 0) {
LOG.info("Checked in" + i);
}
i++;
} else {
requestStream.onCompleted();
countDownLatch.countDown();
}
}
}
});
}
I am working on my application which sends data to zeromq. Below is what my application does:
I have a class SendToZeroMQ that send data to zeromq.
Add same data to retryQueue in the same class so that it can be retried later on if acknowledgment is not received. It uses guava cache with maximumSize limit.
Have a separate thread which receives acknowledgement from the zeromq for the data that was sent earlier and if acknowledgement is not received, then SendToZeroMQ will retry sending that same piece of data. And if acknowledgement is received, then we will remove it from retryQueue so that it cannot be retried again.
Idea is very simple and I have to make sure my retry policy works fine so that I don't loose my data. This is very rare but in case if we don't receive acknolwedgements.
I am thinking of building two types of RetryPolicies but I am not able to understand how to build that here corresponding to my program:
RetryNTimes: In this it will retry N times with a particular sleep between each retry and after that, it will drop the record.
ExponentialBackoffRetry: In this it will exponentially keep retrying. We can set some max retry limit and after that it won't retry and will drop the record.
Below is my SendToZeroMQ class which sends data to zeromq, also retry every 30 seconds from a background thread and start ResponsePoller runnable which keeps running forever:
public class SendToZeroMQ {
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
private final Cache<Long, byte[]> retryQueue =
CacheBuilder
.newBuilder()
.maximumSize(10000000)
.concurrencyLevel(200)
.removalListener(
RemovalListeners.asynchronous(new CustomListener(), executorService)).build();
private static class Holder {
private static final SendToZeroMQ INSTANCE = new SendToZeroMQ();
}
public static SendToZeroMQ getInstance() {
return Holder.INSTANCE;
}
private SendToZeroMQ() {
executorService.submit(new ResponsePoller());
// retry every 30 seconds for now
executorService.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
for (Entry<Long, byte[]> entry : retryQueue.asMap().entrySet()) {
sendTo(entry.getKey(), entry.getValue());
}
}
}, 0, 30, TimeUnit.SECONDS);
}
public boolean sendTo(final long address, final byte[] encodedRecords) {
Optional<ZMQSocketInfo> liveSockets = PoolManager.getInstance().getNextSocket();
if (!liveSockets.isPresent()) {
return false;
}
return sendTo(address, encodedRecords, liveSockets.get().getSocket());
}
public boolean sendTo(final long address, final byte[] encodedByteArray, final Socket socket) {
ZMsg msg = new ZMsg();
msg.add(encodedByteArray);
boolean sent = msg.send(socket);
msg.destroy();
// adding to retry queue
retryQueue.put(address, encodedByteArray);
return sent;
}
public void removeFromRetryQueue(final long address) {
retryQueue.invalidate(address);
}
}
Below is my ResponsePoller class which polls all the acknowledgement from the zeromq. And if we get an acknowledgement back from the zeromq then we will remove that record from the retry queue so that it doesn't get retried otherwise it will get retried.
public class ResponsePoller implements Runnable {
private static final Random random = new Random();
#Override
public void run() {
ZContext ctx = new ZContext();
Socket client = ctx.createSocket(ZMQ.PULL);
String identity = String.format("%04X-%04X", random.nextInt(), random.nextInt());
client.setIdentity(identity.getBytes(ZMQ.CHARSET));
client.bind("tcp://" + TestUtils.getIpaddress() + ":8076");
PollItem[] items = new PollItem[] {new PollItem(client, Poller.POLLIN)};
while (!Thread.currentThread().isInterrupted()) {
// Tick once per second, pulling in arriving messages
for (int centitick = 0; centitick < 100; centitick++) {
ZMQ.poll(items, 10);
if (items[0].isReadable()) {
ZMsg msg = ZMsg.recvMsg(client);
Iterator<ZFrame> it = msg.iterator();
while (it.hasNext()) {
ZFrame frame = it.next();
try {
long address = TestUtils.getAddress(frame.getData());
// remove from retry queue since we got the acknowledgment for this record
SendToZeroMQ.getInstance().removeFromRetryQueue(address);
} catch (Exception ex) {
// log error
} finally {
frame.destroy();
}
}
msg.destroy();
}
}
}
ctx.destroy();
}
}
Question:
As you can see above, I am sending encodedRecords to zeromq using SendToZeroMQ class and then it gets retried every 30 seconds depending on whether we got an acknolwedgement back from ResponsePoller class or not.
For each encodedRecords there is a unique key called address and that's what we will get back from zeromq as an acknowledgement.
How can I go ahead and extend this example to build two retry policies that I mentioned above and then I can pick what retry policy I want to use while sending data. I came up with below interface but then I am not able understand how should I move forward to implement those retry policies and use it in my above code.
public interface RetryPolicy {
/**
* Called when an operation has failed for some reason. This method should return
* true to make another attempt.
*/
public boolean allowRetry(int retryCount, long elapsedTimeMs);
}
Can I use guava-retrying or failsafe here becuase these libraries already have many retry policies which I can use?
I am not able to work out all the details regarding how to use the relevant API-s, but as for algorithm, you could try:
the retry-policy needs to have some sort of state attached to each message (atleast the number of times the current message has been retried, possible what the current delay is). You need to decide whether the RetryPolicy should keep that itself or if you want to store it inside the message.
instead of allowRetry, you could have a method calculating when the next retry should occur (in absolute time or as a number of milliseconds in the future), which will be a function of the state mentioned above
the retry queue should contain information on when each message should be retried.
instead of using scheduleAtFixedRate, find the message in the retry queue which has the lowest when_is_next_retry (possibly by sorting on absolute retry-timestamp and picking the first), and let the executorService reschedule itself using schedule and the time_to_next_retry
for each retry, pull it from the retry queue, send the message, use the RetryPolicy for calculating when the next retry should be (if it is to be retried) and insert back into the retry queue with a new value for when_is_next_retry (if the RetryPolicy returns -1, it could mean that the message shall not be retried any more)
not a perfect way, but can be achieved by below way as well.
public interface RetryPolicy {
public boolean allowRetry();
public void decreaseRetryCount();
}
Create two implementation. For RetryNTimes
public class RetryNTimes implements RetryPolicy {
private int maxRetryCount;
public RetryNTimes(int maxRetryCount) {
this.maxRetryCount = maxRetryCount;
}
public boolean allowRetry() {
return maxRetryCount > 0;
}
public void decreaseRetryCount()
{
maxRetryCount = maxRetryCount-1;
}}
For ExponentialBackoffRetry
public class ExponentialBackoffRetry implements RetryPolicy {
private int maxRetryCount;
private final Date retryUpto;
public ExponentialBackoffRetry(int maxRetryCount, Date retryUpto) {
this.maxRetryCount = maxRetryCount;
this.retryUpto = retryUpto;
}
public boolean allowRetry() {
Date date = new Date();
if(maxRetryCount <= 0 || date.compareTo(retryUpto)>=0)
{
return false;
}
return true;
}
public void decreaseRetryCount() {
maxRetryCount = maxRetryCount-1;
}}
You need to make some changes in SendToZeroMQ class
public class SendToZeroMQ {
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
private final Cache<Long,RetryMessage> retryQueue =
CacheBuilder
.newBuilder()
.maximumSize(10000000)
.concurrencyLevel(200)
.removalListener(
RemovalListeners.asynchronous(new CustomListener(), executorService)).build();
private static class Holder {
private static final SendToZeroMQ INSTANCE = new SendToZeroMQ();
}
public static SendToZeroMQ getInstance() {
return Holder.INSTANCE;
}
private SendToZeroMQ() {
executorService.submit(new ResponsePoller());
// retry every 30 seconds for now
executorService.scheduleAtFixedRate(new Runnable() {
public void run() {
for (Map.Entry<Long, RetryMessage> entry : retryQueue.asMap().entrySet()) {
RetryMessage retryMessage = entry.getValue();
if(retryMessage.getRetryPolicy().allowRetry())
{
retryMessage.getRetryPolicy().decreaseRetryCount();
entry.setValue(retryMessage);
sendTo(entry.getKey(), retryMessage.getMessage(),retryMessage);
}else
{
retryQueue.asMap().remove(entry.getKey());
}
}
}
}, 0, 30, TimeUnit.SECONDS);
}
public boolean sendTo(final long address, final byte[] encodedRecords, RetryMessage retryMessage) {
Optional<ZMQSocketInfo> liveSockets = PoolManager.getInstance().getNextSocket();
if (!liveSockets.isPresent()) {
return false;
}
if(null==retryMessage)
{
RetryPolicy retryPolicy = new RetryNTimes(10);
retryMessage = new RetryMessage(retryPolicy,encodedRecords);
retryQueue.asMap().put(address,retryMessage);
}
return sendTo(address, encodedRecords, liveSockets.get().getSocket());
}
public boolean sendTo(final long address, final byte[] encodedByteArray, final ZMQ.Socket socket) {
ZMsg msg = new ZMsg();
msg.add(encodedByteArray);
boolean sent = msg.send(socket);
msg.destroy();
return sent;
}
public void removeFromRetryQueue(final long address) {
retryQueue.invalidate(address);
}}
Here is a working little simulation of your environment that shows how this can be done. Note the Guava cache is the wrong data structure here, since you aren't interested in eviction (I think). So I'm using a concurrent hashmap:
package experimental;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
class Experimental {
/** Return the desired backoff delay in millis for the given retry number, which is 1-based. */
interface RetryStrategy {
long getDelayMs(int retry);
}
enum ConstantBackoff implements RetryStrategy {
INSTANCE;
#Override
public long getDelayMs(int retry) {
return 1000L;
}
}
enum ExponentialBackoff implements RetryStrategy {
INSTANCE;
#Override
public long getDelayMs(int retry) {
return 100 + (1L << retry);
}
}
static class Sender {
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(4);
private final ConcurrentMap<Long, Retrier> pending = new ConcurrentHashMap<>();
/** Send the given data with given address on the given socket. */
void sendTo(long addr, byte[] data, int socket) {
System.err.println("Sending " + Arrays.toString(data) + "#" + addr + " on " + socket);
}
private class Retrier implements Runnable {
private final RetryStrategy retryStrategy;
private final long addr;
private final byte[] data;
private final int socket;
private int retry;
private Future<?> future;
Retrier(RetryStrategy retryStrategy, long addr, byte[] data, int socket) {
this.retryStrategy = retryStrategy;
this.addr = addr;
this.data = data;
this.socket = socket;
this.retry = 0;
}
synchronized void start() {
if (future == null) {
future = executorService.submit(this);
pending.put(addr, this);
}
}
synchronized void cancel() {
if (future != null) {
future.cancel(true);
future = null;
}
}
private synchronized void reschedule() {
if (future != null) {
future = executorService.schedule(this, retryStrategy.getDelayMs(++retry), MILLISECONDS);
}
}
#Override
synchronized public void run() {
sendTo(addr, data, socket);
reschedule();
}
}
long getVerifiedAddr() {
System.err.println("Pending messages: " + pending.size());
Iterator<Long> i = pending.keySet().iterator();
long addr = i.hasNext() ? i.next() : 0;
return addr;
}
class CancellationPoller implements Runnable {
#Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
long addr = getVerifiedAddr();
if (addr == 0) {
continue;
}
System.err.println("Verified message (to be cancelled) " + addr);
Retrier retrier = pending.remove(addr);
if (retrier != null) {
retrier.cancel();
}
}
}
}
Sender initialize() {
executorService.submit(new CancellationPoller());
return this;
}
void sendWithRetriesTo(RetryStrategy retryStrategy, long addr, byte[] data, int socket) {
new Retrier(retryStrategy, addr, data, socket).start();
}
}
public static void main(String[] args) {
Sender sender = new Sender().initialize();
for (long i = 1; i <= 10; i++) {
sender.sendWithRetriesTo(ConstantBackoff.INSTANCE, i, null, 42);
}
for (long i = -1; i >= -10; i--) {
sender.sendWithRetriesTo(ExponentialBackoff.INSTANCE, i, null, 37);
}
}
}
You can use apache camel. It provides a component for zeromq, and tools like errohandler, redeliverypolicy, deadletter channel and such things are natively provided.
I have the problem regarding the implementation of One Publisher - Multiple Subscribers pattern. The Publisher uses the fixed-size buffer and queue the messages. The messages are send to all subscribers. The ordering of messages get by subscribers must be the same as the ordering of publishing messages.
I use BlockingQueue to hold publisher messages (publisherQueue) and pass them to each subscriber BlockingQueue (subscriberQueue).
The issue is that the buffer and subscribers are working correctly, but the buffer size (publisherQueue.size()) always returns 1.
System.out.println("Actual number of messages in buffer: " + publisherQueue.size());
Here is my full code:
PublisherSubscriberService.java
package program;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class PublisherSubscriberService {
private int buffer;
private int subscribersNumber;
static Set<subscriber> subscribers = new HashSet<subscriber>();
public PublisherSubscriberService(int buffer, int subscribersNumber) {
this.buffer = buffer;
this.subscribersNumber = subscribersNumber;
}
public void addsubscriber(subscriber subscriber) {
subscribers.add(subscriber);
}
public void start() {
publisher publisher = new publisher(buffer);
System.out.println("publisher started the job");
for (int i = 0; i < subscribersNumber; i++) {
subscriber subscriber = new subscriber(buffer);
subscriber.setName(Integer.toString(i + 1));
subscribers.add(subscriber);
new Thread(subscriber).start();
System.out.println("Subscriber " + subscriber.getName() + " started the job");
}
new Thread(publisher).start();
}
public class Publisher implements Runnable {
private int buffer;
final BlockingQueue<Message> publisherQueue;
public Publisher(int buffer) {
this.buffer = buffer;
publisherQueue = new LinkedBlockingQueue<>(buffer);
}
#Override
public void run() {
for (int i = 1; i < 100; i++) {
Message messageObject = new Message("" + i);
try {
Thread.sleep(50);
publisherQueue.put(messageObject);
System.out.println("Queued message no " + messageObject.getMessage());
System.out.println("Actual number of messages in buffer: " + publisherQueue.size());
for (subscriber subscriber : subscribers) {
subscriber.subscriberQueue.put(messageObject);
}
publisherQueue.take();
} catch (InterruptedException e) {
System.out.println("Some error");
e.printStackTrace();
}
}
}
}
class Subscriber implements Runnable {
private String name;
private int buffer;
final BlockingQueue<Message> subscriberQueue;
public Subscriber(int buffer) {
this.buffer = buffer;
subscriberQueue = new LinkedBlockingQueue<>(buffer);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
#Override
public void run() {
try {
Message messageObject;
while (true) {
Thread.sleep(100);
messageObject = subscriberQueue.take();
System.out.println(this.getName() + " got message: " + messageObject.getMessage());
}
} catch (InterruptedException e) {
System.out.println("Some error");
e.printStackTrace();
}
}
}
class Message {
private String message;
public Message(String str) {
this.message = str;
}
public String getMessage() {
return message;
}
}
}
PublisherSubscriberProgram.java
package program;
public class ProducerConsumerProgram {
public static void main(String[] args) {
ProducerConsumerService service = new ProducerConsumerService(10, 3);
service.start();
}
}
Your publisher never has more than 1 item in the queue. Each time through your loop you put and take a single item:
**publisherQueue.put(messageObject);**
System.out.println("Queued message no " + messageObject.getMessage());
System.out.println("Actual number of messages in buffer: " + publisherQueue.size());
for (subscriber subscriber : subscribers) {
subscriber.subscriberQueue.put(messageObject);
}
**publisherQueue.take();**
With the code you have provided, there is point in even having the publisher queue.
I am implementing a module where my main process spawns a set of parallel and sequential child processes (tasks) to complete its work. The tasks themselves are mainly fetching data from various sources and performing computations. Some are CPU- while others are IO-bound.
The current implementation uses Java Executor/Completion service in multiple steps to achieve this. An example of this process workflow can be depicted as follows:
Task A1 ---------------->
Task0 -> Task A2 ---------------->
Task A3 -> Task B1 -> Task C (combines results from all tasks to generate output)
Task B2 ->
Task A4 --------------->
Tasks A1-A4 run in parallel, and so does tasks B1 and B2. Finally, the task C depends on all the tasks A's and B's to compile the final output.
Building this using Executor service didn't seem very clean and I've been looking for better ways to do this as these task dependencies could change or increase in complexity with time and having Futures and Callable to manage them can get uglier over time.
I've been exploring the topic a little and came across reactive extensions and actor model frameworks. Akka has seemed a little too much for this while RxJava on a high level seemed like a reasonable fit that would simplify and make the design more extensible due to its stream/event-based processing pattern.
Some of the examples in RxJava Threading Examples have also looked quite promising.
I'm here to seek for some advice from the community as to whether this is the right approach and if there are other ways/better frameworks to solve such problems.
=====================================================================================================
Wrote the following using JGraphT but still need to figure out how to reuse the thread pools. As in this case I end up creating new Thread Executors for each request. Posting main parts of the code here which should give an idea of the approach.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.jgrapht.DirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.traverse.TopologicalOrderIterator;
public class GraphTaskExecutor {
ThreadExecutor executor;
private List<Result> results;
private List<TaskInfo> log;
private DirectedGraph<GraphTask, DefaultEdge> graph;
Set<GraphTask> executing;
public GraphTaskExecutor() {
executor = new ThreadExecutor(Runtime.getRuntime()
.availableProcessors() * 4, 60,
new LinkedBlockingQueue<Runnable>());
results = new ArrayList<Result>();
log = new ArrayList<TaskInfo>();
executing = new HashSet<GraphTask>();
}
public List<Result> execute(Request request, List<GraphTask> tasks) {
System.out.println("Preparing task runner. Num Tasks: " + tasks.size());
graph = new GraphTaskBuilder(tasks).buildGraph();
processTasks();
awaitCompletion();
return results;
}
private void awaitCompletion() {
try {
executor.awaitTermination(3, TimeUnit.DAYS);
System.out.println("Results " + results.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void processTasks() {
if (graph.vertexSet().size() == 0) {
executor.shutdown();
System.out
.println("All tasks completed... shutting down executor service");
} else {
synchronized (graph) {
Iterator<GraphTask> iter = new TopologicalOrderIterator<GraphTask, DefaultEdge>(
graph);
while (iter.hasNext()) {
GraphTask task = iter.next();
if (graph.incomingEdgesOf(task).size() == 0
&& !executing.contains(task)) {
executor.execute(task);
executing.add(task);
}
}
}
}
}
private void completed(GraphTask t) {
System.out.println("Completed Task: " + t.getName());
synchronized (graph) {
for (DefaultEdge edge : graph.outgoingEdgesOf(t)) {
GraphTask target = graph.getEdgeTarget(edge);
target.addData(t.getData());
}
if (t.isEndPoint())
results.add(t.getResult());
graph.removeVertex(t);
executing.remove(t);
}
processTasks();
}
private class ThreadExecutor extends ThreadPoolExecutor {
public ThreadExecutor(int corePoolSize, long keepAliveSeconds,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, corePoolSize, keepAliveSeconds,
TimeUnit.SECONDS, workQueue);
}
#Override
protected void beforeExecute(Thread thread, Runnable runTask) {
super.beforeExecute(thread, runTask);
}
#Override
protected void afterExecute(Runnable runTask, Throwable e) {
super.afterExecute(runTask, e);
completed((GraphTask) runTask);
}
}
public static void main(String arg[]) throws Exception {
GraphTaskExecutor graphTaskExecutor = new GraphTaskExecutor();
TaskContext context = new TaskContext();
List<GraphTask> tasks = new ArrayList<GraphTask>();
Request request = new Request(1);
Set<DataType> empty = new HashSet<DataType>();
Set<DataType> producer = new HashSet<DataType>(Arrays.asList(
DataType.ACCT_INFO, DataType.PROJECTIONS));
Set<DataType> consumer = new HashSet<DataType>(Arrays.asList(
DataType.ACCT_INFO, DataType.PROJECTIONS));
Set<DataType> accountResult = new HashSet<DataType>(
Arrays.asList(DataType.ACCT_INFO));
Set<DataType> projectionResult = new HashSet<DataType>(
Arrays.asList(DataType.PROJECTIONS));
Set<DataType> intraDayResult = new HashSet<DataType>(
Arrays.asList(DataType.PROJECTIONS));
tasks.add(new GraphTask(context, "1", "A", producer, empty, empty));
tasks.add(new GraphTask(context, "2", "X", producer, consumer, empty,
"A"));
tasks.add(new GraphTask(context, "3", "Y", producer, consumer,
accountResult, "A"));
tasks.add(new GraphTask(context, "4", "B", producer, consumer, empty,
"A"));
tasks.add(new GraphTask(context, "5", "C", producer, consumer, empty,
"B"));
tasks.add(new GraphTask(context, "6", "D", producer, consumer,
intraDayResult, "C"));
tasks.add(new GraphTask(context, "7", "E", producer, consumer,
projectionResult, "D", "X", "Y"));
graphTaskExecutor.execute(request, tasks);
System.out.println("All DONE");
}
}
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
public class GraphTask extends AbstractTask {
private Set<String> dependencies = new TreeSet<String>();
public GraphTask(TaskContext context, String id, String name,
Set<DataType> produces, Set<DataType> consumes,
Set<DataType> endpoints, String... dependency) {
super(id, name, context, produces, consumes, endpoints);
dependencies.addAll(Arrays.asList(dependency));
}
public GraphTask(TaskContext context, String id, String name,
Set<DataType> produces, Set<DataType> consumes,
Set<DataType> endpoints) {
super(id, name, context, produces, consumes, endpoints);
}
public void addDependency(String dependency) {
this.dependencies.add(dependency);
}
public Data process(TaskContext context, Data data) throws TaskException {
int time = (int) (Math.random() * 10);
System.out.println("Task " + getName() + " estimated to run for "
+ time + " secs");
TaskResult result = null;
try {
Thread.sleep(time * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = new TaskResult(getName());
for (DataType d: getProduces()) {
result.addData(d, d.toString());
}
return result;
}
public Set<String> getDependencies() {
return dependencies;
}
public void setDependencies(Set<String> dependencies) {
this.dependencies = dependencies;
}
}
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public abstract class AbstractTask implements Task<Data, Result> {
private String identifier;
private String name;
private TaskContext context;
private List<Data> prevData;
private Data data;
private Set<DataType> produces;
private Set<DataType> consumes;
private Set<DataType> endpoints;
private TaskStatus status;
private LocalTime startTime;
private LocalTime endTime;
private Result result;
public AbstractTask(String id, String name, TaskContext context,
Set<DataType> produces, Set<DataType> consumes,
Set<DataType> endpoints) {
this.identifier = id;
this.name = name;
this.context = context;
this.consumes = consumes;
this.produces = produces;
this.endpoints = endpoints;
this.data = new Data();
this.prevData = new ArrayList<Data>();
this.status = TaskStatus.SUCCESS;
}
public AbstractTask(String id, String name, TaskContext context) {
this(id, name, context, new HashSet<DataType>(),
new HashSet<DataType>(), new HashSet<DataType>());
}
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TaskContext getContext() {
return context;
}
public void setContext(TaskContext context) {
this.context = context;
}
public boolean isEndPoint() {
return (endpoints.size() > 0);
}
private Data preProcess() throws MissingDataException,
SkippedTaskException, ErrorTaskException {
Map<TaskStatus, Data> statusData = new HashMap<TaskStatus, Data>();
Map<DataType, Object> allData = new HashMap<DataType, Object>();
// Get all data from previous results
for (Data r : prevData) {
statusData.put(r.getStatus(), r);
allData.putAll(r.getObjects());
}
Data data = new Data();
data.addData(allData);
data.setStatus(deriveTaskStatus(statusData));
switch (data.getStatus()) {
case SUCCESS:
for (DataType d : consumes) {
if (!allData.containsKey(d)) {
throw new MissingDataException("Task " + name + " Missing input data for "
+ d);
}
}
break;
case SKIPPED:
throw new SkippedTaskException("Previous Task was skipped");
case ERROR:
throw new ErrorTaskException("Previous Task failed");
}
return data;
}
private TaskStatus deriveTaskStatus(Map<TaskStatus, Data> statusData) {
if (statusData.containsKey(TaskStatus.ERROR))
return TaskStatus.ERROR;
if (statusData.containsKey(TaskStatus.SKIPPED))
return TaskStatus.SKIPPED;
return TaskStatus.SUCCESS;
}
private Result postProcess(Data outputData) throws MissingDataException {
Result result = new Result();
for (DataType d : endpoints) {
if (!outputData.getObjects().containsKey(d)) {
throw new MissingDataException("Missing end point data for " + d);
}
result.addData(d, outputData.getObject(d));
}
return result;
}
#Override
public void run() {
System.out.println("Running task: " + name);
try {
Data inputData = preProcess();
data = process(context, inputData);
result = postProcess(data);
} catch (MissingDataException | SkippedTaskException
| ErrorTaskException e) {
data = new Data(TaskStatus.SKIPPED, new Error("SKIP_TASK",
"Skip Task", e));
e.printStackTrace();
} catch (TaskException e) {
data = new Data(TaskStatus.ERROR, new Error("PREV_ERROR",
"Error in dependent task", e));
e.printStackTrace();
}
}
public abstract Data process(TaskContext context, Data data)
throws TaskException;
#Override
public void addData(Data data) {
this.prevData.add(data);
}
public TaskStatus getStatus() {
return status;
}
public void setStatus(TaskStatus status) {
this.status = status;
}
public Result getResult() {
return result;
}
public Set<DataType> getProduces() {
return produces;
}
public void setProduces(Set<DataType> produces) {
this.produces = produces;
}
public Set<DataType> getConsumes() {
return consumes;
}
public void setConsumes(Set<DataType> consumes) {
this.consumes = consumes;
}
public Data getData() {
return data;
}
#Override
public String toString() {
return "[" + name + "]";
}
#Override
public TaskInfo getTaskInfo() {
return new TaskInfo(this.identifier, this.name, this.status,
this.startTime, this.endTime);
}
public Set<DataType> getEndpoints() {
return endpoints;
}
public void setEndpoints(Set<DataType> endpoints) {
this.endpoints = endpoints;
}
}
Rx-Java would be a good option here as it's designed to build sophisticated message flows, fork and join parallel executions inside your app (just another compile dependency). While CPU bound tasks could end up with a simple callback, IO bound require async IO support (you can also do it yourself with rx-java).
Akka and Vert.x are complete frameworks to build applications compared to Rx-Java being just a library to bring advantages of asynchronous functional programming into your app.
I have an application in Eclipse RCP where I want to fire a function called 'LogOutUser()' if the user leaves his/ her application idle for, say, five minutes.
How do I go about doing this?
I don't know if the RCP framework supports this internally. However, I wrote my own "helper" class, which is a singleton client session manager. Eclipse won't know natively how you connect to your datasource. In my case I am connecting using EJB3 calls and listening to JMS queues and topics.
My class was written to detect when the datasource or "server" went down. It would also reconnect when the server came up. The server inactivity is detected by listening to heartbeat DTO's sent by the server. This feedback is useful to present to the user. I have adapted this class to cater for user interface inactivity.
The class is quite simple. It is a singleton, so it can be called simply at any point in your client-side RCP application. The heartbeat uses an observer and so you will have to add a HeartBeatEventListener to hook into this functionality. You can adapt the class to do the same for the user interface inactivity. However, I have just provided an updateUserInterfaceActivity() method which you must call when there is user activity. Perhaps this can be hooked into a global mouse and a global keyboard event handler.
I have also added a TrayItem to update the user...
Here is the class:
package com.kingsleywebb.clientsessionmanagement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolTip;
import org.eclipse.swt.widgets.TrayItem;
import com.kingsleywebb.clientsessionmanagement.entity.HeartbeatDTO;
public class ClientSessionManagement implements HeartbeatEventListener {
private static final Image IMG_CONNECTED = null; // Set this to a "connected image"
private static final Image IMG_DISCONNECTED = null; // Set this to a "disconnected image"
private static final long CONNECTION_INACTIVITY_TIME_MS = 30000L; // 30 seconds
private static final long USER_INTERFACE_INACTIVITY_TIME_MS = 300000L; // 5 minutes
private static final Log LOG = LogFactory.getLog(ClientSessionManagement.class);
private static ClientSessionManagement theInstance = null;
private static long connectionTimestamp = 0;
private static long userInterfaceActivityTimestamp = 0;
private synchronized static void createInstance() {
if (theInstance == null) {
theInstance = new ClientSessionManagement();
}
}
public static ClientSessionManagement getInstance() {
if (theInstance == null) {
createInstance();
}
return theInstance;
}
private ClientSessionManagement() {
this.connectionListenerList = new ArrayList<ConnectionListener>();
updateConnectionTimestamp();
Cron cron = new Cron();
Thread cronThread = new Thread(cron);
cronThread.start();
}
private boolean connected = true;
private ToolTip toolTipConnected;
private ToolTip toolTipDisconnected;
private TrayItem trayItem = null;
private String appName = null;
private String version = null;
private String whiteLabel = null;
private String userName = null;
private String deskName = null;
private String serverName = null;
private String userMnemonic = null;
private MenuItem miShowPopups;
private MenuItem miSoundBeep;
private List<ConnectionListener> connectionListenerList;
private void updateConnectionTimestamp() {
ClientSessionManagement.connectionTimestamp = System.currentTimeMillis();
}
private synchronized long getLastHeartbeatInMsAgo() {
return System.currentTimeMillis() - ClientSessionManagement.connectionTimestamp;
}
public synchronized void updateHeartbeat() {
updateConnectionTimestamp();
}
public synchronized void checkHeartbeatInterval() {
if (getLastHeartbeatInMsAgo() < CONNECTION_INACTIVITY_TIME_MS) {
showConnected();
}
else {
showDisconnected();
}
}
private void updateUserInterfaceActivityTimestamp() {
ClientSessionManagement.userInterfaceActivityTimestamp = System.currentTimeMillis();
}
private synchronized long getLastUserInterfaceActivityInMsAgo() {
return System.currentTimeMillis() - ClientSessionManagement.userInterfaceActivityTimestamp;
}
public synchronized void updateUserInterfaceActivity() {
updateUserInterfaceActivityTimestamp();
}
public synchronized void checkUserInterfaceActivityInterval() {
if (getLastUserInterfaceActivityInMsAgo() > USER_INTERFACE_INACTIVITY_TIME_MS) {
logoutUser();
}
}
private void logoutUser() {
// Implement logout functionality here
}
private void showConnected() {
if (!connected) {
connected = true;
Display.getDefault().asyncExec(new Runnable() {
public void run() {
// Update icon
if (trayItem != null) {
trayItem.setImage(ClientSessionManagement.IMG_CONNECTED);
trayItem.getToolTip().setVisible(false);
trayItem.setToolTip(toolTipConnected);
trayItem.getToolTip().setVisible(true);
}
// Update hover tooltip
updateHoverTooltip();
}
});
notifyConnectionListeners();
}
}
private void showDisconnected() {
if (connected) {
connected = false;
Display.getDefault().asyncExec(new Runnable() {
public void run() {
// Update icon
if (trayItem != null) {
trayItem.setImage(ClientSessionManagement.IMG_DISCONNECTED);
trayItem.getToolTip().setVisible(false);
trayItem.setToolTip(toolTipDisconnected);
trayItem.getToolTip().setVisible(true);
}
// Update hover tooltip
updateHoverTooltip();
}
});
notifyConnectionListeners();
}
}
private void updateHoverTooltip() {
if (trayItem != null) {
// Application info
String applicationInfo = null;
if (appName != null && version != null && whiteLabel != null) {
// appName* | version | whitelabel
applicationInfo = " Application: " + " " + appName + " " + version + " [" + whiteLabel + "]\r\n";
}
// User info
String userInfo = null;
if (userName != null && deskName != null && serverName != null) {
userInfo = " User: " + " " + userName + " (" + deskName + ") on " + serverName + "\r\n";
}
// Connection info
String connectionInfo = connected ? " Server Connected" : " SERVER DISCONNECTED!!!";
String status = connectionInfo + "\r\n\r\n" + (applicationInfo != null ? applicationInfo : "") +
(userInfo != null ? userInfo : "");
trayItem.setToolTipText(status);
LOG.info(status);
}
}
public void setTrayItem(Shell shell, TrayItem trayItem) {
this.trayItem = trayItem;
/*
* Property files to persist these settings - removed for simplicity
*
* final WorkstationProperties p = WorkstationProperties.getInstance();
* boolean showNotificationPopups = !"No".equalsIgnoreCase(p.getProperty("notifications.showNotificationPopups"));
* boolean soundNotificationBeep = !"No".equalsIgnoreCase(p.getProperty("notifications.soundNotificationBeep"));
*/
boolean showNotificationPopups = true;
boolean soundNotificationBeep = true;
final Menu menu = new Menu (shell, SWT.POP_UP);
miShowPopups = new MenuItem (menu, SWT.CHECK);
miShowPopups.setSelection(showNotificationPopups);
miShowPopups.setText("Show Notification Popups");
miShowPopups.addListener (SWT.Selection, new Listener () {
public void handleEvent (Event event) {
LOG.info("notifications.showNotificationPopups = " + miShowPopups.getSelection());
// Property files to persist these settings - removed for simplicity
//p.setProperty("notifications.showNotificationPopups", miShowPopups.getSelection() ? "Yes" : "No");
}
});
miSoundBeep = new MenuItem (menu, SWT.CHECK);
miSoundBeep.setSelection(soundNotificationBeep);
miSoundBeep.setText("Play Notification Beep");
miSoundBeep.addListener (SWT.Selection, new Listener () {
public void handleEvent (Event event) {
LOG.info("notifications.soundNotificationBeep = " + miSoundBeep.getSelection());
// Property files to persist these settings - removed for simplicity
//p.setProperty("notifications.soundNotificationBeep", miSoundBeep.getSelection() ? "Yes" : "No");
}
});
this.trayItem.addListener (SWT.MenuDetect, new Listener () {
public void handleEvent (Event event) {
menu.setVisible (true);
}
});
toolTipConnected = new ToolTip(shell, SWT.BALLOON);
toolTipConnected.setText((appName != null ? appName : "<Application Name>") + " Status");
toolTipConnected.setMessage("Connected to server.");
toolTipConnected.setLocation(600, 600);
toolTipConnected.setVisible(false);
toolTipDisconnected = new ToolTip(shell, SWT.ICON_WARNING);
toolTipDisconnected.setText((appName != null ? appName : "<Application Name>") + " Status");
toolTipDisconnected.setMessage("DISCONNECTED from server.");
toolTipDisconnected.setLocation(500, 500);
toolTipDisconnected.setVisible(false);
this.trayItem.setToolTip(toolTipConnected);
}
public boolean isShowPopups() {
return miShowPopups.getSelection();
}
public boolean isSoundBeep() {
return miSoundBeep.getSelection();
}
public void setAppName(String appName) {
this.appName = appName;
}
public void setVersion(String version) {
this.version = version;
}
public void setWhiteLabel(String whiteLabel) {
this.whiteLabel = whiteLabel;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setDeskName(String deskName) {
this.deskName = deskName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
updateHoverTooltip();
}
public String getUserMnemonic() {
return userMnemonic;
}
public void setUserMnemonic(String userMnemonic) {
this.userMnemonic = userMnemonic;
}
public void heartbeatArrived(HeartbeatDTO heartbeatDTO) {
updateHeartbeat();
}
public boolean isConnected() {
return connected;
}
public boolean addConnectionListener(ConnectionListener connectionListener) {
return connectionListenerList.add(connectionListener);
}
public boolean removeConnectionListener(ConnectionListener connectionListener) {
return connectionListenerList.remove(connectionListener);
}
public void notifyConnectionListeners() {
for (Iterator<ConnectionListener> i = connectionListenerList.iterator(); i.hasNext();) {
ConnectionListener connectionListener = i.next();
if (connected) {
connectionListener.connected();
}
else {
connectionListener.disconnected();
}
}
}
/**
*
* #author Kingsley Webb
*
* Check heartbeat interval periodically display warning to user accordingly.
*/
class Cron implements Runnable {
public void run() {
// Wait 15s extra before 1st check
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
LOG.error(e);
}
while (true) {
// Check every 5s - increase for better performance, but you get the idea...
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
LOG.error(e);
}
checkHeartbeatInterval();
checkUserInterfaceActivityInterval();
}
}
}
}
Some other supporting classes:
package com.kingsleywebb.clientsessionmanagement;
public interface ConnectionListener {
public void connected();
public void disconnected();
}
package com.kingsleywebb.clientsessionmanagement;
import com.kingsleywebb.clientsessionmanagement.entity.HeartbeatDTO;
public interface HeartbeatEventListener {
public void heartbeatArrived(HeartbeatDTO heartbeatDTO);
}
If you take a look in the bundle org.eclipse.ui.ide.application there is a class org.eclipse.ui.internal.ide.application.IDEIdleHelper which tries to perform gc after a interval of user inactivity. Probably you can reuse the logic that detects the inactivity
I usually use a Display.addFilter(eventType, listener) for the event types that should keep the session alive combined with a Display.timerExec(milliseconds, runnable) that is run periodically and tests for the last interesting event.
I use milliseconds = 5000 so the period is 5 min up to 5 min 5 sec before the user is logged out (or whatever...). Also I listener for the SWT event types (in 3.7) KeyDown, KeyUp, MouseDown, MouseUp, MouseVerticalWheel, MouseHorizontalWheel MouseDoubleClick, Touch, Gesture, Activate, Iconify, Deiconify, Move, and Resize.