/*
* Copyright 2013 Eediom Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.araqne.logdb.query.engine;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.araqne.logdb.Query;
import org.araqne.logdb.QueryCommand;
import org.araqne.logdb.QueryCommand.Status;
import org.araqne.logdb.QueryStatusCallback;
import org.araqne.logdb.QueryTask;
import org.araqne.logdb.QueryTask.TaskStatus;
import org.araqne.logdb.QueryTaskEvent;
import org.araqne.logdb.QueryTaskListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class QueryTaskScheduler implements Runnable {
private final Logger logger = LoggerFactory.getLogger(QueryTaskScheduler.class);
private boolean started;
// regardless of canceled or not, just finished
private boolean finished;
private long startTime;
private long finishTime;
private Query query;
// logical query command pipe
private List<QueryCommand> pipeline = new ArrayList<QueryCommand>();
// monitor task complete and start new ready tasks
private QueryTaskTracer tracer = new QueryTaskTracer();
private ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
public QueryTaskScheduler(Query query, List<QueryCommand> pipeline) {
this.query = query;
this.pipeline = pipeline;
}
public Query getQuery() {
return query;
}
public boolean isStarted() {
return started;
}
public long getStartTime() {
return startTime;
}
public boolean isFinished() {
return finished;
}
public long getFinishTime() {
return finishTime;
}
@Override
public void run() {
started = true;
startTime = System.currentTimeMillis();
for (QueryCommand cmd : pipeline) {
cmd.setStatus(Status.Running);
QueryTask mainTask = cmd.getMainTask();
if (mainTask != null) {
tracer.addDependency(mainTask);
mainTask.addListener(tracer);
}
}
startReadyTasks();
}
public void cancel() {
// query can be cancelled at preRun(), tracer don't have any link.
// tracer itself should be started even if query is cancelled.
for (QueryCommand cmd : pipeline) {
QueryTask mainTask = cmd.getMainTask();
if (mainTask != null) {
markCancelRecursively(mainTask);
}
}
}
public void awaitTaskDoneRecusively(QueryTask task) throws InterruptedException {
for (QueryTask subTask : task.getSubTasks()) {
awaitTaskDoneRecusively(subTask);
}
task.await();
}
public void stop() {
Lock lock = rwLock.writeLock();
try {
lock.lock();
if (!tracer.isStopped()) {
for (QueryTask dependency : tracer.getDependencies()) {
markCancelRecursively(dependency);
}
for (QueryTask dependency : tracer.getDependencies()) {
try {
awaitTaskDoneRecusively(dependency);
} catch (InterruptedException e) {
logger.error("QueryTaskScheduler.stop() failed because waiting runnigTask failed", e);
}
}
}
} finally {
lock.unlock();
}
startRecursively(tracer);
}
private synchronized void startReadyTasks() {
// later task runner can be completed before tracer.run(), and can cause
// duplicated query finish callback
boolean finished = tracer.isRunnable() || tracer.getStatus() == TaskStatus.CANCELED;
for (QueryCommand cmd : pipeline) {
QueryTask mainTask = cmd.getMainTask();
if (mainTask != null)
startRecursively(mainTask);
}
// all main task completed?
if (finished)
tracer.run();
}
private void startRecursively(QueryTask task) {
if (task.isRunnable()) {
// prevent duplicated run caused by late thread start
if (logger.isDebugEnabled())
logger.debug("araqne logdb: query [{}] task [{}:{}] start thread",
new Object[] { query.getId(), task.getID(), task });
task.setStatus(TaskStatus.RUNNING);
new QueryTaskRunner(this, task).start();
} else {
if (logger.isDebugEnabled()) {
StringBuilder sb = new StringBuilder();
for (QueryTask d : task.getDependencies())
sb.append("\n\t" + d.getID() + ":" + d + " " + d.getStatus());
if (logger.isDebugEnabled())
logger.debug("araqne logdb: query [{}] task [{}:{} {}] is not runnable. dependencies => [{}]",
new Object[] { query.getId(), task.getID(), task, task.getStatus(), sb.toString() });
}
}
for (QueryTask subTask : task.getSubTasks())
startRecursively(subTask);
}
private void markCancel(QueryTask task) {
if (task.getStatus() != TaskStatus.COMPLETED) {
task.setStatus(TaskStatus.CANCELED);
task.done();
if (logger.isDebugEnabled()) {
String msg = null;
Throwable failure = task.getFailure();
if (failure != null)
msg = failure.getMessage() != null ? failure.getMessage() : failure.getClass().getName();
logger.debug("araqne logdb: canceled query [{}] task [{}] cause [{}]", new Object[] { query.getId(), task, msg });
}
}
}
private void markCancelRecursively(QueryTask task) {
for (QueryTask subTask : task.getSubTasks()) {
markCancelRecursively(subTask);
}
markCancel(task);
}
private class QueryTaskTracer extends QueryTask implements QueryTaskListener {
private AtomicBoolean closed = new AtomicBoolean();
@Override
public void run() {
if (!closed.compareAndSet(false, true))
return;
if (logger.isDebugEnabled())
logger.debug("araqne logdb: all query [{}] tasks are completed", query.getId());
query.postRun();
finished = true;
finishTime = System.currentTimeMillis();
// notify finish immediately
for (QueryStatusCallback c : query.getCallbacks().getStatusCallbacks()) {
try {
c.onChange(query);
} catch (Throwable t) {
logger.warn("araqne logdb: query [" + query.getId() + "] change callback should not throw any exception", t);
}
}
}
@Override
public void onStart(QueryTaskEvent event) {
}
@Override
public void onComplete(QueryTaskEvent event) {
event.setHandled(true);
if (logger.isDebugEnabled())
logger.debug("araqne logdb: query [{}] task [{}:{}] completed",
new Object[] { query.getId(), event.getTask().getID(), event.getTask() });
}
@Override
public void onCleanUp(QueryTaskEvent event) {
startReadyTasks();
}
@Override
public String toString() {
return "tracer for query " + query.getId() + ", " + query.getQueryString();
}
}
}