/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package org.voltdb.iv2; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.ArrayDeque; import java.util.Deque; import junit.framework.TestCase; import org.junit.Test; import org.voltdb.StarvationTracker; import org.voltdb.dtxn.TransactionState; import org.voltdb.messaging.CompleteTransactionMessage; import org.voltdb.messaging.FragmentTaskMessage; import org.voltdb.messaging.Iv2InitiateTaskMessage; public class TestTransactionTaskQueue extends TestCase { private static SiteTaskerQueue getSiteTaskerQueue() { SiteTaskerQueue queue = new SiteTaskerQueue(); queue.setStarvationTracker(new StarvationTracker(0)); return queue; } // Cases to test: // several single part txns private SpProcedureTask createSpProc(long localTxnId, TransactionTaskQueue queue) { // Mock an initiate message; override its txnid to return // the default SP value (usually set by ClientInterface). Iv2InitiateTaskMessage init = mock(Iv2InitiateTaskMessage.class); when(init.getTxnId()).thenReturn(Iv2InitiateTaskMessage.UNUSED_MP_TXNID); when(init.getSpHandle()).thenReturn(localTxnId); InitiatorMailbox mbox = mock(InitiatorMailbox.class); when(mbox.getHSId()).thenReturn(1337l); SpProcedureTask task = new SpProcedureTask(mbox, "TestProc", queue, init); return task; } private FragmentTask createFrag(long localTxnId, long mpTxnId, TransactionTaskQueue queue) { return createFrag(localTxnId, mpTxnId, queue, false); } // Create the first fragment of a MP txn private FragmentTask createFrag(long localTxnId, long mpTxnId, TransactionTaskQueue queue, boolean forReplay) { FragmentTaskMessage msg = mock(FragmentTaskMessage.class); when(msg.getTxnId()).thenReturn(mpTxnId); when(msg.isForReplay()).thenReturn(forReplay); InitiatorMailbox mbox = mock(InitiatorMailbox.class); when(mbox.getHSId()).thenReturn(1337l); ParticipantTransactionState pft = new ParticipantTransactionState(localTxnId, msg); FragmentTask task = new FragmentTask(mbox, pft, queue, msg, null); return task; } // Create follow-on fragments of an MP txn private FragmentTask createFrag(TransactionState txn, long mpTxnId, TransactionTaskQueue queue) { FragmentTaskMessage msg = mock(FragmentTaskMessage.class); when(msg.getTxnId()).thenReturn(mpTxnId); InitiatorMailbox mbox = mock(InitiatorMailbox.class); when(mbox.getHSId()).thenReturn(1337l); FragmentTask task = new FragmentTask(mbox, (ParticipantTransactionState)txn, queue, msg, null); return task; } private CompleteTransactionTask createComplete(TransactionState txn, long mpTxnId, TransactionTaskQueue queue) { CompleteTransactionMessage msg = mock(CompleteTransactionMessage.class); when(msg.getTxnId()).thenReturn(mpTxnId); CompleteTransactionTask task = new CompleteTransactionTask(mock(InitiatorMailbox.class), txn, queue, msg); return task; } private void addTask(TransactionTask task, TransactionTaskQueue dut, Deque<TransactionTask> teststorage) { if (teststorage != null) { teststorage.addLast(task); } dut.offer(task); dut.flush(task.getTxnId()); } @Test public void testBasicParticipantOps() throws InterruptedException { long localTxnId = 0; long mpTxnId = 0; SiteTaskerQueue task_queue = getSiteTaskerQueue(); TransactionTaskQueue dut = new TransactionTaskQueue(task_queue); Deque<TransactionTask> expected_order = new ArrayDeque<TransactionTask>(); // add a few SP procs TransactionTask next = createSpProc(localTxnId++, dut); addTask(next, dut, expected_order); next = createSpProc(localTxnId++, dut); addTask(next, dut, expected_order); next = createSpProc(localTxnId++, dut); addTask(next, dut, expected_order); // Should squirt on through the queue assertEquals(0, dut.size()); // Now a fragment task to block things long blocking_mp_txnid = mpTxnId; next = createFrag(localTxnId++, mpTxnId++, dut); TransactionTask block = next; addTask(next, dut, expected_order); assertEquals(1, dut.size()); // Add some tasks that are going to be blocked // Manually track the should-be-blocked procedures // for comparison later. ArrayDeque<TransactionTask> blocked = new ArrayDeque<TransactionTask>(); next = createSpProc(localTxnId++, dut); addTask(next, dut, blocked); next = createSpProc(localTxnId++, dut); addTask(next, dut, blocked); // here's our next blocker next = createFrag(localTxnId++, mpTxnId++, dut); addTask(next, dut, blocked); assertEquals(blocked.size() + 1, dut.size()); // Add a completion for the next blocker, too. Simulates rollback causing // an additional task for this TXN ID to appear before it's blocking the queue next = createComplete(next.getTransactionState(), next.getTxnId(), dut); addTask(next, dut, blocked); assertEquals(blocked.size() + 1, dut.size()); System.out.println("blocked: " + blocked); // now, do more work on the blocked task next = createFrag(block.getTransactionState(), blocking_mp_txnid, dut); addTask(next, dut, expected_order); // Should have passed through and not be in the queue assertEquals(blocked.size() + 1, dut.size()); // now, complete the blocked task next = createComplete(block.getTransactionState(), blocking_mp_txnid, dut); addTask(next, dut, expected_order); // Should have passed through and not be in the queue assertEquals(blocked.size() + 1, dut.size()); // DONE! Should flush everything to the next blocker block.getTransactionState().setDone(); int offered = dut.flush(block.getTxnId()); assertEquals(blocked.size(), offered); assertEquals(1, dut.size()); expected_order.addAll(blocked); while (!expected_order.isEmpty()) { TransactionTask next_poll = (TransactionTask)task_queue.take(); TransactionTask expected = expected_order.removeFirst(); assertEquals(expected.getSpHandle(), next_poll.getSpHandle()); assertEquals(expected.getTxnId(), next_poll.getTxnId()); } } }