/* 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.nio.ByteBuffer; import java.util.HashMap; import java.util.List; import java.util.Map; import org.voltcore.logging.Level; import org.voltcore.messaging.Mailbox; import org.voltcore.utils.CoreUtils; import org.voltdb.DependencyPair; import org.voltdb.ParameterSet; import org.voltdb.SiteProcedureConnection; import org.voltdb.VoltDB; import org.voltdb.VoltProcedure.VoltAbortException; import org.voltdb.VoltSystemProcedure; import org.voltdb.VoltTable; import org.voltdb.VoltTable.ColumnInfo; import org.voltdb.VoltType; import org.voltdb.exceptions.EEException; import org.voltdb.exceptions.SQLException; import org.voltdb.exceptions.SerializableException; import org.voltdb.exceptions.SpecifiedException; import org.voltdb.messaging.FragmentResponseMessage; import org.voltdb.messaging.FragmentTaskMessage; import org.voltdb.rejoin.TaskLog; import org.voltdb.sysprocs.SysProcFragmentId; import org.voltdb.utils.Encoder; import org.voltdb.utils.LogKeys; import org.voltdb.utils.VoltTableUtil; import org.voltdb.utils.VoltTrace; public class SysprocFragmentTask extends TransactionTask { final Mailbox m_initiator; final FragmentTaskMessage m_fragmentMsg; 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. SysprocFragmentTask(Mailbox mailbox, FragmentTaskMessage message, ParticipantTransactionState txnState) { this(mailbox, txnState, null, message, null); } SysprocFragmentTask(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 (m_inputDeps == null) { m_inputDeps = new HashMap<Integer, List<VoltTable>>(); } assert(m_fragmentMsg.isSysProcTask()); if (txnState != null && !txnState.isReadOnly()) { m_respBufferable = false; } } /** * Respond with a dummy fragment response. */ private void respondWithDummy() { final FragmentResponseMessage response = new FragmentResponseMessage(m_fragmentMsg, m_initiator.getHSId()); response.m_sourceHSId = 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)); } response.setRespBufferable(m_respBufferable); m_initiator.deliver(response); } public void setResponseNotBufferable() { m_respBufferable = false; } @Override public void run(SiteProcedureConnection siteConnection) { waitOnDurabilityBackpressureFuture(); if (!m_txnState.isReadOnly()) { if (m_txnState.getBeginUndoToken() == Site.kInvalidUndoToken) { m_txnState.setBeginUndoToken(siteConnection.getLatestUndoToken()); } } // HACK HACK HACK // We take the coward's way out to prevent rejoining sites from doing // snapshot work by finding every snapshot fragment and responding with the // recovering status instead of running the fragment. // rejoinDataPending() is VoltDB state which will be flipped to false by // the rejoin code once all of the site data is synchronized. This will then // allow truncation snapshots necessary to make the node officially rejoined // to take place. if (m_fragmentMsg.isSysProcTask() && SysProcFragmentId.isSnapshotSaveFragment(m_fragmentMsg.getPlanHash(0)) && !VoltDB.instance().isMpSysprocSafeToExecute(m_txnState.txnId)) { respondWithDummy(); return; } final FragmentResponseMessage response = processFragmentTask(siteConnection); response.m_sourceHSId = m_initiator.getHSId(); response.setRespBufferable(m_respBufferable); m_initiator.deliver(response); } /** * Produce a rejoining response. */ @Override public void runForRejoin(SiteProcedureConnection siteConnection, TaskLog taskLog) throws IOException { // special case @UpdateApplicationCatalog to die die die during rejoin if (SysProcFragmentId.isCatalogUpdateFragment(m_fragmentMsg.getPlanHash(0))) { VoltDB.crashLocalVoltDB("@UpdateApplicationCatalog is not supported during a rejoin. " + "The rejoining node's VoltDB process will now exit.", false, null); } //If this is a snapshot creation we have the nonce of the snapshot //Provide it to the site so it can decide to enable recording in the task log //if it is our rejoin snapshot start if (SysProcFragmentId.isFirstSnapshotFragment(m_fragmentMsg.getPlanHash(0))) { siteConnection.notifyOfSnapshotNonce((String)m_fragmentMsg.getParameterSetForFragment(0).toArray()[1], m_fragmentMsg.getSpHandle()); } taskLog.logTask(m_fragmentMsg); respondWithDummy(); } @Override public void runFromTaskLog(SiteProcedureConnection siteConnection) { if (!m_txnState.isReadOnly()) { if (m_txnState.getBeginUndoToken() == Site.kInvalidUndoToken) { m_txnState.setBeginUndoToken(siteConnection.getLatestUndoToken()); } } processFragmentTask(siteConnection); } // Extracted the sysproc portion of ExecutionSite processFragmentTask(), then // modified to work in the new world public FragmentResponseMessage processFragmentTask(SiteProcedureConnection siteConnection) { final FragmentResponseMessage currentFragResponse = new FragmentResponseMessage(m_fragmentMsg, m_initiator.getHSId()); currentFragResponse.setStatus(FragmentResponseMessage.SUCCESS, null); for (int frag = 0; frag < m_fragmentMsg.getFragmentCount(); frag++) { final long fragmentId = VoltSystemProcedure.hashToFragId(m_fragmentMsg.getPlanHash(frag)); // equivalent to dep.depId: // final int outputDepId = m_fragmentMsg.getOutputDepId(frag); 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()), "fragmentId", String.valueOf(fragmentId))); } ParameterSet params = m_fragmentMsg.getParameterSetForFragment(frag); try { // run the overloaded sysproc planfragment. pass an empty dependency // set since remote (non-aggregator) fragments don't receive dependencies. final DependencyPair dep = siteConnection.executeSysProcPlanFragment(m_txnState, m_inputDeps, fragmentId, params); // @Shutdown returns null, handle it here if (dep != null) { currentFragResponse.addDependency(dep); } } catch (final EEException e) { hostLog.l7dlog(Level.TRACE, LogKeys.host_ExecutionSite_ExceptionExecutingPF.name(), new Object[] { Encoder.hexEncode(m_fragmentMsg.getFragmentPlan(frag)) }, 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(m_fragmentMsg.getOutputDepId(0), m_rawDummyResult, 0, m_rawDummyResult.length)); } break; } catch (final SQLException e) { hostLog.l7dlog(Level.TRACE, LogKeys.host_ExecutionSite_ExceptionExecutingPF.name(), new Object[] { Encoder.hexEncode(m_fragmentMsg.getFragmentPlan(frag)) }, 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(m_fragmentMsg.getOutputDepId(0), m_rawDummyResult, 0, m_rawDummyResult.length)); } break; } catch (final SpecifiedException e) { // Note that with SpecifiedException, the error code here might get changed before // the client/user sees it. It really just needs to indicate failure. // // Key point here vs the next catch block for VAE is to not wrap the subclass of // SerializableException here to preserve it during the serialization. // currentFragResponse.setStatus( FragmentResponseMessage.USER_ERROR, e); if (currentFragResponse.getTableCount() == 0) { // Make sure the response has at least 1 result with a valid DependencyId currentFragResponse.addDependency(new DependencyPair.BufferDependencyPair(m_fragmentMsg.getOutputDepId(0), m_rawDummyResult, 0, m_rawDummyResult.length)); } } catch (final VoltAbortException e) { currentFragResponse.setStatus( FragmentResponseMessage.USER_ERROR, new SerializableException(CoreUtils.throwableToString(e))); if (currentFragResponse.getTableCount() == 0) { // Make sure the response has at least 1 result with a valid DependencyId currentFragResponse.addDependency(new DependencyPair.BufferDependencyPair(m_fragmentMsg.getOutputDepId(0), m_rawDummyResult, 0, m_rawDummyResult.length)); } break; } if (traceLog != null) { traceLog.add(VoltTrace::endDuration); } } return currentFragResponse; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("SysprocFragmentTask:"); 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(); } }