/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.sparql.transaction; import static org.apache.jena.query.ReadWrite.READ ; import static org.apache.jena.query.ReadWrite.WRITE ; import java.util.ArrayList ; import java.util.List ; import java.util.concurrent.* ; import java.util.concurrent.atomic.AtomicLong ; import org.apache.jena.atlas.junit.BaseTest ; import org.apache.jena.atlas.lib.Lib ; import org.apache.jena.query.Dataset ; import org.apache.jena.query.ReadWrite ; import org.apache.jena.sparql.JenaTransactionException ; import static org.junit.Assume.* ; import org.junit.Test ; public abstract class AbstractTestTransactionLifecycle extends BaseTest { protected abstract Dataset create() ; protected boolean supportsAbort() { return true ; } @Test public void transaction_00() { Dataset ds = create() ; assertTrue(ds.supportsTransactions()) ; } @Test public void transaction_r01() { Dataset ds = create() ; ds.begin(ReadWrite.READ) ; assertTrue(ds.isInTransaction()) ; ds.end() ; assertFalse(ds.isInTransaction()) ; } @Test public void transaction_r02() { Dataset ds = create() ; ds.begin(ReadWrite.READ) ; assertTrue(ds.isInTransaction()) ; ds.commit() ; assertFalse(ds.isInTransaction()) ; ds.end() ; assertFalse(ds.isInTransaction()) ; } @Test public void transaction_r03() { Dataset ds = create() ; ds.begin(ReadWrite.READ) ; assertTrue(ds.isInTransaction()) ; ds.abort() ; assertFalse(ds.isInTransaction()) ; ds.end() ; assertFalse(ds.isInTransaction()) ; } @Test public void transaction_w01() { Dataset ds = create() ; ds.begin(ReadWrite.WRITE) ; assertTrue(ds.isInTransaction()) ; ds.commit() ; assertFalse(ds.isInTransaction()) ; } @Test public void transaction_w02() { assumeTrue(supportsAbort()) ; Dataset ds = create() ; ds.begin(ReadWrite.WRITE) ; assertTrue(ds.isInTransaction()) ; ds.abort() ; assertFalse(ds.isInTransaction()) ; } @Test public void transaction_w03() { Dataset ds = create() ; ds.begin(ReadWrite.WRITE) ; assertTrue(ds.isInTransaction()) ; ds.commit() ; assertFalse(ds.isInTransaction()) ; ds.end() ; assertFalse(ds.isInTransaction()) ; } @Test public void transaction_w04() { assumeTrue(supportsAbort()) ; Dataset ds = create() ; ds.begin(ReadWrite.WRITE) ; assertTrue(ds.isInTransaction()) ; ds.abort() ; assertFalse(ds.isInTransaction()) ; ds.end() ; assertFalse(ds.isInTransaction()) ; } @Test public void transaction_w05() { assumeTrue(supportsAbort()) ; // .end is not necessary Dataset ds = create() ; ds.begin(ReadWrite.WRITE) ; assertTrue(ds.isInTransaction()) ; ds.abort() ; assertFalse(ds.isInTransaction()) ; ds.begin(ReadWrite.WRITE) ; assertTrue(ds.isInTransaction()) ; ds.abort() ; assertFalse(ds.isInTransaction()) ; } // Patterns. @Test public void transaction_pattern_01() { Dataset ds = create() ; read1(ds) ; read1(ds) ; } @Test public void transaction_pattern_02() { Dataset ds = create() ; read2(ds) ; read2(ds) ; } @Test public void transaction_pattern_03() { Dataset ds = create() ; write(ds) ; write(ds) ; } @Test public void transaction_pattern_04() { Dataset ds = create() ; write(ds) ; read2(ds) ; read2(ds) ; write(ds) ; read2(ds) ; } // Cycle misalignment. // test : commit // test : abort // Permit explain .end() - the case of "end" when not sure: begin...end.end. @Test(expected=JenaTransactionException.class) public void transaction_err_nontxn_commit_1() { Dataset ds = create() ; ds.commit() ; } @Test(expected=JenaTransactionException.class) public void transaction_err_nontxn_commit_2() { Dataset ds = create() ; ds.begin(READ) ; ds.end() ; ds.commit() ; } @Test(expected=JenaTransactionException.class) public void transaction_err_nontxn_commit_3() { Dataset ds = create() ; ds.begin(WRITE) ; ds.end() ; ds.commit() ; } @Test(expected=JenaTransactionException.class) public void transaction_err_nontxn_abort_1() { Dataset ds = create() ; ds.abort() ; } @Test(expected=JenaTransactionException.class) public void transaction_err_nontxn_abort_2() { Dataset ds = create() ; ds.begin(READ) ; ds.end() ; ds.abort() ; } @Test(expected=JenaTransactionException.class) public void transaction_err_nontxn_abort_3() { Dataset ds = create() ; ds.begin(WRITE) ; ds.end() ; ds.abort() ; } @Test public void transaction_err_01() { testBeginBegin(WRITE, WRITE) ; } @Test public void transaction_err_02() { testBeginBegin(WRITE, READ) ; } @Test public void transaction_err_03() { testBeginBegin(READ, READ) ; } @Test public void transaction_err_04() { testBeginBegin(READ, WRITE) ; } @Test public void transaction_err_05() { testCommitCommit(READ) ; } @Test public void transaction_err_06() { testCommitCommit(WRITE) ; } @Test public void transaction_err_07() { testCommitAbort(READ) ; } @Test public void transaction_err_08() { testCommitAbort(WRITE) ; } @Test public void transaction_err_09() { testAbortAbort(READ) ; } @Test public void transaction_err_10() { testAbortAbort(WRITE) ; } @Test public void transaction_err_11() { testAbortCommit(READ) ; } @Test public void transaction_err_12() { testAbortCommit(WRITE) ; } private void read1(Dataset ds) { ds.begin(ReadWrite.READ) ; assertTrue(ds.isInTransaction()) ; ds.commit() ; assertFalse(ds.isInTransaction()) ; ds.end() ; } private void read2(Dataset ds) { ds.begin(ReadWrite.READ) ; assertTrue(ds.isInTransaction()) ; ds.end() ; assertFalse(ds.isInTransaction()) ; } private void write(Dataset ds) { ds.begin(ReadWrite.WRITE) ; assertTrue(ds.isInTransaction()) ; ds.commit() ; assertFalse(ds.isInTransaction()) ; ds.end() ; } private static void safeEnd(Dataset ds) { try { ds.end() ; } catch (JenaTransactionException ex) {} } // Error conditions that should be detected. private void testBeginBegin(ReadWrite mode1, ReadWrite mode2) { Dataset ds = create() ; ds.begin(mode1) ; try { ds.begin(mode2) ; fail("Expected transaction exception - begin-begin (" + mode1 + ", " + mode2 + ")") ; } catch (JenaTransactionException ex) { safeEnd(ds) ; } } private void testCommitCommit(ReadWrite mode) { Dataset ds = create() ; ds.begin(mode) ; ds.commit() ; try { ds.commit() ; fail("Expected transaction exception - commit-commit(" + mode + ")") ; } catch (JenaTransactionException ex) { safeEnd(ds) ; } } private void testCommitAbort(ReadWrite mode) { assumeTrue(supportsAbort()) ; Dataset ds = create() ; ds.begin(mode) ; ds.commit() ; try { ds.abort() ; fail("Expected transaction exception - commit-abort(" + mode + ")") ; } catch (JenaTransactionException ex) { safeEnd(ds) ; } } private void testAbortAbort(ReadWrite mode) { assumeTrue(supportsAbort()) ; Dataset ds = create() ; ds.begin(mode) ; ds.abort() ; try { ds.abort() ; fail("Expected transaction exception - abort-abort(" + mode + ")") ; } catch (JenaTransactionException ex) { ds.end() ; } } private void testAbortCommit(ReadWrite mode) { assumeTrue(supportsAbort()) ; Dataset ds = create() ; ds.begin(mode) ; ds.abort() ; try { ds.commit() ; fail("Expected transaction exception - abort-commit(" + mode + ")") ; } catch (JenaTransactionException ex) { safeEnd(ds) ; } } // ---- Concurrency tests. @Test public synchronized void transaction_concurrency_writer() throws InterruptedException, ExecutionException, TimeoutException { ExecutorService executor = Executors.newFixedThreadPool(2); AtomicLong counter = new AtomicLong(0) ; try { final Dataset ds = create() ; Callable<Boolean> callable = new Callable<Boolean>() { @Override public Boolean call() { ds.begin(ReadWrite.WRITE); long x = counter.incrementAndGet() ; // Hold the lock for a short while. // The W threads will take the sleep serially. Lib.sleep(500) ; long x1 = counter.get() ; assertEquals("Two writers in the transaction", x, x1); ds.commit(); return true; } }; // Fire off two threads Future<Boolean> f1 = executor.submit(callable); Future<Boolean> f2 = executor.submit(callable); // Wait longer than the cumulative threads sleep assertTrue(f1.get(4, TimeUnit.SECONDS)); assertTrue(f2.get(1, TimeUnit.SECONDS)); } finally { executor.shutdownNow(); } } @Test public synchronized void transaction_concurrency_reader() throws InterruptedException, ExecutionException, TimeoutException { ExecutorService executor = Executors.newCachedThreadPool(); AtomicLong counter = new AtomicLong(0) ; try { final Dataset ds = create() ; Callable<Boolean> callable = new Callable<Boolean>() { @Override public Boolean call() { ds.begin(ReadWrite.READ); long x = counter.incrementAndGet() ; // Hold the lock for a few seconds - these should be in parallel. Lib.sleep(1000) ; ds.commit(); return true; } }; // Run the callable a bunch of times List<Future<Boolean>> futures = new ArrayList<>(); for (int i = 0; i < 25; i++) { futures.add(executor.submit(callable)); } // Check all the futures come back OK // Wait shorter than sum total of all sleep by thread // which proves concurrent access. for (Future<Boolean> f : futures) { assertTrue(f.get(4, TimeUnit.SECONDS)); } } finally { executor.shutdownNow(); } } }