/* This file is part of VoltDB.
* Copyright (C) 2008-2017 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.io.IOException;
import java.util.List;
import java.util.Map;
import org.voltcore.logging.Level;
import org.voltcore.logging.VoltLogger;
import org.voltcore.messaging.Mailbox;
import org.voltcore.utils.CoreUtils;
import org.voltdb.DependencyPair;
import org.voltdb.ParameterSet;
import org.voltdb.ProcedureRunner;
import org.voltdb.SiteProcedureConnection;
import org.voltdb.VoltTable;
import org.voltdb.VoltTable.ColumnInfo;
import org.voltdb.VoltType;
import org.voltdb.client.BatchTimeoutOverrideType;
import org.voltdb.exceptions.EEException;
import org.voltdb.exceptions.InterruptException;
import org.voltdb.exceptions.SQLException;
import org.voltdb.jni.ExecutionEngine;
import org.voltdb.messaging.FastDeserializer;
import org.voltdb.messaging.FragmentResponseMessage;
import org.voltdb.messaging.FragmentTaskMessage;
import org.voltdb.planner.ActivePlanRepository;
import org.voltdb.rejoin.TaskLog;
import org.voltdb.utils.Encoder;
import org.voltdb.utils.LogKeys;
import org.voltdb.utils.MiscUtils;
import org.voltdb.utils.VoltTableUtil;
import org.voltdb.utils.VoltTrace;
public class FragmentTask extends TransactionTask
{
/** java.util.logging logger. */
private static final VoltLogger LOG = new VoltLogger("HOST");
final Mailbox m_initiator;
final FragmentTaskMessage m_fragmentMsg;
final Map<Integer, List<VoltTable>> m_inputDeps;
boolean m_respBufferable = true;
static final byte[] m_rawDummyResponse;
static {
VoltTable dummyResponse = new VoltTable(new ColumnInfo("STATUS", VoltType.TINYINT));
dummyResponse.setStatusCode(VoltTableUtil.NULL_DEPENDENCY_STATUS);
m_rawDummyResponse = dummyResponse.buildReusableDependenyResult();
}
// This constructor is used during live rejoin log replay.
FragmentTask(Mailbox mailbox,
FragmentTaskMessage message,
ParticipantTransactionState txnState)
{
this(mailbox,
txnState,
null,
message,
null);
}
// This constructor is used during normal operation.
FragmentTask(Mailbox mailbox,
ParticipantTransactionState txnState,
TransactionTaskQueue queue,
FragmentTaskMessage message,
Map<Integer, List<VoltTable>> inputDeps)
{
super(txnState, queue);
m_initiator = mailbox;
m_fragmentMsg = message;
m_inputDeps = inputDeps;
if (txnState != null && !txnState.isReadOnly()) {
m_respBufferable = false;
}
}
public void setResponseNotBufferable() {
m_respBufferable = false;
}
private void deliverResponse(FragmentResponseMessage response) {
response.m_sourceHSId = m_initiator.getHSId();
response.setRespBufferable(m_respBufferable);
m_initiator.deliver(response);
}
@Override
protected void durabilityTraceEnd() {
final VoltTrace.TraceEventBatch traceLog = VoltTrace.log(VoltTrace.Category.SPI);
if (traceLog != null) {
traceLog.add(() -> VoltTrace.endAsync("durability",
MiscUtils.hsIdTxnIdToString(m_initiator.getHSId(), m_fragmentMsg.getSpHandle())));
}
}
@Override
public void run(SiteProcedureConnection siteConnection)
{
waitOnDurabilityBackpressureFuture();
if (hostLog.isDebugEnabled()) {
hostLog.debug("STARTING: " + this);
}
final VoltTrace.TraceEventBatch traceLog = VoltTrace.log(VoltTrace.Category.SPSITE);
if (traceLog != null) {
traceLog.add(() -> VoltTrace.beginDuration("runfragmenttask",
"txnId", TxnEgo.txnIdToString(getTxnId()),
"partition", Integer.toString(siteConnection.getCorrespondingPartitionId())));
}
// if this has a procedure name from the initiation bundled,
// inform the site connection here
String procName = m_fragmentMsg.getProcedureName();
if (procName != null) {
siteConnection.setProcedureName(procName);
}
// Set the begin undo token if we haven't already
// In the future we could record a token per batch
// and do partial rollback
if (!m_txnState.isReadOnly()) {
if (m_txnState.getBeginUndoToken() == Site.kInvalidUndoToken) {
m_txnState.setBeginUndoToken(siteConnection.getLatestUndoToken());
}
}
int originalTimeout = siteConnection.getBatchTimeout();
int individualTimeout = m_fragmentMsg.getBatchTimeout();
try {
if (BatchTimeoutOverrideType.isUserSetTimeout(individualTimeout)) {
siteConnection.setBatchTimeout(individualTimeout);
}
// execute the procedure
final FragmentResponseMessage response = processFragmentTask(siteConnection);
deliverResponse(response);
} finally {
if (BatchTimeoutOverrideType.isUserSetTimeout(individualTimeout)) {
siteConnection.setBatchTimeout(originalTimeout);
}
}
completeFragment();
if (hostLog.isDebugEnabled()) {
hostLog.debug("COMPLETE: " + this);
}
if (traceLog != null) {
traceLog.add(VoltTrace::endDuration);
}
}
@Override
public long getSpHandle()
{
return m_fragmentMsg.getSpHandle();
}
/**
* Produce a rejoining response.
*/
@Override
public void runForRejoin(SiteProcedureConnection siteConnection, TaskLog taskLog)
throws IOException
{
if (!m_txnState.isReadOnly()) {
taskLog.logTask(m_fragmentMsg);
}
final FragmentResponseMessage response =
new FragmentResponseMessage(m_fragmentMsg, m_initiator.getHSId());
response.setRecovering(true);
response.setStatus(FragmentResponseMessage.SUCCESS, null);
// Set the dependencies even if this is a dummy response. This site could be the master
// on elastic join, so the fragment response message is actually going to the MPI.
for (int frag = 0; frag < m_fragmentMsg.getFragmentCount(); frag++) {
final int outputDepId = m_fragmentMsg.getOutputDepId(frag);
response.addDependency(new DependencyPair.BufferDependencyPair(outputDepId,
m_rawDummyResponse, 0, m_rawDummyResponse.length));
}
deliverResponse(response);
completeFragment();
}
/**
* Run for replay after a live rejoin snapshot transfer.
*/
@Override
public void runFromTaskLog(SiteProcedureConnection siteConnection)
{
// Set the begin undo token if we haven't already
// In the future we could record a token per batch
// and do partial rollback
if (!m_txnState.isReadOnly()) {
if (m_txnState.getBeginUndoToken() == Site.kInvalidUndoToken) {
m_txnState.setBeginUndoToken(siteConnection.getLatestUndoToken());
}
}
// ignore response.
processFragmentTask(siteConnection);
completeFragment();
}
private void completeFragment()
{
// Check and see if we can flush early
// right now, this is just read-only and final task
// This
if (m_fragmentMsg.isFinalTask() && m_txnState.isReadOnly())
{
doCommonSPICompleteActions();
}
}
// Cut and pasted from ExecutionSite processFragmentTask(), then
// modifed to work in the new world
public FragmentResponseMessage processFragmentTask(SiteProcedureConnection siteConnection)
{
// Ensure default procs loaded here
// Also used for LoadMultipartitionTable
String procNameToLoad = m_fragmentMsg.getProcNameToLoad();
if (procNameToLoad != null) {
// this will ensure proc is loaded
ProcedureRunner runner = siteConnection.getProcedureRunner(procNameToLoad);
assert(runner != null);
}
// IZZY: actually need the "executor" HSId these days?
final FragmentResponseMessage currentFragResponse =
new FragmentResponseMessage(m_fragmentMsg, m_initiator.getHSId());
currentFragResponse.setStatus(FragmentResponseMessage.SUCCESS, null);
if (m_inputDeps != null) {
siteConnection.stashWorkUnitDependencies(m_inputDeps);
}
if (m_fragmentMsg.isEmptyForRestart()) {
int outputDepId = m_fragmentMsg.getOutputDepId(0);
currentFragResponse.addDependency(new DependencyPair.BufferDependencyPair(outputDepId,
m_rawDummyResponse, 0, m_rawDummyResponse.length));
return currentFragResponse;
}
ProcedureRunner currRunner = siteConnection.getProcedureRunner(m_fragmentMsg.getProcedureName());
long[] executionTimes = null;
int succeededFragmentsCount = 0;
if (currRunner != null) {
currRunner.getExecutionEngine().setPerFragmentTimingEnabled(m_fragmentMsg.isPerFragmentStatsRecording());
if (m_fragmentMsg.isPerFragmentStatsRecording()) {
// At this point, we will execute the fragments one by one.
executionTimes = new long[1];
}
}
for (int frag = 0; frag < m_fragmentMsg.getFragmentCount(); frag++)
{
byte[] planHash = m_fragmentMsg.getPlanHash(frag);
final int outputDepId = m_fragmentMsg.getOutputDepId(frag);
ParameterSet params = m_fragmentMsg.getParameterSetForFragment(frag);
final int inputDepId = m_fragmentMsg.getOnlyInputDepId(frag);
long fragmentId = 0;
byte[] fragmentPlan = null;
/*
* Currently the error path when executing plan fragments
* does not adequately distinguish between fatal errors and
* abort type errors that should result in a roll back.
* Assume that it is ninja: succeeds or doesn't return.
* No roll back support.
*
* AW in 2012, the preceding comment might be wrong,
* I am pretty sure what we don't support is partial rollback.
* The entire procedure will roll back successfully on failure
*/
VoltTable dependency = null;
try {
FastDeserializer fragResult;
fragmentPlan = m_fragmentMsg.getFragmentPlan(frag);
String stmtText = null;
// if custom fragment, load the plan and get local fragment id
if (fragmentPlan != null) {
// statement text for unplanned fragments are pulled from the message,
// to ensure that we get the correct constants from the most recent
// invocation.
stmtText = m_fragmentMsg.getStmtText(frag);
fragmentId = ActivePlanRepository.loadOrAddRefPlanFragment(planHash, fragmentPlan, null);
}
// otherwise ask the plan source for a local fragment id
else {
fragmentId = ActivePlanRepository.getFragmentIdForPlanHash(planHash);
stmtText = ActivePlanRepository.getStmtTextForPlanHash(planHash);
}
// set up the batch context for the fragment set
siteConnection.setBatch(m_fragmentMsg.getCurrentBatchIndex());
fragResult = siteConnection.executePlanFragments(
1,
new long[] { fragmentId },
new long [] { inputDepId },
new ParameterSet[] { params },
null,
stmtText == null ? null : new String[] { stmtText }, // for long-running queries
new boolean[] { false }, // FragmentTasks don't generate statement hashes,
null,
m_txnState.txnId,
m_txnState.m_spHandle,
m_txnState.uniqueId,
m_txnState.isReadOnly(),
VoltTrace.log(VoltTrace.Category.EE) != null);
// get a copy of the result buffers from the cache buffer so we can post the
// fragment response to the network
final int tableSize;
final byte fullBacking[];
try {
// read the complete size of the buffer used
fragResult.readInt();
// read number of dependencies (1)
fragResult.readInt();
// read the dependencyId() -1;
fragResult.readInt();
tableSize = fragResult.readInt();
fullBacking = new byte[tableSize];
// get a copy of the buffer
fragResult.readFully(fullBacking);
} catch (final IOException ex) {
LOG.error("Failed to deserialze result table" + ex);
throw new EEException(ExecutionEngine.ERRORCODE_WRONG_SERIALIZED_BYTES);
}
if (hostLog.isTraceEnabled()) {
hostLog.l7dlog(Level.TRACE,
LogKeys.org_voltdb_ExecutionSite_SendingDependency.name(),
new Object[] { outputDepId }, null);
}
currentFragResponse.addDependency(new DependencyPair.BufferDependencyPair(outputDepId, fullBacking, 0, tableSize));
} catch (final EEException e) {
hostLog.l7dlog( Level.TRACE, LogKeys.host_ExecutionSite_ExceptionExecutingPF.name(), new Object[] { Encoder.hexEncode(planHash) }, e);
currentFragResponse.setStatus(FragmentResponseMessage.UNEXPECTED_ERROR, e);
if (currentFragResponse.getTableCount() == 0) {
// Make sure the response has at least 1 result with a valid DependencyId
currentFragResponse.addDependency(new DependencyPair.BufferDependencyPair(outputDepId,
m_rawDummyResult, 0, m_rawDummyResult.length));
}
break;
} catch (final SQLException e) {
hostLog.l7dlog( Level.TRACE, LogKeys.host_ExecutionSite_ExceptionExecutingPF.name(), new Object[] { Encoder.hexEncode(planHash) }, e);
currentFragResponse.setStatus(FragmentResponseMessage.UNEXPECTED_ERROR, e);
if (currentFragResponse.getTableCount() == 0) {
// Make sure the response has at least 1 result with a valid DependencyId
currentFragResponse.addDependency(new DependencyPair.BufferDependencyPair(outputDepId,
m_rawDummyResult, 0, m_rawDummyResult.length));
}
break;
}
catch (final InterruptException e) {
hostLog.l7dlog( Level.TRACE, LogKeys.host_ExecutionSite_ExceptionExecutingPF.name(), new Object[] { Encoder.hexEncode(planHash) }, e);
currentFragResponse.setStatus(FragmentResponseMessage.UNEXPECTED_ERROR, e);
if (currentFragResponse.getTableCount() == 0) {
// Make sure the response has at least 1 result with a valid DependencyId
currentFragResponse.addDependency(new DependencyPair.BufferDependencyPair(outputDepId,
m_rawDummyResult, 0, m_rawDummyResult.length));
}
break;
}
finally {
// ensure adhoc plans are unloaded
if (fragmentPlan != null) {
ActivePlanRepository.decrefPlanFragmentById(fragmentId);
}
// If the executed fragment comes from a stored procedure, we need to update the per-fragment stats for it.
// Notice that this code path is used to handle multi-partition stored procedures.
// The single-partition stored procedure handler is in the ProcedureRunner.
if (currRunner != null) {
succeededFragmentsCount = currRunner.getExecutionEngine().extractPerFragmentStats(1, executionTimes);
long stmtDuration = 0;
int stmtResultSize = 0;
int stmtParameterSetSize = 0;
if (m_fragmentMsg.isPerFragmentStatsRecording()) {
stmtDuration = executionTimes == null ? 0 : executionTimes[0];
stmtResultSize = dependency == null ? 0 : dependency.getSerializedSize();
stmtParameterSetSize = params == null ? 0 : params.getSerializedSize();
}
currRunner.getStatsCollector().endFragment(m_fragmentMsg.getStmtName(frag),
m_fragmentMsg.isCoordinatorTask(),
succeededFragmentsCount == 0,
m_fragmentMsg.isPerFragmentStatsRecording(),
stmtDuration,
stmtResultSize,
stmtParameterSetSize);
}
}
}
return currentFragResponse;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("FragmentTask:");
sb.append(" TXN ID: ").append(TxnEgo.txnIdToString(getTxnId()));
sb.append(" SP HANDLE ID: ").append(TxnEgo.txnIdToString(getSpHandle()));
sb.append(" ON HSID: ").append(CoreUtils.hsIdToString(m_initiator.getHSId()));
return sb.toString();
}
}