/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.iv2;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import org.voltcore.logging.VoltLogger;
import org.voltdb.dtxn.TransactionState;
public class TransactionTaskQueue
{
protected static final VoltLogger hostLog = new VoltLogger("HOST");
final protected SiteTaskerQueue m_taskQueue;
/*
* Multi-part transactions create a backlog of tasks behind them. A queue is
* created for each multi-part task to maintain the backlog until the next
* multi-part task.
*/
private Deque<TransactionTask> m_backlog = new ArrayDeque<TransactionTask>();
/*
* Track the maximum spHandle offered to the task queue
*/
private long m_maxTaskedSpHandle;
TransactionTaskQueue(SiteTaskerQueue queue, long initialSpHandle)
{
m_taskQueue = queue;
m_maxTaskedSpHandle = initialSpHandle;
}
/**
* If necessary, stick this task in the backlog.
* Many network threads may be racing to reach here, synchronize to
* serialize queue order
* @param task
* @return true if this task was stored, false if not
*/
synchronized boolean offer(TransactionTask task)
{
Iv2Trace.logTransactionTaskQueueOffer(task);
TransactionState txnState = task.getTransactionState();
if (!txnState.isReadOnly()) {
m_maxTaskedSpHandle = Math.max(m_maxTaskedSpHandle, txnState.m_spHandle);
}
boolean retval = false;
if (!m_backlog.isEmpty()) {
/*
* This branch happens during regular execution when a multi-part is in progress.
* The first task for the multi-part is the head of the queue, and all the single parts
* are being queued behind it. The txnid check catches tasks that are part of the multi-part
* and immediately queues them for execution.
*/
if (task.getTxnId() != m_backlog.getFirst().getTxnId())
{
m_backlog.addLast(task);
retval = true;
}
else {
taskQueueOffer(task);
}
}
else {
/*
* Base case nothing queued nothing in progress
* If the task is a multipart then put an entry in the backlog which
* will act as a barrier for single parts, queuing them for execution after the
* multipart
*/
if (!task.getTransactionState().isSinglePartition()) {
m_backlog.addLast(task);
retval = true;
}
taskQueueOffer(task);
}
return retval;
}
// Add a local method to offer to the SiteTaskerQueue so we have
// a single point we can log through.
private void taskQueueOffer(TransactionTask task)
{
Iv2Trace.logSiteTaskerQueueOffer(task);
m_taskQueue.offer(task);
}
/**
* @return the maximum spHandle offered to the task queue
*/
public long getMaxTaskedSpHandle() {
return m_maxTaskedSpHandle;
}
/**
* Try to offer as many runnable Tasks to the SiteTaskerQueue as possible.
* @param txnId The transaction ID of the TransactionTask which is completing and causing the flush
* @return the number of TransactionTasks queued to the SiteTaskerQueue
*/
synchronized int flush(long txnId)
{
int offered = 0;
// If the first entry of the backlog is a completed transaction, clear it so it no longer
// blocks the backlog then iterate the backlog for more work.
//
// Note the kooky corner case where a multi-part transaction can actually have multiple outstanding
// tasks. At first glance you would think that because the relationship is request response there
// can be only one outstanding task for a given multi-part transaction.
//
// That isn't true.
//
// A rollback can cause there to be a fragment task as well as a rollback
// task. The rollback is generated asynchronously by another partition.
// If we don't flush all the associated tasks now then flush won't be called again because it is waiting
// for the complete transaction task that is languishing in the queue to do the flush post multi-part.
// It can't be called eagerly because that would destructively flush single parts as well.
if (m_backlog.isEmpty() || !m_backlog.getFirst().getTransactionState().isDone()) {
return offered;
}
m_backlog.removeFirst();
Iterator<TransactionTask> iter = m_backlog.iterator();
while (iter.hasNext()) {
TransactionTask task = iter.next();
long lastQueuedTxnId = task.getTxnId();
taskQueueOffer(task);
++offered;
if (task.getTransactionState().isSinglePartition()) {
// single part can be immediately removed and offered
iter.remove();
continue;
}
else {
// leave the mp fragment at the head of the backlog but
// iterate and take care of the kooky case explained above.
while (iter.hasNext()) {
task = iter.next();
if (task.getTxnId() == lastQueuedTxnId) {
iter.remove();
taskQueueOffer(task);
++offered;
}
}
break;
}
}
return offered;
}
/**
* Restart the current task at the head of the queue. This will be called
* instead of flush by the currently blocking MP transaction in the event a
* restart is necessary.
*/
synchronized void restart()
{
taskQueueOffer(m_backlog.getFirst());
}
/**
* How many Tasks are un-runnable?
* @return
*/
synchronized int size()
{
return m_backlog.size();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("TransactionTaskQueue:").append("\n");
sb.append("\tSIZE: ").append(size());
if (!m_backlog.isEmpty()) {
sb.append("\tHEAD: ").append(m_backlog.getFirst());
}
return sb.toString();
}
}