/* 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.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import junit.framework.TestCase; import org.junit.Test; public class TestMpTransactionTaskQueue extends TestCase { MpProcedureTask makeTransactionTask(long txnid, boolean readOnly) { MpTransactionState state = mock(MpTransactionState.class); when(state.isReadOnly()).thenReturn(readOnly); MpProcedureTask task = mock(MpProcedureTask.class); when(task.getTransactionState()).thenReturn(state); when(task.getTxnId()).thenReturn(txnid); return task; } SiteTaskerQueue m_writeQueue; MpRoSitePool m_MPpool; MpTransactionTaskQueue m_dut; @Override public void setUp() { m_writeQueue = mock(SiteTaskerQueue.class); m_MPpool = mock(MpRoSitePool.class); // Accept work for a while when(m_MPpool.canAcceptWork()).thenReturn(true); m_dut = new MpTransactionTaskQueue(m_writeQueue); m_dut.setMpRoSitePool(m_MPpool); } // Test cases: // Reads will continue to emit until the MpRoSite pool says stop @Test public void testMultiReads() { // We'll keep handing reads to the pool until it tells us to stop TxnEgo txnId = TxnEgo.makeZero(MpInitiator.MP_INIT_PID); List<Long> activeTxns = new ArrayList<Long>(); for (int i = 0; i < 100; i++) { txnId = txnId.makeNext(); activeTxns.add(txnId.getTxnId()); m_dut.offer(makeTransactionTask(txnId.getTxnId(), true)); verify(m_MPpool).doWork(eq(txnId.getTxnId()), any(TransactionTask.class)); } verify(m_MPpool, times(100)).doWork(anyLong(), any(TransactionTask.class)); // Pool says no mas when(m_MPpool.canAcceptWork()).thenReturn(false); List<Long> delayedTxns = new ArrayList<Long>(); for (int i = 0; i < 10; i++) { txnId = txnId.makeNext(); m_dut.offer(makeTransactionTask(txnId.getTxnId(), true)); delayedTxns.add(txnId.getTxnId()); verify(m_MPpool, never()).doWork(eq(txnId.getTxnId()), any(TransactionTask.class)); } // flush something and watch the delayed ones come out for (int i = 0; i < 10; i++) { // flush will cause the pool to be able to accept work before it // attempts to give it more, we'll have to fake it by telling the mock // to accept more before flush, then we'll test MpRoSitePool separately when(m_MPpool.canAcceptWork()).thenReturn(true); m_dut.flush(activeTxns.get(i)); verify(m_MPpool).completeWork(activeTxns.get(i)); verify(m_MPpool).doWork(eq(delayedTxns.get(i)), any(TransactionTask.class)); } } // Single write completes before any more reads are executed but after pending reads finish @Test public void testReadWriteBlocking() { TxnEgo txnId = TxnEgo.makeZero(MpInitiator.MP_INIT_PID); // Offer a bunch of reads and see that they're sent to the pool Deque<Long> offeredReads = new ArrayDeque<Long>(); for (int i = 0; i < 10; i++) { txnId = txnId.makeNext(); offeredReads.push(txnId.getTxnId()); m_dut.offer(makeTransactionTask(txnId.getTxnId(), true)); verify(m_MPpool).doWork(eq(txnId.getTxnId()), any(TransactionTask.class)); } // Offer a write and verify that it's not yet sent to the write queue txnId = txnId.makeNext(); long writetxnid = txnId.getTxnId(); m_dut.offer(makeTransactionTask(writetxnid, false)); verify(m_writeQueue, never()).offer(any(TransactionTask.class)); // Offer another read and verify that it's not sent to the pool txnId = txnId.makeNext(); long readtxnid = txnId.getTxnId(); m_dut.offer(makeTransactionTask(readtxnid, true)); verify(m_MPpool, never()).doWork(eq(readtxnid), any(TransactionTask.class)); // Now flush the first set of reads, make sure we don't offer the write or read early for (int i = 0; i < 10; i++) { verify(m_writeQueue, never()).offer(any(TransactionTask.class)); verify(m_MPpool, never()).doWork(eq(readtxnid), any(TransactionTask.class)); long txnid = offeredReads.pop(); m_dut.flush(txnid); verify(m_MPpool).completeWork(txnid); } // the write should come out now, but not the read verify(m_writeQueue).offer(any(TransactionTask.class)); verify(m_MPpool, never()).doWork(eq(readtxnid), any(TransactionTask.class)); // add another read, see that it doesn't come out either txnId = txnId.makeNext(); long readtxnid2 = txnId.getTxnId(); m_dut.offer(makeTransactionTask(readtxnid2, true)); verify(m_MPpool, never()).doWork(eq(readtxnid2), any(TransactionTask.class)); // now flush the write and verify that the reads emerge m_dut.flush(writetxnid); verify(m_MPpool).doWork(eq(readtxnid), any(TransactionTask.class)); verify(m_MPpool).doWork(eq(readtxnid2), any(TransactionTask.class)); } }