/** * Pass the VoltMessage to CI's handleRead() and inspect if the expected parameters are passed to * the initiator's createTranction() method. This is a convenient method if the caller expects the * result of handling this message is to create a new transaction. * * @param msg * @param procName * @param partitionParam null if it's a multi-part txn * @param isAdmin * @param isReadonly * @param isSinglePart * @param isEverySite * @return StoredProcedureInvocation object passed to createTransaction() * @throws IOException */ private StoredProcedureInvocation readAndCheck( ByteBuffer msg, String procName, Object partitionParam, boolean isAdmin, boolean isReadonly, boolean isSinglePart, boolean isEverySite) throws IOException { ClientResponseImpl resp = m_ci.handleRead(msg, m_handler, m_cxn); assertNull(resp); ArgumentCaptor<Long> destinationCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor<Iv2InitiateTaskMessage> messageCaptor = ArgumentCaptor.forClass(Iv2InitiateTaskMessage.class); verify(m_messenger).send(destinationCaptor.capture(), messageCaptor.capture()); Iv2InitiateTaskMessage message = messageCaptor.getValue(); // assertEquals(isAdmin, message.); // is admin assertEquals(isReadonly, message.isReadOnly()); // readonly assertEquals(isSinglePart, message.isSinglePartition()); // single-part // assertEquals(isEverySite, message.g); // every site assertEquals(procName, message.getStoredProcedureName()); if (isSinglePart) { int expected = TheHashinator.hashToPartition(partitionParam); assertEquals( new Long(m_cartographer.getHSIdForMaster(expected)), destinationCaptor.getValue()); } else { assertEquals( new Long(m_cartographer.getHSIdForMultiPartitionInitiator()), destinationCaptor.getValue()); } return message.getStoredProcedureInvocation(); }
private boolean filter(TransactionInfoBaseMessage tibm) { // don't log sysproc fragments or iv2 intiiate task messages. // this is all jealously; should be refactored to ask tibm // if it wants to be filtered for rejoin and eliminate this // horrible introspection. This implementation mimics the // original live rejoin code for ExecutionSite... if (tibm instanceof FragmentTaskMessage && ((FragmentTaskMessage) tibm).isSysProcTask()) { return true; } else if (tibm instanceof Iv2InitiateTaskMessage) { Iv2InitiateTaskMessage itm = (Iv2InitiateTaskMessage) tibm; if ((itm.getStoredProcedureName().startsWith("@") == false) || (itm.getStoredProcedureName().startsWith("@AdHoc") == true)) { return false; } else { return true; } } return false; }
/** Fake an adhoc compiler result and return it to the CI, see if CI initiates the txn. */ @Test public void testFinishedMPAdHocPlanning() throws Exception { // Need a batch and a statement AdHocPlannedStmtBatch plannedStmtBatch = new AdHocPlannedStmtBatch( "select * from a", null, 0, 0, "localhost", false, ProcedureInvocationType.ORIGINAL, 0, 0, null); AdHocPlannedStatement s = new AdHocPlannedStatement( "select * from a".getBytes(Constants.UTF8ENCODING), new CorePlan( new byte[0], new byte[0], new byte[20], new byte[20], false, false, true, new VoltType[0], 0), ParameterSet.emptyParameterSet(), null, null, null); plannedStmtBatch.addStatement(s); m_ci.processFinishedCompilerWork(plannedStmtBatch).run(); ArgumentCaptor<Long> destinationCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor<Iv2InitiateTaskMessage> messageCaptor = ArgumentCaptor.forClass(Iv2InitiateTaskMessage.class); verify(m_messenger).send(destinationCaptor.capture(), messageCaptor.capture()); Iv2InitiateTaskMessage message = messageCaptor.getValue(); // assertFalse(boolValues.get(0)); // is admin assertTrue(message.isReadOnly()); // readonly assertFalse(message.isSinglePartition()); // single-part // assertFalse(boolValues.get(3)); // every site assertEquals("@AdHoc_RO_MP", message.getStoredProcedureName()); byte[] serializedData = (byte[]) message.getStoredProcedureInvocation().getParameterAtIndex(0); AdHocPlannedStatement[] statements = AdHocPlannedStmtBatch.planArrayFromBuffer(ByteBuffer.wrap(serializedData)); assertEquals(1, statements.length); String sql = new String(statements[0].sql, Constants.UTF8ENCODING); assertEquals("select * from a", sql); }
/** * Do the work necessary to turn the Iv2InitiateTaskMessage into a TransactionTask which can be * queued to the TransactionTaskQueue. This is reused by both the normal message handling path and * the repair path, and assumes that the caller has dealt with or ensured that the necessary ID, * SpHandles, and replication issues are resolved. */ private void doLocalInitiateOffer(Iv2InitiateTaskMessage msg) { final String procedureName = msg.getStoredProcedureName(); final SpProcedureTask task = new SpProcedureTask(m_mailbox, procedureName, m_pendingTasks, msg, m_drGateway); if (!msg.isReadOnly()) { if (!m_cl.log(msg, msg.getSpHandle(), m_durabilityListener, task)) { m_pendingTasks.offer(task); } } else { m_pendingTasks.offer(task); } }
@Test public void testFinishedSPAdHocPlanning() throws Exception { // Need a batch and a statement AdHocPlannedStmtBatch plannedStmtBatch = new AdHocPlannedStmtBatch( "select * from a where i = 3", 3, 0, 0, "localhost", false, ProcedureInvocationType.ORIGINAL, 0, 0, null); AdHocPlannedStatement s = new AdHocPlannedStatement( "select * from a where i = 3".getBytes(Constants.UTF8ENCODING), new CorePlan( new byte[0], null, new byte[20], null, false, false, true, new VoltType[0], 0), ParameterSet.fromArrayNoCopy(new Object[0]), null, null, 3); plannedStmtBatch.addStatement(s); m_ci.processFinishedCompilerWork(plannedStmtBatch).run(); ArgumentCaptor<Long> destinationCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor<Iv2InitiateTaskMessage> messageCaptor = ArgumentCaptor.forClass(Iv2InitiateTaskMessage.class); verify(m_messenger).send(destinationCaptor.capture(), messageCaptor.capture()); Iv2InitiateTaskMessage message = messageCaptor.getValue(); assertTrue(message.isReadOnly()); // readonly assertTrue(message.isSinglePartition()); // single-part assertEquals("@AdHoc_RO_SP", message.getStoredProcedureName()); // SP AdHoc should have partitioning parameter serialized in the parameter set Object partitionParam = message.getStoredProcedureInvocation().getParameterAtIndex(0); assertTrue(partitionParam instanceof byte[]); VoltType type = VoltType.get((Byte) message.getStoredProcedureInvocation().getParameterAtIndex(1)); assertTrue(type.isInteger()); byte[] serializedData = (byte[]) message.getStoredProcedureInvocation().getParameterAtIndex(2); AdHocPlannedStatement[] statements = AdHocPlannedStmtBatch.planArrayFromBuffer(ByteBuffer.wrap(serializedData)); assertTrue(Arrays.equals(TheHashinator.valueToBytes(3), (byte[]) partitionParam)); assertEquals(1, statements.length); String sql = new String(statements[0].sql, Constants.UTF8ENCODING); assertEquals("select * from a where i = 3", sql); }
/** * Fake a catalog diff compiler result and send it back to the CI, see if CI initiates a new txn. */ @Test public void testFinishedCatalogDiffing() { CatalogChangeResult catalogResult = new CatalogChangeResult(); catalogResult.clientData = null; catalogResult.clientHandle = 0; catalogResult.connectionId = 0; catalogResult.adminConnection = false; catalogResult.hostname = "localhost"; // catalog change specific boiler plate catalogResult.catalogHash = "blah".getBytes(); catalogResult.catalogBytes = "blah".getBytes(); catalogResult.deploymentString = "blah"; catalogResult.deploymentCRC = 1234l; catalogResult.expectedCatalogVersion = 3; catalogResult.encodedDiffCommands = "diff"; catalogResult.invocationType = ProcedureInvocationType.REPLICATED; catalogResult.originalTxnId = 12345678l; catalogResult.originalUniqueId = 87654321l; m_ci.processFinishedCompilerWork(catalogResult).run(); ArgumentCaptor<Long> destinationCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor<Iv2InitiateTaskMessage> messageCaptor = ArgumentCaptor.forClass(Iv2InitiateTaskMessage.class); verify(m_messenger).send(destinationCaptor.capture(), messageCaptor.capture()); Iv2InitiateTaskMessage message = messageCaptor.getValue(); // assertFalse(boolValues.get(0)); // is admin assertFalse(message.isReadOnly()); // readonly assertFalse(message.isSinglePartition()); // single-part // assertFalse(boolValues.get(3)); // every site assertEquals("@UpdateApplicationCatalog", message.getStoredProcedureName()); assertEquals("diff", message.getStoredProcedureInvocation().getParameterAtIndex(0)); assertTrue( Arrays.equals( "blah".getBytes(), (byte[]) message.getStoredProcedureInvocation().getParameterAtIndex(2))); assertEquals(3, message.getStoredProcedureInvocation().getParameterAtIndex(3)); assertEquals("blah", message.getStoredProcedureInvocation().getParameterAtIndex(4)); assertEquals(1234l, message.getStoredProcedureInvocation().getParameterAtIndex(5)); assertEquals( ProcedureInvocationType.REPLICATED, message.getStoredProcedureInvocation().getType()); assertEquals(12345678l, message.getStoredProcedureInvocation().getOriginalTxnId()); assertEquals(87654321l, message.getStoredProcedureInvocation().getOriginalUniqueId()); }
private void handleIv2InitiateTaskMessageRepair( List<Long> needsRepair, Iv2InitiateTaskMessage message) { if (!message.isSinglePartition()) { throw new RuntimeException( "SpScheduler.handleIv2InitiateTaskMessageRepair " + "should never receive multi-partition initiations."); } // set up duplicate counter. expect exactly the responses corresponding // to needsRepair. These may, or may not, include the local site. // We currently send the final response into the ether, since we don't // have the original ClientInterface HSID stored. It would be more // useful to have the original ClienInterface HSId somewhere handy. List<Long> expectedHSIds = new ArrayList<Long>(needsRepair); DuplicateCounter counter = new DuplicateCounter( HostMessenger.VALHALLA, message.getTxnId(), expectedHSIds, message.getStoredProcedureName()); m_duplicateCounters.put( new DuplicateCounterKey(message.getTxnId(), message.getSpHandle()), counter); m_uniqueIdGenerator.updateMostRecentlyGeneratedUniqueId(message.getUniqueId()); // is local repair necessary? if (needsRepair.contains(m_mailbox.getHSId())) { needsRepair.remove(m_mailbox.getHSId()); // make a copy because handleIv2 non-repair case does? Iv2InitiateTaskMessage localWork = new Iv2InitiateTaskMessage( message.getInitiatorHSId(), message.getCoordinatorHSId(), message); doLocalInitiateOffer(localWork); } // is remote repair necessary? if (!needsRepair.isEmpty()) { Iv2InitiateTaskMessage replmsg = new Iv2InitiateTaskMessage(m_mailbox.getHSId(), m_mailbox.getHSId(), message); m_mailbox.send(com.google.common.primitives.Longs.toArray(needsRepair), replmsg); } }
// SpScheduler expects to see InitiateTaskMessages corresponding to single-partition // procedures only. public void handleIv2InitiateTaskMessage(Iv2InitiateTaskMessage message) { if (!message.isSinglePartition()) { throw new RuntimeException( "SpScheduler.handleIv2InitiateTaskMessage " + "should never receive multi-partition initiations."); } final String procedureName = message.getStoredProcedureName(); long newSpHandle; long uniqueId = Long.MIN_VALUE; Iv2InitiateTaskMessage msg = message; if (m_isLeader || message.isReadOnly()) { /* * A short circuit read is a read where the client interface is local to * this node. The CI will let a replica perform a read in this case and * it does looser tracking of client handles since it can't be * partitioned from the local replica. */ if (!m_isLeader && CoreUtils.getHostIdFromHSId(msg.getInitiatorHSId()) != CoreUtils.getHostIdFromHSId(m_mailbox.getHSId())) { VoltDB.crashLocalVoltDB("Only allowed to do short circuit reads locally", true, null); } /* * If this is for CL replay or DR, update the unique ID generator */ if (message.isForReplay()) { uniqueId = message.getUniqueId(); try { m_uniqueIdGenerator.updateMostRecentlyGeneratedUniqueId(uniqueId); } catch (Exception e) { hostLog.fatal(e.getMessage()); hostLog.fatal("Invocation: " + message); VoltDB.crashLocalVoltDB(e.getMessage(), true, e); } } else if (message.isForDR()) { uniqueId = message.getStoredProcedureInvocation().getOriginalUniqueId(); // @LoadSinglepartitionTable does not have a valid uid if (UniqueIdGenerator.getPartitionIdFromUniqueId(uniqueId) == m_partitionId) { m_uniqueIdGenerator.updateMostRecentlyGeneratedUniqueId(uniqueId); } } /* * If this is CL replay use the txnid from the CL and also * update the txnid to match the one from the CL */ if (message.isForReplay()) { newSpHandle = message.getTxnId(); setMaxSeenTxnId(newSpHandle); } else if (m_isLeader) { TxnEgo ego = advanceTxnEgo(); newSpHandle = ego.getTxnId(); uniqueId = m_uniqueIdGenerator.getNextUniqueId(); } else { /* * The short circuit read case. Since we are not a master * we can't create new transaction IDs, so reuse the last seen * txnid. For a timestamp, might as well give a reasonable one * for a read heavy workload so time isn't bursty. */ uniqueId = UniqueIdGenerator.makeIdFromComponents( Math.max(System.currentTimeMillis(), m_uniqueIdGenerator.lastUsedTime), 0, m_uniqueIdGenerator.partitionId); // Don't think it wise to make a new one for a short circuit read newSpHandle = getCurrentTxnId(); } // Need to set the SP handle on the received message // Need to copy this or the other local sites handling // the same initiate task message will overwrite each // other's memory -- the message isn't copied on delivery // to other local mailboxes. msg = new Iv2InitiateTaskMessage( message.getInitiatorHSId(), message.getCoordinatorHSId(), m_repairLogTruncationHandle, message.getTxnId(), message.getUniqueId(), message.isReadOnly(), message.isSinglePartition(), message.getStoredProcedureInvocation(), message.getClientInterfaceHandle(), message.getConnectionId(), message.isForReplay()); msg.setSpHandle(newSpHandle); // Also, if this is a vanilla single-part procedure, make the TXNID // be the SpHandle (for now) // Only system procedures are every-site, so we'll check through the SystemProcedureCatalog if (SystemProcedureCatalog.listing.get(procedureName) == null || !SystemProcedureCatalog.listing.get(procedureName).getEverysite()) { msg.setTxnId(newSpHandle); msg.setUniqueId(uniqueId); } // Don't replicate reads, this really assumes that DML validation // is going to be integrated soonish if (m_isLeader && !msg.isReadOnly() && m_sendToHSIds.length > 0) { Iv2InitiateTaskMessage replmsg = new Iv2InitiateTaskMessage( m_mailbox.getHSId(), m_mailbox.getHSId(), m_repairLogTruncationHandle, msg.getTxnId(), msg.getUniqueId(), msg.isReadOnly(), msg.isSinglePartition(), msg.getStoredProcedureInvocation(), msg.getClientInterfaceHandle(), msg.getConnectionId(), msg.isForReplay()); // Update the handle in the copy since the constructor doesn't set it replmsg.setSpHandle(newSpHandle); m_mailbox.send(m_sendToHSIds, replmsg); DuplicateCounter counter = new DuplicateCounter( msg.getInitiatorHSId(), msg.getTxnId(), m_replicaHSIds, msg.getStoredProcedureName()); m_duplicateCounters.put(new DuplicateCounterKey(msg.getTxnId(), newSpHandle), counter); } } else { setMaxSeenTxnId(msg.getSpHandle()); newSpHandle = msg.getSpHandle(); uniqueId = msg.getUniqueId(); } Iv2Trace.logIv2InitiateTaskMessage(message, m_mailbox.getHSId(), msg.getTxnId(), newSpHandle); doLocalInitiateOffer(msg); return; }
void replayFromTaskLog() throws IOException { // not yet time to catch-up. if (m_rejoinState != kStateReplayingRejoin) { return; } // replay 10:1 in favor of replay for (int i = 0; i < 10; ++i) { if (m_rejoinTaskLog.isEmpty()) { break; } TransactionInfoBaseMessage tibm = m_rejoinTaskLog.getNextMessage(); if (tibm == null) { break; } // Apply the readonly / sysproc filter. With Iv2 read optimizations, // reads should not reach here; the cost of post-filtering shouldn't // be particularly high (vs pre-filtering). if (filter(tibm)) { continue; } if (tibm instanceof Iv2InitiateTaskMessage) { Iv2InitiateTaskMessage m = (Iv2InitiateTaskMessage) tibm; SpProcedureTask t = new SpProcedureTask(m_initiatorMailbox, m.getStoredProcedureName(), null, m, null); t.runFromTaskLog(this); } else if (tibm instanceof FragmentTaskMessage) { FragmentTaskMessage m = (FragmentTaskMessage) tibm; if (global_replay_mpTxn == null) { global_replay_mpTxn = new ParticipantTransactionState(m.getTxnId(), m); } else if (global_replay_mpTxn.txnId != m.getTxnId()) { VoltDB.crashLocalVoltDB( "Started a MP transaction during replay before completing " + " open transaction.", false, null); } FragmentTask t = new FragmentTask(m_initiatorMailbox, m, global_replay_mpTxn); t.runFromTaskLog(this); } else if (tibm instanceof CompleteTransactionMessage) { // Needs improvement: completes for sysprocs aren't filterable as sysprocs. // Only complete transactions that are open... if (global_replay_mpTxn != null) { CompleteTransactionMessage m = (CompleteTransactionMessage) tibm; CompleteTransactionTask t = new CompleteTransactionTask(global_replay_mpTxn, null, m, null); if (!m.isRestart()) { global_replay_mpTxn = null; } t.runFromTaskLog(this); } } else { VoltDB.crashLocalVoltDB( "Can not replay message type " + tibm + " during live rejoin. Unexpected error.", false, null); } } // exit replay being careful not to exit in the middle of a multi-partititon // transaction. The SPScheduler doesn't have a valid transaction state for a // partially replayed MP txn and in case of rollback the scheduler's undo token // is wrong. Run MP txns fully kStateRejoining or fully kStateRunning. if (m_rejoinTaskLog.isEmpty() && global_replay_mpTxn == null) { setReplayRejoinComplete(); } }