com.kolibrifx.plovercrest.server.internal.folds.EngineFoldTriggerer Maven / Gradle / Ivy
Show all versions of plovercrest-server Show documentation
/*
* Copyright (c) 2010-2017, KolibriFX AS. Licensed under the Apache License, version 2.0.
*/
package com.kolibrifx.plovercrest.server.internal.folds;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Logger;
import com.kolibrifx.common.Disposable;
import com.kolibrifx.common.dispatcher.Dispatcher;
import com.kolibrifx.common.dispatcher.ThreadedDispatcher;
import com.kolibrifx.plovercrest.server.streams.Stream;
import com.kolibrifx.plovercrest.server.streams.StreamCreateListener;
import com.kolibrifx.plovercrest.server.streams.StreamDataTriggerer;
import com.kolibrifx.plovercrest.server.streams.StreamEngine;
import com.kolibrifx.plovercrest.server.streams.folds.CombinatorStrategy;
/**
* Triggers fold callbacks for a set of input tables.
*
* An event is triggered for each element in each table from the given timstamp, with timestamps in
* order across input tables. No events are triggered until there is data available in all input
* tables.
*/
public class EngineFoldTriggerer implements Disposable {
private static final Logger log = Logger.getLogger(EngineFoldTriggerer.class);
private final Collection disposables = new ArrayList<>();
private final Dispatcher dispatcher = new ThreadedDispatcher("EngineFoldTriggerer");
private final Runnable consumeTask;
private final AtomicBoolean closed = new AtomicBoolean();
private final AtomicInteger remainingReaders;
private final long fromTimestamp;
private final List readers;
public EngineFoldTriggerer(final StreamEngine engine,
final Collection tableNames,
final long fromTimestamp,
final CombinatorStrategy combinatorStrategy,
final FoldCallback callback) {
this.fromTimestamp = fromTimestamp;
remainingReaders = new AtomicInteger(tableNames.size());
readers = new ArrayList<>();
for (int i = 0; i < tableNames.size(); i++) {
readers.add(null);
}
final StreamDataTriggerer dataTriggerer = new StreamDataTriggerer() {
@Override
public void wakeUpNow() {
runConsumeTask();
}
@Override
public void wakeUpAtTime(final long clockMillis) {
if (closed.get()) {
return;
}
final long delay = clockMillis - dispatcher.currentTimeMillis();
dispatcher.runLater(consumeTask, Math.max(0, delay));
}
};
consumeTask = new Runnable() {
@Override
public void run() {
try {
int count = 0;
while (!closed.get()) {
if (!combinatorStrategy.tryConsumeNext(readers, callback)) {
// nothing more to produce for this fold (for now)
for (final PeekableInputStream> reader : readers) {
reader.registerDataTriggererIfReachedEnd(dataTriggerer);
}
break;
}
count++;
}
if (log.isTraceEnabled()) {
log.trace("Consumed " + count + " elements from " + readers.size() + " streams");
}
} catch (final RuntimeException e) {
e.printStackTrace();
}
}
};
int i = 0;
for (final String tableName : tableNames) {
setUpCreateListener(engine, tableName, i++);
}
}
private void runConsumeTask() {
if (closed.get()) {
return;
}
dispatcher.runLater(consumeTask);
}
private void setUpCreateListener(final StreamEngine engine, final String tableName, final int readerIndex) {
final AtomicBoolean done = new AtomicBoolean();
final Disposable disposable = engine.addCreateListener(tableName, new StreamCreateListener() {
@Override
public void onCreated(final String name) {
final Stream stream = engine.openRaw(name);
if (stream == null) {
log.error("Got create event, but failed to open stream: " + name);
return;
}
if (!done.compareAndSet(false, true)) {
log.warn("Got more than one create event for table " + tableName + " (index " + readerIndex + ")");
return;
}
readers.set(readerIndex, new ByteArrayPeekableInputStream(stream, fromTimestamp));
final int remaining = remainingReaders.decrementAndGet();
assert remaining >= 0;
if (remaining == 0) {
if (log.isInfoEnabled()) {
log.info("All input tables exist, starting consume logic");
}
runConsumeTask();
}
}
});
if (done.get()) {
// this can happen if the stream already exists
disposable.close();
} else {
disposables.add(disposable);
}
}
@Override
public void close() {
if (!closed.compareAndSet(false, true)) {
return;
}
for (final Disposable d : disposables) {
d.close();
}
disposables.clear();
dispatcher.close();
}
}