Package org.helidb.txn

Source Code of org.helidb.txn.AbstractTransactionalDatabaseOnConstantRecordSizeBackendTest

/* HeliDB -- A simple database for Java, http://www.helidb.org
* Copyright (C) 2008, 2009 Karl Gustafsson
*
* This file is a part of HeliDB.
*
* HeliDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeliDB 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, see <http://www.gnu.org/licenses/>.
*/
package org.helidb.txn;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import org.entityfs.Directory;
import org.entityfs.fs.FSRWFileSystemBuilder;
import org.entityfs.util.IteratorDeleter;
import org.entityfs.util.cap.entity.ECFileResolvableUtil;
import org.helidb.AbstractDatabaseOnConstantRecordSizeBackendTest;
import org.helidb.Database;
import org.helidb.test.support.FileSupport;
import org.helidb.test.support.concurrent.ConcurrentTestRunner;
import org.helidb.test.support.concurrent.TestStep;
import org.helidb.txn.NoTransactionException;
import org.helidb.txn.ReadOnlyTransactionException;
import org.helidb.txn.Transaction;
import org.helidb.txn.TransactionCollaborator;
import org.helidb.txn.TransactionalDatabase;
import org.helidb.txn.UnableToCommitException;
import org.junit.Test;

public abstract class AbstractTransactionalDatabaseOnConstantRecordSizeBackendTest extends AbstractDatabaseOnConstantRecordSizeBackendTest
{
  private final Map<Integer, Directory> m_dbDirs = new HashMap<Integer, Directory>();

  protected abstract TransactionalDatabase<Integer, Long> createDatabaseWoTxnInDirectory(Directory dir);

  protected TransactionalDatabase<Integer, Long> createDatabaseWoTxn()
  {
    File tmpf = FileSupport.createTempDirectory();
    // A locking file system
    Directory tmpDir = new FSRWFileSystemBuilder().setRoot(tmpf).disableAccessControls().create().getRootDirectory();
    // tmpDir.getFileSystem().getLogAdapter().setLevel(Level.ALL);
    TransactionalDatabase<Integer, Long> res = createDatabaseWoTxnInDirectory(tmpDir);
    m_dbDirs.put(System.identityHashCode(res), tmpDir);
    return res;
  }

  @Override
  protected TransactionalDatabase<Integer, Long> createDatabase()
  {
    assert m_dbDirs.size() > 0 || Transaction.getCurrentTransactionOrNull() == null;

    TransactionalDatabase<Integer, Long> res = createDatabaseWoTxn();
    Transaction.getOrStartTransaction(false);
    return res;
  }

  protected abstract TransactionalDatabase<Character, Character> createCharacterDatabaseWoTxnInDirectory(Directory dir);

  protected TransactionalDatabase<Character, Character> createCharacterDatabaseWoTxn()
  {
    File tmpf = FileSupport.createTempDirectory();
    // A locking file system
    Directory tmpDir = new FSRWFileSystemBuilder().setRoot(tmpf).disableAccessControls().create().getRootDirectory();
    // tmpDir.getFileSystem().getLogAdapter().setLevel(Level.ALL);
    TransactionalDatabase<Character, Character> res = createCharacterDatabaseWoTxnInDirectory(tmpDir);
    m_dbDirs.put(System.identityHashCode(res), tmpDir);
    return res;
  }

  @Override
  protected TransactionalDatabase<Character, Character> createCharacterDatabase()
  {
    assert m_dbDirs.size() > 0 || Transaction.getCurrentTransactionOrNull() == null;

    TransactionalDatabase<Character, Character> res = createCharacterDatabaseWoTxn();
    Transaction.getOrStartTransaction(false);
    return res;
  }

  @Override
  protected void tearDownDatabase(Database<?, ?> db)
  {
    Transaction txn = Transaction.getCurrentTransactionOrNull();
    // The transaction may already be rolled back if we're working with
    // several databases.
    if (txn != null)
    {
      if (!txn.isFinished())
      {
        txn.rollback();
      }
    }

    db.close();
    Directory d = m_dbDirs.remove(System.identityHashCode(db));
    if (d != null)
    {
      new IteratorDeleter(d).delete();
      assertTrue(ECFileResolvableUtil.getFileObject(d).delete());
    }
  }

  /**
   * Load some data into the database.
   */
  protected void populateDb(Database<Integer, Long> db)
  {
    boolean successful = false;
    Transaction txn = Transaction.startTransaction(false);
    try
    {
      db.fasterInsert(Integer.valueOf(1), Long.valueOf(2));
      db.fasterInsert(Integer.valueOf(123), Long.valueOf(234));
      assertEquals(Long.valueOf(2), db.get(Integer.valueOf(1)));
      assertEquals(Long.valueOf(234), db.get(Integer.valueOf(123)));
      txn.commit();
      successful = true;
    }
    catch (RuntimeException e)
    {
      e.printStackTrace();
    }
    finally
    {
      if (!successful && !txn.isFinished())
      {
        txn.rollback();
      }
    }
    assertNull(Transaction.getCurrentTransactionOrNull());
  }

  @Test
  public void testCommit()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      populateDb(db);

      Transaction txn = Transaction.startTransaction(true);
      try
      {
        assertEquals(Long.valueOf(2), db.get(Integer.valueOf(1)));
        assertEquals(Long.valueOf(234), db.get(Integer.valueOf(123)));
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testRollback()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.fasterInsert(Integer.valueOf(1), Long.valueOf(2));
        db.fasterInsert(Integer.valueOf(123), Long.valueOf(234));
        assertEquals(Long.valueOf(2), db.get(Integer.valueOf(1)));
        assertEquals(Long.valueOf(234), db.get(Integer.valueOf(123)));
      }
      finally
      {
        txn.rollback();
      }

      txn = Transaction.startTransaction(true);
      try
      {
        assertNull(db.get(Integer.valueOf(1)));
        assertNull(db.get(Integer.valueOf(123)));
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testReadWoTransaction()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      populateDb(db);

      // Existing record
      try
      {
        db.get(Integer.valueOf(1));
        fail();
      }
      catch (NoTransactionException e)
      {
        // ok
      }

      // Nonexistent record
      try
      {
        db.get(Integer.valueOf(2));
        fail();
      }
      catch (NoTransactionException e)
      {
        // ok
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testWriteWoTransaction()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      populateDb(db);

      // Existing record
      try
      {
        db.fasterInsert(Integer.valueOf(1), Long.valueOf(222));
        fail();
      }
      catch (NoTransactionException e)
      {
        // ok
      }

      // Nonexistent record
      try
      {
        db.fasterInsert(Integer.valueOf(2), Long.valueOf(23));
        fail();
      }
      catch (NoTransactionException e)
      {
        // ok
      }

      Transaction txn = Transaction.startTransaction(true);
      try
      {
        assertEquals(Long.valueOf(2), db.get(Integer.valueOf(1)));
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testWriteToRoTransaction()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      populateDb(db);

      assertNull(Transaction.getCurrentTransactionOrNull());
      Transaction txn = Transaction.startTransaction(true);
      try
      {
        try
        {
          db.fasterInsert(Integer.valueOf(3), Long.valueOf(333));
          fail();
        }
        catch (ReadOnlyTransactionException e)
        {
          // ok
        }

        assertNull(db.get(Integer.valueOf(3)));
      }
      finally
      {
        txn.commit();
      }

      txn = Transaction.startTransaction(true);
      try
      {
        assertNull(db.get(Integer.valueOf(3)));
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testCommitOnClosedDb()
  {
    boolean destroyed = false;
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      populateDb(db);
      boolean committed = false;
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.fasterInsert(Integer.valueOf(3), Long.valueOf(333L));
        tearDownDatabase(db);
        destroyed = true;
        committed = true;
        try
        {
          txn.commit();
        }
        catch (IllegalStateException e)
        {
          // ok
        }
      }
      finally
      {
        if (!committed)
        {
          txn.rollback();
        }
      }
    }
    finally
    {
      if (!destroyed)
      {
        tearDownDatabase(db);
      }
    }
  }

  @Test
  public void testRollbackOnClosedDb()
  {
    boolean destroyed = false;
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      populateDb(db);
      boolean committed = false;
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.fasterInsert(Integer.valueOf(3), Long.valueOf(333L));
        tearDownDatabase(db);
        destroyed = true;
        committed = true;
        try
        {
          txn.rollback();
        }
        catch (IllegalStateException e)
        {
          // ok
        }
      }
      finally
      {
        if (!committed)
        {
          txn.rollback();
        }
      }
    }
    finally
    {
      if (!destroyed)
      {
        tearDownDatabase(db);
      }
    }
  }

  @Test
  public void testInsertCommit()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        txn.commit();

        txn = Transaction.startTransaction(true);
        assertEquals(2, db.size());
        assertEquals(20L, db.get(2).longValue());
        assertEquals(10L, db.get(1).longValue());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testInsertRollback()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.insert(3, 30L);
        db.insert(4, 40L);
        txn.rollback();

        txn = Transaction.startTransaction(true);
        assertEquals(2, db.size());
        assertEquals(20L, db.get(2).longValue());
        assertEquals(10L, db.get(1).longValue());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testUpdateCommit()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.update(1, 100L);
        db.update(2, 200L);
        txn.rollback();

        txn = Transaction.startTransaction(true);
        assertEquals(2, db.size());
        assertEquals(20L, db.get(2).longValue());
        assertEquals(10L, db.get(1).longValue());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testUpdateRollback()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.update(1, 100L);
        db.update(2, 200L);
        txn.rollback();

        txn = Transaction.startTransaction(true);
        assertEquals(2, db.size());
        assertEquals(20L, db.get(2).longValue());
        assertEquals(10L, db.get(1).longValue());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testClearCommit()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.clear();
        txn.commit();

        txn = Transaction.startTransaction(true);
        assertEquals(0, db.size());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testClearRollback()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.clear();
        db.insert(1, 100L);
        txn.rollback();

        txn = Transaction.startTransaction(true);
        assertEquals(2, db.size());
        assertEquals(20L, db.get(2).longValue());
        assertEquals(10L, db.get(1).longValue());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testCompactCommit()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.delete(1);
        db.insert(3, 30L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.compact();
        txn.commit();

        txn = Transaction.startTransaction(true);
        assertEquals(2, db.size());
        assertEquals(30L, db.get(3).longValue());
        assertEquals(20L, db.get(2).longValue());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testCompactRollback()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.delete(1);
        db.insert(3, 30L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.compact();
        txn.rollback();

        txn = Transaction.startTransaction(true);
        assertEquals(2, db.size());
        assertEquals(30L, db.get(3).longValue());
        assertEquals(20L, db.get(2).longValue());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testDeleteCommit()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        db.insert(3, 30L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.delete(2);
        txn.commit();

        txn = Transaction.startTransaction(true);
        assertEquals(2, db.size());
        assertEquals(30L, db.get(3).longValue());
        assertEquals(10L, db.get(1).longValue());
        assertNull(db.get(2));
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testDeleteRollback()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        db.insert(3, 30L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.delete(2);
        txn.rollback();

        txn = Transaction.startTransaction(true);
        assertEquals(3, db.size());
        assertEquals(30L, db.get(3).longValue());
        assertEquals(10L, db.get(1).longValue());
        assertEquals(20L, db.get(2).longValue());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testCommitRightValue()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.update(1, 1000L);
        db.update(1, 10000L);
        txn.commit();

        txn = Transaction.startTransaction(true);
        assertEquals(2, db.size());
        assertEquals(20L, db.get(2).longValue());
        assertEquals(10000L, db.get(1).longValue());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testRollbackToRightValue()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.insert(1, 10L);
        db.insert(2, 20L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.update(1, 100L);
        db.update(2, 200L);
        txn.commit();

        txn = Transaction.startTransaction(false);
        db.update(1, 1000L);
        db.update(1, 10000L);
        txn.rollback();

        txn = Transaction.startTransaction(true);
        assertEquals(2, db.size());
        assertEquals(200L, db.get(2).longValue());
        assertEquals(100L, db.get(1).longValue());
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testAutoRollback()
  {
    TransactionalDatabase<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      populateDb(db);

      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db.fasterInsert(Integer.valueOf(12), Long.valueOf(345823L));
        db.close();
      }
      finally
      {
        txn.rollback();
      }
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testAutoRollbackWithSeveralActiveTransactions()
  {
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      populateDb(db);
      Map<String, Object> m = new HashMap<String, Object>();
      m.put("db", db);
      m.put("latch1", new CountDownLatch(3));
      m.put("flag1", new AtomicBoolean(false));
      TestStep readingTestStep = new TestStep() {
        public void runStep(Map<String, Object> mp) throws Exception
        {
          CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
          try
          {
            TransactionalDatabase<?, ?> tdb = (TransactionalDatabase<?, ?>) mp.get("db");
            Transaction txn = Transaction.startTransaction(true);
            try
            {
              tdb.joinTransaction(true);

              l1.countDown();
              l1.await();
              l1 = null;

              Thread.sleep(1000);
              // Now the database is closed.
              assertFalse(((AtomicBoolean) mp.get("flag1")).get());
            }
            finally
            {
              txn.rollback();
            }
          }
          finally
          {
            if (l1 != null)
            {
              l1.countDown();
            }
          }
        }
      };

      new ConcurrentTestRunner(new TestStep[][] {
      // Thread that will close the database
          new TestStep[] { new TestStep() {
            public void runStep(Map<String, Object> mp) throws Exception
            {
              CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
              try
              {
                TransactionalDatabase<?, ?> tdb = (TransactionalDatabase<?, ?>) mp.get("db");
                // The other threads start their transactions

                l1.countDown();
                l1.await();
                l1 = null;

                tdb.close();
                ((AtomicBoolean) mp.get("flag1")).set(true);
              }
              finally
              {
                if (l1 != null)
                {
                  l1.countDown();
                }
              }
            }
          } },
          // Threads with reading transactions
          new TestStep[] { readingTestStep }, new TestStep[] { readingTestStep } }, m).run();
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testConcurrentWritingTransactions()
  {
    // Pessimistic locking
    Database<Integer, Long> db = createDatabaseWoTxn();
    try
    {
      populateDb(db);
      Map<String, Object> m = new HashMap<String, Object>();
      m.put("db", db);
      m.put("latch1", new CountDownLatch(2));
      m.put("flag1", new AtomicBoolean(false));
      new ConcurrentTestRunner(new TestStep[][] { new TestStep[] { new TestStep() {
        public void runStep(Map<String, Object> mp) throws Exception
        {
          CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
          try
          {
            TransactionalDatabase<?, ?> tdb = (TransactionalDatabase<?, ?>) mp.get("db");
            // Start a transaction and count down the latch
            Transaction txn = Transaction.startTransaction(false);
            try
            {
              // Let the database join the transaction
              tdb.joinTransaction(false);
              l1.countDown();
              l1.await();
              l1 = null;

              // Sleep awhile to give the other thread plenty of
              // time to set the flag
              Thread.sleep(1000);
              // Still not set
              assertFalse(((AtomicBoolean) mp.get("flag1")).get());
            }
            finally
            {
              txn.rollback();
            }
          }
          finally
          {
            if (l1 != null)
            {
              l1.countDown();
            }
          }
        }
      } }, new TestStep[] { new TestStep() {
        public void runStep(Map<String, Object> mp) throws Exception
        {
          CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
          try
          {
            TransactionalDatabase<?, ?> tdb = (TransactionalDatabase<?, ?>) mp.get("db");
            Transaction txn = Transaction.startTransaction(false);
            try
            {
              l1.countDown();
              l1.await();
              l1 = null;
              // Try to join the transaction. This should not
              // be possible until the other thread leaves it
              tdb.joinTransaction(false);
              ((AtomicBoolean) mp.get("flag1")).set(true);
            }
            finally
            {
              txn.rollback();
            }
          }
          finally
          {
            if (l1 != null)
            {
              l1.countDown();
            }
          }
        }
      } } }, m).run();
    }
    finally
    {
      tearDownDatabase(db);
    }
  }

  @Test
  public void testConcurrentReadingAndWritingTransactionsOverTwoDatabases()
  {
    Database<Integer, Long> db1 = createDatabaseWoTxn();
    try
    {
      Database<Integer, Long> db2 = createDatabaseWoTxn();
      try
      {
        populateDb(db1);
        Map<String, Object> m = new HashMap<String, Object>();
        m.put("db1", db1);
        m.put("db2", db2);
        m.put("latch1", new CountDownLatch(2));
        m.put("flag1", new AtomicBoolean(false));
        new ConcurrentTestRunner(new TestStep[][] { new TestStep[] { new TestStep() {
          public void runStep(Map<String, Object> mp) throws Exception
          {
            CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
            try
            {
              TransactionalDatabase<?, ?> tdb1 = (TransactionalDatabase<?, ?>) mp.get("db1");
              TransactionalDatabase<?, ?> tdb2 = (TransactionalDatabase<?, ?>) mp.get("db2");
              // Start a transaction and count down the latch
              Transaction txn = Transaction.startTransaction(false);
              try
              {
                // Let the databases join the transaction
                tdb1.joinTransaction(false);
                tdb2.joinTransaction(false);
                l1.countDown();
                l1.await();
                l1 = null;

                // Sleep awhile to give the other thread plenty
                // of time to
                // set the flag
                Thread.sleep(1000);
                // Still not set
                assertFalse(((AtomicBoolean) mp.get("flag1")).get());
              }
              finally
              {
                txn.rollback();
              }
            }
            finally
            {
              if (l1 != null)
              {
                l1.countDown();
              }
            }
          }
        } }, new TestStep[] { new TestStep() {
          public void runStep(Map<String, Object> mp) throws Exception
          {
            CountDownLatch l1 = (CountDownLatch) mp.get("latch1");
            try
            {
              TransactionalDatabase<?, ?> tdb1 = (TransactionalDatabase<?, ?>) mp.get("db1");
              TransactionalDatabase<?, ?> tdb2 = (TransactionalDatabase<?, ?>) mp.get("db2");
              Transaction txn = Transaction.startTransaction(false);
              try
              {
                l1.countDown();
                l1.await();
                l1 = null;
                // Try to join the transaction. This should not
                // be possible until the other thread leaves it
                tdb1.joinTransaction(false);
                tdb2.joinTransaction(false);
                ((AtomicBoolean) mp.get("flag1")).set(true);
              }
              finally
              {
                txn.rollback();
              }
            }
            finally
            {
              if (l1 != null)
              {
                l1.countDown();
              }
            }
          }
        } } }, m).run();
      }
      finally
      {
        tearDownDatabase(db2);
      }
    }
    finally
    {
      tearDownDatabase(db1);
    }
  }

  protected abstract void injectCannotCommit(TransactionCollaborator<?, ?, ?> collab);

  @Test
  public void testThatNothingIsChangedIfOneDatabaseCannotCommit()
  {
    TransactionalDatabase<Integer, Long> db1 = createDatabaseWoTxn();
    try
    {
      populateDb(db1);
      TransactionalDatabase<Integer, Long> db2 = createDatabaseWoTxn();
      try
      {
        populateDb(db2);

        Transaction txn = Transaction.startTransaction(false);
        try
        {
          db1.update(Integer.valueOf(1), Long.valueOf(1235L));
          db2.update(Integer.valueOf(1), Long.valueOf(2432398L));
          db2.fasterInsert(Integer.valueOf(438), Long.valueOf(248238L));

          injectCannotCommit(txn.getCollaborator(db2));

          try
          {
            txn.commit();
            fail();
          }
          catch (UnableToCommitException e)
          {
            assertTrue(e.getMessage().contains("inject"));
          }
          assertTrue(txn.isFinished());
          txn = null;
        }
        finally
        {
          if (txn != null)
          {
            txn.rollback();
          }
        }

        // Verify that nothing was updated in the databases
        txn = Transaction.startTransaction(true);
        try
        {
          assertEquals(Long.valueOf(2), db1.get(Integer.valueOf(1)));
          assertEquals(Long.valueOf(2), db2.get(Integer.valueOf(1)));
          assertNull(db2.get(Integer.valueOf(438)));
        }
        finally
        {
          txn.rollback();
        }
      }
      finally
      {
        tearDownDatabase(db2);
      }
    }
    finally
    {
      tearDownDatabase(db1);
    }
  }

  protected abstract TransactionalDatabase<Integer, Long> createNewDatabaseUsingSameFiles(TransactionalDatabase<Integer, Long> otherDb);

  @Test
  public void testStateMaintenanceOverDbCrash()
  {
    TransactionalDatabase<Integer, Long> db1 = createDatabaseWoTxn();
    try
    {
      populateDb(db1);
      Transaction txn = Transaction.startTransaction(false);
      try
      {
        db1.delete(1);
        db1.update(123, 10000L);
        db1.insert(2, 2L);

        TransactionalDatabase<Integer, Long> db2 = createNewDatabaseUsingSameFiles(db1);
        try
        {
          // This should contain the old values.
          assertEquals(234L, db2.get(123).longValue());
          assertEquals(2L, db2.get(1).longValue());
        }
        finally
        {
          tearDownDatabase(db2);
        }
      }
      finally
      {
        if (!txn.isFinished())
        {
          txn.rollback();
        }
      }
    }
    finally
    {
      tearDownDatabase(db1);
    }
  }

  @Override
  public void testEqualsAndHashCode()
  {
    // reimplemented
  }
}
TOP

Related Classes of org.helidb.txn.AbstractTransactionalDatabaseOnConstantRecordSizeBackendTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.