/** 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 */ /* * Created on Jan 3, 2008 */ package com.bigdata.rdf.sail; import info.aduna.iteration.CloseableIteration; import java.io.File; import java.io.IOException; import java.util.Properties; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import junit.framework.TestCase2; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.model.Value; import org.openrdf.model.impl.LiteralImpl; import org.openrdf.model.impl.URIImpl; import org.openrdf.sail.SailConnection; import org.openrdf.sail.SailException; import com.bigdata.journal.ITx; import com.bigdata.journal.Journal; import com.bigdata.rdf.model.BigdataStatement; import com.bigdata.rdf.sail.BigdataSail.BigdataSailConnection; import com.bigdata.rdf.sail.BigdataSail.Options; import com.bigdata.rdf.store.AbstractTripleStore; import com.bigdata.rdf.store.LocalTripleStore; import com.bigdata.util.InnerCause; import com.bigdata.concurrent.AccessSemaphore.AccessSemaphoreNotReentrantException; /** * Bootstrap test case for bringing up the {@link BigdataSail}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> */ public class TestBootstrapBigdataSail extends TestCase2 { /** * */ public TestBootstrapBigdataSail() { } /** * @param arg0 */ public TestBootstrapBigdataSail(final String arg0) { super(arg0); } /** * Test create and shutdown of the default store. * * @throws SailException * @throws IOException */ public void test_ctor_1() throws SailException, IOException { final File file = new File(BigdataSail.Options.DEFAULT_FILE); /* * If the default file exists, then delete it before creating the SAIL. */ if (file.exists()) { if (!file.delete()) { throw new IOException("Unable to remove default file:" + file); } } final BigdataSail sail = new BigdataSail(); try { if (!file.exists()) fail("Expected file does not exist: " + file); sail.initialize(); sail.shutDown(); } finally { sail.getIndexManager().destroy(); } } /** * Test create and shutdown of a named store. * * @throws SailException */ public void test_ctor_2() throws SailException { final File file = new File(getName() + Options.JNL); if (file.exists()) { if (!file.delete()) { fail("Could not delete file before test: " + file); } } final Properties properties = new Properties(); properties.setProperty(Options.FILE, file.toString()); final BigdataSail sail = new BigdataSail(properties); try { sail.initialize(); sail.shutDown(); } finally { if (!file.exists()) { fail("Could not locate store: " + file); if (!file.delete()) { fail("Could not delete file after test: " + file); } } } } /** * Test creates a database, obtains a writable connection on the database, * and then closes the connection and shutdown the database. * * @throws SailException */ public void test_getConnection() throws SailException { final Properties properties = new Properties(); properties.setProperty(Options.CREATE_TEMP_FILE, "true"); final BigdataSail sail = new BigdataSail(properties); try { sail.initialize(); final SailConnection conn = sail.getConnection(); conn.close(); sail.shutDown(); } finally { sail.getIndexManager().destroy(); } } /** * Unit test verifies that a thread may not obtain more than one instance of * the unisolated connection at a time from the {@link BigdataSail} via a * reentrant invocation. The reentrant request should immediately throw an * exception to prevent an unbreakable deadlock (a thread can not wait on * itself). * * @throws SailException * @throws InterruptedException * @throws ExecutionException */ public void test_getConnectionAllowedExactlyOnce1_oneThread() throws SailException, InterruptedException, ExecutionException { final Properties properties = new Properties(); properties.setProperty(Options.CREATE_TEMP_FILE, "true"); ExecutorService service = null; final BigdataSail sail = new BigdataSail(properties); try { sail.initialize(); service = Executors.newSingleThreadExecutor(); Future<Void> f = null; try { final Callable<Void> task = new Callable<Void>() { public Void call() throws Exception { SailConnection conn1 = null; SailConnection conn2 = null; try { log.info("Requesting 1st unisolated connection."); conn1 = sail.getUnisolatedConnection(); log.info("Requesting 2nd unisolated connection."); try { conn2 = sail.getUnisolatedConnection(); fail("Not expecting a 2nd unisolated connection"); } catch (IllegalStateException ex) { if (log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex); } return (Void) null; } finally { if (conn1 != null) conn1.close(); if (conn2 != null) conn2.close(); } } }; // run task. f = service.submit(task); // should succeed quietly. f.get(); } finally { if (f != null) { // Cancel task. f.cancel(true/* mayInterruptIfRunning */); } sail.shutDown(); } } finally { if (service != null) { service.shutdownNow(); } sail.getIndexManager().destroy(); } } /** * Unit test verifies that a thread may not obtain more than one instance of * the unisolated connection at a time from the {@link BigdataSail} using * two threads. The second thread should block until the first thread * releases the connection, at which point the second thread should obtain * the connection. * * @throws SailException * @throws InterruptedException * @throws ExecutionException */ public void test_getConnectionAllowedExactlyOnce1_twoThreads() throws SailException, InterruptedException, ExecutionException, TimeoutException { final Properties properties = new Properties(); properties.setProperty(Options.CREATE_TEMP_FILE, "true"); ExecutorService service = null; final BigdataSail sail = new BigdataSail(properties); try { sail.initialize(); service = Executors.newFixedThreadPool(2/*nThreads*/); Future<Void> f = null; Future<Void> f2 = null; final ReentrantLock lock = new ReentrantLock(); final AtomicBoolean haveFirst = new AtomicBoolean(false); final AtomicBoolean releaseFirst = new AtomicBoolean(false); // final AtomicBoolean haveSecond = new AtomicBoolean(false); final Condition haveFirstConnection = lock.newCondition(); final Condition releaseFirstConnection = lock.newCondition(); // final Condition haveSecondConnection = lock.newCondition(); try { final Callable<Void> task1 = new Callable<Void>() { public Void call() throws Exception { SailConnection conn1 = null; try { log.info("Requesting 1st unisolated connection."); lock.lock(); try { conn1 = sail.getUnisolatedConnection(); haveFirst.set(true); haveFirstConnection.signal(); // Wait on condition to release connection. while(!releaseFirst.get()) releaseFirstConnection.await(); log.info("Releasing 1st unisolated connection."); } finally { lock.unlock(); } return (Void) null; } finally { if (conn1 != null) conn1.close(); } } }; final Callable<Void> task2 = new Callable<Void>() { public Void call() throws Exception { SailConnection conn2 = null; try { log.info("Requesting 2nd unisolated connection."); conn2 = sail.getUnisolatedConnection(); log.info("Have 2nd unisolated connection"); return (Void) null; } finally { if (conn2 != null) conn2.close(); } } }; /* * Run task. It should obtain the unisolated connection and THEN * block wait on our signal. */ f = service.submit(task1); lock.lock(); try { while(!haveFirst.get()) { haveFirstConnection.await(); } // start task2 f2 = service.submit(task2); log.info("Will instruct to release 1st connection."); releaseFirst.set(true); releaseFirstConnection.signal(); } finally { lock.unlock(); } // wait on both tasks and verify outcomes. they should terminate quickly. f.get(1000,TimeUnit.MILLISECONDS); f2.get(1000,TimeUnit.MILLISECONDS); } finally { if (f != null) { // Cancel task. f.cancel(true/* mayInterruptIfRunning */); } if (f2 != null) { // Cancel task. f2.cancel(true/* mayInterruptIfRunning */); } sail.shutDown(); } } finally { if (service != null) { service.shutdownNow(); } sail.getIndexManager().destroy(); } } /** * Unit test verifies exactly one unisolated connection for two different * {@link BigdataSail} instances for the same {@link AbstractTripleStore} on * the same {@link Journal}. * * @throws SailException * @throws InterruptedException */ public void test_getConnectionAllowedExactlyOnce2() throws SailException, InterruptedException, ExecutionException { final Properties properties = new Properties(); properties.setProperty(Options.CREATE_TEMP_FILE, "true"); ExecutorService service = null; final BigdataSail sail = new BigdataSail(properties); try { sail.initialize(); service = Executors.newSingleThreadExecutor(); // wrap a 2nd sail around the same namespace. final BigdataSail sail2 = new BigdataSail(sail.getNamespace(), sail.getIndexManager()); sail2.initialize(); Future<Void> f = null; try { final Callable<Void> task = new Callable<Void>() { @Override public Void call() throws Exception { SailConnection conn1 = null; SailConnection conn2 = null; try { log.info("Requesting 1st unisolated connection."); conn1 = sail.getUnisolatedConnection(); log.info("Requesting 2nd unisolated connection."); conn2 = sail2.getUnisolatedConnection(); fail("Not expecting a 2nd unisolated connection"); return (Void) null; } finally { if (conn1 != null) conn1.close(); if (conn2 != null) conn2.close(); } } }; // run task. it should block when attempting to get the 2nd // connection. f = service.submit(task); // // wait up to a timeout to verify that the task blocked rather // // than acquiring the 2nd connection. // f.get(250, TimeUnit.MILLISECONDS); f.get(); } catch (ExecutionException e) { if (InnerCause.isInnerCause(e, AccessSemaphoreNotReentrantException.class)) { /* * This is the expected outcome. */ log.info(e); } else { throw e; } } finally { if (f != null) { // Cancel task. f.cancel(true/* mayInterruptIfRunning */); } if (sail2 != null) sail2.shutDown(); sail.shutDown(); } } finally { if (service != null) { service.shutdownNow(); } sail.getIndexManager().destroy(); } } /** * Unit test verifying that exactly one unisolated connection is allowed at * a time for two sails wrapping different {@link AbstractTripleStore} * instances. (This guarantee is needed to preserve ACID semantics for the * unisolated connection when there is more than one * {@link AbstractTripleStore} on the same {@link Journal}. However, * scale-out should not enforce this constraint since it is shard-wise ACID * for unisolated operations.) * * @throws SailException * @throws InterruptedException */ public void test_getConnectionAllowedExactlyOnce3() throws SailException, InterruptedException, ExecutionException { final Properties properties = new Properties(); properties.setProperty(Options.CREATE_TEMP_FILE, "true"); ExecutorService service = null; final BigdataSail sail = new BigdataSail(properties); try { sail.initialize(); service = Executors.newFixedThreadPool(3/*threads*/); // wrap a 2nd sail around a different tripleStore. final BigdataSail sail2; { // tunnel through to the Journal. final Journal jnl = (Journal) sail.getIndexManager(); // describe another tripleStore with a distinct namespace. final AbstractTripleStore tripleStore = new LocalTripleStore( jnl, "foo", ITx.UNISOLATED, properties); // create that triple store. tripleStore.create(); // wrap a 2nd sail around the 2nd tripleStore. sail2 = new BigdataSail(tripleStore); sail2.initialize(); } try { final Callable<SailConnection> task = new Callable<SailConnection>() { public SailConnection call() throws Exception { SailConnection conn1 = null; log.info("Requesting 1st unisolated connection."); conn1 = sail.getUnisolatedConnection(); return conn1; } }; // run task. it should block when attempting to get the 2nd // connection (on a different thread) Future<SailConnection> f1 = service.submit(task); Future<SailConnection> f2 = service.submit(task); // wait up to a timeout to verify that the task blocked rather // than acquiring the 2nd connection. try { f2.get(250, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { /* * This is the expected outcome. */ log.info("timeout"); } f1.get().close(); // Now we can get the second connection try { f2.get(250, TimeUnit.MILLISECONDS).close(); } catch (TimeoutException e) { fail("Should have been able to get second connection"); } } finally { if (sail2 != null) sail2.shutDown(); sail.shutDown(); } } finally { if (service != null) { service.shutdownNow(); } sail.getIndexManager().destroy(); } } /** * Test creates a database, obtains a writable connection, writes some data * on the store, verifies that the data can be read back from within the * connection but that it is not visible in a read-committed view, commits * the write set, and verifies that the data is now visible in a * read-committed view. * * TODO variant that writes, aborts the write, and verifies that the data * was not made restart safe. * * @throws SailException * @throws InterruptedException */ public void test_isolationOfUnisolatedConnection() throws SailException, InterruptedException { final Properties properties = new Properties(); properties.setProperty(Options.CREATE_TEMP_FILE, "true"); BigdataSailConnection conn = null; BigdataSailConnection readConn = null; final BigdataSail sail = new BigdataSail(properties); try { sail.initialize(); // the unisolated connection conn = sail.getUnisolatedConnection(); // a read-only transaction. readConn = sail.getReadOnlyConnection(); final URI s = new URIImpl("http://www.bigdata.com/s"); final URI p = new URIImpl("http://www.bigdata.com/p"); final Value o = new LiteralImpl("o"); // add a statement. conn.addStatement(s, p, o); // verify read back within the connection. { int n = 0; final CloseableIteration<? extends Statement, SailException> itr = conn .getStatements(s, p, o, false/* includeInferred */); try { while (itr.hasNext()) { BigdataStatement stmt = (BigdataStatement) itr.next(); assertEquals("subject", s, stmt.getSubject()); assertEquals("predicate", p, stmt.getPredicate()); assertEquals("object", o, stmt.getObject()); // // @todo what value should the context have? // assertEquals("context", null, stmt.getContext()); n++; } } finally { itr.close(); } assertEquals("#statements visited", 1, n); } // verify NO read back in the read-committed view. { int n = 0; final CloseableIteration<? extends Statement, SailException> itr = readConn .getStatements(s, p, o, false/* includeInferred */); try { while (itr.hasNext()) { itr.next(); n++; } } finally { itr.close(); } assertEquals("#statements visited", 0, n); } // commit the connection. conn.commit(); // verify read back in the read-committed view. { int n = 0; final CloseableIteration<? extends Statement, SailException> itr = conn .getStatements(s, p, o, false/* includeInferred */); try { while (itr.hasNext()) { BigdataStatement stmt = (BigdataStatement) itr.next(); assertEquals("subject", s, stmt.getSubject()); assertEquals("predicate", p, stmt.getPredicate()); assertEquals("object", o, stmt.getObject()); // // @todo what value should the context have? // assertEquals("context", null, stmt.getContext()); n++; } } finally { itr.close(); } assertEquals("#statements visited", 1, n); } } finally { if (conn != null) conn.close(); if (readConn != null) readConn.close(); sail.getIndexManager().destroy(); } } // /** // * Unit test verifies that we can mix read/write transactions and the use // * of the unisolated connection. // */ // public void test_readWriteTxAndUnisolatedConnection() { // fail("write this test"); // } }