/**
* IncomingMessageBuffer
* Copyright 04.01.2016 by Michael Peter Christen, @0rb1t3r
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program in the file lgpl21.txt
* If not, see <http://www.gnu.org/licenses/>.
*/
package org.loklak.data;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.util.log.Log;
import org.loklak.objects.MessageEntry;
import org.loklak.objects.Timeline;
import org.loklak.objects.UserEntry;
public class IncomingMessageBuffer extends Thread {
private final static int MESSAGE_QUEUE_MAXSIZE = 100000;
//private final static int bufferLimit = MESSAGE_QUEUE_MAXSIZE * 3 / 4;
private static BlockingQueue<DAO.MessageWrapper> messageQueue = new ArrayBlockingQueue<DAO.MessageWrapper>(MESSAGE_QUEUE_MAXSIZE);
private static AtomicInteger queueClients = new AtomicInteger(0);
private boolean shallRun = true, isBusy = false;
public static int getMessageQueueSize() {
return messageQueue.size();
}
public static int getMessageQueueMaxSize() {
return MESSAGE_QUEUE_MAXSIZE;
}
public static int getMessageQueueClients() {
return queueClients.get();
}
public IncomingMessageBuffer() {
}
public MessageEntry readMessage(String id) {
if (id == null || id.length() == 0) return null;
for (DAO.MessageWrapper mw: messageQueue) {
if (id.equals(mw.t.getIdStr())) return mw.t;
}
return null;
}
/**
* ask the thread to shut down
*/
public void shutdown() {
this.shallRun = false;
this.interrupt();
Log.getLog().info("catched QueuedIndexing termination signal");
}
public boolean isBusy() {
return this.isBusy;
}
@Override
public void run() {
// work loop
loop: while (this.shallRun) try {
this.isBusy = false;
if (messageQueue.isEmpty() || !DAO.wait_ready(1000)) {
// in case that the queue is empty, try to fill it with previously pushed content
//List<Map<String, Object>> shard = this.jsonBufferHandler.getBufferShard();
// if the shard has content, turn this into messages again
// if such content does not exist, simply sleep a while
try {Thread.sleep(10000);} catch (InterruptedException e) {}
continue loop;
}
DAO.MessageWrapper mw;
this.isBusy = true;
AtomicInteger candMessages = new AtomicInteger();
AtomicInteger knownMessagesCache = new AtomicInteger();
int maxBulkSize = 200;
List<DAO.MessageWrapper> bulk = new ArrayList<>();
pollloop: while ((mw = messageQueue.poll()) != null) {
if (DAO.messages.existsCache(mw.t.getIdStr())) {
knownMessagesCache.incrementAndGet();
continue pollloop;
}
candMessages.incrementAndGet();
// in case that the message queue is too large, dump the queue into a file here
// to make room that clients can continue to push without blocking
/*
if (messageQueue.size() > bufferLimit) {
this.jsonBufferHandler.buffer(mw.t.getCreatedAt(), mw.t.toMap(mw.u, false, Integer.MAX_VALUE, ""));
continue pollloop;
}
*/
// if there is time enough to finish this, continue to write into the index
mw.t.enrich(); // we enrich here again because the remote peer may have done this with an outdated version or not at all
bulk.add(mw);
if (bulk.size() >= maxBulkSize) {
dumpbulk(bulk, candMessages, knownMessagesCache);
bulk.clear();
}
}
if (bulk.size() >= 0) {
dumpbulk(bulk, candMessages, knownMessagesCache);
bulk.clear();
}
this.isBusy = false;
} catch (Throwable e) {
Log.getLog().warn("QueuedIndexing THREAD", e);
}
Log.getLog().info("QueuedIndexing terminated");
}
private void dumpbulk(List<DAO.MessageWrapper> bulk, AtomicInteger candMessages, AtomicInteger knownMessagesCache) {
long dumpstart = System.currentTimeMillis();
int notWrittenDouble = DAO.writeMessageBulk(bulk).size();
knownMessagesCache.addAndGet(notWrittenDouble);
candMessages.addAndGet(-notWrittenDouble);
long dumpfinish = System.currentTimeMillis();
DAO.log("dumped timelines: " + candMessages + " new " + knownMessagesCache + " known from cache, storage time: " + (dumpfinish - dumpstart) + " ms, remaining messages: " + messageQueue.size());
candMessages.set(0);
knownMessagesCache.set(0);
}
public static void addScheduler(Timeline tl, final boolean dump) {
queueClients.incrementAndGet();
for (MessageEntry me: tl) addScheduler(me, tl.getUser(me), dump);
queueClients.decrementAndGet();
}
public static void addScheduler(final MessageEntry t, final UserEntry u, final boolean dump) {
try {
messageQueue.put(new DAO.MessageWrapper(t, u, dump));
} catch (InterruptedException e) {
Log.getLog().warn(e);
}
}
public static boolean addSchedulerAvailable() {
return messageQueue.remainingCapacity() > 0;
}
}