/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.bigdata.ha.althalog; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Random; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import org.apache.log4j.Logger; import com.bigdata.ha.msg.HAWriteMessage; import com.bigdata.ha.msg.IHAWriteMessage; import com.bigdata.io.ChecksumUtility; import com.bigdata.journal.IRootBlockView; import com.bigdata.journal.RootBlockView; import com.bigdata.journal.StoreTypeEnum; import junit.framework.TestCase; public class TestAltHALogWriter extends TestCase { private static final Logger log = Logger .getLogger(TestAltHALogWriter.class); final File m_logdir = new File("/tmp/halogdir"); // final File m_logdir = new File("/Volumes/SSDData/tmp/halogdir"); protected void setUp() { try { m_logdir.mkdirs(); } catch (Exception e) { throw new RuntimeException(e); } } protected void tearDown() { // clear directory final File[] files = m_logdir.listFiles(); for (int i = 0; i < files.length; i++) { try { files[i].delete(); } catch (Exception e) { // ignore } } } /* * Need to mock up some valid rootblocks * * RootBlockView(// final boolean rootBlock0, final int offsetBits, final * long nextOffset, final long firstCommitTime, final long lastCommitTime, * final long commitCounter, final long commitRecordAddr, final long * commitRecordIndexAddr, final UUID uuid, final long blockSequence, // * VERSION3 final long quorumToken, // VERSION2 final long metaStartAddr, // * VERSION1 final long metaBitsAddr, // VERSION1 final StoreTypeEnum * storeTypeEnum, // VERSION1 final long createTime, final long closeTime, * final int version, final ChecksumUtility checker) */ @SuppressWarnings("deprecation") private IRootBlockView openRBV(final StoreTypeEnum st) { return new RootBlockView( // true /* rb0 */, 0, 0, 0 /* commitTime */, 0, 0 /* commitCounter */, 0, 0, new UUID(1, 2), 0, // VERSION3 23, // NOQUORUM 0, // VERSION1 0, // VERSION1 st, // storetype System.currentTimeMillis(), 0, RootBlockView.currentVersion, ChecksumUtility.getCHK()); } @SuppressWarnings("deprecation") private static IRootBlockView closeRBV(final IRootBlockView rbv) { return new RootBlockView( // !rbv.isRootBlock0(), 0, 0, System.currentTimeMillis() /* commitTime */, 0, rbv .getCommitCounter() + 1 /* commitCounter */, 100, 100, // non-zero commit records rbv.getUUID(), 0, // VERSION3 rbv.getQuorumToken(), 0, // VERSION1 0, // VERSION1 rbv.getStoreType(), // storetype rbv.getCreateTime(), System.currentTimeMillis(), RootBlockView.currentVersion, ChecksumUtility.getCHK()); } final static Random r = new Random(); static ByteBuffer randomData(final int sze) { byte[] buf = new byte[sze]; r.nextBytes(buf); return ByteBuffer.wrap(buf, 0, sze); } /** * Simple writelog test, open file, write data and commit. */ @SuppressWarnings("deprecation") public void testSimpleRWWriter() throws FileNotFoundException, IOException { final ChecksumUtility checker = ChecksumUtility.getCHK(); final HALogManager manager = new HALogManager(m_logdir); final IRootBlockView rbv = openRBV(StoreTypeEnum.RW); assertTrue(rbv.getStoreType() == StoreTypeEnum.RW); final IHALogWriter writer = manager.createLog(rbv).getWriter(); int sequence = 0; final ByteBuffer data = randomData(2000); final UUID storeUUID = UUID.randomUUID(); final IHAWriteMessage msg = new HAWriteMessage(storeUUID, rbv.getCommitCounter(), rbv .getFirstCommitTime(), sequence, data.limit(), checker .checksum(data), rbv.getStoreType(), rbv.getQuorumToken(), 1000, 0); writer.write(msg, data); writer.close(closeRBV(rbv)); // for sanity, let's run through the standard reader try { HALogManager.main(new String[] { m_logdir.getAbsolutePath() }); } catch (InterruptedException e) { // NOP } } /** * Simple WriteReader, no concurrency, confirms non-delayed responses. */ @SuppressWarnings("deprecation") public void testSimpleRWWriterReader() throws FileNotFoundException, IOException { final ChecksumUtility checker = ChecksumUtility.getCHK(); final HALogManager manager = new HALogManager(m_logdir); final IRootBlockView rbv = openRBV(StoreTypeEnum.RW); assertTrue(rbv.getStoreType() == StoreTypeEnum.RW); final HALogFile logfile = manager.createLog(rbv); final IHALogWriter writer = logfile.getWriter(); int sequence = 0; final ByteBuffer data = randomData(2000); final UUID storeUUID = UUID.randomUUID(); final IHAWriteMessage msg = new HAWriteMessage(storeUUID, rbv.getCommitCounter(), rbv .getFirstCommitTime(), sequence, data.limit(), checker .checksum(data), rbv.getStoreType(), rbv.getQuorumToken(), 1000, 0); writer.write(msg, data); final IHALogReader reader = logfile.getReader(); assertTrue(reader.hasMoreBuffers()); ByteBuffer rbuf = ByteBuffer.allocate(1 * 1024 * 1024); // 1 mb IHAWriteMessage rmsg = reader.processNextBuffer(rbuf); assertTrue(rmsg.getSize() == msg.getSize()); // commit the log file writer.close(closeRBV(rbv)); // the writer should have closed the file, so the reader should return // immediately to report no more buffers assertFalse(reader.hasMoreBuffers()); assertTrue(logfile.isOpen()); reader.close(); assertFalse(logfile.isOpen()); // for sanity, let's run through the standard reader try { HALogManager.main(new String[] { m_logdir.getAbsolutePath() }); } catch (InterruptedException e) { // NOP } } @SuppressWarnings("deprecation") public void testDisableLogFile() throws IOException { final ChecksumUtility checker = ChecksumUtility.getCHK(); final HALogManager manager = new HALogManager(m_logdir); final IRootBlockView rbv = openRBV(StoreTypeEnum.RW); assertTrue(rbv.getStoreType() == StoreTypeEnum.RW); final IHALogWriter writer = manager.createLog(rbv).getWriter(); int sequence = 0; final ByteBuffer data = randomData(2000); final UUID storeUUID = UUID.randomUUID(); final IHAWriteMessage msg = new HAWriteMessage(storeUUID, rbv.getCommitCounter(), rbv .getFirstCommitTime(), sequence, data.limit(), checker .checksum(data), rbv.getStoreType(), rbv.getQuorumToken(), 1000, 0); writer.write(msg, data); // disable current writer manager.disable(); try { final IHAWriteMessage msg2 = new HAWriteMessage(storeUUID, rbv.getCommitCounter(), rbv .getFirstCommitTime(), sequence+1, data.limit(), checker .checksum(data), rbv.getStoreType(), rbv.getQuorumToken(), 1000, 0); writer.write(msg2, data); fail("The file should have been disabled!"); } catch (IllegalStateException ise) { // expected since it was disabled } } /** * SimpleWriter writes a number of log files with a set of messages in each */ static class SimpleWriter implements Runnable { final ByteBuffer data = randomData(2000); int sequence = 0; private final HALogManager manager; private IRootBlockView rbv; private IHALogWriter writer; private ChecksumUtility checker; private int count; SimpleWriter(IRootBlockView rbv, HALogManager manager, ChecksumUtility checker, int count) throws IOException { this.manager = manager; this.rbv = rbv; this.writer = manager.createLog(rbv).getWriter(); this.checker = checker; this.count = count; } @Override public void run() { final UUID storeUUID = UUID.randomUUID(); try { for (int i = 0; i < count; i++) { // add delay to write thread to test read thread waiting for // data Thread.sleep(1); data.limit(200 + r.nextInt(1800)); final IHAWriteMessage msg = new HAWriteMessage(storeUUID, rbv .getCommitCounter(), rbv.getLastCommitTime(), sequence++, data.limit(), checker.checksum(data), rbv.getStoreType(), rbv.getQuorumToken(), 1000, 0); writer.write(msg, data); if (((i + 1) % (1 + r.nextInt(count / 5))) == 0) { log.info("Cycling HALog after " + sequence + " records"); rbv = closeRBV(rbv); writer.close(rbv); sequence = 0; writer = manager.createLog(rbv).getWriter(); } } rbv = closeRBV(rbv); writer.close(rbv); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } static class ReaderRun implements Runnable { final HALogManager manager; final AtomicInteger reads; final AtomicInteger openReaders; ReaderRun(final HALogManager manager, final AtomicInteger reads, final AtomicInteger openReaders) { this.manager = manager; this.reads = reads; this.openReaders = openReaders; } volatile ByteBuffer rbuf = ByteBuffer.allocate(100 * 1024); // 100K @Override public void run() { IHALogReader reader = null; try { final HALogFile file = manager.getOpenLogFile(); if (file == null) return; reader = file.getReader(); } catch (IOException e1) { e1.printStackTrace(); } if (reader == null) { return; } try { openReaders.incrementAndGet(); while (reader.hasMoreBuffers()) { rbuf.position(0); final IHAWriteMessage rmsg = reader .processNextBuffer(rbuf); reads.incrementAndGet(); if (log.isDebugEnabled()) log.debug("Read message: " + rmsg.getSequence() + ", size: " + rmsg.getSize()); } } catch (IOException e) { e.printStackTrace(); } finally { openReaders.decrementAndGet(); try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * While a writer thread writes a number of HALogs, readers are opened to * process them. */ @SuppressWarnings("deprecation") public void testStressConcurrentRWWriterReader() throws FileNotFoundException, IOException { // establish halogdir final ChecksumUtility checker = ChecksumUtility.getCHK(); final HALogManager manager = new HALogManager(m_logdir); final IRootBlockView rbv = openRBV(StoreTypeEnum.RW); assertTrue(rbv.getStoreType() == StoreTypeEnum.RW); final int recordWrites = 5000; Thread wthread = new Thread( new SimpleWriter(rbv, manager, checker, recordWrites)); final AtomicInteger reads = new AtomicInteger(0); final AtomicInteger openReaders = new AtomicInteger(0); // start the writer first wthread.start(); // now keep on opening readers for "current file" while writer continues while (wthread.isAlive()) { // Prevent too many readers starting up that can result in too much // contention. if (openReaders.get() < 20) { Thread rthread = new Thread(new ReaderRun(manager, reads, openReaders)); rthread.start(); } try { // keep starting readers, there may be multiple readers over a single file Thread.sleep(r.nextInt(100)); } catch (InterruptedException e) { break; } } while (openReaders.get() > 0) try { Thread.sleep(50); } catch (InterruptedException e) { // fine } log.info("Writes: " + recordWrites + ", Reads: " + reads.get()); assertTrue(reads.get() >= recordWrites); } }